Skip to content

Commit

Permalink
Avoid rerendering of overlaid DynamicMaps with non-triggered streams
Browse files Browse the repository at this point in the history
  • Loading branch information
philippjfr committed Jan 11, 2018
1 parent 2e4e81d commit c50c5dd
Show file tree
Hide file tree
Showing 5 changed files with 175 additions and 14 deletions.
5 changes: 3 additions & 2 deletions holoviews/core/spaces.py
Expand Up @@ -570,10 +570,11 @@ def get_nested_dmaps(dmap):
"""
Get all DynamicMaps referenced by the supplied DynamicMap's callback.
"""
if not isinstance(dmap, DynamicMap):
return []
dmaps = [dmap]
for o in dmap.callback.inputs:
if isinstance(o, DynamicMap):
dmaps.extend(get_nested_dmaps(o))
dmaps.extend(get_nested_dmaps(o))
return list(set(dmaps))


Expand Down
11 changes: 10 additions & 1 deletion holoviews/plotting/bokeh/element.py
Expand Up @@ -1461,9 +1461,18 @@ def update_frame(self, key, ranges=None, element=None):

if element and not self.overlaid and not self.tabs and not self.batched:
self._update_ranges(element, ranges)


# Determine which stream (if any) triggered the update
triggering = [stream for stream in self.streams if stream._triggering]

for k, subplot in self.subplots.items():
el = None

# Skip updates to subplots when its streams is not one of
# the streams that initiated the update
if triggering and all(s not in triggering for s in subplot.streams):
continue

# If in Dynamic mode propagate elements to subplots
if isinstance(self.hmap, DynamicMap) and element:
# In batched mode NdOverlay is passed to subplot directly
Expand Down
20 changes: 12 additions & 8 deletions holoviews/plotting/plot.py
Expand Up @@ -22,7 +22,8 @@
from ..element import Table
from .util import (get_dynamic_mode, initialize_unbounded, dim_axis_label,
attach_streams, traverse_setter, get_nested_streams,
compute_overlayable_zorders, get_plot_frame)
compute_overlayable_zorders, get_plot_frame,
split_dmap_overlay)


class Plot(param.Parameterized):
Expand Down Expand Up @@ -565,7 +566,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={}, stream_sources=[], **params):
overlay_dims={}, stream_sources=[], streams=None, **params):
self.zorder = zorder
self.cyclic_index = cyclic_index
self.overlaid = overlaid
Expand Down Expand Up @@ -608,10 +609,7 @@ def __init__(self, element, keys=None, ranges=None, dimensions=None,
super(GenericElementPlot, self).__init__(keys=keys, dimensions=dimensions,
dynamic=dynamic,
**dict(params, **plot_opts))
streams = []
if isinstance(self.hmap, DynamicMap):
streams = get_nested_streams(self.hmap)
self.streams = streams
self.streams = get_nested_streams(self.hmap) if streams is None else streams
if self.top_level:
self.comm = self.init_comm()
self.traverse(lambda x: setattr(x, 'comm', self.comm))
Expand Down Expand Up @@ -850,6 +848,12 @@ def _create_subplots(self, ranges):
self.batched = False
keys, vmaps = self.hmap.split_overlays()

if isinstance(self.hmap, DynamicMap):
dmap_streams = [get_nested_streams(layer) for layer in
split_dmap_overlay(self.hmap)]
else:
dmap_streams = [None]*len(keys)

# Compute global ordering
length = self.style_grouping
group_fn = lambda x: (x.type.__name__, x.last.group, x.last.label)
Expand All @@ -861,7 +865,7 @@ def _create_subplots(self, ranges):
group_counter = Counter()

subplots = OrderedDict()
for (key, vmap) in zip(keys, vmaps):
for (key, vmap, streams) in zip(keys, vmaps, dmap_streams):
opts = {'overlaid': overlay_type}
if self.hmap.type == Overlay:
style_key = (vmap.type.__name__,) + key
Expand Down Expand Up @@ -911,7 +915,7 @@ def _create_subplots(self, ranges):
layout_dimensions=self.layout_dimensions,
ranges=ranges, show_title=self.show_title,
style=style, uniform=self.uniform,
fontsize=self.fontsize,
fontsize=self.fontsize, streams=streams,
renderer=self.renderer, stream_sources=stream_sources,
zorder=zorder, adjoined=self.adjoined, **passed_handles)

