diff --git a/holoviews/plotting/plot.py b/holoviews/plotting/plot.py index eb963afea2..37e18e483f 100644 --- a/holoviews/plotting/plot.py +++ b/holoviews/plotting/plot.py @@ -25,7 +25,7 @@ from ..core.options import Compositor, SkipRendering, Store, lookup_options from ..core.overlay import CompositeOverlay, NdOverlay, Overlay from ..core.spaces import DynamicMap, HoloMap -from ..core.util import isfinite, stream_parameters +from ..core.util import isfinite, stream_parameters, unique_iterator from ..element import Graph, Table from ..selection import NoOpSelectionDisplay from ..streams import RangeX, RangeXY, RangeY, Stream @@ -1817,8 +1817,8 @@ def _create_subplots(self, ranges): keys, vmaps = self.hmap._split_overlays() if isinstance(self.hmap, DynamicMap): - dmap_streams = [get_nested_streams(layer) for layer in - split_dmap_overlay(self.hmap)] + dmap_streams = [streams+get_nested_streams(layer) for layer, streams in + zip(*split_dmap_overlay(self.hmap))] else: dmap_streams = [None]*len(keys) @@ -1830,6 +1830,8 @@ def _create_subplots(self, ranges): subplots = {} for (key, vmap, streams) in zip(keys, vmaps, dmap_streams): + if streams: + streams = list(unique_iterator(streams)) subplot = self._create_subplot(key, vmap, streams, ranges) if subplot is None: continue diff --git a/holoviews/plotting/util.py b/holoviews/plotting/util.py index 27b035ab81..d643216cbf 100644 --- a/holoviews/plotting/util.py +++ b/holoviews/plotting/util.py @@ -114,7 +114,7 @@ def isoverlay_fn(obj): """ Determines whether object is a DynamicMap returning (Nd)Overlay types. """ - return isinstance(obj, DynamicMap) and (isinstance(obj.last, CompositeOverlay)) + return isinstance(obj, CompositeOverlay) or (isinstance(obj, DynamicMap) and (isinstance(obj.last, CompositeOverlay))) def overlay_depth(obj): @@ -233,28 +233,35 @@ def split_dmap_overlay(obj, depth=0): to determine if a stream update should redraw a particular subplot. """ - layers = [] + layers, streams = [], [] if isinstance(obj, DynamicMap): initialize_dynamic(obj) if issubclass(obj.type, NdOverlay) and not depth: for _ in obj.last.values(): layers.append(obj) + streams.append(obj.streams) elif issubclass(obj.type, Overlay): if obj.callback.inputs and is_dynamic_overlay(obj): for inp in obj.callback.inputs: - layers += split_dmap_overlay(inp, depth+1) + split, sub_streams = split_dmap_overlay(inp, depth+1) + layers += split + streams += [s+obj.streams for s in sub_streams] else: for _ in obj.last.values(): layers.append(obj) + streams.append(obj.streams) else: layers.append(obj) - return layers + streams.append(obj.streams) + return layers, streams if isinstance(obj, Overlay): for _k, v in obj.items(): layers.append(v) + streams.append([]) else: layers.append(obj) - return layers + streams.append([]) + return layers, streams def initialize_dynamic(obj): diff --git a/holoviews/tests/plotting/test_plotutils.py b/holoviews/tests/plotting/test_plotutils.py index 1651607db8..4f77a53c2c 100644 --- a/holoviews/tests/plotting/test_plotutils.py +++ b/holoviews/tests/plotting/test_plotutils.py @@ -202,6 +202,13 @@ def test_dynamic_compute_overlayable_zorders_mixed_dynamic_and_non_dynamic_ndove self.assertIn(curve, sources[2]) self.assertNotIn(ndoverlay, sources[2]) + def test_dynamic_compute_overlayable_zorders_ndoverlays_as_input(self): + ndoverlay1 = NdOverlay({i: Area(range(10+i)) for i in range(2)}).apply(lambda el: el.get(0), dynamic=True) + ndoverlay2 = NdOverlay({i: Area((range(15, 25+i), range(10+i))) for i in range(2)}).apply(lambda el: el.get(0), dynamic=True) + combined = ndoverlay1*ndoverlay2 + combined[()] + sources = compute_overlayable_zorders(combined) + assert len(sources) == 2 def test_dynamic_compute_overlayable_zorders_mixed_dynamic_and_dynamic_ndoverlay_with_streams(self): ndoverlay = DynamicMap(lambda x: NdOverlay({i: Area(range(10+i)) for i in range(2)}), @@ -364,61 +371,61 @@ def test_dmap_ndoverlay(self): test = self.dmap_ndoverlay initialize_dynamic(test) layers = [self.dmap_ndoverlay, self.dmap_ndoverlay] - self.assertEqual(split_dmap_overlay(test), layers) + self.assertEqual(split_dmap_overlay(test)[0], layers) def test_dmap_overlay(self): test = self.dmap_overlay initialize_dynamic(test) layers = [self.dmap_overlay, self.dmap_overlay] - self.assertEqual(split_dmap_overlay(test), layers) + self.assertEqual(split_dmap_overlay(test)[0], layers) def test_dmap_element_mul_dmap_overlay(self): test = self.dmap_element * self.dmap_overlay initialize_dynamic(test) layers = [self.dmap_element, self.dmap_overlay, self.dmap_overlay] - self.assertEqual(split_dmap_overlay(test), layers) + self.assertEqual(split_dmap_overlay(test)[0], layers) def test_dmap_element_mul_dmap_ndoverlay(self): test = self.dmap_element * self.dmap_ndoverlay initialize_dynamic(test) layers = [self.dmap_element, self.dmap_ndoverlay] - self.assertEqual(split_dmap_overlay(test), layers) + self.assertEqual(split_dmap_overlay(test)[0], layers) def test_dmap_element_mul_element(self): test = self.dmap_element * self.element initialize_dynamic(test) layers = [self.dmap_element, self.element] - self.assertEqual(split_dmap_overlay(test), layers) + self.assertEqual(split_dmap_overlay(test)[0], layers) def test_dmap_element_mul_overlay(self): test = self.dmap_element * self.overlay initialize_dynamic(test) layers = [self.dmap_element, self.el1, self.el2] - self.assertEqual(split_dmap_overlay(test), layers) + self.assertEqual(split_dmap_overlay(test)[0], layers) def test_dmap_element_mul_ndoverlay(self): test = self.dmap_element * self.ndoverlay initialize_dynamic(test) layers = [self.dmap_element, self.ndoverlay] - self.assertEqual(split_dmap_overlay(test), layers) + self.assertEqual(split_dmap_overlay(test)[0], layers) def test_dmap_overlay_mul_dmap_ndoverlay(self): test = self.dmap_overlay * self.dmap_ndoverlay initialize_dynamic(test) layers = [self.dmap_overlay, self.dmap_overlay, self.dmap_ndoverlay] - self.assertEqual(split_dmap_overlay(test), layers) + self.assertEqual(split_dmap_overlay(test)[0], layers) def test_dmap_overlay_mul_element(self): test = self.dmap_overlay * self.element initialize_dynamic(test) layers = [self.dmap_overlay, self.dmap_overlay, self.element] - self.assertEqual(split_dmap_overlay(test), layers) + self.assertEqual(split_dmap_overlay(test)[0], layers) def test_dmap_overlay_mul_overlay(self): test = self.dmap_overlay * self.overlay initialize_dynamic(test) layers = [self.dmap_overlay, self.dmap_overlay, self.el1, self.el2] - self.assertEqual(split_dmap_overlay(test), layers) + self.assertEqual(split_dmap_overlay(test)[0], layers) def test_dmap_all_combinations(self): test = (self.dmap_overlay * self.element * self.dmap_ndoverlay * @@ -427,28 +434,28 @@ def test_dmap_all_combinations(self): layers = [self.dmap_overlay, self.dmap_overlay, self.element, self.dmap_ndoverlay, self.el1, self.el2, self.dmap_element, self.ndoverlay] - self.assertEqual(split_dmap_overlay(test), layers) + self.assertEqual(split_dmap_overlay(test)[0], layers) def test_dmap_overlay_operation_mul_dmap_ndoverlay(self): mapped = operation(self.dmap_overlay) test = mapped * self.dmap_ndoverlay initialize_dynamic(test) layers = [mapped, mapped, self.dmap_ndoverlay] - self.assertEqual(split_dmap_overlay(test), layers) + self.assertEqual(split_dmap_overlay(test)[0], layers) def test_dmap_overlay_linked_operation_mul_dmap_ndoverlay(self): mapped = operation(self.dmap_overlay, link_inputs=True) test = mapped * self.dmap_ndoverlay initialize_dynamic(test) layers = [mapped, mapped, self.dmap_ndoverlay] - self.assertEqual(split_dmap_overlay(test), layers) + self.assertEqual(split_dmap_overlay(test)[0], layers) def test_dmap_overlay_linked_operation_mul_dmap_element_ndoverlay(self): mapped = self.dmap_overlay.map(lambda x: x.get(0), Overlay) test = mapped * self.element * self.dmap_ndoverlay initialize_dynamic(test) layers = [mapped, self.element, self.dmap_ndoverlay] - self.assertEqual(split_dmap_overlay(test), layers) + self.assertEqual(split_dmap_overlay(test)[0], layers) class TestPlotColorUtils(ComparisonTestCase):