0

In a bokeh app, I want to reuse the rendering of tooltips created by the HoverTool.

In particular, I want to select a datapoint in the datasource by some means and then show further information for that point. The selection may happen for example from a slider. I am able to add a self-made label (see code example), but it would be much nicer if it were possible to show the tooltips that are generated by the HoverTool, because they are already nicely formatted.

The example code shows a slider that selects a datapoint and sets a custom label. I would like to avoid using the custom label but trigger the hover tooltips somehow.

from bokeh.io import show
from bokeh.layouts import column
from bokeh.models import HoverTool, ColumnDataSource, Slider, CustomJS, LabelSet
from bokeh.plotting import figure
import numpy as np

x = np.linspace(0,1)
y = np.linspace(0,2)
ds = ColumnDataSource(data=dict(x=x,y=y))

fig = figure()
fig.scatter(x='x', y='y', source=ds)

# a datasource to show labels at a x/y position, set in the JS callback
labels = ColumnDataSource(data=dict(x=[], y=[], t=[], ind=[]))
fig.add_layout(LabelSet(x='x', y='y', text='t', source=labels))

# slider that selects a datapoint and creates the label for the point
slider = Slider(start=0, end=len(x), value=0, step=1)
code = """
    labels.data = {'x':[],'y':[],'t':[]}
    source.selected.indices = [slider.value]
    labels.data = {'ind':[slider.value],
            'x':[source.data.x[slider.value]],
            'y':[source.data.y[slider.value]],
            't':[source.data.x[slider.value]]}
    labels.change.emit()
    source.change.emit()
    """

callback = CustomJS(args=dict(source=ds, slider=slider, labels=labels), code=code)
slider.js_on_change('value', callback)

# hover to show default tooltips, can those be triggered?
hover = HoverTool(mode='vline')
fig.add_tools(hover)

show(column(slider, fig))
gotzl
  • 15
  • 3

1 Answers1

0

Call the HoverToolView._inspect(x, y) where (x, y) is the canvas coordinate of the point. We need to use xscale.compute() and yscale.compute() to convert data coordinate to canvas coordinate.

import numpy as np

from bokeh.plotting import figure, show, output_notebook
from bokeh.models import Slider, HoverTool, CustomJS, ColumnDataSource
from bokeh.layouts import column
output_notebook()

N = 100
x = np.random.random(size=N) * 100
y = np.random.random(size=N) * 100

radii = np.random.random(size=N) * 1.5
colors = [
    "#%02x%02x%02x" % (int(r), int(g), 150) for r, g in zip(50+2*x, 30+2*y)
]

TOOLS="hover,crosshair,pan,wheel_zoom,zoom_in,zoom_out,box_zoom,undo,redo,reset,tap,save,box_select,poly_select,lasso_select,"

p = figure(tools=TOOLS)

source = ColumnDataSource(dict(x=x, y=y, radius=radii, colors=colors))

renderer = p.scatter("x", "y", radius="radius",
          fill_color="colors", fill_alpha=0.6,
          line_color=None, source=source)

slider = Slider(start=0, end=len(x), value=0, step=1)
code = """
let ind = slider.value;
let x = source.data.x[ind];
let y = source.data.y[ind];
let fig_view = Bokeh.index["myplot"].child_views[1];
let hover_view = fig_view.tool_views[hovertool.id];
let renderer_view = fig_view.renderer_views[renderer.id];
let xs = renderer_view.xscale.compute(x);
let ys = renderer_view.yscale.compute(y);
hover_view._inspect(xs, ys);
"""

callback = CustomJS(args=dict(
    fig=p,
    slider=slider, 
    hovertool=p.select_one(HoverTool),
    source=source,
    renderer=renderer
    ), code=code)
slider.js_on_change('value', callback)

show(column(slider, p, id="myplot"))
HYRY
  • 94,853
  • 25
  • 187
  • 187
  • Perfect! Thanks alot! Could you tell me where I could've found this in the docu, in case you know it... – gotzl Jun 10 '19 at 10:17
  • @gotzl, there are no documents about this, you need to read the source code. – HYRY Jun 11 '19 at 12:51
  • Is it possible to get it to work on current version of Bokeh? (2.2.1+). renderer_view is undefined and I couldn't get around it to make it work. – bachree Mar 03 '21 at 13:01