From 200e4b56dbe273029a346df78c63931250f22a23 Mon Sep 17 00:00:00 2001 From: maximlt Date: Fri, 8 Sep 2023 11:08:51 +0200 Subject: [PATCH 1/4] add comment --- holoviews/plotting/bokeh/plot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 325d38e26d3ef83fbf40745efcb2d2ab6583c87a Mon Sep 17 00:00:00 2001 From: maximlt Date: Fri, 8 Sep 2023 11:37:39 +0200 Subject: [PATCH 2/4] allow to link to an overlay --- holoviews/core/overlay.py | 6 ++++++ holoviews/plotting/bokeh/links.py | 2 +- holoviews/plotting/plot.py | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/holoviews/core/overlay.py b/holoviews/core/overlay.py index c8042dd3be..ccd3e14a3c 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 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/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] From 30fdf957db3f09dbd625211b3e944a2b9326ae7f Mon Sep 17 00:00:00 2001 From: maximlt Date: Fri, 8 Sep 2023 11:38:02 +0200 Subject: [PATCH 3/4] add a test --- holoviews/tests/plotting/bokeh/test_links.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/holoviews/tests/plotting/bokeh/test_links.py b/holoviews/tests/plotting/bokeh/test_links.py index 7ccb0c9aa4..766e25ecdc 100644 --- a/holoviews/tests/plotting/bokeh/test_links.py +++ b/holoviews/tests/plotting/bokeh/test_links.py @@ -26,6 +26,20 @@ 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_both_axes(self): from bokeh.models import RangeTool array = np.random.rand(100, 2) From 14869550861111538584840503f237113a403726 Mon Sep 17 00:00:00 2001 From: maximlt Date: Mon, 11 Sep 2023 13:48:48 +0200 Subject: [PATCH 4/4] add test with Image as a source --- holoviews/core/overlay.py | 2 +- holoviews/tests/plotting/bokeh/test_links.py | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/holoviews/core/overlay.py b/holoviews/core/overlay.py index ccd3e14a3c..39eebb244e 100644 --- a/holoviews/core/overlay.py +++ b/holoviews/core/overlay.py @@ -289,7 +289,7 @@ def shape(self): def clone(self, data=None, shared_data=True, new_type=None, link=True, *args, **overrides): - if link: + 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) diff --git a/holoviews/tests/plotting/bokeh/test_links.py b/holoviews/tests/plotting/bokeh/test_links.py index 766e25ecdc..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 @@ -40,6 +40,20 @@ def test_range_tool_link_callback_single_axis_overlay_target(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_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)