I have an issue when trying to link several plots and at the same time to draw cross at mouse position (also on all the plots). The linking is done using js callback, because I need to link different types of ranges (like datetime with numerical, or two numericals with offset). The hover cross is done by drawing rays and updating ColumnDataSource in js callback.
What happens here is that when I try to pan/zoom the first plot before moving the second, it’s stuck, but once the second plot is moved it starts to work as it should. The linking only works fine, as well as only crosses, but not both at the same time.
I’ve made a small example to reproduce the issue:
from datetime import datetime, timedelta
import bokeh
from bokeh.layouts import column
from bokeh.plotting import figure, show
from bokeh.io import output_file
from bokeh.model import Model
from bokeh.models import HoverTool, Range, CustomJS, ColumnDataSource
output_file(‘axis-link-bug.html’)
first = figure(x_axis_type=‘datetime’)
second = figure(x_axis_type=‘datetime’)
first.line([datetime.now(), datetime.now()+timedelta(seconds=100000)], [0, 100])
second.line([datetime.now(), datetime.now()+timedelta(seconds=100000)], [0, 100])
first_source = ColumnDataSource(data=dict(x=[0], y=[0], angle=[90]))
second_source = ColumnDataSource(data=dict(x=[0], y=[0], angle=[90]))
first.ray(x=‘x’, y=‘y’, length=0, angle=‘angle’, angle_units=“deg”,
color=‘grey’, line_width=1, source=first_source)
second.ray(x=‘x’, y=‘y’, length=0, angle=‘angle’, angle_units=“deg”,
color=‘grey’, line_width=1, source=second_source)
hover_js = ‘’’
var geometry = cb_data[‘geometry’];
first_source.data[‘x’][0] = geometry.x;
second_source.data[‘x’][0] = geometry.x;
first_source.change.emit();
second_source.change.emit();
‘’’
hover = HoverTool(tooltips=None,
callback=CustomJS(code=hover_js, args={‘first_source’: first_source,
‘second_source’: second_source}))
first.add_tools(hover)
second.add_tools(hover)
class RangeLinker(Model):
ranges = List(Instance(Range))
implementation = “”"
import * as p from “core/properties”
import {Model} from “model”
export class RangeLinker extends Model
type: ‘RangeLinker’
@define {
ranges: [ p.Array, ]
}
handler: (rangeIndex) =>
return (() => @sync(rangeIndex))
initialize: (attrs, options) =>
super(attrs, options)
for r, i in @ranges
@connect(r.change, @handler(i))
@sync(0)
sync: (rangeIndex) =>
range = @ranges[rangeIndex]
for r, i in @ranges
if (i != rangeIndex)
r.attributes[‘start’] = range.start
r.attributes[‘end’] = range.end
if (r.plots? and r.plots.length > 0)
r.plots[0].change.emit()
“”"
time_link = RangeLinker(ranges=[first.x_range, second.x_range])
numeric_link = RangeLinker(ranges=[first.y_range, second.y_range])
first.x_range.callback = CustomJS(args=dict(time_link=time_link))
first.y_range.callback = CustomJS(args=dict(numeric_link=numeric_link))
show(column(first, second))
``