问题
I am trying to add checkboxes in my bokeh plot so that I can hide or show different lines in my plot. I found some code from github and modified it to fulfil my purpose. Please have a look into the below code,
for data, name, color in zip([AAPL, IBM, MSFT, GOOG], ["AAPL", "IBM", "MSFT", "GOOG"], Spectral4):
df = pd.DataFrame(data)
source = ColumnDataSource(data = dict(date = pd.to_datetime(df['date']), close = df['close']))
plt = fig.line('date', 'close', line_width=2, color=color, alpha=0.8,
muted_color=color, muted_alpha=0.2, legend = name, source=source)
graph_labels.append(name)
plots.append(plt)
checkbox = CheckboxGroup(labels = graph_labels, active = [0,1,2,3])
checkbox.callback = CustomJS(args = dict(line0 = plots[0], line1=plots[1], line2=plots[2], line3=plots[3]), code="""
//console.log(cb_obj.active);
line0.visible = false;
line1.visible = false;
line2.visible = false;
line3.visible = false;
for (i in cb_obj.active) {
//console.log(cb_obj.active[i]);
if (cb_obj.active[i] == 0) {
line0.visible = true;
} else if (cb_obj.active[i] == 1) {
line1.visible = true;
} else if (cb_obj.active[i] == 2) {
line2.visible = true;
} else if (cb_obj.active[i] == 3) {
line3.visible = true;
}
}
""")
layout = row(fig, widgetbox(checkbox), sizing_mode='fixed')
show(layout)
This code works perfectly. However my requirment is something else. In my case the number of plts will be different each time I run the code as my data is different. So I tried to modify this code but have not had any success yet.
The changes I made are
checkbox = CheckboxGroup(labels = graph_labels, active = list(range(0, len(plots))))
arg_list = []
for idx in range(0, len(plots)):
arg_list.append('line' + str(idx))
arg_list.append(plots[idx])
i = iter(arg_list)
checkbox.callback = CustomJS(args = dict(izip(i, i)), code="""
// Here I don't know how to use dynamic names for line0 and line1 and use them to control their visibility
// As type of line0 is object and if I 'm trying to make a dynamic string I can't convert it to object and it fails
I also tried using
source = ColumnDataSource(data = dict( ... ) ...
callback = CustomJS(args=dict(source=source), code="""
But it failed as well and did not show any plot. I'm using the latest version of Bokeh and python 2.7 Any suggestion is highly appriciated and thanks in advance !! :)
回答1:
You can do something like this:
from bokeh.io import show
from bokeh.plotting import figure
from bokeh.models import CustomJS, CheckboxGroup
from bokeh.layouts import Row
from bokeh.palettes import Category20_20
from random import random,choice
N_lines = int(100*random())/10 # undefined but known from the start number of lines.
x= range(3)
fig = figure()
args = []
code = "active = cb_obj.active;"
for i in range(N_lines):
glyph = fig.line(x,[random() for j in x],color=choice(Category20_20))
args += [('glyph'+str(i),glyph)]
code += "glyph{}.visible = active.includes({});".format(i,i)
checkbox = CheckboxGroup(labels=[str(i) for i in range(N_lines)],active=range(N_lines))
checkbox.callback = CustomJS(args={key:value for key,value in args},code=code)
show(Row(fig,checkbox))
This builds the callback code based on the number of lines. Since you asked for the code to adapt to your data, you can certainly determine the number of lines from the data.
After that, if you also want to add lines dynamically through interactions you need to update:
checkbox.labels
(just add one label name)
checkbox.active
(set it to a range() list with one more number)
checkbox.callback
(with one more pair for "args" and one more entry for "code")
回答2:
Actually I found out the answer to this question from the following post
How to interactively display and hide lines in a Bokeh plot?
In the Comment section user2561747 has suggested something similar. Initially I was having problems becasue I wanted to use hover tool as well for which I was trying to name each plot differently and then something like that
for column, color in zip(df.columns.values, Spectral4):
......
......
plt = fig.line('date', 'value', name = column, line_width = 2, color = color, alpha = 0.8,
muted_color = color, muted_alpha = 0.2, legend = column, source = source)
graph_labels.append(column)
plots.append(plt)
......
......
checkbox = CheckboxGroup(labels = graph_labels, active = list(range(0, len(plots))))
checkbox.callback = CustomJS.from_coffeescript(args = dict(plot = fig, checkbox = checkbox), code="""
rends = [];
rends.push plot.select(label) for label in checkbox.labels;
rends[i].visible = i in checkbox.active for i in [0...rends.length];
""")
I could never figure out what the problem was but I ended up naming all the plots "hideable" and it worked for the hover tool as well. So the final code looks something like this,
checkbox.callback = CustomJS.from_coffeescript(args = dict(plot = fig, checkbox = checkbox), code="""
rends = plot.select("hideable");
rends[i].visible = i in checkbox.active for i in [0...rends.length];
""")
If anyone can find out the problem with the 1st approach that would be awesome. I saw absolutely no error in the browser console however the elements of rends array had different structures in both cases.
来源:https://stackoverflow.com/questions/49203844/using-checkbox-widget-in-bokeh-to-hide-or-show-lines-for-dynamic-number-of-lines