diff --git a/holoviews/core/overlay.py b/holoviews/core/overlay.py index c8042dd3be..39eebb244e 100644 --- a/holoviews/core/overlay.py +++ b/holoviews/core/overlay.py @@ -287,6 +287,12 @@ def ddims(self): def shape(self): raise NotImplementedError + def clone(self, data=None, shared_data=True, new_type=None, link=True, + *args, **overrides): + if data is None and link: + overrides['plot_id'] = self._plot_id + return super().clone(data, shared_data=shared_data, new_type=new_type, link=link, *args, **overrides) + class NdOverlay(Overlayable, UniformNdMapping, CompositeOverlay): """ diff --git a/holoviews/plotting/bokeh/links.py b/holoviews/plotting/bokeh/links.py index 2ce573c20c..55850eeb08 100644 --- a/holoviews/plotting/bokeh/links.py +++ b/holoviews/plotting/bokeh/links.py @@ -85,7 +85,7 @@ def find_links(cls, root_plot): Traverses the supplied plot and searches for any Links on the plotted objects. """ - plot_fn = lambda x: isinstance(x, GenericElementPlot) and not isinstance(x, GenericOverlayPlot) + plot_fn = lambda x: isinstance(x, (GenericElementPlot, GenericOverlayPlot)) plots = root_plot.traverse(lambda x: x, [plot_fn]) potentials = [cls.find_link(plot) for plot in plots] source_links = [p for p in potentials if p is not None] diff --git a/holoviews/plotting/bokeh/plot.py b/holoviews/plotting/bokeh/plot.py index 9c936b901d..5001c6ba02 100644 --- a/holoviews/plotting/bokeh/plot.py +++ b/holoviews/plotting/bokeh/plot.py @@ -331,11 +331,11 @@ def init_links(self): cb = Link._callbacks['bokeh'][type(link)] if src_plot is None or (link._requires_target and tgt_plot is None): continue + # The link callback (`cb`) is instantiated (with side-effects). callbacks.append(cb(self.root, link, src_plot, tgt_plot)) return callbacks - class CompositePlot(BokehPlot): """ CompositePlot is an abstract baseclass for plot types that draw diff --git a/holoviews/plotting/plot.py b/holoviews/plotting/plot.py index d4c91e9a16..097d2941a7 100644 --- a/holoviews/plotting/plot.py +++ b/holoviews/plotting/plot.py @@ -994,7 +994,7 @@ def link_sources(self): zorders = [self.zorder] if isinstance(self, GenericOverlayPlot) and not self.batched: - sources = [] + sources = [self.hmap.last] elif not self.static or isinstance(self.hmap, DynamicMap): sources = [o for i, inputs in self.stream_sources.items() for o in inputs if i in zorders] diff --git a/holoviews/tests/plotting/bokeh/test_links.py b/holoviews/tests/plotting/bokeh/test_links.py index 7ccb0c9aa4..78fdb9f3cb 100644 --- a/holoviews/tests/plotting/bokeh/test_links.py +++ b/holoviews/tests/plotting/bokeh/test_links.py @@ -2,7 +2,7 @@ import pytest from holoviews.core.spaces import DynamicMap -from holoviews.element import Curve, Polygons, Table, Scatter, Path, Points +from holoviews.element import Curve, Image, Polygons, Table, Scatter, Path, Points from holoviews.plotting.links import (Link, RangeToolLink, DataLink) from bokeh.models import ColumnDataSource @@ -26,6 +26,34 @@ def test_range_tool_link_callback_single_axis(self): self.assertEqual(range_tool.x_range, tgt_plot.handles['x_range']) self.assertIs(range_tool.y_range, None) + def test_range_tool_link_callback_single_axis_overlay_target(self): + from bokeh.models import RangeTool + array = np.random.rand(100, 2) + src = Curve(array) + target = Scatter(array, label='a') * Scatter(array, label='b') + RangeToolLink(src, target) + layout = target + src + plot = bokeh_renderer.get_plot(layout) + tgt_plot = plot.subplots[(0, 0)].subplots['main'] + src_plot = plot.subplots[(0, 1)].subplots['main'] + range_tool = src_plot.state.select_one({'type': RangeTool}) + self.assertEqual(range_tool.x_range, tgt_plot.handles['x_range']) + self.assertIs(range_tool.y_range, None) + + def test_range_tool_link_callback_single_axis_overlay_target_image_source(self): + from bokeh.models import RangeTool + data = np.random.rand(50, 50) + target = Curve(data) * Curve(data) + source = Image(np.random.rand(50, 50), bounds=(0, 0, 1, 1)) + RangeToolLink(source, target) + layout = target + source + plot = bokeh_renderer.get_plot(layout) + tgt_plot = plot.subplots[(0, 0)].subplots['main'] + src_plot = plot.subplots[(0, 1)].subplots['main'] + range_tool = src_plot.state.select_one({'type': RangeTool}) + self.assertEqual(range_tool.x_range, tgt_plot.handles['x_range']) + self.assertIs(range_tool.y_range, None) + def test_range_tool_link_callback_both_axes(self): from bokeh.models import RangeTool array = np.random.rand(100, 2)