Skip to content

Commit

Permalink
Merge pull request #1297 from ioam/overlay_source_fix
Browse files Browse the repository at this point in the history
Fixed dynamic stream sources assignment in plotting code
  • Loading branch information
jlstevens committed Apr 17, 2017
2 parents 078de8b + 1be4abe commit 8d6be58
Show file tree
Hide file tree
Showing 10 changed files with 429 additions and 60 deletions.
14 changes: 12 additions & 2 deletions holoviews/core/operation.py
Expand Up @@ -103,6 +103,16 @@ class ElementOperation(Operation):
first component is a Normalization.ranges list and the second
component is Normalization.keys. """)

link_inputs = param.Boolean(default=False, doc="""
If the operation is dynamic, whether or not linked streams
should be transferred from the operation inputs for backends
that support linked streams.
For example if an operation is applied to a DynamicMap with an
RangeXY, this switch determines whether the corresponding
visualization should update this stream with range changes
originating from the newly generated axes.""")

streams = param.List(default=[], doc="""
List of streams that are applied if dynamic=True, allowing
for dynamic interaction with the plot.""")
Expand Down Expand Up @@ -139,8 +149,8 @@ def __call__(self, element, **params):
processed = element.clone(grid_data)
elif dynamic:
from ..util import Dynamic
streams = getattr(self.p, 'streams', [])
processed = Dynamic(element, streams=streams,
processed = Dynamic(element, streams=self.p.streams,
link_inputs=self.p.link_inputs,
operation=self, kwargs=params)
elif isinstance(element, ViewableElement):
processed = self._process(element)
Expand Down
13 changes: 11 additions & 2 deletions holoviews/core/overlay.py
Expand Up @@ -29,6 +29,7 @@ def dynamic_mul(*args, **kwargs):
element = other[args]
return self * element
callback = Callable(dynamic_mul, inputs=[self, other])
callback._is_overlay = True
return other.clone(shared_data=False, callback=callback,
streams=[])
if isinstance(other, UniformNdMapping) and not isinstance(other, CompositeOverlay):
Expand All @@ -41,7 +42,6 @@ def dynamic_mul(*args, **kwargs):




class CompositeOverlay(ViewableElement, Composable):
"""
CompositeOverlay provides a common baseclass for Overlay classes.
Expand Down Expand Up @@ -136,7 +136,16 @@ def __add__(self, other):


def __mul__(self, other):
if not isinstance(other, ViewableElement):
if type(other).__name__ == 'DynamicMap':
from .spaces import Callable
def dynamic_mul(*args, **kwargs):
element = other[args]
return self * element
callback = Callable(dynamic_mul, inputs=[self, other])
callback._is_overlay = True
return other.clone(shared_data=False, callback=callback,
streams=[])
elif not isinstance(other, ViewableElement):
raise NotImplementedError
return Overlay.from_values([self, other])

Expand Down
46 changes: 26 additions & 20 deletions holoviews/core/spaces.py
Expand Up @@ -117,29 +117,21 @@ def _dynamic_mul(self, dimensions, other, keys):
map_obj = self if isinstance(self, DynamicMap) else other

def dynamic_mul(*key, **kwargs):
key_map = {d.name: k for d, k in zip(dimensions, key)}
layers = []
try:
if isinstance(self, DynamicMap):
safe_key = () if not self.kdims else key
_, self_el = util.get_dynamic_item(self, dimensions, safe_key)
if self_el is not None:
layers.append(self_el)
else:
layers.append(self[key])
self_el = self.select(**key_map) if self.kdims else self[()]
layers.append(self_el)
except KeyError:
pass
try:
if isinstance(other, DynamicMap):
safe_key = () if not other.kdims else key
_, other_el = util.get_dynamic_item(other, dimensions, safe_key)
if other_el is not None:
layers.append(other_el)
else:
layers.append(other[key])
other_el = other.select(**key_map) if other.kdims else other[()]
layers.append(other_el)
except KeyError:
pass
return Overlay(layers)
callback = Callable(dynamic_mul, inputs=[self, other])
callback._is_overlay = True
if map_obj:
return map_obj.clone(callback=callback, shared_data=False,
kdims=dimensions, streams=[])
Expand Down Expand Up @@ -207,6 +199,7 @@ def dynamic_mul(*args, **kwargs):
element = self[args]
return element * other
callback = Callable(dynamic_mul, inputs=[self, other])
callback._is_overlay = True
return self.clone(shared_data=False, callback=callback,
streams=[])
items = [(k, v * other) for (k, v) in self.data.items()]
Expand Down Expand Up @@ -413,7 +406,11 @@ class Callable(param.Parameterized):
when composite objects such as Layouts are returned from the
callback. This is required for building interactive, linked
visualizations (for the backends that support them) when returning
Layouts, NdLayouts or GridSpace objects.
Layouts, NdLayouts or GridSpace objects. When chaining multiple
DynamicMaps into a pipeline, the link_inputs parameter declares
whether the visualization generated using this Callable will
inherit the linked streams. This parameter is used as a hint by
the applicable backend.
The mapping should map from an appropriate key to a list of
streams associated with the selected object. The appropriate key
Expand All @@ -429,6 +426,16 @@ class Callable(param.Parameterized):
The list of inputs the callable function is wrapping. Used
to allow deep access to streams in chained Callables.""")

link_inputs = param.Boolean(default=True, doc="""
If the Callable wraps around other DynamicMaps in its inputs,
determines whether linked streams attached to the inputs are
transferred to the objects returned by the Callable.
For example the Callable wraps a DynamicMap with an RangeXY
stream, this switch determines whether the corresponding
visualization should update this stream with range changes
originating from the newly generated axes.""")

memoize = param.Boolean(default=True, doc="""
Whether the return value of the callable should be memoized
based on the call arguments and any streams attached to the
Expand All @@ -441,6 +448,8 @@ class Callable(param.Parameterized):
def __init__(self, callable, **params):
super(Callable, self).__init__(callable=callable, **params)
self._memoized = {}
self._is_overlay = False


