Last active
June 5, 2021 00:09
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
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)
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)
@covelloz I copied your code and ran it, it does output to an html file but the filtering doesn't work.
I have the same problem as @LEEVE, it produces an html but the filtering does not work
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
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!