I want a CheckboxGroup to represent different columns of a dataframe. The idea is for the user to be able to add multiple column values if they select multiple columns and interactively display the sum as a plot.
I have the following plot from a previous help I got:
from bokeh.io import output_file, show
from bokeh.plotting import figure
from bokeh.layouts import layout, widgetbox
from bokeh.models import ColumnDataSource, CustomJS
from bokeh.models.widgets import CheckboxGroup
import pandas as pd
output_file('additive_checkbox.html')
names = ['Number', 'A', 'B', 'C']
rows = [(340, 77, 70, 15),
(341, 80, 73, 15),
(342, 83, 76, 16),
(343, 86, 78, 17)]
data = pd.DataFrame(rows, columns=names)
data['combined'] = None
source = ColumnDataSource(data)
callback = CustomJS(args=dict(source=source), code="""
const labels = cb_obj.labels;
const active = cb_obj.active;
const data = source.data;
const sourceLen = data.combined.length;
const combined=Array(sourceLen).fill(undefined);
var combined_label = ''
if (active.length > 0) {
const selectedColumns = labels.filter((val, ind) => active.includes(ind));
for(let i = 0; i < sourceLen; i++) {
let sum = 0;
for(col of selectedColumns){
sum += data[col][i];
}
combined[i] = sum;
}
// get index positions of active buttons; use that to retrieve labels to build "combined" label string
for (let j=0; j < active.length; j++) {
combined_label += labels[active[j]]+'+';
}
combined_label = '('+combined_label.substring(0, combined_label.length - 1)+')';
}
else { // if there are no active buttons, label as 'none'
combined_label = '(none)';
}
legend_item.label.value = 'combined '+combined_label;
data.combined=combined;
source.change.emit();
""")
checkbox_group = CheckboxButtonGroup(labels=names[1:], active=[], width=400)
checkbox_group.js_on_change("active", callback)
final_col = column(p, checkbox_group)
show(final_col)
And it is giving me the following error:
ValueError: failed to validate CheckboxButtonGroup(id='1223', ...).js_event_callbacks: expected an element of Dict(String, List(Instance(CustomJS))), got CustomJS(id='1222', ...)
I am not good with JS and have no idea what it means!
Update: Now, after frustrating syntax changes that I am not sure if they are accurate, the above error went away but it doesn't plot combined or any columns from the source!
Here is a solution for your problem using lines to plot the data.
The idea in the JS part is, to initialize for every click your values for combined with 0 and then loop over all selected labels given through the values in active and add them to combined.
This is done in two cascaded for loops.
import pandas as pd
from bokeh.plotting import figure, output_notebook, show
from bokeh.layouts import column
from bokeh.models import ColumnDataSource, CustomJS
from bokeh.models.widgets import CheckboxButtonGroup
output_notebook()
# data
names = ['Number', 'A', 'B', 'C']
rows = [(340, 77, 70, 15),
(341, 80, 73, 15),
(342, 83, 76, 16),
(343, 86, 78, 17)]
data = pd.DataFrame(rows, columns=names)
data['combined'] = 0
source = ColumnDataSource(data)
# figure
p = figure(width=300, height=300, y_range=(-10, 200))
p.line(x='Number', y='A', color='red', source=source)
p.line(x='Number', y='B', color='green', source=source)
p.line(x='Number', y='C', color='blue', source=source)
p.line(x='Number', y='combined', color='black', line_width=2, source=source)
# JS
callback = CustomJS(args=dict(source=source), code="""
const labels = cb_obj.labels;
const active = cb_obj.active;
const data = source.data;
// initialize with 0
for(let i=0; i<data['Number'].length; i++)
data['combined'][i] = 0
// add new value for every selected label
for(let a of active){
let active_label = labels[a]
for(let i=0; i<data['Number'].length; i++)
data['combined'][i] += data[active_label][i]
}
source.change.emit();
""")
checkbox_group = CheckboxButtonGroup(labels=names[1:], active=[], width=400)
checkbox_group.js_on_change("active", callback)
# output
final_col = column(p, checkbox_group)
show(final_col)
Output
Comment
To add a legend label for each line renderer a legend_label="value" to p.line().