FChm
FChm

Reputation: 2600

Bokeh hovertools which run arbitrary python code

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)

example_hover_tool

Upvotes: 4

Views: 1450

Answers (1)

Tony
Tony

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:

enter image description here

Upvotes: 4

Related Questions