Hi,
I have been using bokeh with python for some time now. For this new project, I have a huge amount of lines that I need to be able to plot in the same graph, with options to see only some of them at the time.
I found that plotting all glyphs in the same plot, and just make the ones that are not “chosen” invisible, was extremely time consuming wrt loading the figure.
Instead, I use BokehJS in the CustomJS callback to be able to add and remove different glyphs from the plot. It is working fine, but I am struggling with how to update the legends.
Initially, I am adding the legends in the python script by using LegendItems:
legend = Legend(items=legend_items_initial)
plot.add_layout(legend)
Later on, in the BokehJS, I am able to access the legend in the callback, but when I try to update it, it seems like a second plot is added on top of the initial figure. I get the following error-message:
“bokeh-1.3.1.min.js:31 Uncaught TypeError: Cannot read property ‘draw_legend’ of undefined”
My way of trying to update the legend is simply:
legends.items.push(new_legend_item)
There seems to be a link between the figure and the legend, but i cannot access it through e.g. plot.legend. Is there any other way?
An excerpt of my callback is shown below:
checkbox_callback = CustomJS(args = dict(source_list = checkbox_sources, labels = checkbox_labels, glyphs = checkbox_glyphs,
plot = plot, y_ranges = checkbox_yranges, already_active = already_active,
renderer_list = renderer_list, yaxes = yaxes, legends = legend, legend_items = legend_items), code = """
var require = Bokeh.require;
var plt = Bokeh.Plotting.require;
const selected_values = cb_obj.active
//index of glyph in glyphs
//Remove a glyph if it is unclicked in the checkbox
var remove_glyphs = already_active.data['active'].filter(function(obj) { return selected_values.indexOf(obj) == -1; });
var remove_glyphs_index = remove_glyphs[0]
var renderer_index = renderer_list.data['renderer_index'].indexOf(remove_glyphs_index)
if (remove_glyphs.length != 0) {
// visible = false is done because there is a lag between renderer update and plot update -> plot is not updated immediately
plot.renderers[renderer_index].visible = false;
plot.renderers.splice(renderer_index, 1);
renderer_list.data['renderer_index'].splice(renderer_index, 1);
var y_range_name = y_ranges[remove_glyphs_index]
var location = yaxes[y_range_name]['location']
var axis_index = yaxes[y_range_name]['index']
old_no_active = yaxes[y_range_name]['number_of_active']
yaxes[y_range_name]['number_of_active'] = old_no_active - 1
if (yaxes[y_range_name]['number_of_active'] == 0){
if (location == 'left'){
plot.left[axis_index].visible = false;
} else {
plot.right[axis_index].visible = false;
}
}
plot.change.emit();
}
//Add a glyph if it is selected
var new_glyphs = selected_values.filter(function(obj) { return already_active.data['active'].indexOf(obj) == -1; });
var new_glyphs_index = new_glyphs[0];
if (new_glyphs.length != 0) {
var new_glyph = plot.add_glyph(glyphs[new_glyphs_index], source_list[new_glyphs_index]);
renderer_list.data['renderer_index'].push(new_glyphs_index);
var y_range_name = y_ranges[new_glyphs_index]
plot.extra_y_ranges[y_range_name].renderers.push(new_glyph)
plot.renderers[plot.renderers.length - 1].y_range_name = y_range_name
var location = yaxes[y_range_name]['location']
var axis_index = yaxes[y_range_name]['index']
if (location == 'left'){
plot.left[axis_index].visible = true;
}
if (location == 'right'){
plot.right[axis_index].visible = true;
}
old_no_active = yaxes[y_range_name]['number_of_active']
yaxes[y_range_name]['number_of_active'] = old_no_active + 1
legends.items.push(legend_items[new_glyphs_index])
plot.change.emit();
}
already_active.data['active'] = selected_values
already_active.change.emit()
renderer_list.change.emit()
""")