It’s a little complicated but here’s a commented working example.
Basically you:
- Create a callback to attach to each HoverTool
- Use
cb_data
in the callback to retrieve both the underlying CDS and the indices of the thing being hovered
- From that CDS and the indices, retrieve the corresponding “lookup field” values (“sample” for your MRE)
- Loop through each source you want linked, finding the indices within that source that match the “sample” values, and updating the inspected.indices property to those
The only wrinkle/question mark is if you want the linkage to “include itself” or not… see “self_hvr” in the code, it can be flipped true/false to handle either.
import pandas as pd
import bokeh.plotting as bk
from bokeh.models.sources import ColumnDataSource
from bokeh.io import show, save
from bokeh.layouts import gridplot
from bokeh.models.tools import HoverTool
from bokeh.models import CustomJS
source1 = ColumnDataSource({'x':[1,2,3],'y':[4,5,6],'sample':['C1','C2','C3']})
source2 = ColumnDataSource({'x':[11,11.5,12,12.5,13,13.5],'y':[14,14.5,15,15.5,16,16.5],'sample':['C1','C2','C3','C1','C2','C3']})
f=bk.figure(tools='hover')
g = bk.figure(tools='hover')
f.scatter('x','y',size=20.0,color='red',source=source1, hover_color='gold')
g.scatter('x','y',size=20.0,color='green',source=source2, hover_color='gold')
hvr_cb = CustomJS(args=dict(srcs=[source1,source2],self_hvr=True)
,code='''
var ins_i = cb_data.index.indices //gets the indices of the thing being hovered
var ins_src = cb_data.renderer.data_source //gets the CDS driving the thing being hovered
//get the 'sample' values you need to look for
var smps = ins_src.data['sample'].filter((x,i)=>ins_i.includes(i))
//if there's a hit/hovered index
if (ins_i.length>0){
//go through all sources to be "linked"
for (var src of srcs){
//self_hvr arg == if you want the hover linkage to include itself
//e.g. you have multiple "C1s" in source1 and you want all "C1s" in source1 to highlight when you hover over all of them
if ((src.id == ins_src.id && self_hvr == true)|src.id != ins_src.id){
//collect all the indices in that source that match with smps
var upd_smps = []
for (var i = 0; i <src.data['sample'].length;i++){
if (smps.includes(src.data['sample'][i])){
upd_smps.push(i)
}
}
//update that source's inspected indices
src.inspected.indices = upd_smps
}
else {
// otherwise no hover
src.inspected.indices = []
}
src.change.emit()
}
}
else {
//if no hit, make all sources have no inspected indices
for (var src of srcs){
src.inspected.indices = []
src.change.emit()
}
}
'''
)
#assign callback to each HoverTool
f_hvr = f.tools[0]
f_hvr.callback = hvr_cb
g_hvr = g.tools[0]
g_hvr.callback = hvr_cb
q = gridplot([[f, g]])
show(q)
