Skip to content

Commit

Permalink
Ensure linked streams are resolved on HoloMap and Table objects (#2252)
Browse files Browse the repository at this point in the history
  • Loading branch information
philippjfr authored and jlstevens committed Jan 9, 2018
1 parent d0c1d74 commit 2e4e81d
Show file tree
Hide file tree
Showing 6 changed files with 65 additions and 40 deletions.
33 changes: 0 additions & 33 deletions holoviews/plotting/bokeh/element.py
Expand Up @@ -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)
Expand Down
40 changes: 38 additions & 2 deletions holoviews/plotting/bokeh/plot.py
Expand Up @@ -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)
Expand Down Expand Up @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions holoviews/plotting/bokeh/tabular.py
Expand Up @@ -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):
"""
Expand Down
7 changes: 7 additions & 0 deletions holoviews/plotting/util.py
Expand Up @@ -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)
Expand Down
6 changes: 4 additions & 2 deletions tests/testbinneddatasets.py
Expand Up @@ -10,6 +10,7 @@
from holoviews.core.spaces import HoloMap
from holoviews.core.data import Dataset
from holoviews.core.data.interface import DataError
from holoviews.core.util import OrderedDict
from holoviews.element import Histogram, QuadMesh
from holoviews.element.comparison import ComparisonTestCase

Expand Down Expand Up @@ -177,9 +178,10 @@ def test_construct_from_xarray(self):
import xarray as xr
except:
raise SkipError("Test requires xarray")
coords = OrderedDict([('lat', (('y', 'x'), self.ys)),
('lon', (('y', 'x'), self.xs))])
da = xr.DataArray(self.zs, dims=['y', 'x'],
coords = {'lat': (('y', 'x'), self.ys),
'lon': (('y', 'x'), self.xs)}, name='z')
coords=coords, name='z')
dataset = Dataset(da)

# Ensure that dimensions are inferred correctly
Expand Down
15 changes: 14 additions & 1 deletion tests/testplotutils.py
Expand Up @@ -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
Expand All @@ -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=[])
Expand Down

0 comments on commit 2e4e81d

Please sign in to comment.