diff --git a/holoviews/plotting/bokeh/element.py b/holoviews/plotting/bokeh/element.py index 89e31d2d92..ea243584c4 100644 --- a/holoviews/plotting/bokeh/element.py +++ b/holoviews/plotting/bokeh/element.py @@ -21,6 +21,7 @@ GlyphRenderer, Legend, Renderer, + Span, Title, tools, ) @@ -577,10 +578,11 @@ def _init_tools(self, element, callbacks=None): tool_list.append(tool) copied_tools = [] + skip_models = (Span,) for tool in tool_list: if isinstance(tool, tools.Tool): properties = { - p: v.clone() if isinstance(v, Model) else v + p: v.clone() if isinstance(v, Model) and not isinstance(v, skip_models) else v for p, v in tool.properties_with_values(include_defaults=False).items() } tool = type(tool)(**properties) diff --git a/holoviews/tests/plotting/bokeh/test_plot.py b/holoviews/tests/plotting/bokeh/test_plot.py index b5c1a5978c..5c7a08a2e1 100644 --- a/holoviews/tests/plotting/bokeh/test_plot.py +++ b/holoviews/tests/plotting/bokeh/test_plot.py @@ -2,10 +2,12 @@ import pyviz_comms as comms from bokeh.models import ( ColumnDataSource, + CrosshairTool, CustomJS, HoverTool, LinearColorMapper, LogColorMapper, + Span, ) from param import concrete_descendents @@ -124,3 +126,17 @@ def test_sync_three_plots(): assert v[0].code == "dst.muted = src.muted" assert isinstance(v[1], CustomJS) assert v[1].code == "dst.muted = src.muted" + + +def test_span_not_cloned_crosshair(): + # See https://github.com/holoviz/holoviews/issues/6386 + height = Span(dimension="height") + cht = CrosshairTool(overlay=height) + + layout = Curve([]).opts(tools=[cht]) + Curve([]).opts(tools=[cht]) + + (fig1, *_), (fig2, *_) = bokeh_renderer.get_plot(layout).handles["plot"].children + tool1 = next(t for t in fig1.tools if isinstance(t, CrosshairTool)) + tool2 = next(t for t in fig2.tools if isinstance(t, CrosshairTool)) + + assert tool1.overlay is tool2.overlay