diff --git a/holoviews/plotting/bokeh/element.py b/holoviews/plotting/bokeh/element.py index 3cd90d7103..2f9ba7b65f 100644 --- a/holoviews/plotting/bokeh/element.py +++ b/holoviews/plotting/bokeh/element.py @@ -179,39 +179,6 @@ def __init__(self, element, plot=None, **params): self._shared = {'x': False, 'y': False} - def _construct_callbacks(self): - """ - Initializes any callbacks for streams which have defined - the plotted object as a source. - """ - if isinstance(self, OverlayPlot): - zorders = [] - elif self.batched: - zorders = list(range(self.zorder, self.zorder+len(self.hmap.last))) - else: - zorders = [self.zorder] - - if isinstance(self, OverlayPlot) and not self.batched: - sources = [] - elif not self.static or isinstance(self.hmap, DynamicMap): - sources = [(i, o) for i, inputs in self.stream_sources.items() - for o in inputs if i in zorders] - else: - sources = [(self.zorder, self.hmap.last)] - cb_classes = set() - for _, source in sources: - streams = Stream.registry.get(id(source), []) - registry = Stream._callbacks['bokeh'] - cb_classes |= {(registry[type(stream)], stream) for stream in streams - if type(stream) in registry and stream.linked} - cbs = [] - sorted_cbs = sorted(cb_classes, key=lambda x: id(x[0])) - for cb, group in groupby(sorted_cbs, lambda x: x[0]): - cb_streams = [s for _, s in group] - cbs.append(cb(self, cb_streams, source)) - return cbs - - def _hover_opts(self, element): if self.batched: dims = list(self.hmap.last.kdims) diff --git a/holoviews/plotting/bokeh/plot.py b/holoviews/plotting/bokeh/plot.py index 5d5ba1642d..b954a691c1 100644 --- a/holoviews/plotting/bokeh/plot.py +++ b/holoviews/plotting/bokeh/plot.py @@ -8,11 +8,13 @@ from bokeh.models.widgets import Panel, Tabs from ...core import (OrderedDict, Store, GridMatrix, AdjointLayout, - NdLayout, Empty, GridSpace, HoloMap, Element) + NdLayout, Empty, GridSpace, HoloMap, Element, + DynamicMap) from ...core.util import basestring, wrap_tuple, unique_iterator from ...element import Histogram +from ...streams import Stream from ..plot import (DimensionedPlot, GenericCompositePlot, GenericLayoutPlot, - GenericElementPlot) + GenericElementPlot, GenericOverlayPlot) from ..util import attach_streams from .util import (layout_padding, pad_plots, filter_toolboxes, make_axis, update_shared_sources, empty_plot) @@ -99,6 +101,40 @@ def get_data(self, element, ranges, style): raise NotImplementedError + def _construct_callbacks(self): + """ + Initializes any callbacks for streams which have defined + the plotted object as a source. + """ + if isinstance(self, GenericOverlayPlot): + zorders = [] + elif self.batched: + zorders = list(range(self.zorder, self.zorder+len(self.hmap.last))) + else: + zorders = [self.zorder] + + if isinstance(self, GenericOverlayPlot) and not self.batched: + sources = [] + elif not self.static or isinstance(self.hmap, DynamicMap): + sources = [(i, o) for i, inputs in self.stream_sources.items() + for o in inputs if i in zorders] + else: + sources = [(self.zorder, self.hmap.last)] + + cb_classes = set() + for _, source in sources: + streams = Stream.registry.get(id(source), []) + registry = Stream._callbacks['bokeh'] + cb_classes |= {(registry[type(stream)], stream) for stream in streams + if type(stream) in registry and stream.linked} + cbs = [] + sorted_cbs = sorted(cb_classes, key=lambda x: id(x[0])) + for cb, group in groupby(sorted_cbs, lambda x: x[0]): + cb_streams = [s for _, s in group] + cbs.append(cb(self, cb_streams, source)) + return cbs + + def push(self): """ Pushes updated plot data via the Comm. diff --git a/holoviews/plotting/bokeh/tabular.py b/holoviews/plotting/bokeh/tabular.py index adb4c0b4ea..257750d565 100644 --- a/holoviews/plotting/bokeh/tabular.py +++ b/holoviews/plotting/bokeh/tabular.py @@ -30,9 +30,9 @@ def __init__(self, element, plot=None, **params): self.handles = {} if plot is None else self.handles['plot'] element_ids = self.hmap.traverse(lambda x: id(x), [Dataset, ItemTable]) self.static = len(set(element_ids)) == 1 and len(self.keys) == len(self.hmap) - self.callbacks = [] # Callback support on tables not implemented + self.callbacks = self._construct_callbacks() self.streaming = [s for s in self.streams if isinstance(s, Buffer)] - + self.static_source = False def _execute_hooks(self, element): """ diff --git a/holoviews/plotting/util.py b/holoviews/plotting/util.py index a2680f1edb..63d5698370 100644 --- a/holoviews/plotting/util.py +++ b/holoviews/plotting/util.py @@ -117,6 +117,13 @@ def compute_overlayable_zorders(obj, path=[]): if isinstance(obj, CompositeOverlay): for z, o in enumerate(obj): zorder_map[z] = [o, obj] + elif isinstance(obj, HoloMap): + for el in obj.values(): + if isinstance(el, CompositeOverlay): + for k, v in compute_overlayable_zorders(el, path).items(): + zorder_map[k] += v + [obj] + else: + zorder_map[0] += [obj, el] else: if obj not in zorder_map[0]: zorder_map[0].append(obj) diff --git a/tests/testplotutils.py b/tests/testplotutils.py index 854ca7929a..ce2b77aad6 100644 --- a/tests/testplotutils.py +++ b/tests/testplotutils.py @@ -4,7 +4,7 @@ import numpy as np from holoviews import NdOverlay, Overlay -from holoviews.core.spaces import DynamicMap +from holoviews.core.spaces import DynamicMap, HoloMap from holoviews.core.options import Store, Cycle from holoviews.element.comparison import ComparisonTestCase from holoviews.element import Curve, Area, Points @@ -21,6 +21,19 @@ class TestOverlayableZorders(ComparisonTestCase): + def test_compute_overlayable_zorders_holomap(self): + hmap = HoloMap({0: Points([])}) + sources = compute_overlayable_zorders(hmap) + self.assertEqual(sources[0], [hmap, hmap.last]) + + def test_compute_overlayable_zorders_with_overlaid_holomap(self): + points = Points([]) + hmap = HoloMap({0: points}) + curve = Curve([]) + combined = hmap*curve + sources = compute_overlayable_zorders(combined) + self.assertEqual(sources[0], [points, combined.last, combined]) + def test_dynamic_compute_overlayable_zorders_two_mixed_layers(self): area = Area(range(10)) dmap = DynamicMap(lambda: Curve(range(10)), kdims=[])