☰Read-only archive⌕
Looking for help with Bokeh?

This is a read-only archive. Ask questions and join the conversation on GitHub.

Visit Community Support on GitHub →
Archived discussions · Captured October 28, 2026

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

Community Supportserver
Andrew_Kettmann
Andrew_Kettmann

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

⌁   Link to post
p-himik
p-himik

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.

⌁   Link to post
Andrew_Kettmann
Andrew_Kettmann

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.

⌁   Link to post
Andrew_Kettmann
Andrew_Kettmann

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
⌁   Link to post
p-himik
p-himik

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.

⌁   Link to post
Andrew_Kettmann
Andrew_Kettmann

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!

⌁   Link to post

End of archived discussion · Replies are disabled