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) |
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 have the same problem as @LEEVE, it produces an html but the filtering does not work