Reputation: 2600
I am using Bokeh to try and create a figure whose data points when 'hovered' over by the user will display another graph within the hover tool, showing additional information about that data point (i.e., in the main figure data points are the mean of a time-series over a set interval, I want the hover tool to show all the data in that interval).
The user guide (full code copied in below) provides one solution: use a custom HTML tooltip to reference figures on file. This would, however, require me creating all the figures on file (which could be up 10,000) to be referenced. This is too large a time overhead so I was hoping for a better solution. Namely: Is it possible for hover tools to run python code on the fly, such that they can display plots of the data interactively?
(Example image, take from user guide, below code)
The below code was copied from the bokeh user guide on 19th March 2019.
from bokeh.plotting import figure, output_file, show, ColumnDataSource
output_file("toolbar.html")
source = ColumnDataSource(data=dict(
x=[1, 2, 3, 4, 5],
y=[2, 5, 8, 2, 7],
desc=['A', 'b', 'C', 'd', 'E'],
imgs=[
'http://docs.bokeh.org/static/snake.jpg',
'http://docs.bokeh.org/static/snake2.png',
'http://docs.bokeh.org/static/snake3D.png',
'http://docs.bokeh.org/static/snake4_TheRevenge.png',
'http://docs.bokeh.org/static/snakebite.jpg'
],
fonts=[
'<i>italics</i>',
'<pre>pre</pre>',
'<b>bold</b>',
'<small>small</small>',
'<del>del</del>'
]
))
TOOLTIPS = """
<div>
<div>
<img
src="@imgs" height="42" alt="@imgs" width="42"
style="float: left; margin: 0px 15px 15px 0px;"
border="2"
></img>
</div>
<div>
<span style="font-size: 17px; font-weight: bold;">@desc</span>
<span style="font-size: 15px; color: #966;">[$index]</span>
</div>
<div>
<span>@fonts{safe}</span>
</div>
<div>
<span style="font-size: 15px;">Location</span>
<span style="font-size: 10px; color: #696;">($x, $y)</span>
</div>
</div>
"""
p = figure(plot_width=400, plot_height=400, tooltips=TOOLTIPS,
title="Mouse over the dots")
p.circle('x', 'y', size=20, source=source)
show(p)
Upvotes: 4
Views: 1450
Reputation: 8297
You can use Python callback only in Bokeh server applications. It seems impossible to use Python callbacks for a HoverTool (it must be always a JS callback, or you get this error: ValueError: expected an instance of type Callback, got <function callback at 0x114fdbb90> of type function
).
The following solution uses JS callback and it shows a small "tooltip plot" when hovering the circles on the main plot (works for Bokeh v1.0.4 and only if there are 2 plots in the Bokeh document):
from bokeh.plotting import figure, show
from bokeh.layouts import gridplot, Row
from bokeh.models import ColumnDataSource, CDSView, BooleanFilter, CustomJS, BoxSelectTool, HoverTool
import pandas as pd
data = {'x': [1, 2, 3],
'y':[1, 2, 3],
'xs':[[9, 8, 7], [6, 5, 4], [3, 2, 1]],
'ys':[[29, 28, 29], [27, 28, 27], [25, 25, 20]]}
source = ColumnDataSource(data)
plot = figure(title = 'PLOT IN HOVER TOOLTIP', tools = '')
circles = plot.circle('x', 'y', size = 20, source = source)
plot_tooltip = figure(name = 'plot_tooltip', plot_width = 200, plot_height = 200, x_axis_location = None, y_axis_location = None, title = None, tools = 'hover', tooltips = [("x", "@x"), ("y", "@y")], toolbar_location = None)
lines = plot_tooltip.line('x', 'y', source = ColumnDataSource({'x': [], 'y': []}))
circles2 = plot_tooltip.circle('x', 'y', source = ColumnDataSource({'x': [], 'y': []}))
code = """
var indices = cb_data.index['1d'].indices;
if (indices.length > 0){
if(plot_tooltip.x_range.bounds == null)
{
Bokeh.documents[0].add_root(plot_tooltip)
}
const idx = indices[0]
lines.data_source.data['x'] = source.data['xs'][idx]
lines.data_source.data['y'] = source.data['ys'][idx]
lines.data_source.change.emit();
circles.data_source.data['x'] = source.data['xs'][idx]
circles.data_source.data['y'] = source.data['ys'][idx]
circles.data_source.change.emit();
div = document.getElementsByClassName('bk-root')[1];
div.style = "position:absolute; left:" + cb_data.geometry['sx'] + "px; top:" + cb_data.geometry['sy'] + "px;";
} """
callback = CustomJS(args = dict(source = source, lines = lines, circles = circles2, plot_tooltip = plot_tooltip), code = code)
hover = HoverTool()
hover.callback = callback
hover.tooltips = None
hover.renderers = [circles]
plot.add_tools(hover)
show(plot)
Result:
Upvotes: 4