Hover color sync between line and scatter plot

Hello everyone!
I am building a data analysis app, for viewing data collected by sensors in a formula-like vehicle during the race. I would like to do a line plot (of a sensor reading) and a scatter plot of the logged gps positions during the lap, which is the easy part and I have done it.
However I am struggling on the following: When hovering my mouse over the line plot, I would like the corresponding scatter plot point to change color, because it is easier for me to understand which track section the vehicle is at.
A sample code that represents what have I done is the following:

from bokeh.layouts import gridplot
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure, show

source = ColumnDataSource(data=dict(x=[1, 2, 3, 4, 5], y1=[1, 2, 3, 4, 5], y2=[1, 4, 9, 16, 25]))

TOOLS = "box_select,hover,reset"

p1 = figure(height=300, width=300, tools=TOOLS)
p1.line(x="x", y="y1", hover_color="red", source=source)

p2 = figure(height=300, width=300, tools=TOOLS)
p2.scatter(x="x", y="y2", size=10, hover_color="red", source=source)

show(gridplot([[p1, p2]]))

As you can see I have set hover_color parameter on both line and scatter plot, but when I hover my mouser over the line plot, the whole line becomes red and so do all the scatter glyphs. If i change my line plot to scatter the code works as expected, however it is not easy to read time series data using scatter plot.

Maximum length of input data table is 100,000 rows, so a delicate solution (maybe involving javascript ) would be highly appreciated!

Thanks in advance

One idea could be to use a dummy hover glyph for the scatter plot where one updates a source data matching the index from the line hover. It requires the use of CustomJS callback.

In the code below I have a dummy source scat_hover_src which is empty when no line is being hit, and it got data with line glyph hovered.

In CustomJS one can get the hovered index of a source with inspected.line_indices. Hence I update the dummy source with empty arrays if not index, and if hovered, I get the correct data based on the index.

from bokeh.layouts import gridplot
from bokeh.models import ColumnDataSource, HoverTool, CustomJS
from bokeh.plotting import figure, save

source = ColumnDataSource(
	data=dict(
		x=[1, 2, 3, 4, 5],
		y1=[1, 2, 3, 4, 5],
		y2=[1, 4, 9, 16, 25]
		)
	)

scat_hover_src = ColumnDataSource(data = {'x': [], 'y': []})

TOOLS = "box_select,reset"

p1 = figure(height=300, width=300, tools=TOOLS)
r_line = p1.line(x="x", y="y1", hover_color="red", source=source)

p2 = figure(height=300, width=300, tools=TOOLS)
p2.scatter(x="x", y="y2", size=10, source=source)
p2.scatter(x="x", y="y", size=15, fill_color = "red", source=scat_hover_src)

hover_line = HoverTool(
    tooltips = None,
    renderers = [r_line]
)
hover_js_cb_code = '''
  const idx = src.inspected.line_indices;
  const x = [];
  const y = [];

  if (idx.length != 0) {
    console.log(idx);
    x.push(src.data['x'][idx]);
    y.push(src.data['y2'][idx]);
  }
  
  scat_hover_src.data['x'] = x;
  scat_hover_src.data['y'] = y;
  scat_hover_src.change.emit();
'''
callback = CustomJS(
    args = {
        'src': source,
        'scat_hover_src': scat_hover_src
    },
    code = hover_js_cb_code
)
hover_line.callback = callback
p1.add_tools(hover_line)

save(gridplot([[p1, p2]]))
3 Likes

Thanks this is a great idea and it works as expected

Just FYI I think the callback could be simplified along these lines (untested)

hover_js_cb_code = '''
  const idx = src.inspected.line_indices;
  const new_data = {x: [], y: []};

  if (idx.length != 0) {
    new_data.x.push(src.data.x[idx]);
    new_data.y.push(src.data.y2[idx]);
  }

  scat_hover_src.data = new_data;
'''

When possible, it’s always preferable to completely re-assign an entirely new data dict. Bokeh will pick this up automatically, no need to call low-level event changes.

1 Like

I tested your code and it works
Thanks for the suggestion

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.