4

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

1 Answer 1

4

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

Sign up to request clarification or add additional context in comments.

4 Comments

Thanks for you answer Tony, I will accept it in due time if no other answer comes forward. I did fear such an implementation would be necessary. Its a shame it can't be easily done as hovering figure would look much cleaner in my code.
This is nice, thanks! I'll implement it for some example datasets and compare to your previous suggestion (static figure next to main figure). I will be working in tabs, with only two figures on this tab, so I might be able to make it work for my solution.
On reflection, while this does have the desired functionality it requires use of a JavaScript callback, it would be nice if you could use Python in the callback. I will do some more research into callbacks as I've never done them... Edit: It looks like fundamentally JS must be used on callbacks but there are some functions which may be of use: CustomJS.from_py_func
from_py_func() can contain only simple JS code that can be easily translated onto BokehJS. It will not fulfil your requirements.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.