Using bokeh, I would like to generate a figure with glyphs that can be selected or un-selected. The selection of a given glyph should result on updating a plot with specific data associated with that given glyph point. I would like to be able to click and select multiple glyphs at the same time, with the plot showing all the different data.
The code below shows where I am
from bokeh.io import show, curdoc
from bokeh.layouts import column
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure
import pandas as pd
import numpy as np
# Create data for clickable plot
x = [0, 1]
y = [0, 1]
table_index = [0, 1]
# Create the clickable plot
plot = figure(height=400, width=600,title='Select a point', tools='tap')
plot_source = ColumnDataSource(data=dict(x=x, y=y))
renderer = plot.circle('x', 'y', source=plot_source, size=30)
# Create two sets of data for the updatable plot
master_data = {}
master_data[0] = {'x_values': [1, 2, 3, 4, 5],'y_values': [6, 7, 2, 3, 6]}
master_data[1] = {'x_values': [1, 2, 3, 4, 5], 'y_values': [6, 5, 4, 3, 2]}
# Create updatable plot
data = master_data[0]
plot_source2 = ColumnDataSource(data=data)
c = figure(title="test", x_axis_label='numbers', y_axis_label='more numbers')
c.line(x='x_values', y='y_values', source=plot_source2, line_width=2)
# Here the reactions of the server are defined
def my_tap_handler(attr, old, new):
index = new[0]
c.line.source = ColumnDataSource(master_data[index]) #problematic line
plot_source.selected.on_change("indices", my_tap_handler)
# Collect it all together in the current doc
curdoc().add_root(column(plot, c))
curdoc().title = 'Select experiment'
This code runs and generates a clickable plot as well as the plot with the data, but there are two problems with it: first, I cannot select both (or neither!) glyphs on the "clickable plot", so as to display both data sets (or none!) in the "updatable plot". Second, the problematic line above is not doing what it should.
Perhaps I don't want to use the on_tap
handler, but rather on_click
, but I could not figure how. As a side, I would love to run this directly on a jupyter notebook, but right now I am forced to save a regular python code "test.py" and run it via bokeh serve --show test.py
. Is there a way of embedding this all on Jupyter?
There are multiple approaches:
multi_line
and change the data for it (shown below)multi_line
and create a ``CDSView` with an index-based filterline
, but plot all of them at the start and later just change their visible
propertyIt's not really documented anywhere (at least, I couldn't find anything), but selecting glyphs with taps also respects Shift and Ctrl keys, although it's not really intuitive. Here's the relevant code from Bokeh that should be self-explanatory.
protected _select_mode(ev: UIEvent): SelectionMode {
const {shiftKey, ctrlKey} = ev
if (!shiftKey && !ctrlKey)
return "replace"
else if (shiftKey && !ctrlKey)
return "append"
else if (!shiftKey && ctrlKey)
return "intersect"
else if (shiftKey && ctrlKey)
return "subtract"
else
unreachable()
}
Notice also that having no selection and having all glyphs selected do not differ visually, but the callback will receive different values.
And here's an example implementation of what you want to achieve.
from bokeh.io import curdoc
from bokeh.layouts import column
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure
plot = figure(height=400, width=600, title='Select a point', tools='tap')
plot_source = ColumnDataSource(data=dict(x=[0, 1], y=[0, 1]))
renderer = plot.circle('x', 'y', source=plot_source, size=30)
master_data = [{'x_values': [1, 2, 3, 4, 5], 'y_values': [6, 7, 2, 3, 6]},
{'x_values': [1, 2, 3, 4, 5], 'y_values': [6, 5, 4, 3, 2]}]
plot_source2 = ColumnDataSource(data=dict(x_values_s=[], y_values_s=[]))
c = figure(title="test", x_axis_label='numbers', y_axis_label='more numbers')
c.multi_line(xs='x_values_s', ys='y_values_s', source=plot_source2, line_width=2)
def my_tap_handler(attr, old, new):
print(new)
plot_source2.data = dict(x_values_s=[master_data[i]['x_values'] for i in new],
y_values_s=[master_data[i]['y_values'] for i in new])
plot_source.selected.on_change("indices", my_tap_handler)
curdoc().add_root(column(plot, c))
curdoc().title = 'Select experiment'