Skip to content

Commit

Permalink
Simplified and improved linked_zorders function
Browse files Browse the repository at this point in the history
  • Loading branch information
philippjfr committed Apr 17, 2017
1 parent 75d6261 commit 1423a3a
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 93 deletions.
10 changes: 6 additions & 4 deletions holoviews/core/overlay.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,12 @@ class Overlayable(object):

def __mul__(self, other):
if type(other).__name__ == 'DynamicMap':
from .spaces import OverlayCallable
from .spaces import Callable
def dynamic_mul(*args, **kwargs):
element = other[args]
return self * element
callback = OverlayCallable(dynamic_mul, inputs=[self, other])
callback = Callable(dynamic_mul, inputs=[self, other])
callback._overlay = True
return other.clone(shared_data=False, callback=callback,
streams=[])
if isinstance(other, UniformNdMapping) and not isinstance(other, CompositeOverlay):
Expand Down Expand Up @@ -136,11 +137,12 @@ def __add__(self, other):

def __mul__(self, other):
if type(other).__name__ == 'DynamicMap':
from .spaces import OverlayCallable
from .spaces import Callable
def dynamic_mul(*args, **kwargs):
element = other[args]
return self * element
callback = OverlayCallable(dynamic_mul, inputs=[self, other])
callback = Callable(dynamic_mul, inputs=[self, other])
callback._overlay = True
return other.clone(shared_data=False, callback=callback,
streams=[])
elif not isinstance(other, ViewableElement):
Expand Down
94 changes: 30 additions & 64 deletions holoviews/core/spaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@
from itertools import groupby
from functools import partial
from contextlib import contextmanager
from collections import defaultdict

import numpy as np
import param

Expand Down Expand Up @@ -100,6 +98,7 @@ def _dimension_keys(self):
return [tuple(zip([d.name for d in self.kdims], [k] if self.ndims == 1 else k))
for k in self.keys()]


def _dynamic_mul(self, dimensions, other, keys):
"""
Implements dynamic version of overlaying operation overlaying
Expand All @@ -117,11 +116,14 @@ 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.kdims:
self_el = self.select(key_map)
else:
self_el = self[()]
if self_el is not None:
layers.append(self_el)
else:
Expand All @@ -130,16 +132,19 @@ def dynamic_mul(*key, **kwargs):
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.kdims:
other_el = other.select(key_map)
else:
other_el = other[()]
if other_el is not None:
layers.append(other_el)
else:
layers.append(other[key])
except KeyError:
pass
return Overlay(layers)
callback = OverlayCallable(dynamic_mul, inputs=[self, other])
callback = Callable(dynamic_mul, inputs=[self, other])
callback._overlay = True
if map_obj:
return map_obj.clone(callback=callback, shared_data=False,
kdims=dimensions, streams=[])
Expand Down Expand Up @@ -206,7 +211,8 @@ def __mul__(self, other):
def dynamic_mul(*args, **kwargs):
element = self[args]
return element * other
callback = OverlayCallable(dynamic_mul, inputs=[self, other])
callback = Callable(dynamic_mul, inputs=[self, other])
callback._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 @@ -429,6 +435,9 @@ 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="""
Whether the inputs on the Callable should be linked.""")

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 +450,17 @@ class Callable(param.Parameterized):
def __init__(self, callable, **params):
super(Callable, self).__init__(callable=callable, **params)
self._memoized = {}
self._overlay = False


def get_nested_inputs(self):
inputs = []
for inp in self.inputs:
inputs.append(inp)
if isinstance(inp, DynamicMap):
inputs += inp.callback.get_nested_inputs()
return inputs


@property
def argspec(self):
Expand Down Expand Up @@ -495,13 +515,6 @@ def __call__(self, *args, **kwargs):
return ret


class OverlayCallable(Callable):
"""
A Callable subclass specifically meant to indicate that the Callable
represents an overlay operation between two objects.
"""


def get_nested_streams(dmap):
"""
Get all (potentially nested) streams from DynamicMap with Callable
Expand All @@ -516,47 +529,6 @@ def get_nested_streams(dmap):
return list(set(layer_streams))



def get_stream_sources(obj, branch=0):
"""
Traverses Callable graph to resolve sources on DynamicMap objects,
returning a dictionary of sources indexed by the Overlay layer. The
branch variables is used for internal record-keeping while recursing
through the graph.
"""
inputs = defaultdict(list)
if not isinstance(obj, DynamicMap):
if isinstance(obj, CompositeOverlay):
for i, o in enumerate(obj):
inputs[branch+i] = [o, obj]
else:
if obj not in inputs[branch]:
inputs[branch].append(obj)
return inputs

isbranch = isinstance(obj.last, CompositeOverlay)

if obj not in inputs[branch] and not isbranch:
inputs[branch].append(obj)

