I want to build a little thing that allows you to visualize different plots by either scrubbing through a dataset, or by hovering over points in scatter plots. I have a working example here:
import numpy as np
import bokeh
from bokeh.models import ColumnDataSource, CustomJS, Slider
from bokeh.plotting import figure, show, output_notebook
from bokeh.models import HoverTool
from bokeh.layouts import layout
output_notebook()
# make some fake data
n = 100
n_time_pts = 1000
time = np.linspace(0,1,n_time_pts)
all_traces = [np.random.random(n_time_pts) + x for x in np.arange(n)]
x = [np.mean(x) for x in all_traces]
y = [np.std(x) for x in all_traces]
z = [np.std(x)/np.mean(x) for x in all_traces]
source = ColumnDataSource(data=dict(time=time, y=all_traces[0]))
scatter_source = ColumnDataSource(data=dict(x=x, y=y,z=z))
marker_source = ColumnDataSource(data=dict(x=[1], y=[1], z=[1]))
slider = Slider(start=0, end=n, step=1, value=0, max_height=30, max_width=500)
# make figure panels
trace_fig = figure(width=1000, height=500, tools=[])
scatter1 = figure(sizing_mode="stretch_width",
height=500,
max_width = 500,
tools=[])
scatter2 = figure(sizing_mode="stretch_width",
height=500,
max_width = 500,
tools=[])
# make colors
colors = list(bokeh.palettes.inferno(n))
rejected_color = "#969696"
for i in np.arange(10):
colors[i] = rejected_color
colors = tuple(colors)
# make plots in figures
trace_fig.line('time','y',source=source)
scatter1.circle(x,y, size=10, color=colors, alpha=0.8, hover_alpha=0)
marker1 = scatter1.circle('x','y',size=20,fill_color = None, color="red", source=marker_source)
scatter2.circle(x,z, size=10, color=colors, alpha=0.8)
marker2 = scatter2.circle('x','z',size=20,fill_color = None, color="red", source=marker_source)
# add a hover tool that sets the link data for a hovered circle
code = """
if (cb_data.index.indices.length > 0) {
if (cb_data.index.indices[0] > 0) { // dirty hack to ignore the zero index of the marker
slider.value = (cb_data.index.indices[0])
}
};
"""
callback = CustomJS(args=dict(slider=slider), code=code)
scatter1.add_tools(HoverTool(tooltips=[],callback=callback))
scatter2.add_tools(HoverTool(tooltips=[],callback=callback))
# slider callback
callback = CustomJS(args=dict(source=source,
all_traces=all_traces,
slider=slider,
marker_source=marker_source,
scatter_source=scatter_source),
code="""
source.data.y = all_traces[slider.value];
source.change.emit();
marker_source.data.x[0] = scatter_source.data.x[slider.value];
marker_source.data.y[0] = scatter_source.data.y[slider.value];
marker_source.data.z[0] = scatter_source.data.z[slider.value];
marker_source.change.emit();
""")
slider.js_on_change('value', callback)
show(layout([
[slider],
[trace_fig],
[scatter1, scatter2],
]))
but it’s pretty hacky. specifically:
- the way I update the “hovered” point in the two scatter plots is by using a different scatter, and ignoring callbacks with index 0.
- the tooltips are buggy too, and render as small white boxes.
any advice to improve this would be welcome! thank you!