I am still trying to learn the nuances of Bokeh and am trying to craft a simple example that allows me to filter data by different methods. In this case I want to click one of two CheckboxButtons that will filter out, or filter back in certain data depending on the value in a specific column. In addition, I want to use a slider to filter out data that is above a certain value. In order to ensure that the execution of one filter does not undo the results of another filter, I am combining all filters into one javascript callback. The code as written (shown below) allows be to filter data by clicking one of the two CheckboxButtons at the instance of clicking the button. However, when I move the slider, the plot does not update on change, even through I am using the js_on_change
function. If I move the slider, then click one of the ChecboxButtons, the plot will then correctly update based on both widgets. I am guessing I am missing something simple, any help would be appreciated.
from bokeh.models import ColumnDataSource, CustomJS, Button
from bokeh.models import CheckboxButtonGroup, CDSView, GroupFilter, Slider
from bokeh.plotting import Figure, output_file, show
from bokeh.layouts import column
import pandas as pd
output_file("js_on_change.html")
x = [1.2, 2.4, 0.8, 5.6, 12.1, 8.8, 12.1, 13.8, 6.5]
y = [3.4, 1.1, 4.2, 6.6, 1.8, 12.1, 7.8, 3.4, 9.4]
z = ['Apple', 'Apple', 'Orange', 'Orange', 'Orange',
'Orange', 'Apple', 'Apple', 'Orange']
a = ['Water', 'Water', 'Water', 'Land', 'Land',
'Water', 'Water', 'Land', 'Water']
source = ColumnDataSource(data = dict(x=x, y=y, z=z, a=a))
source_copy = ColumnDataSource(data = dict(x=x, y=y, z=z, a=a))
view = CDSView(source=source, filters=[GroupFilter(column_name='a', group='Water')])
plot = Figure(plot_width=400, plot_height=400)
plot.circle('x', 'y', source=source, size=8, line_alpha=0.6)
LABELS = ["Apple", "Orange"]
checkbox_button_group = CheckboxButtonGroup(labels=LABELS, active=[0, 1])
range_slider = Slider(start=0.0, end=15, value=15, step=0.2,
title="Max Value")
callback = CustomJS(args=dict(source=source, source_copy=source_copy,
slider=range_slider), code="""
// Active buttons ["Apple", "Orange"]
var active_labels = cb_obj.active.map((item) => cb_obj.labels[item])
var d1 = source.data;
var d2 = source_copy.data;
var f = slider.value
function getAllIndexes(arr, values, y_val, slider_val) {
var indexes = [], i;
for(i = 0; i < arr.length; i++)
if (values.includes(arr[i]) && y_val[i] <= slider_val)
indexes.push(i);
return indexes;
}
// Get indices for columns with specific values
var indexes = getAllIndexes(d2['z'], active_labels,
d2['y'], f);
d1['x'] = []
d1['y'] = []
d1['z'] = []
d1['a'] = []
// remap dataframes based on results
for (var i = 0; i < indexes.length; i++) {
d1['x'].push(d2['x'][indexes[i]])
d1['y'].push(d2['y'][indexes[i]])
d1['z'].push(d2['z'][indexes[i]])
d1['a'].push(d2['a'][indexes[i]])
}
source.change.emit()
""")
checkbox_button_group.js_on_click(callback)
range_slider.js_on_change('value', callback)
layout = column(checkbox_button_group, range_slider, plot)
show(layout)