# Process the inputs of the DynamicMap callback
offset = 0
for i, inp in enumerate(obj.callback.inputs):
i = i if isbranch else 0
dinputs = get_stream_sources(inp, branch=branch+i+offset)
offset += max(dinputs.keys())-(branch+offset+i)
for k, v in dinputs.items():
inputs[k] = list(util.unique_iterator(inputs[k]+dinputs[k]))

# If object branches but does not declare inputs
if isbranch and not isinstance(obj.callback, OverlayCallable):
for i, o in enumerate(obj.last):
if o not in inputs[branch+i]:
inputs[branch+i].append(o)

return inputs


@contextmanager
def dynamicmap_memoization(callable_obj, streams):
"""
Expand Down Expand Up @@ -734,14 +706,8 @@ 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 isinstance(clone.callback, OverlayCallable):
from ..util import Dynamic
return Dynamic(clone, shared_data=shared_data)
elif clone.callback is self.callback:
clone.callback = clone.callback.clone()
if not any(inp is self for inputs in get_stream_sources(clone).values() for inp in inputs):
with util.disable_constant(clone.callback):
clone.callback.inputs = [self]+self.callback.inputs
if clone.callback is self.callback:
clone.callback = clone.callback.clone(inputs=[self])
return clone


Expand Down
7 changes: 4 additions & 3 deletions holoviews/plotting/plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@
from ..core.layout import Empty, NdLayout, Layout
from ..core.options import Store, Compositor, SkipRendering
from ..core.overlay import NdOverlay
from ..core.spaces import HoloMap, DynamicMap, get_stream_sources
from ..core.spaces import HoloMap, DynamicMap
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,
linked_zorders)


class Plot(param.Parameterized):
Expand Down Expand Up @@ -570,7 +571,7 @@ def __init__(self, element, keys=None, ranges=None, dimensions=None,
if overlaid:
self.stream_sources = stream_sources
else:
self.stream_sources = get_stream_sources(self.hmap)
self.stream_sources = linked_zorders(self.hmap)

plot_element = self.hmap.last
if self.batched and not isinstance(self, GenericOverlayPlot):
Expand Down
54 changes: 52 additions & 2 deletions holoviews/plotting/util.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
from __future__ import unicode_literals
from collections import defaultdict

import numpy as np
import param

from ..core import (HoloMap, DynamicMap, CompositeOverlay, Layout,
Overlay, GridSpace, NdLayout, Store, Dataset)
from ..core.spaces import get_nested_streams, Callable, OverlayCallable
from ..core.spaces import get_nested_streams, Callable
from ..core.util import (match_spec, is_number, wrap_tuple, basestring,
get_overlay_spec, unique_iterator)
get_overlay_spec, unique_iterator, unique_iterator)


def displayable(obj):
Expand Down Expand Up @@ -72,6 +73,55 @@ def collate(obj):
raise Exception(undisplayable_info(obj))


def isoverlay_fn(obj):
return isinstance(obj, DynamicMap) and (isinstance(obj.last, CompositeOverlay))


def linked_zorders(obj, path=[]):
"""
Traverses Callable graph to resolve sources on DynamicMap objects,
returning a dictionary of sources indexed by the Overlay layer. The
branch variables is used for internal record-keeping while recursing
through the graph.
"""
path = path+[obj]
zorder_map = defaultdict(list)
if not isinstance(obj, DynamicMap):
if isinstance(obj, CompositeOverlay):
for i, o in enumerate(obj):
zorder_map[i] = [o, obj]
else:
if obj not in zorder_map[0]:
zorder_map[0].append(obj)
return zorder_map

isoverlay = isinstance(obj.last, CompositeOverlay)
isdynoverlay = obj.callback._overlay
found = any(isinstance(p, DynamicMap) and p.callback._overlay for p in path)

if obj not in zorder_map[0] and not isoverlay:
zorder_map[0].append(obj)

# Process the inputs of the DynamicMap callback
dmap_inputs = obj.callback.inputs if obj.callback.link_inputs else []
for i, inp in enumerate(dmap_inputs):
if any(not (isoverlay_fn(p) or p.last is None) for p in path) and isoverlay_fn(inp):
continue
i = i if isdynoverlay else 0
dinputs = linked_zorders(inp, path=path)
offset = max(zorder_map.keys())
for k, v in dinputs.items():
zorder_map[offset+k+i] = list(unique_iterator(zorder_map[offset+k+i]+v))

# If object branches but does not declare inputs
if found and isoverlay and not isdynoverlay:
offset = max(zorder_map.keys())
for i, o in enumerate(obj.last):
if o not in zorder_map[offset+i]:
zorder_map[offset+i].append(o)
return zorder_map


def initialize_dynamic(obj):
"""
Initializes all DynamicMap objects contained by the object
Expand Down
Loading

0 comments on commit 1423a3a

Please sign in to comment.