Update HoverTool's value when data source changes, without moving mouse

A minimal reproducible example of this would be somewhat long, but the gist is this:

  1. I have a choropleth of the US (created via fig.patches) whose data is an ordinary ColumnDataSource with a filtering CDSView. The regions of the choropleth are US states.
  2. This figure is configured to display a HoverTool tooltip when I mouse over individual states.
  3. In CustomJS code, I have a javascript timer (from setInterval) that periodically modifies the data source when I press a “play” button, creating an animation. This works as expected; once I press “play”, the map’s colors change automatically to show how states’ numbers have changed over time.

Now, the issue is that when I start the animation, mouseover a state, and then leave my cursor there without moving it, the tooltip’s values do not update as the animation plays and the data source updates. In order to force the tooltip to update, I have to jiggle the mouse slightly; it seems that it will only update in response to mouse movement and not to the source.change.emit() call in my CustomJS.

I tried to fake mouse movement by adding the following to the CustomJS:

const bkElems = document.getElementsByClassName('bk')
for (let bkElem of bkElems) {
    bkElem.dispatchEvent(new Event('mousemove'))
}

But this caused the hover tooltip to disappear altogether (until I moved it again, and then it would disappear again the next time the CustomJS was called).

So, I am wondering how to connect a HoverTool to a data source so that changes in the data source propagate to the HoverTool without the mouse moving.

Something like this seems to work:

from bokeh.layouts import column
from bokeh.models import Button, CustomJS, HoverTool, ColumnDataSource
from bokeh.plotting import figure, show

ds = ColumnDataSource(dict(r=[1]))
ht = HoverTool(tooltips=[('Radius', '@r')])
p = figure(x_range=(-1, 1), y_range=(-1, 1), tools=[ht])
p.circle(x=0, y=0, radius='r', source=ds)

b = Button(label='Start')
b.js_on_click(CustomJS(args=dict(ds=ds),
                       code="""\
                           let prev_val = null;
                           ds.inspect.connect(v => prev_val = v);
                           let i = 0;
                           setInterval(() => {
                               ds.data = {r: [(Math.sin(i) + 1.2) / 2.2]};
                               if (prev_val != null) {
                                   ds.inspect.emit(prev_val);
                               }
                               i += 0.05;
                           }, 10);
                       """))

show(column(b, p))

That worked perfectly! Thank you very much. And thanks for putting together a minimal example as well.

Also do you know where I can find documentation on ds.inspect?

There is not any, it’s up till now still mostly considered an internal implementation detail. I’d suggest if you are interested in something that is more officially supported / documented that it would be appropriate to open a GH issue to discuss these new use-cases, so that appropriate user-facing API can be exposed and maintained under test.