From 5558dbef274b11d038ef66d4bcf95645ce658062 Mon Sep 17 00:00:00 2001
From: Philipp Rudiger
Date: Mon, 24 Oct 2016 20:57:22 +0100
Subject: [PATCH 01/10] Added Callable and OperationCallable
Allows traversing DynamicMap callbacks to get streams and sources
---
holoviews/core/operation.py | 6 +++++-
holoviews/core/overlay.py | 10 +++++++---
holoviews/core/spaces.py | 33 ++++++++++++++++++++++++---------
holoviews/util.py | 25 +++++++++++++++----------
4 files changed, 51 insertions(+), 23 deletions(-)
diff --git a/holoviews/core/operation.py b/holoviews/core/operation.py
index 99f20e596d..2d92e2a5a3 100644
--- a/holoviews/core/operation.py
+++ b/holoviews/core/operation.py
@@ -14,7 +14,7 @@
from .element import Element, HoloMap, GridSpace, Collator
from .layout import Layout
from .overlay import NdOverlay, Overlay
-from .spaces import DynamicMap
+from .spaces import DynamicMap, Callable
from .traversal import unique_dimkeys
from . import util
@@ -160,6 +160,10 @@ def __call__(self, element, **params):
return processed
+class OperationCallable(Callable):
+
+ operation = param.ClassSelector(class_=ElementOperation)
+
class MapOperation(param.ParameterizedFunction):
"""
diff --git a/holoviews/core/overlay.py b/holoviews/core/overlay.py
index cf162b1597..e066f1cb04 100644
--- a/holoviews/core/overlay.py
+++ b/holoviews/core/overlay.py
@@ -24,10 +24,14 @@ class Overlayable(object):
def __mul__(self, other):
if type(other).__name__ == 'DynamicMap':
- from ..util import Dynamic
- def dynamic_mul(element):
+ from .spaces import Callable
+ def dynamic_mul(*args, **kwargs):
+ element = other[args]
return self * element
- return Dynamic(other, operation=dynamic_mul)
+ callback = Callable(callable_function=dynamic_mul,
+ objects=[self, other])
+ return other.clone(shared_data=False, callback=callback,
+ streams=[])
if isinstance(other, UniformNdMapping) and not isinstance(other, CompositeOverlay):
items = [(k, self * v) for (k, v) in other.items()]
return other.clone(items)
diff --git a/holoviews/core/spaces.py b/holoviews/core/spaces.py
index 9a9eaa461f..929f10d575 100644
--- a/holoviews/core/spaces.py
+++ b/holoviews/core/spaces.py
@@ -120,12 +120,13 @@ def _dynamic_mul(self, dimensions, other, keys):
map_obj = self if isinstance(self, DynamicMap) else other
mode = map_obj.mode
- def dynamic_mul(*key):
+ def dynamic_mul(*key, **kwargs):
key = key[0] if mode == 'open' else key
layers = []
try:
if isinstance(self, DynamicMap):
- _, self_el = util.get_dynamic_item(self, dimensions, key)
+ 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:
@@ -134,7 +135,8 @@ def dynamic_mul(*key):
pass
try:
if isinstance(other, DynamicMap):
- _, other_el = util.get_dynamic_item(other, dimensions, key)
+ 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:
@@ -142,11 +144,12 @@ def dynamic_mul(*key):
except KeyError:
pass
return Overlay(layers)
+ callback = Callable(callable_function=dynamic_mul, objects=[self, other])
if map_obj:
- return map_obj.clone(callback=dynamic_mul, shared_data=False,
- kdims=dimensions)
+ return map_obj.clone(callback=callback, shared_data=False,
+ kdims=dimensions, streams=[])
else:
- return DynamicMap(callback=dynamic_mul, kdims=dimensions)
+ return DynamicMap(callback=callback, kdims=dimensions)
def __mul__(self, other):
@@ -204,10 +207,13 @@ def __mul__(self, other):
return self.clone(items, kdims=dimensions, label=self._label, group=self._group)
elif isinstance(other, self.data_type):
if isinstance(self, DynamicMap):
- from ..util import Dynamic
- def dynamic_mul(element):
+ def dynamic_mul(*args, **kwargs):
+ element = self[args]
return element * other
- return Dynamic(self, operation=dynamic_mul)
+ callback = Callable(callable_function=dynamic_mul,
+ objects=[self, other])
+ return self.clone(shared_data=False, callback=callback,
+ streams=[])
items = [(k, v * other) for (k, v) in self.data.items()]
return self.clone(items, label=self._label, group=self._group)
else:
@@ -393,6 +399,15 @@ def hist(self, num_bins=20, bin_range=None, adjoin=True, individually=True, **kw
return histmaps[0]
+class Callable(param.Parameterized):
+
+ callable_function = param.Callable(default=lambda x: x)
+
+ objects = param.List(default=[])
+
+ def __call__(self, *args, **kwargs):
+ return self.callable_function(*args, **kwargs)
+
class DynamicMap(HoloMap):
"""
diff --git a/holoviews/util.py b/holoviews/util.py
index e42aa3f339..6d52436e10 100644
--- a/holoviews/util.py
+++ b/holoviews/util.py
@@ -5,6 +5,8 @@
from .core import DynamicMap, ViewableElement
from .core.operation import ElementOperation
from .core.util import Aliases
+from .core.operation import OperationCallable
+from .core.spaces import Callable
from .core import util
from .streams import Stream
@@ -33,7 +35,8 @@ def __call__(self, map_obj, **params):
self.p = param.ParamOverrides(self, params)
callback = self._dynamic_operation(map_obj)
if isinstance(map_obj, DynamicMap):
- dmap = map_obj.clone(callback=callback, shared_data=False)
+ dmap = map_obj.clone(callback=callback, shared_data=False,
+ streams=[])
else:
dmap = self._make_dynamic(map_obj, callback)
if isinstance(self.p.operation, ElementOperation):
@@ -69,15 +72,17 @@ def _dynamic_operation(self, map_obj):
def dynamic_operation(*key, **kwargs):
self.p.kwargs.update(kwargs)
return self._process(map_obj[key], key)
- return dynamic_operation
-
- def dynamic_operation(*key, **kwargs):
- key = key[0] if map_obj.mode == 'open' else key
- self.p.kwargs.update(kwargs)
- _, el = util.get_dynamic_item(map_obj, map_obj.kdims, key)
- return self._process(el, key)
-
- return dynamic_operation
+ else:
+ def dynamic_operation(*key, **kwargs):
+ key = key[0] if map_obj.mode == 'open' else key
+ self.p.kwargs.update(kwargs)
+ _, el = util.get_dynamic_item(map_obj, map_obj.kdims, key)
+ return self._process(el, key)
+ if isinstance(self.p.operation, ElementOperation):
+ return OperationCallable(callable_function=dynamic_operation,
+ objects=[map_obj], operation=self.p.operation)
+ else:
+ return Callable(callable_function=dynamic_operation, objects=[map_obj])
def _make_dynamic(self, hmap, dynamic_fn):
From 4a47dff4d554aaeed0ad3cb9abc883783021e8be Mon Sep 17 00:00:00 2001
From: Philipp Rudiger
Date: Mon, 24 Oct 2016 21:44:08 +0100
Subject: [PATCH 02/10] Process Callable streams on Plots
---
doc/builder | 2 +-
holoviews/core/spaces.py | 16 ++++++++++-
holoviews/core/util.py | 8 +++---
holoviews/plotting/plot.py | 6 ++---
holoviews/plotting/renderer.py | 2 +-
holoviews/plotting/util.py | 37 ++++++++++++++++++++++++--
holoviews/plotting/widgets/__init__.py | 6 ++++-
7 files changed, 64 insertions(+), 13 deletions(-)
diff --git a/doc/builder b/doc/builder
index f8685fee76..fe584ca37d 160000
--- a/doc/builder
+++ b/doc/builder
@@ -1 +1 @@
-Subproject commit f8685fee7691375a064d4f4265fd826cdbe3d3cd
+Subproject commit fe584ca37d850315c21fa9e7602f56213c7a48ca
diff --git a/holoviews/core/spaces.py b/holoviews/core/spaces.py
index 929f10d575..0b69e9fbda 100644
--- a/holoviews/core/spaces.py
+++ b/holoviews/core/spaces.py
@@ -409,6 +409,19 @@ def __call__(self, *args, **kwargs):
return self.callable_function(*args, **kwargs)
+def get_streams(dmap):
+ """
+ Get streams from DynamicMap with Callable callback.
+ """
+ layer_streams = list(dmap.streams)
+ if not isinstance(dmap.callback, Callable):
+ return layer_streams
+ for o in dmap.callback.objects:
+ if isinstance(o, DynamicMap):
+ layer_streams += get_streams(o)
+ return layer_streams
+
+
class DynamicMap(HoloMap):
"""
A DynamicMap is a type of HoloMap where the elements are dynamically
@@ -704,7 +717,8 @@ def __getitem__(self, key):
# Cache lookup
try:
- dimensionless = util.dimensionless_contents(self.streams, self.kdims)
+ dimensionless = util.dimensionless_contents(get_streams(self),
+ self.kdims, False)
if (dimensionless and not self._dimensionless_cache):
raise KeyError('Using dimensionless streams disables DynamicMap cache')
cache = super(DynamicMap,self).__getitem__(key)
diff --git a/holoviews/core/util.py b/holoviews/core/util.py
index 797103fba3..1fca496ede 100644
--- a/holoviews/core/util.py
+++ b/holoviews/core/util.py
@@ -799,21 +799,21 @@ def stream_parameters(streams, no_duplicates=True, exclude=['name']):
return [name for name in names if name not in exclude]
-def dimensionless_contents(streams, kdims):
+def dimensionless_contents(streams, kdims, no_duplicates=True):
"""
Return a list of stream parameters that have not been associated
with any of the key dimensions.
"""
- names = stream_parameters(streams)
+ names = stream_parameters(streams, no_duplicates)
return [name for name in names if name not in kdims]
-def unbound_dimensions(streams, kdims):
+def unbound_dimensions(streams, kdims, no_duplicates=True):
"""
Return a list of dimensions that have not been associated with
any streams.
"""
- params = stream_parameters(streams)
+ params = stream_parameters(streams, no_duplicates)
return [d for d in kdims if d not in params]
diff --git a/holoviews/plotting/plot.py b/holoviews/plotting/plot.py
index d52691a18f..4b42e83128 100644
--- a/holoviews/plotting/plot.py
+++ b/holoviews/plotting/plot.py
@@ -22,7 +22,7 @@
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)
+ attach_streams, traverse_setter, get_streams)
class Plot(param.Parameterized):
@@ -578,7 +578,7 @@ def __init__(self, element, keys=None, ranges=None, dimensions=None,
**dict(params, **plot_opts))
if top_level:
self.comm = self.init_comm(element)
- self.streams = self.hmap.streams if isinstance(self.hmap, DynamicMap) else []
+ self.streams = get_streams(self.hmap) if isinstance(self.hmap, DynamicMap) else []
# Update plot and style options for batched plots
if self.batched:
@@ -928,7 +928,7 @@ def __init__(self, layout, keys=None, dimensions=None, **params):
if top_level:
self.comm = self.init_comm(layout)
self.traverse(lambda x: setattr(x, 'comm', self.comm))
- self.streams = [s for streams in layout.traverse(lambda x: x.streams,
+ self.streams = [s for streams in layout.traverse(lambda x: get_streams(streams),
[DynamicMap])
for s in streams]
diff --git a/holoviews/plotting/renderer.py b/holoviews/plotting/renderer.py
index 5bdc01ff6d..046c8d9f28 100644
--- a/holoviews/plotting/renderer.py
+++ b/holoviews/plotting/renderer.py
@@ -204,7 +204,7 @@ def _validate(self, obj, fmt):
if (((len(plot) == 1 and not plot.dynamic)
or (len(plot) > 1 and self.holomap is None) or
(plot.dynamic and len(plot.keys[0]) == 0)) or
- not unbound_dimensions(plot.streams, plot.dimensions)):
+ not unbound_dimensions(plot.streams, plot.dimensions, False)):
fmt = fig_formats[0] if self.fig=='auto' else self.fig
else:
fmt = holomap_formats[0] if self.holomap=='auto' else self.holomap
diff --git a/holoviews/plotting/util.py b/holoviews/plotting/util.py
index 26aaaa5d14..b928ae198f 100644
--- a/holoviews/plotting/util.py
+++ b/holoviews/plotting/util.py
@@ -4,7 +4,8 @@
import param
from ..core import (HoloMap, DynamicMap, CompositeOverlay, Layout,
- GridSpace, NdLayout, Store, Overlay)
+ GridSpace, NdLayout, Store, Callable, Overlay)
+from ..core.spaces import get_streams
from ..core.util import (match_spec, is_number, wrap_tuple, basestring,
get_overlay_spec, unique_iterator, safe_unicode)
@@ -295,11 +296,43 @@ def attach_streams(plot, obj):
Attaches plot refresh to all streams on the object.
"""
def append_refresh(dmap):
- for stream in dmap.streams:
+ for stream in get_streams(dmap):
stream._hidden_subscribers.append(plot.refresh)
return obj.traverse(append_refresh, [DynamicMap])
+def get_sources(obj, index=None):
+ """
+ Traverses Callable graph to resolve sources on
+ DynamicMap objects, returning a list of sources
+ indexed by the Overlay layer.
+ """
+ if isinstance(obj, DynamicMap):
+ if isinstance(obj.callback, Callable):
+ if len(obj.callback.objects) > 1:
+ layers = [(None, obj)]
+ else:
+ layers = [(index, obj)]
+ else:
+ return [(index, obj)]
+ else:
+ return [(index, obj)]
+ index = 0 if index is None else int(index)
+ for o in obj.callback.objects:
+ if isinstance(o, Overlay):
+ layers.append((None, o))
+ for i, o in enumerate(overlay):
+ layers.append((index+i, o))
+ index += len(o)
+ elif isinstance(o, DynamicMap):
+ layers += get_sources(o, index)
+ index = layers[-1][0]+1
+ else:
+ layers.append((index, o))
+ index += 1
+ return layers
+
+
def traverse_setter(obj, attribute, value):
"""
Traverses the object and sets the supplied attribute on the
diff --git a/holoviews/plotting/widgets/__init__.py b/holoviews/plotting/widgets/__init__.py
index eb38fdb481..0d1b634b8a 100644
--- a/holoviews/plotting/widgets/__init__.py
+++ b/holoviews/plotting/widgets/__init__.py
@@ -108,7 +108,11 @@ def __init__(self, plot, renderer=None, **params):
super(NdWidget, self).__init__(**params)
self.id = plot.comm.target if plot.comm else uuid.uuid4().hex
self.plot = plot
- self.dimensions, self.keys = drop_streams(plot.streams,
+ streams = []
+ for stream in plot.streams:
+ if any(k in plot.dimensions for k in stream.contents):
+ streams.append(stream)
+ self.dimensions, self.keys = drop_streams(streams,
plot.dimensions,
plot.keys)
From 52d716c4687e070963806400d9834827e66c350c Mon Sep 17 00:00:00 2001
From: Philipp Rudiger
Date: Mon, 24 Oct 2016 21:45:10 +0100
Subject: [PATCH 03/10] Register bokeh callbacks for Callable streams
---
holoviews/plotting/bokeh/element.py | 29 ++++++++++++++++++++---------
1 file changed, 20 insertions(+), 9 deletions(-)
diff --git a/holoviews/plotting/bokeh/element.py b/holoviews/plotting/bokeh/element.py
index d6104e4932..88ca993702 100644
--- a/holoviews/plotting/bokeh/element.py
+++ b/holoviews/plotting/bokeh/element.py
@@ -31,7 +31,7 @@
from ...element import RGB
from ...streams import Stream, RangeXY, RangeX, RangeY
from ..plot import GenericElementPlot, GenericOverlayPlot
-from ..util import dynamic_update
+from ..util import dynamic_update, get_sources
from .plot import BokehPlot
from .util import (mpl_to_bokeh, convert_datetime, update_plot,
bokeh_version, mplcmap_to_palette)
@@ -177,15 +177,18 @@ def _construct_callbacks(self):
the plotted object as a source.
"""
if not self.static or isinstance(self.hmap, DynamicMap):
- source = self.hmap
+ sources = [(i, o) for i, o in get_sources(self.hmap)
+ if i in [None, self.zorder]]
else:
- source = self.hmap.last
- streams = Stream.registry.get(id(source), [])
- registry = Stream._callbacks['bokeh']
- callbacks = {(registry[type(stream)], stream) for stream in streams
- if type(stream) in registry and streams}
+ 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 streams}
cbs = []
- sorted_cbs = sorted(callbacks, key=lambda x: id(x[0]))
+ 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))
@@ -560,6 +563,11 @@ def initialize_plot(self, ranges=None, plot=None, plots=None, source=None):
if plot is None:
plot = self._init_plot(key, style_element, ranges=ranges, plots=plots)
self._init_axes(plot)
+ else:
+ self.handles['xaxis'] = plot.xaxis[0]
+ self.handles['x_range'] = plot.x_range
+ self.handles['y_axis'] = plot.yaxis[0]
+ self.handles['y_range'] = plot.y_range
self.handles['plot'] = plot
# Get data and initialize data source
@@ -675,7 +683,10 @@ def current_handles(self):
rangex, rangey = True, True
elif isinstance(self.hmap, DynamicMap):
rangex, rangey = True, True
- for stream in self.hmap.streams:
+ callbacks = [cb for p in [self]+list(self.subplots.values())
+ for cb in p.callbacks]
+ streams = [s for cb in callbacks for s in cb.streams]
+ for stream in streams:
if isinstance(stream, RangeXY):
rangex, rangey = False, False
break
From 67e5a390c957686829c4ef2361ba4154cd104107 Mon Sep 17 00:00:00 2001
From: Philipp Rudiger
Date: Mon, 24 Oct 2016 22:04:42 +0100
Subject: [PATCH 04/10] Fixed bokeh ElementPlot.current_handles bug
---
holoviews/plotting/bokeh/element.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/holoviews/plotting/bokeh/element.py b/holoviews/plotting/bokeh/element.py
index 88ca993702..4ecb70c6fa 100644
--- a/holoviews/plotting/bokeh/element.py
+++ b/holoviews/plotting/bokeh/element.py
@@ -683,8 +683,8 @@ def current_handles(self):
rangex, rangey = True, True
elif isinstance(self.hmap, DynamicMap):
rangex, rangey = True, True
- callbacks = [cb for p in [self]+list(self.subplots.values())
- for cb in p.callbacks]
+ subplots = list(self.subplots.values()) if self.subplots else []
+ callbacks = [cb for p in [self]+subplots for cb in p.callbacks]
streams = [s for cb in callbacks for s in cb.streams]
for stream in streams:
if isinstance(stream, RangeXY):
From 628c19ed1d4323b749a7d629eeea8295f0223afe Mon Sep 17 00:00:00 2001
From: Philipp Rudiger
Date: Mon, 24 Oct 2016 22:18:01 +0100
Subject: [PATCH 05/10] Fixed streams bug in GenericCompositePlot
---
holoviews/plotting/plot.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/holoviews/plotting/plot.py b/holoviews/plotting/plot.py
index 4b42e83128..586724a2bf 100644
--- a/holoviews/plotting/plot.py
+++ b/holoviews/plotting/plot.py
@@ -928,7 +928,7 @@ def __init__(self, layout, keys=None, dimensions=None, **params):
if top_level:
self.comm = self.init_comm(layout)
self.traverse(lambda x: setattr(x, 'comm', self.comm))
- self.streams = [s for streams in layout.traverse(lambda x: get_streams(streams),
+ self.streams = [s for streams in layout.traverse(lambda x: get_streams(x),
[DynamicMap])
for s in streams]
From e3b2b888cabccc968bc5881571ce176c6fcc3676 Mon Sep 17 00:00:00 2001
From: Philipp Rudiger
Date: Sun, 30 Oct 2016 19:12:28 +0000
Subject: [PATCH 06/10] Updated doc/builder
---
doc/builder | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/doc/builder b/doc/builder
index fe584ca37d..f8685fee76 160000
--- a/doc/builder
+++ b/doc/builder
@@ -1 +1 @@
-Subproject commit fe584ca37d850315c21fa9e7602f56213c7a48ca
+Subproject commit f8685fee7691375a064d4f4265fd826cdbe3d3cd
From 2361036cb65900964b68a50eae1d27966215955f Mon Sep 17 00:00:00 2001
From: Philipp Rudiger
Date: Sun, 30 Oct 2016 19:37:48 +0000
Subject: [PATCH 07/10] Added docstrings for Callable classes
---
holoviews/core/operation.py | 8 +++++++-
holoviews/core/spaces.py | 13 +++++++++++--
2 files changed, 18 insertions(+), 3 deletions(-)
diff --git a/holoviews/core/operation.py b/holoviews/core/operation.py
index 2d92e2a5a3..4a3c2c9e7e 100644
--- a/holoviews/core/operation.py
+++ b/holoviews/core/operation.py
@@ -161,8 +161,14 @@ def __call__(self, element, **params):
class OperationCallable(Callable):
+ """
+ OperationCallable allows wrapping an ElementOperation and the
+ objects it is processing to allow traversing the operations
+ applied on a DynamicMap.
+ """
- operation = param.ClassSelector(class_=ElementOperation)
+ operation = param.ClassSelector(class_=ElementOperation, doc="""
+ The ElementOperation being wrapped.""")
class MapOperation(param.ParameterizedFunction):
diff --git a/holoviews/core/spaces.py b/holoviews/core/spaces.py
index 0b69e9fbda..ed72b24f0e 100644
--- a/holoviews/core/spaces.py
+++ b/holoviews/core/spaces.py
@@ -400,10 +400,19 @@ def hist(self, num_bins=20, bin_range=None, adjoin=True, individually=True, **kw
class Callable(param.Parameterized):
+ """
+ Callable allows wrapping callbacks on one or more DynamicMaps such
+ that the operation and the objects that are part of the operation
+ are made available. This allows traversing a DynamicMap that wraps
+ multiple operations and is primarily used to extracting any
+ streams attached to the object.
+ """
- callable_function = param.Callable(default=lambda x: x)
+ callable_function = param.Callable(default=lambda x: x, doc="""
+ The callable function being wrapped.""")
- objects = param.List(default=[])
+ objects = param.List(default=[], doc="""
+ The objects the callable function is processing.""")
def __call__(self, *args, **kwargs):
return self.callable_function(*args, **kwargs)
From f710fa2ffaec5c41b8a2c5272672683c293a79ad Mon Sep 17 00:00:00 2001
From: Philipp Rudiger
Date: Sun, 30 Oct 2016 19:38:08 +0000
Subject: [PATCH 08/10] Small fix for Dynamic
---
holoviews/util.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/holoviews/util.py b/holoviews/util.py
index 6d52436e10..597309a6e7 100644
--- a/holoviews/util.py
+++ b/holoviews/util.py
@@ -47,7 +47,7 @@ def __call__(self, map_obj, **params):
elif not isinstance(stream, Stream):
raise ValueError('Stream must only contain Stream '
'classes or instances')
- stream.update(**{k: self.p.operation.p.get(k) for k, v in
+ stream.update(**{k: self.p.operation.p.get(k, v) for k, v in
stream.contents.items()})
streams.append(stream)
return dmap.clone(streams=streams)
From ca945bf92e41bcfe9d3975bd53fbdbf4719f01a3 Mon Sep 17 00:00:00 2001
From: Philipp Rudiger
Date: Sun, 30 Oct 2016 23:16:00 +0000
Subject: [PATCH 09/10] Renamed Callable.objects to Callable.inputs
---
holoviews/core/overlay.py | 2 +-
holoviews/core/spaces.py | 20 ++++++++++----------
holoviews/plotting/util.py | 4 ++--
holoviews/util.py | 4 ++--
4 files changed, 15 insertions(+), 15 deletions(-)
diff --git a/holoviews/core/overlay.py b/holoviews/core/overlay.py
index e066f1cb04..6cd903d4b8 100644
--- a/holoviews/core/overlay.py
+++ b/holoviews/core/overlay.py
@@ -29,7 +29,7 @@ def dynamic_mul(*args, **kwargs):
element = other[args]
return self * element
callback = Callable(callable_function=dynamic_mul,
- objects=[self, other])
+ inputs=[self, other])
return other.clone(shared_data=False, callback=callback,
streams=[])
if isinstance(other, UniformNdMapping) and not isinstance(other, CompositeOverlay):
diff --git a/holoviews/core/spaces.py b/holoviews/core/spaces.py
index ed72b24f0e..9b2063375a 100644
--- a/holoviews/core/spaces.py
+++ b/holoviews/core/spaces.py
@@ -144,7 +144,7 @@ def dynamic_mul(*key, **kwargs):
except KeyError:
pass
return Overlay(layers)
- callback = Callable(callable_function=dynamic_mul, objects=[self, other])
+ callback = Callable(callable_function=dynamic_mul, inputs=[self, other])
if map_obj:
return map_obj.clone(callback=callback, shared_data=False,
kdims=dimensions, streams=[])
@@ -211,7 +211,7 @@ def dynamic_mul(*args, **kwargs):
element = self[args]
return element * other
callback = Callable(callable_function=dynamic_mul,
- objects=[self, other])
+ inputs=[self, other])
return self.clone(shared_data=False, callback=callback,
streams=[])
items = [(k, v * other) for (k, v) in self.data.items()]
@@ -401,18 +401,18 @@ def hist(self, num_bins=20, bin_range=None, adjoin=True, individually=True, **kw
class Callable(param.Parameterized):
"""
- Callable allows wrapping callbacks on one or more DynamicMaps such
- that the operation and the objects that are part of the operation
- are made available. This allows traversing a DynamicMap that wraps
- multiple operations and is primarily used to extracting any
- streams attached to the object.
+ Callable allows wrapping callbacks on one or more DynamicMaps
+ allowing their inputs (and in future outputs) to be defined.
+ This makes it possible to wrap DynamicMaps with streams and
+ makes it possible to traverse the graph of operations applied
+ to a DynamicMap.
"""
callable_function = param.Callable(default=lambda x: x, doc="""
The callable function being wrapped.""")
- objects = param.List(default=[], doc="""
- The objects the callable function is processing.""")
+ inputs = param.List(default=[], doc="""
+ The list of inputs the callable function is wrapping.""")
def __call__(self, *args, **kwargs):
return self.callable_function(*args, **kwargs)
@@ -425,7 +425,7 @@ def get_streams(dmap):
layer_streams = list(dmap.streams)
if not isinstance(dmap.callback, Callable):
return layer_streams
- for o in dmap.callback.objects:
+ for o in dmap.callback.inputs:
if isinstance(o, DynamicMap):
layer_streams += get_streams(o)
return layer_streams
diff --git a/holoviews/plotting/util.py b/holoviews/plotting/util.py
index b928ae198f..81347581ef 100644
--- a/holoviews/plotting/util.py
+++ b/holoviews/plotting/util.py
@@ -309,7 +309,7 @@ def get_sources(obj, index=None):
"""
if isinstance(obj, DynamicMap):
if isinstance(obj.callback, Callable):
- if len(obj.callback.objects) > 1:
+ if len(obj.callback.inputs) > 1:
layers = [(None, obj)]
else:
layers = [(index, obj)]
@@ -318,7 +318,7 @@ def get_sources(obj, index=None):
else:
return [(index, obj)]
index = 0 if index is None else int(index)
- for o in obj.callback.objects:
+ for o in obj.callback.inputs:
if isinstance(o, Overlay):
layers.append((None, o))
for i, o in enumerate(overlay):
diff --git a/holoviews/util.py b/holoviews/util.py
index 597309a6e7..3961b3c084 100644
--- a/holoviews/util.py
+++ b/holoviews/util.py
@@ -80,9 +80,9 @@ def dynamic_operation(*key, **kwargs):
return self._process(el, key)
if isinstance(self.p.operation, ElementOperation):
return OperationCallable(callable_function=dynamic_operation,
- objects=[map_obj], operation=self.p.operation)
+ inputs=[map_obj], operation=self.p.operation)
else:
- return Callable(callable_function=dynamic_operation, objects=[map_obj])
+ return Callable(callable_function=dynamic_operation, inputs=[map_obj])
def _make_dynamic(self, hmap, dynamic_fn):
From e019eabad5938f326ffad838d3208f1e7ad30b94 Mon Sep 17 00:00:00 2001
From: Philipp Rudiger
Date: Mon, 31 Oct 2016 00:51:58 +0000
Subject: [PATCH 10/10] Cleanup of Callable changes
---
holoviews/core/spaces.py | 11 ++++++-----
holoviews/plotting/plot.py | 13 ++++++++-----
holoviews/plotting/renderer.py | 2 +-
holoviews/plotting/util.py | 17 +++++------------
4 files changed, 20 insertions(+), 23 deletions(-)
diff --git a/holoviews/core/spaces.py b/holoviews/core/spaces.py
index 9b2063375a..2f5ced9940 100644
--- a/holoviews/core/spaces.py
+++ b/holoviews/core/spaces.py
@@ -418,16 +418,17 @@ def __call__(self, *args, **kwargs):
return self.callable_function(*args, **kwargs)
-def get_streams(dmap):
+def get_nested_streams(dmap):
"""
- Get streams from DynamicMap with Callable callback.
+ Get all (potentially nested) streams from DynamicMap with Callable
+ callback.
"""
layer_streams = list(dmap.streams)
if not isinstance(dmap.callback, Callable):
return layer_streams
for o in dmap.callback.inputs:
if isinstance(o, DynamicMap):
- layer_streams += get_streams(o)
+ layer_streams += get_nested_streams(o)
return layer_streams
@@ -726,8 +727,8 @@ def __getitem__(self, key):
# Cache lookup
try:
- dimensionless = util.dimensionless_contents(get_streams(self),
- self.kdims, False)
+ dimensionless = util.dimensionless_contents(get_nested_streams(self),
+ self.kdims, no_duplicates=False)
if (dimensionless and not self._dimensionless_cache):
raise KeyError('Using dimensionless streams disables DynamicMap cache')
cache = super(DynamicMap,self).__getitem__(key)
diff --git a/holoviews/plotting/plot.py b/holoviews/plotting/plot.py
index 586724a2bf..66669c3397 100644
--- a/holoviews/plotting/plot.py
+++ b/holoviews/plotting/plot.py
@@ -22,7 +22,7 @@
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_streams)
+ attach_streams, traverse_setter, get_nested_streams)
class Plot(param.Parameterized):
@@ -578,7 +578,10 @@ def __init__(self, element, keys=None, ranges=None, dimensions=None,
**dict(params, **plot_opts))
if top_level:
self.comm = self.init_comm(element)
- self.streams = get_streams(self.hmap) if isinstance(self.hmap, DynamicMap) else []
+ streams = []
+ if isinstance(self.hmap, DynamicMap):
+ streams = get_nested_streams(self.hmap)
+ self.streams = streams
# Update plot and style options for batched plots
if self.batched:
@@ -928,9 +931,9 @@ def __init__(self, layout, keys=None, dimensions=None, **params):
if top_level:
self.comm = self.init_comm(layout)
self.traverse(lambda x: setattr(x, 'comm', self.comm))
- self.streams = [s for streams in layout.traverse(lambda x: get_streams(x),
- [DynamicMap])
- for s in streams]
+ nested_streams = layout.traverse(lambda x: get_nested_streams(x),
+ [DynamicMap])
+ self.streams = [s for streams in nested_streams for s in streams]
def _get_frame(self, key):
diff --git a/holoviews/plotting/renderer.py b/holoviews/plotting/renderer.py
index 046c8d9f28..a2eed14c2f 100644
--- a/holoviews/plotting/renderer.py
+++ b/holoviews/plotting/renderer.py
@@ -204,7 +204,7 @@ def _validate(self, obj, fmt):
if (((len(plot) == 1 and not plot.dynamic)
or (len(plot) > 1 and self.holomap is None) or
(plot.dynamic and len(plot.keys[0]) == 0)) or
- not unbound_dimensions(plot.streams, plot.dimensions, False)):
+ not unbound_dimensions(plot.streams, plot.dimensions, no_duplicates=False)):
fmt = fig_formats[0] if self.fig=='auto' else self.fig
else:
fmt = holomap_formats[0] if self.holomap=='auto' else self.holomap
diff --git a/holoviews/plotting/util.py b/holoviews/plotting/util.py
index 81347581ef..80f6e4ea4f 100644
--- a/holoviews/plotting/util.py
+++ b/holoviews/plotting/util.py
@@ -5,7 +5,7 @@
from ..core import (HoloMap, DynamicMap, CompositeOverlay, Layout,
GridSpace, NdLayout, Store, Callable, Overlay)
-from ..core.spaces import get_streams
+from ..core.spaces import get_nested_streams
from ..core.util import (match_spec, is_number, wrap_tuple, basestring,
get_overlay_spec, unique_iterator, safe_unicode)
@@ -296,7 +296,7 @@ def attach_streams(plot, obj):
Attaches plot refresh to all streams on the object.
"""
def append_refresh(dmap):
- for stream in get_streams(dmap):
+ for stream in get_nested_streams(dmap):
stream._hidden_subscribers.append(plot.refresh)
return obj.traverse(append_refresh, [DynamicMap])
@@ -307,16 +307,9 @@ def get_sources(obj, index=None):
DynamicMap objects, returning a list of sources
indexed by the Overlay layer.
"""
- if isinstance(obj, DynamicMap):
- if isinstance(obj.callback, Callable):
- if len(obj.callback.inputs) > 1:
- layers = [(None, obj)]
- else:
- layers = [(index, obj)]
- else:
- return [(index, obj)]
- else:
- return [(index, obj)]
+ layers = [(index, obj)]
+ if not isinstance(obj, DynamicMap) or not isinstance(obj.callback, Callable):
+ return layers
index = 0 if index is None else int(index)
for o in obj.callback.inputs:
if isinstance(o, Overlay):