In [2]:
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()
In [3]:
df = wb.download(indicator='NY.GDP.PCAP.KD', country=['US', 'CA', 'MX'], start=2005, end=2008)
df = df.reset_index()
In [4]:
df.head()
Out[4]:
In [12]:
source = bokeh.models.ColumnDataSource(df)
original_source = bokeh.models.ColumnDataSource(df)
columns = [
bokeh.models.widgets.TableColumn(field="country", title="Country", width=10),
bokeh.models.widgets.TableColumn(field="year", title="Year", width=10),
bokeh.models.widgets.TableColumn(field="NY.GDP.PCAP.KD", title="NY.GDP.PCAP.KD", width=10),
]
data_table = bokeh.models.widgets.DataTable(source=source, columns=columns, width=0)
# 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)
Out[12]:
In [23]:
from bokeh.models import TableColumn, ColumnDataSource, DataTable, Select, Slider, CustomJS, Button, HBox
from bokeh.plotting import vplot
from bokeh.sampledata.periodic_table import elements
def plot_datatable(df):
df = df.copy()
# deal with some atomic mass values of the form '[98]'
df['atomic mass'] = df['atomic mass'].str.extract('([\d\.]+)').astype(float)
columns = [
TableColumn(field='atomic number', title='Atomic Number'),
TableColumn(field='symbol', title='Symbol'),
TableColumn(field='name', title='Name'),
TableColumn(field='metal', title='Type'),
TableColumn(field='atomic mass', title='Atomic Mass')
]
column_names = [tc.field for tc in columns]
source = ColumnDataSource(df[column_names])
original_source = ColumnDataSource(df)
data_table = DataTable(source=source, columns=columns, height=600, editable=False)
widget_callback_code = """
var filtered_data = filtered_source.get('data');
var original_data = original_source.get('data');
var element_type = element_type_select.get('value');
var min_mass = min_mass_slider.get('value');
// now construct the new data object based on the filtered values
for (var key in original_data) {
filtered_data[key] = [];
for (var i = 0; i < original_data[key].length; ++i) {
if ((element_type === "ALL" || original_data["metal"][i] === element_type) &&
(original_data['atomic mass'][i] >= min_mass)) {
filtered_data[key].push(original_data[key][i]);
}
}
}
target_obj.trigger('change');
filtered_source.trigger('change');
"""
# define the filter widgets, without callbacks for now
element_type_list = ['ALL'] + df['metal'].unique().tolist()
element_type_select = Select(title="Element Type:", value=element_type_list[0], options=element_type_list)
min_mass_slider = Slider(start=0, end=df['atomic mass'].max(), value=1, step=1, title="minimum atomic mass")
# now define the callback objects now that the filter widgets exist
arg_dct = dict(
filtered_source=source,
original_source=original_source,
element_type_select=element_type_select,
min_mass_slider=min_mass_slider,
target_obj=data_table
)
generic_callback = CustomJS(args=arg_dct, code=widget_callback_code)
# connect the callbacks to the filter widgets
element_type_select.callback = generic_callback
min_mass_slider.callback = generic_callback
# create a button to collect the filtered results
# for now, just send json to new window
send_button_callback_code = """
var filtered_data = filtered_source.get('data');
var action_items = [];
for (var i = 0; i < filtered_data['atomic number'].length; ++i) {
var item = new Object();
for (var key in filtered_data) {
item[key] = filtered_data[key][i]
}
action_items.push(item);
}
var new_window = window.open("data:text/html," + encodeURIComponent(JSON.stringify(action_items)),
"_blank", "location=yes,height=570,width=520,scrollbars=yes,status=yes");
new_window.focus();
"""
send_button_callback = CustomJS(args=dict(filtered_source=source), code=send_button_callback_code)
send_button = Button(label="Send", type="success", callback=send_button_callback)
input_widgets = HBox(
children=[
HBox(children=[element_type_select, ]),
HBox(children=[min_mass_slider]),
HBox(children=[send_button]),
]
)
p = vplot(input_widgets, data_table)
bokeh.io.show(p)
plot_datatable(elements)
In [ ]: