Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Filter a Bokeh DataTable using multiple filter widgets. Runs in a Jupyter notebook.
import bokeh.embed
import bokeh.io
import bokeh.models
import bokeh.models.widgets
import bokeh.plotting
import pandas as pd
from pandas_datareader import wb
bokeh.plotting.output_notebook()
df = wb.download(indicator='NY.GDP.PCAP.KD', country=['US', 'CA', 'MX'], start=2005, end=2008)
df = df.reset_index()
source = bokeh.models.ColumnDataSource(df)
original_source = bokeh.models.ColumnDataSource(df)
columns = [
bokeh.models.widgets.TableColumn(field="country", title="Country"),
bokeh.models.widgets.TableColumn(field="year", title="Year"),
bokeh.models.widgets.TableColumn(field="NY.GDP.PCAP.KD", title="NY.GDP.PCAP.KD"),
]
data_table = bokeh.models.widgets.DataTable(source=source, columns=columns)
# callback code to be used by all the filter widgets
# requires (source, original_source, country_select_obj, year_select_obj, target_object)
combined_callback_code = """
var data = source.get('data');
var original_data = original_source.get('data');
var country = country_select_obj.get('value');
console.log("country: " + country);
var year = year_select_obj.get('value');
console.log("year: " + year);
for (var key in original_data) {
data[key] = [];
for (var i = 0; i < original_data['country'].length; ++i) {
if ((country === "ALL" || original_data['country'][i] === country) &&
(year === "ALL" || original_data['year'][i] === year)) {
data[key].push(original_data[key][i]);
}
}
}
target_obj.trigger('change');
source.trigger('change');
"""
# define the filter widgets, without callbacks for now
country_list = ['ALL'] + df['country'].unique().tolist()
country_select = bokeh.models.widgets.Select(title="Country:", value=country_list[0], options=country_list)
year_list = ['ALL'] + df['year'].unique().tolist()
year_select = bokeh.models.widgets.Select(title="Year:", value=year_list[0], options=year_list)
# now define the callback objects now that the filter widgets exist
generic_callback = bokeh.models.CustomJS(
args=dict(source=source,
original_source=original_source,
country_select_obj=country_select,
year_select_obj=year_select,
target_obj=data_table),
code=combined_callback_code
)
# finally, connect the callbacks to the filter widgets
country_select.callback = generic_callback
year_select.callback = generic_callback
p = bokeh.io.vplot(country_select, year_select, data_table)
bokeh.plotting.show(p)
@lsuttle

This comment has been minimized.

Copy link

lsuttle commented Feb 22, 2017

I know this is a very old gist at this point, but do you have any tips for using this to update a scatterplot? Your callbacks solve a problem I was having with multiple figures, but I don't know enough javascript to figure out where to hook in my plotting function.

Edit: I figured this out, thanks for this great gist!

@covelloz

This comment has been minimized.

Copy link

covelloz commented Jun 13, 2017

Updated your code for Bokeh version 0.12.5 & saved output to HTML instead of Jupyter notebook.
vplot got deprecated; instead use column from bokeh.layouts

Thanks, your code was very helpful.

from bokeh.layouts import column
from bokeh.models import ColumnDataSource, CustomJS
from bokeh.models.widgets import DataTable, TableColumn, Select
from bokeh.plotting import save, output_file
from pandas_datareader import wb

df = wb.download(indicator='NY.GDP.PCAP.KD', country=['US', 'CA', 'MX'], start=2005, end=2008)
df = df.reset_index()

source = ColumnDataSource(df)
original_source = ColumnDataSource(df)
columns = [
   TableColumn(field="country", title="Country"),
   TableColumn(field="year", title="Year"),
   TableColumn(field="NY.GDP.PCAP.KD", title="NY.GDP.PCAP.KD"),
]
data_table = DataTable(source=source, columns=columns)

# callback code to be used by all the filter widgets
# requires (source, original_source, country_select_obj, year_select_obj, target_object)
combined_callback_code = """
var data = source.get('data');
var original_data = original_source.get('data');
var country = country_select_obj.get('value');
console.log("country: " + country);
var year = year_select_obj.get('value');
console.log("year: " + year);
for (var key in original_data) {
    data[key] = [];
    for (var i = 0; i < original_data['country'].length; ++i) {
        if ((country === "ALL" || original_data['country'][i] === country) &&
            (year === "ALL" || original_data['year'][i] === year)) {
            data[key].push(original_data[key][i]);
        }
    }
}
target_obj.trigger('change');
source.trigger('change');
"""

# define the filter widgets, without callbacks for now
country_list = ['ALL'] + df['country'].unique().tolist()
country_select = Select(title="Country:", value=country_list[0], options=country_list)
year_list = ['ALL'] + df['year'].unique().tolist()
year_select = Select(title="Year:", value=year_list[0], options=year_list)

# now define the callback objects now that the filter widgets exist
generic_callback = CustomJS(
    args=dict(source=source, 
              original_source=original_source, 
              country_select_obj=country_select, 
              year_select_obj=year_select, 
              target_obj=data_table),
    code=combined_callback_code
)