@property
def argspec(self):
Expand Down Expand Up @@ -687,10 +696,7 @@ def clone(self, data=None, shared_data=True, new_type=None, *args, **overrides):
# Ensure the clone references this object to ensure
# stream sources are inherited
if clone.callback is self.callback:
clone.callback = self.callback.clone()
if self not in clone.callback.inputs:
with util.disable_constant(clone.callback):
clone.callback.inputs = clone.callback.inputs+[self]
clone.callback = clone.callback.clone(inputs=[self])
return clone


Expand Down Expand Up @@ -1104,7 +1110,7 @@ def dynamic_hist(obj):
adjoin=False, **kwargs)

from ..util import Dynamic
hist = Dynamic(self, operation=dynamic_hist)
hist = Dynamic(self, link_inputs=False, operation=dynamic_hist)
if adjoin:
return self << hist
else:
Expand Down
10 changes: 10 additions & 0 deletions holoviews/operation/datashader.py
Expand Up @@ -264,6 +264,11 @@ class shade(ElementOperation):
and any valid transfer function that accepts data, mask, nbins
arguments.""")

link_inputs = param.Boolean(default=True, doc="""
By default, the link_inputs parameter is set to True so that
when applying shade, backends that support linked streams
update RangeXY streams on the inputs of the shade operation.""")

@classmethod
def concatenate(cls, overlay):
"""
Expand Down Expand Up @@ -395,6 +400,11 @@ class dynspread(ElementOperation):
Higher values give more spreading, up to the max_px
allowed.""")

link_inputs = param.Boolean(default=True, doc="""
By default, the link_inputs parameter is set to True so that
when applying dynspread, backends that support linked streams
update RangeXY streams on the inputs of the dynspread operation.""")

@classmethod
def uint8_to_uint32(cls, img):
shape = img.shape
Expand Down
2 changes: 1 addition & 1 deletion holoviews/plotting/bokeh/callbacks.py
Expand Up @@ -500,7 +500,7 @@ def initialize(self):
if cb_hash in self._callbacks:
# Merge callbacks if another callback has already been attached
cb = self._callbacks[cb_hash]
cb.streams += self.streams
cb.streams = list(set(cb.streams+self.streams))
for k, v in self.handle_ids.items():
cb.handle_ids[k].update(v)
continue
Expand Down
16 changes: 13 additions & 3 deletions holoviews/plotting/bokeh/element.py
Expand Up @@ -27,7 +27,7 @@
from ...element import RGB
from ...streams import Stream, RangeXY, RangeX, RangeY
from ..plot import GenericElementPlot, GenericOverlayPlot
from ..util import dynamic_update, get_sources, attach_streams
from ..util import dynamic_update, attach_streams
from .plot import BokehPlot, TOOLS
from .util import (mpl_to_bokeh, convert_datetime, update_plot, get_tab_title,
bokeh_version, mplcmap_to_palette, py2js_tickformatter,
Expand Down Expand Up @@ -190,9 +190,18 @@ 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 = []
if not self.static or isinstance(self.hmap, DynamicMap):
sources = [(i, o) for i, o in get_sources(self.hmap)
if i in [None, self.zorder]]
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()
Expand All @@ -208,6 +217,7 @@ def _construct_callbacks(self):
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
13 changes: 10 additions & 3 deletions holoviews/plotting/plot.py
Expand Up @@ -21,7 +21,8 @@
from ..core.util import stream_parameters
from ..element import Table
from .util import (get_dynamic_mode, initialize_sampled, dim_axis_label,
attach_streams, traverse_setter, get_nested_streams)
attach_streams, traverse_setter, get_nested_streams,
compute_overlayable_zorders)


class Plot(param.Parameterized):
Expand Down Expand Up @@ -554,7 +555,7 @@ class GenericElementPlot(DimensionedPlot):

def __init__(self, element, keys=None, ranges=None, dimensions=None,
batched=False, overlaid=0, cyclic_index=0, zorder=0, style=None,
overlay_dims={}, **params):
overlay_dims={}, stream_sources=[], **params):
self.zorder = zorder
self.cyclic_index = cyclic_index
self.overlaid = overlaid
Expand All @@ -567,6 +568,11 @@ def __init__(self, element, keys=None, ranges=None, dimensions=None,
else:
self.hmap = element

if overlaid:
self.stream_sources = stream_sources
else:
self.stream_sources = compute_overlayable_zorders(self.hmap)

plot_element = self.hmap.last
if self.batched and not isinstance(self, GenericOverlayPlot):
plot_element = [el for el in plot_element if el][-1]
Expand Down Expand Up @@ -836,6 +842,7 @@ def _create_subplots(self, ranges):
ordering = util.layer_sort(self.hmap)
registry = Store.registry[self.renderer.backend]
batched = self.batched and type(self.hmap.last) is NdOverlay
stream_sources = self.stream_sources
if batched:
batchedplot = registry.get(type(self.hmap.last.last))
if (batched and batchedplot and 'batched' in batchedplot._plot_methods and
Expand Down Expand Up @@ -902,7 +909,7 @@ def _create_subplots(self, ranges):
layout_dimensions=self.layout_dimensions,
ranges=ranges, show_title=self.show_title,
style=style, uniform=self.uniform,
renderer=self.renderer,
renderer=self.renderer, stream_sources=stream_sources,
zorder=zorder, **passed_handles)

if not isinstance(key, tuple): key = (key,)
Expand Down

0 comments on commit 8d6be58

Please sign in to comment.