问题
Short version: bokeh checkboxes with JS callbacks to plot subsets of a dataframe?
Longer version: The answer here gives a good explanation for multiselect, but I want to do the same thing for checkboxes. All my data is in one pandas dataframe, and I'm using the checkboxes to select bits of that dataframe for plotting. I believe that with checkboxes it is something to do with cb_obj.active
but I'm very unsure how to get it to work. In particular my plot includes colored rectangles and text, all information for which: position, text, color, is taken from my dataframe. And I don't know how much plotting must be done in the callback, and how much outside.
As far as I can tell, the callback calls a function to do the actual plotting.
I know I should give a minimal example, but I can't think how to simplify my code enough... so all I really want at the moment is an example of the use of checkboxes, with a JavaScript callback, to plot a subset of a dataframe. Somebody must have done this, but I haven't found an example yet! Thanks.
回答1:
Here is an example:
from bokeh.plotting import figure, show, output_notebook
from bokeh.models import Slider, CheckboxGroup, CustomJS, ColumnDataSource, CDSView
from bokeh.models.filters import CustomJSFilter
from bokeh.layouts import row
from bokeh.transform import factor_cmap
from bokeh.palettes import Category10_10
output_notebook()
You can use CustomJSFilter
to calculate the indice of rows to show:
from bokeh.sampledata import iris
source = ColumnDataSource(data=iris.flowers)
species = iris.flowers.species.unique().tolist()
checkboxes = CheckboxGroup(labels=species, active=list(range(len(species))))
fig = figure()
filter = CustomJSFilter(code="""
let selected = checkboxes.active.map(i=>checkboxes.labels[i]);
let indices = [];
let column = source.data.species;
for(let i=0; i<column.length; i++){
if(selected.includes(column[i])){
indices.push(i);
}
}
return indices;
""", args=dict(checkboxes=checkboxes, source=source))
checkboxes.js_on_change("active", CustomJS(code="source.change.emit();", args=dict(source=source)))
fig.scatter("sepal_length", "sepal_width",
color=factor_cmap("species", Category10_10, species),
source=source, view=CDSView(source=source, filters=[filter]))
show(row(checkboxes, fig))
回答2:
Here's an adapted version of my answer for MultiSelect that you referred to:
from bokeh.models import CustomJS, ColumnDataSource, CheckboxGroup, Column
from bokeh.plotting import figure, show
import pandas as pd
data = dict(letter = ['A','A','B','C','B','B','A','C','C','B'],
x = [1, 2, 1, 2, 3, 2, 2, 3, 2, 3],
y = ['10','20','10','30','10','40','10','30','10','40'])
data = pd.DataFrame(data)
data_source = ColumnDataSource(data)
source = ColumnDataSource(dict(x = [], y = []))
plot = figure()
plot.circle('x', 'y', line_width = 2, source = source)
callback = CustomJS(args = {'source': source, 'data_source': data_source},
code = """
var data = data_source.data;
var s_data = source.data;
var letter = data['letter'];
var select_vals = cb_obj.active.map(x => cb_obj.labels[x]);
console.log(select_vals);
var x_data = data['x'];
var y_data = data['y'];
var x = s_data['x'];
x.length = 0;
var y = s_data['y'];
y.length = 0;
for (var i = 0; i < x_data.length; i++) {
if (select_vals.indexOf(letter[i]) >= 0) {
x.push(x_data[i]);
y.push(y_data[i]);
}
}
source.change.emit();
console.log("callback completed");
""")
chkbxgrp = CheckboxGroup(labels = ['A', 'B', 'C'], active=[])
chkbxgrp.js_on_change('active', callback)
layout = Column(chkbxgrp, plot)
show(layout)
Remarks:
- The callback won't work in Internet Explorer because it uses arrow functions, which IE does not support. If that is an issue, you need do the mapping using something other than arrow functions
- as user bigreddot commented in the answer you referred to, this could also be done using CDSView using a custom Filter, as GroupFilter does not support multiple values
来源:https://stackoverflow.com/questions/56526738/implementing-javascript-callback-for-checkboxes-in-bokeh