Linked Crosshair

Does someone know what is the easiest way to produce a crosshair pointer that is linked across plots?

I want to use the dedicated crosshair tool from the tool menu and not some dynamic map with (laggy) horizontal and vertical lines. When using Bokeh directly that seems to be easy. See attached stackoverflow question and accepted solution. Essentially you add the SAME crosshair object to several plots.

But I am not familiar enough with bokeh and holoviews to understand how to seamlessly shift between plot manipulation on the bokeh level and holoviews level.

I think I got it. Using a hook.

import numpy as np
import holoviews as hv
from bokeh.models import CrosshairTool

hv.extension('bokeh')

# make plots
s1 = hv.Scatter(np.random.randn(10,10))
s2 = hv.Scatter(np.random.randn(10,10))

# make crosshair object
linked_crosshair = CrosshairTool(dimensions="both")

# make a hook to manipulate bokeh figure properties
def hook(plot, element):
    plot.state.add_tools(linked_crosshair)
    
# apply hook to holoviews plots
s1 = s1.opts(hooks=[hook])
s2 = s2.opts(hooks=[hook])

# show the plots
s1 + s2

2022-10-01 21.47.53

4 Likes

I’ve been looking for ways to do this forever! Can’t believe it’s natively supported by bokeh…

This should probably be the official solution on the holoviews site as opposed to making a dynamic map with a pointer stream (laggy).

@Marc @philippjfr

1 Like

Here are all the available tools as far as I know:

pan, xpan, ypan, xwheel_pan, ywheel_pan, 
wheel_zoom, xwheel_zoom, ywheel_zoom, 
zoom_in, xzoom_in, yzoom_in, 
zoom_out, xzoom_out, yzoom_out, 
click, tap, doubletap, 
crosshair, 
box_select, xbox_select, ybox_select, 
poly_select, lasso_select, 
box_zoom, xbox_zoom, ybox_zoom, 
save, undo, redo, reset, help, 
box_edit, line_edit, point_draw, 
poly_draw, poly_edit, freehand_draw, 
hover

For example there is an undo tool and also a zoom_out tool. All those tools can be added normally to your toolbar. For example this here adds a crosshair tool in the toolbar of a plot.

plot.opts(tools=['crosshair'])

Most of them are described here:

When you look through this page, you will find that you can configure some of the tools the way I did. For configuration, you need instantiate an object. For example the crosshair can be instantiated also as horizontal line only (using dimensions='width'), or as vertical line only (using dimensions='height'), as described on that page.

2 Likes

No, I mean creating a linked cross_hair by using hooks.

Normally crosshairs aren’t linked when supplying the tool object in the plot call, and the fix so far has been to use point stream + dmap. But this is better as it allows you to create linked crosshairs without the lag :slight_smile:

1 Like

Yeah. I am glad I finally figured it out how to do it in holoviews. They are pretty useful. I didn’t expect it to be so easy. :slightly_smiling_face: I have never used a hook before.

@Jan,

This is really really just quite awesome, thanks for sharing your findings! I now have this working over a dmap tap graphs as @Material-Scientist points out now without the lagging alternative

1 Like

Using holoviews 1.16.0 I can’t get the crosshair to link anymore. They just show up on each plot as if they would have been added individually. Has something changed about using hooks or the underlying bokeh tool?

Been using this tip for the last months on previous versions, thanks for pointing it out! :slight_smile:

This update example should work with Holoviews 1.16 and Panel 1.0rc9:

import holoviews as hv
import numpy as np
from bokeh.models import CrosshairTool, Span

hv.extension("bokeh")

# make plots
s1 = hv.Scatter(np.random.randn(10, 10))
s2 = hv.Scatter(np.random.randn(10, 10))

width = Span(dimension="width")
height = Span(dimension="height")


def hook(plot, element):
    plot.state.add_tools(CrosshairTool(overlay=[width, height]))


# apply hook to holoviews plots
s1 = s1.opts(hooks=[hook])
s2 = s2.opts(hooks=[hook])

# show the plots
s1 + s2

Edit: No need for hooks:

import holoviews as hv
import numpy as np
from bokeh.models import CrosshairTool, Span

hv.extension("bokeh")

# make plots
s1 = hv.Scatter(np.random.randn(10, 10))
s2 = hv.Scatter(np.random.randn(10, 10))

width = Span(dimension="width")
height = Span(dimension="height")
cht = CrosshairTool(overlay=[width, height])
s1 = s1.opts(tools=[cht])
s2 = s2.opts(tools=[cht])

# show the plots
s1 + s2
4 Likes

Nice, thanks! :slight_smile: