DataTable with view doesn't appear to update when view is updated

First, obligatory thanks for the hard work on this awesome library, super neat and I love it a lot. I have had more challenges learning how Pandas has to be interacted with than this library. Second onto the meat and potatoes of the post

Using Bokeh 1.4.0 server on Python 3.7.4. I have a DataTable and it is using a source and view, but when I update the view, I don’t seem to be able to make the table actually update.

Currently running it with:

serve --address 0.0.0.0 bug_example.py --allow-websocket-origin=ADDRESS:5006 --log-level debug --dev bug_example.py

I boiled down the code to an example that still fails. It is entirely possible I am the issue here, but I have done a good bit of digging and can’t seem to find what I am doing wrong in any of the examples. Anyway, here is the code.

from bokeh.io import curdoc
from bokeh.models import RangeSlider, WidgetBox, ColumnDataSource, TableColumn, CDSView, DataTable, BooleanFilter

from bokeh.layouts import row

import numpy.random as rand

CHAR_LIST = ['WATCHER', 'IRONCLAD', 'THE_SILENT', 'DEFECT']
run_index_random = {
    'character': rand.choice(CHAR_LIST, 100),
    'ascencion_level': rand.randint(0, 20, 100),
    'victory': rand.choice([True, False], 100),
    'floor_reached': rand.randint(1, 56, 100),

}

run_index_cds = ColumnDataSource(data=run_index_random)
run_index_view = CDSView(source=run_index_cds)

# Ascension filter
asc_slider = RangeSlider(start=0, end=20, step=1, value=(0, 20), title="Ascension")

run_index_columns = [TableColumn(field=x, title=x) for x in run_index_cds.column_names]
run_index_table = DataTable(source=run_index_cds, view=run_index_view, columns=run_index_columns)

widget_box = WidgetBox(children=[asc_slider, run_index_table])

r1 = row(widget_box, sizing_mode='scale_both')

asc_filter = BooleanFilter([True if 5 <= asc <= 20 else False for asc in run_index_cds.data['ascencion_level']],
                           tags=['asc_filter'])
run_index_view.filters.append(asc_filter)


def asc_filter_change(attr, old, new):
    global asc_filter
    global run_index_view
    bot, top = new
    asc_filter = BooleanFilter(
        [True if bot <= asc <= top else False for asc in run_index_cds.data['ascencion_level']])
    run_index_view.filters[0] = asc_filter


asc_slider.on_change('value', asc_filter_change)

curdoc().add_root(r1)

For anyone wondering it is part of something I am writing to parse and present information for runs for Slay the Spire.

I have tried a few different ways of updating the filter. Updating it directly, creating a new one and replacing it (what is happening in the example), and a few other things to try and get the datatable to catch that it should update. Anyone able to point out what I am doing wrong?

Thanks,
Mriswithe

I believe it’s a known issue: Changing CDSView filters' attributes does not trigger rendering · Issue #7273 · bokeh/bokeh · GitHub

Or maybe not, given that you change the filters attribute and not the filters themselves.

In any case, the same workaround could probably be used - try run_index_view.filters = [asc_flter] instead.

I had happened upon that issue but I wasn’t sure if it was still applicable as it was over 2 years old. Either way I tried that method here and still no update.

def asc_filter_change(attr, old, new):
    global asc_filter
    global run_index_view
    bot, top = new
    asc_filter = BooleanFilter(
        [True if bot <= asc <= top else False for asc in run_index_cds.data['ascencion_level']])
    run_index_view.filters = [asc_filter]

Is there some kind of manual way to trigger the redraw? Would it be recreating the datatable each time? I don’t need this to be super duper performant, just trying to get past this issue if I can hah.

Looks like on this example from the gallery they are redefining the data attribute each time. I can give that a shot and see how it rolls. Just was hoping to use a filter because in the actual project I have several filters

This functions, not sure how efficient it is, or if I am leaking memory by redefining the view over and over again, but looks like I need to make the datatable think it is a NEW view to get it to actually rerender.

def asc_filter_change(attr, old, new):
    global asc_filter
    global run_index_view
    bot, top = new
    asc_filter = BooleanFilter(
        [True if bot <= asc <= top else False for asc in run_index_cds.data['ascencion_level']])
    new_view = CDSView(source=run_index_cds, filters=[asc_filter])
    run_index_table.view = new_view

Hmm, right, sorry to have mislead you. In fact, you initial code does send view updates to the UI - it’s just that the DataTable does not pick them up. It’s mentioned in this comment in the linked issue: Changing CDSView filters' attributes does not trigger rendering · Issue #7273 · bokeh/bokeh · GitHub

Here’s how I’d write it:

import numpy.random as rand
from bokeh.io import curdoc
from bokeh.models import RangeSlider, WidgetBox, ColumnDataSource, TableColumn, CDSView, DataTable, BooleanFilter, \
    CustomJS

CHAR_LIST = ['WATCHER', 'IRONCLAD', 'THE_SILENT', 'DEFECT']
run_index_random = {
    'character': rand.choice(CHAR_LIST, 100),
    'ascencion_level': rand.randint(0, 20, 100),
    'victory': rand.choice([True, False], 100),
    'floor_reached': rand.randint(1, 56, 100),
}

run_index_cds = ColumnDataSource(data=run_index_random)


def range_to_booleans(rng):
    bot, top = rng
    return [bot <= asc <= top for asc in run_index_cds.data['ascencion_level']]


init_range = (5, 20)
asc_filter = BooleanFilter(range_to_booleans(init_range), tags=['asc_filter'])
run_index_view = CDSView(source=run_index_cds, filters=[asc_filter])
run_index_columns = [TableColumn(field=x, title=x) for x in run_index_cds.column_names]
run_index_table = DataTable(source=run_index_cds, view=run_index_view, columns=run_index_columns)


def asc_filter_change(attr, old, new):
    bot, top = new
    asc_filter.booleans = [True if bot <= asc <= top else False
                           for asc in run_index_cds.data['ascencion_level']]


# Ascension filter
asc_slider = RangeSlider(start=0, end=20, step=1, value=init_range, title="Ascension")
asc_slider.on_change('value', asc_filter_change)
asc_filter.js_on_change('booleans', CustomJS(args=dict(cds=run_index_cds),
                                             code="cds.change.emit()"))

curdoc().add_root(WidgetBox(children=[asc_slider, run_index_table]))

The essential part is that js_on_change - it basically makes the DataTable think that the actual data has changed when the slider’s value changes.

Excellent, thank you, I will give that a shot and check back in later.

Tested it and confirmed, it definitely works! Thanks for that, I have a lot of Python experience, but Javascript is still pretty blackbox to me, and I haven’t digested how everything actually works underneath. This should get me rolling on this and let me actually just change the filters involved instead of recreating a ton of stuff.

Thanks a ton!