Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@dennisobrien
Last active June 5, 2021 00:09
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save dennisobrien/450d7da20daaba6d39d0 to your computer and use it in GitHub Desktop.
Save dennisobrien/450d7da20daaba6d39d0 to your computer and use it in GitHub Desktop.
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
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
Copy link

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
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
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
Copy link

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

@WeinanHuang
Copy link

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