# finally, connect the callbacks to the filter widgets
country_select.js_on_change('value', generic_callback)
year_select.js_on_change('value', generic_callback)

p = column(country_select,year_select,data_table)
output_file('datatable_filter.html')
save(p)
@Aso1977

This comment has been minimized.

Copy link

Aso1977 commented Mar 9, 2018

Nice code, I made above code working for 'bokeh serve ...'. Tested on bokeh 0.12.14

import pandas as pd
from pandas_datareader import wb

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



df = wb.download(indicator='NY.GDP.PCAP.KD', country=['US', 'CA', 'MX'], start=2005, end=2008)
df = df.reset_index()

source = ColumnDataSource(df)
original_source = ColumnDataSource(df)
columns = [
   TableColumn(field="country", title="Country"),
   TableColumn(field="year", title="Year"),
   TableColumn(field="NY.GDP.PCAP.KD", title="NY.GDP.PCAP.KD"),
]
data_table = DataTable(source=source, columns=columns)

# callback code to be used by all the filter widgets
# requires (source, original_source, country_select_obj, year_select_obj, target_object)
combined_callback_code = """
var data = source.data;
var original_data = original_source.data;
var country = country_select_obj.value;
console.log("country: " + country);
var year = year_select_obj.value;
console.log("year: " + year);
for (var key in original_data) {
    data[key] = [];
    for (var i = 0; i < original_data['country'].length; ++i) {
        if ((country === "ALL" || original_data['country'][i] === country) &&
            (year === "ALL" || original_data['year'][i] === year)) {
            data[key].push(original_data[key][i]);
        }
    }
}
target_obj.change.emit();;
source.change.emit();
"""

# define the filter widgets, without callbacks for now
country_list = ['ALL'] + df['country'].unique().tolist()
country_select = Select(title="Country:", value=country_list[0], options=country_list)
year_list = ['ALL'] + df['year'].unique().tolist()
year_select = Select(title="Year:", value=year_list[0], options=year_list)

# now define the callback objects now that the filter widgets exist
generic_callback = CustomJS(
    args=dict(source=source, 
              original_source=original_source, 
              country_select_obj=country_select, 
              year_select_obj=year_select, 
              target_obj=data_table),
    code=combined_callback_code
)

# finally, connect the callbacks to the filter widgets
country_select.js_on_change('value', generic_callback)
year_select.js_on_change('value', generic_callback)

p = column(country_select,year_select,data_table)

curdoc().add_root(p)

@LEEVE

This comment has been minimized.

Copy link

LEEVE commented Apr 7, 2018

@covelloz I copied your code and ran it, it does output to an html file but the filtering doesn't work.

@joesmaker

This comment has been minimized.

Copy link

joesmaker commented May 15, 2018

I have the same problem as @LEEVE, it produces an html but the filtering does not work

@WeinanHuang

This comment has been minimized.

Copy link

WeinanHuang commented Apr 17, 2019

JS callbacks in above code snippets are not working as expected. Some necessary changes have been made. It should work under bokeh 1.0.1 env.

from bokeh.layouts import column
from bokeh.models import ColumnDataSource, CustomJS
from bokeh.models.widgets import DataTable, TableColumn, Select
from bokeh.plotting import save, output_file
from pandas_datareader import wb

df = wb.download(indicator='NY.GDP.PCAP.KD', country=['US', 'CA', 'MX'], start=2005, end=2008)
df = df.reset_index()

source = ColumnDataSource(df)
original_source = ColumnDataSource(df)
columns = [
   TableColumn(field="country", title="Country"),
   TableColumn(field="year", title="Year"),
   TableColumn(field="NY.GDP.PCAP.KD", title="NY.GDP.PCAP.KD"),
]
data_table = DataTable(source=source, columns=columns)

# callback code to be used by all the filter widgets
# requires (source, original_source, country_select_obj, year_select_obj, target_object)
combined_callback_code = """
var data = source.data;
var original_data = original_source.data;
var country = country_select_obj.value;
console.log("country: " + country);
var year = year_select_obj.value;
console.log("year: " + year);
for (var key in original_data) {
    data[key] = [];
    for (var i = 0; i < original_data['country'].length; ++i) {
        if ((country === "ALL" || original_data['country'][i] === country) &&
            (year === "ALL" || original_data['year'][i] === year)) {
            data[key].push(original_data[key][i]);
        }
    }
}

source.change.emit();
target_obj.change.emit();
"""

# define the filter widgets, without callbacks for now
country_list = ['ALL'] + df['country'].unique().tolist()
country_select = Select(title="Country:", value=country_list[0], options=country_list)
year_list = ['ALL'] + df['year'].unique().tolist()
year_select = Select(title="Year:", value=year_list[0], options=year_list)

# now define the callback objects now that the filter widgets exist
generic_callback = CustomJS(
    args=dict(source=source, 
              original_source=original_source, 
              country_select_obj=country_select, 
              year_select_obj=year_select, 
              target_obj=data_table),
    code=combined_callback_code
)

# finally, connect the callbacks to the filter widgets
country_select.js_on_change('value', generic_callback)
year_select.js_on_change('value', generic_callback)

p = column(country_select,year_select,data_table)
output_file('datatable_filter.html')
save(p)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.