1

I've studied the post:

"How do I link the CrossHairTool in bokeh over several plots?" (See How do I link the CrossHairTool in bokeh over several plots?.

I used the function written by Hamid Fadishei on June 2020 within this post but cannot manage to get the CrossHairTool to correctly display over several plots.

In my implementation, the crosshair displays only within the plot hovered over. I am currently using Bokeh version 2.1.1 with Python Anaconda version 3.7.6 using the Python extension in VSCode version 1.48. I am not familiar with Javascript, so any help to debug my code to correctly display the crosshair across the two plots will be welcomed.

My code:

# Importing libraries:
import pandas as pd
import random
from datetime import datetime, timedelta
from bokeh.models import CustomJS, CrosshairTool, ColumnDataSource, DatetimeTickFormatter, HoverTool
from bokeh.layouts import gridplot
from bokeh.plotting import figure, output_file, show

# Function wrote by Hamid Fadishei to enable a linked crosshair within gridplot:
def add_vlinked_crosshairs(figs):
    js_leave = ''
    js_move = 'if(cb_obj.x >= fig.x_range.start && cb_obj.x <= fig.x_range.end &&\n'
    js_move += 'cb_obj.y >= fig.y_range.start && cb_obj.y <= fig.y_range.end){\n'
    for i in range(len(figs)-1):
        js_move += '\t\t\tother%d.spans.height.computed_location = cb_obj.sx\n' % i
    js_move += '}else{\n'
    for i in range(len(figs)-1):
        js_move += '\t\t\tother%d.spans.height.computed_location = null\n' % i
        js_leave += '\t\t\tother%d.spans.height.computed_location = null\n' % i
    js_move += '}'
    crosses = [CrosshairTool() for fig in figs]
    for i, fig in enumerate(figs):
        fig.add_tools(crosses[i])
        args = {'fig': fig}
        k = 0
        for j in range(len(figs)):
            if i != j:
                args['other%d'%k] = crosses[j]
                k += 1
        fig.js_on_event('mousemove', CustomJS(args=args, code=js_move))
        fig.js_on_event('mouseleave', CustomJS(args=args, code=js_leave))

# Create dataframe consisting of 5 random numbers within column A and B as a function of an arbitrary time range:
startDate = datetime(2020,5,1)
timeStep = timedelta(minutes = 5)
df = pd.DataFrame({
    "Date": [startDate  + (i * timeStep) for i in range(5)],
    "A": [random.randrange(1, 50, 1) for i in range(5)],
    "B": [random.randrange(1, 50, 1) for i in range(5)]})

# Generate output file as html file:
output_file("test_linked_crosshair.html", title='Results')
# Define selection tools within gridplot:
select_tools = ["xpan", "xwheel_zoom", "box_zoom", "reset", "save"]

sample = ColumnDataSource(df)

# Define figures:
fig_1 = figure(plot_height=250,
             plot_width=800,
             x_axis_type="datetime",
             x_axis_label='Time',
             y_axis_label='A',
             toolbar_location='right',
             tools=select_tools)

fig_1.line(x='Date', y='A',
          source=sample,
          color='blue',
          line_width=1)

fig_2 = figure(plot_height=250,
             plot_width=800,
             x_range=fig_1.x_range,
             x_axis_type="datetime",
             x_axis_label='Time',
             y_axis_label='B',
             toolbar_location='right',
             tools=select_tools)

fig_2.line(x='Date', y='B',
          source=sample,
          color='red',
          line_width=1)

# Define hover tool for showing timestep and value of crosshair on graph:
fig_1.add_tools(HoverTool(tooltips=[('','@Date{%F,%H:%M}'),
                                   ('','@A{0.00 a}')],
                         formatters={'@Date':'datetime'},mode='vline'))

fig_2.add_tools(HoverTool(tooltips=[('','@Date{%F,%H:%M}'),
                                   ('','@B{0.00 a}')],
                         formatters={'@Date':'datetime'},mode='vline'))

# Calling function to enable linked crosshairs within gridplot:
add_vlinked_crosshairs([fig_1, fig_2])
# Generate gridplot:
p = gridplot([[fig_1], [fig_2]])
show(p)

myGraphenter code here

Pranav Choudhary
  • 2,726
  • 3
  • 18
  • 38
Marius
  • 13
  • 4

1 Answers1

2

Here's a solution that works as of Bokeh 2.2.1: Just use the same crosshair tool object for all the plots that need it linked. Like so:

import numpy as np
from bokeh.plotting import figure, show
from bokeh.layouts import gridplot
from bokeh.models import CrosshairTool

plots = [figure() for i in range(6)]
[plot.line(np.arange(10), np.random.random(10)) for plot in plots]

linked_crosshair = CrosshairTool(dimensions="both")

for plot in plots:
    plot.add_tools(linked_crosshair)

show(gridplot(children=[plot for plot in plots], ncols=3))
K3---rnc
  • 6,717
  • 3
  • 31
  • 46