Filters break working after source update

Hi,

I’m trying to create a DataTable dashboard with 1) multiple widget filters, 2) updates to the data maybe once per minute.

Problem is that widget filters don’t work as expected after data is updated.

Below is a minimal working example of what I’m trying to do, with a button for manually updating the data (update in demo is just incrementing counter on one row). Filters work as expected before data is updated, but after first update they stop working properly.

My guess is that the data used as source in the DataTable and the same data that is used in the CustomJS as original_source go out of sync when data is updated, but I haven’t been able to figure out just what to do about that. Any help would be appreciated!

My Bokeh version is 2.4.1 and I’m running the app on Bokeh server.

from bokeh.models.widgets import Select
from bokeh.models import Button, CustomJS, ColumnDataSource,DataTable,TableColumn
from bokeh.io import curdoc
from bokeh.layouts import column, row

data_source = {
    'name': ["Bob", "John", "Mark"],
    'size': ["S", "M", "L"],
    'country':["US", "Canada", "US"],
    'counter':[1,2,3]
}

source = ColumnDataSource(data=data_source)
original_source = ColumnDataSource(data=data_source)

columns = [TableColumn(field=col) for col in data_source]

data_table = DataTable(source=source, columns=columns)

size_list = ['ALL', 'S', 'M', 'L', 'XL']
size_select = Select(title="Size:", value=size_list[0], options=size_list)

country_list = ['ALL', 'US', 'Canada']
country_select = Select(title="Country:", value=country_list[0], options=country_list)

callback_code = """
var data = source.data;
var original_data = original_source.data;
var size_select = size_select_obj.value;
var country_select = country_select_obj.value;
for (var key in original_data) {
    data[key]=[]
    for (var i = 0; i < original_data['name'].length; ++i) {
        if (((original_data['size'][i]==size_select || size_select=='ALL') && (original_data['country'][i]==country_select || country_select=='ALL'))) {
            data[key].push(original_data[key][i]);
        }
    }
}
source.change.emit();
target_obj.change.emit();
"""

callback = CustomJS(
    args=dict(source=source,
            original_source=original_source, 
            size_select_obj=size_select,
            country_select_obj=country_select,
            target_obj=data_table),
    code=callback_code
)

size_select.js_on_change('value', callback)
country_select.js_on_change('value', callback)

def update_data() -> None:
    data_source['counter'][0]+=1
    data_table.source=ColumnDataSource(data=data_source)

button = Button(label="Update", button_type="success")
button.on_click(update_data)

curdoc().add_root(column(button, row(size_select, country_select), data_table))

@mf9

The breakage occurs b/c a new ColumnDataSource is being assigned. I think you simply need to update the data of the existing column data source with a dictionary that has what you need.

The following example simply assigns the dictionary in your example. In more data intensive applications, you can make use of patch methods in bokeh to only update specific subsets of the data instead of replacing wholesale.

Try this; hopefully it addresses the issue.

def update_data() -> None:
    data_source['counter'][0]+=1
    data_table.source.data=data_source

Thanks for the reply!

Unfortunately this solution doesn’t work in the manner I hoped it would.

Example: click update, and the data is updated as expected (counter for Bob ==2). Use filter to select only size ‘S’, and Bob’s counter is at 1 again. Click update, and Bob’s counter is 3. The updates and filters seem to work on different data.

Maybe there’s an easier way to do this (DataTable, filters, data updating in the background) in Bokeh without resorting to CustomJS?

See Providing data — Bokeh 2.4.2 Documentation

But you would typically use a CustomJS to update the filter properties (much easier than manually doing the filtering though).

Thanks! This seems to work (will just check if I can get the actual app working before checking the issue solved)

from bokeh.models.filters import BooleanFilter
from bokeh.models.widgets import Select
from bokeh.models import Button, ColumnDataSource,DataTable,TableColumn, CDSView
from bokeh.io import curdoc
from bokeh.layouts import column, row

data_source = {
    'name': ["Bob", "John", "Mark"],
    'size': ["S", "M", "L"],
    'country':["US", "Canada", "US"],
    'counter':[1,2,3]
}

source = ColumnDataSource(data=data_source)
columns = [TableColumn(field=col) for col in data_source]
data_table = DataTable(source=source, columns=columns)

size_list = ['ALL', 'S', 'M', 'L', 'XL']
size_select = Select(title="Size:", value=size_list[0], options=size_list)

country_list = ['ALL', 'US', 'Canada']
country_select = Select(title="Country:", value=country_list[0], options=country_list)

def update(attr, old, new):
    size=size_select.value
    country=country_select.value
    size_filter=BooleanFilter([True if size_val==size or size=='ALL' else False for size_val in source.data['size']])
    country_filter=BooleanFilter([True if country_val==country or country=='ALL' else False for country_val in source.data['country']])
    view=CDSView(source=source, filters=[size_filter,country_filter])
    data_table.view=view

size_select.on_change('value', update)
country_select.on_change('value', update)

def update_data() -> None:
    data_source['counter'][0]+=1
    data_table.source.data=data_source

button = Button(label="Update", button_type="success")
button.on_click(update_data)

curdoc().add_root(column(button, row(size_select,country_select), data_table))
1 Like