Expand Down
43 changes: 42 additions & 1 deletion holoviews/plotting/util.py
Expand Up @@ -6,7 +6,7 @@
import param

from ..core import (HoloMap, DynamicMap, CompositeOverlay, Layout,
Overlay, GridSpace, NdLayout, Store)
Overlay, GridSpace, NdLayout, Store, NdOverlay)
from ..core.options import Cycle
from ..core.spaces import get_nested_streams
from ..core.util import (match_spec, is_number, wrap_tuple, basestring,
Expand Down Expand Up @@ -174,6 +174,47 @@ def compute_overlayable_zorders(obj, path=[]):
return zorder_map


def is_dynamic_overlay(dmap):
"""
Traverses a DynamicMap graph and determines if any components
were overlaid dynamically (i.e. by * on a DynamicMap).
"""
if not isinstance(dmap, DynamicMap):
return False
elif dmap.callback._is_overlay:
return True
else:
return any(is_dynamic_overlay(dm) for dm in dmap.callback.inputs)


def split_dmap_overlay(obj, depth=0):
"""
Splits a DynamicMap into the original component layers it was
constructed from.
"""
layers = []
if isinstance(obj, DynamicMap):
if issubclass(obj.type, NdOverlay) and not depth:
for v in obj.last.values():
layers.append(obj)
elif issubclass(obj.type, Overlay):
if obj.callback.inputs and is_dynamic_overlay(obj):
for inp in obj.callback.inputs:
layers += split_dmap_overlay(inp, depth+1)
else:
for v in obj.last.values():
layers.append(obj)
else:
layers.append(obj)
return layers
if isinstance(obj, Overlay):
for k, v in obj.items():
layers.append(v)
else:
layers.append(obj)
return layers


def initialize_dynamic(obj):
"""
Initializes all DynamicMap objects contained by the object
Expand Down
110 changes: 108 additions & 2 deletions tests/testplotutils.py
Expand Up @@ -7,9 +7,12 @@
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
from holoviews.element import (Image, Scatter, Curve, Text, Points,
Area, VectorField, HLine, Path)
from holoviews.operation import operation
from holoviews.plotting.util import (
compute_overlayable_zorders, get_min_distance, process_cmap)
compute_overlayable_zorders, get_min_distance, process_cmap,
initialize_dynamic, split_dmap_overlay)
from holoviews.streams import PointerX

try:
Expand Down Expand Up @@ -317,6 +320,109 @@ def test_dynamic_compute_overlayable_zorders_three_deep_dynamic_layers_reduced_l
self.assertNotIn(curve, sources[2])


class TestSplitDynamicMapOverlay(ComparisonTestCase):
"""
Tests the split_dmap_overlay utility
"""

def setUp(self):
self.dmap_element = DynamicMap(lambda: Image([]))
self.dmap_overlay = DynamicMap(lambda: Overlay([Curve([]), Points([])]))
self.dmap_ndoverlay = DynamicMap(lambda: NdOverlay({0: Curve([]), 1: Curve([])}))
self.element = Scatter([])
self.el1, self.el2 = Path([]), HLine(0)
self.overlay = Overlay([self.el1, self.el2])
self.ndoverlay = NdOverlay({0: VectorField([]), 1: VectorField([])})

def test_dmap_ndoverlay(self):
test = self.dmap_ndoverlay
initialize_dynamic(test)
layers = [self.dmap_ndoverlay, self.dmap_ndoverlay]
self.assertEqual(split_dmap_overlay(test), layers)

def test_dmap_overlay(self):
test = self.dmap_overlay
initialize_dynamic(test)
layers = [self.dmap_overlay, self.dmap_overlay]
self.assertEqual(split_dmap_overlay(test), layers)

def test_dmap_element_mul_dmap_overlay(self):
test = self.dmap_element * self.dmap_overlay
initialize_dynamic(test)
layers = [self.dmap_element, self.dmap_overlay, self.dmap_overlay]
self.assertEqual(split_dmap_overlay(test), layers)

def test_dmap_element_mul_dmap_ndoverlay(self):
test = self.dmap_element * self.dmap_ndoverlay
initialize_dynamic(test)
layers = [self.dmap_element, self.dmap_ndoverlay]
self.assertEqual(split_dmap_overlay(test), layers)

def test_dmap_element_mul_element(self):
test = self.dmap_element * self.element
initialize_dynamic(test)
layers = [self.dmap_element, self.element]
self.assertEqual(split_dmap_overlay(test), layers)

def test_dmap_element_mul_overlay(self):
test = self.dmap_element * self.overlay
initialize_dynamic(test)
layers = [self.dmap_element, self.el1, self.el2]
self.assertEqual(split_dmap_overlay(test), layers)

def test_dmap_element_mul_ndoverlay(self):
test = self.dmap_element * self.ndoverlay
initialize_dynamic(test)
layers = [self.dmap_element, self.ndoverlay]
self.assertEqual(split_dmap_overlay(test), layers)

def test_dmap_overlay_mul_dmap_ndoverlay(self):
test = self.dmap_overlay * self.dmap_ndoverlay
initialize_dynamic(test)
layers = [self.dmap_overlay, self.dmap_overlay, self.dmap_ndoverlay]
self.assertEqual(split_dmap_overlay(test), layers)

def test_dmap_overlay_mul_element(self):
test = self.dmap_overlay * self.element
initialize_dynamic(test)
layers = [self.dmap_overlay, self.dmap_overlay, self.element]
self.assertEqual(split_dmap_overlay(test), layers)

def test_dmap_overlay_mul_overlay(self):
test = self.dmap_overlay * self.overlay
initialize_dynamic(test)
layers = [self.dmap_overlay, self.dmap_overlay, self.el1, self.el2]
self.assertEqual(split_dmap_overlay(test), layers)

def test_dmap_all_combinations(self):
test = (self.dmap_overlay * self.element * self.dmap_ndoverlay *
self.overlay * self.dmap_element * self.ndoverlay)
initialize_dynamic(test)
layers = [self.dmap_overlay, self.dmap_overlay, self.element,
self.dmap_ndoverlay, self.el1, self.el2, self.dmap_element,
self.ndoverlay]
self.assertEqual(split_dmap_overlay(test), layers)

def test_dmap_overlay_operation_mul_dmap_ndoverlay(self):
mapped = operation(self.dmap_overlay)
test = mapped * self.dmap_ndoverlay
initialize_dynamic(test)
layers = [mapped, mapped, self.dmap_ndoverlay]
self.assertEqual(split_dmap_overlay(test), layers)

def test_dmap_overlay_linked_operation_mul_dmap_ndoverlay(self):
mapped = operation(self.dmap_overlay, link_inputs=True)
test = mapped * self.dmap_ndoverlay
initialize_dynamic(test)
layers = [mapped, mapped, self.dmap_ndoverlay]
self.assertEqual(split_dmap_overlay(test), layers)

def test_dmap_overlay_linked_operation_mul_dmap_ndoverlay(self):
mapped = self.dmap_overlay.map(lambda x: x.get(0), Overlay)
test = mapped * self.element * self.dmap_ndoverlay
initialize_dynamic(test)
layers = [mapped, self.element, self.dmap_ndoverlay]
self.assertEqual(split_dmap_overlay(test), layers)


class TestPlotColorUtils(ComparisonTestCase):
Expand Down

0 comments on commit c50c5dd

Please sign in to comment.