From b00888582cc561c8bf0430c90e8b3ed045246ca9 Mon Sep 17 00:00:00 2001
From: Philipp Rudiger
Date: Fri, 18 Dec 2015 20:26:05 +0000
Subject: [PATCH 1/6] Split out get_overlay_spec function
---
holoviews/core/util.py | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/holoviews/core/util.py b/holoviews/core/util.py
index 057a5710cd..a09806e687 100644
--- a/holoviews/core/util.py
+++ b/holoviews/core/util.py
@@ -611,6 +611,14 @@ def walk_depth_first(name):
(names_by_level.get(i, None)
for i in itertools.count())))
+def get_overlay_spec(o, k, v):
+ """
+ Gets the type.group.label + key spec from an Element in an Overlay.
+ """
+ k = (wrap_tuple(k),)
+ return ((type(v).__name__, v.group, v.label) + k if len(o.kdims) else
+ (type(v).__name__,) + k)
+
def layer_sort(hmap):
"""
@@ -619,8 +627,7 @@ def layer_sort(hmap):
"""
orderings = {}
for o in hmap:
- okeys = [(type(v).__name__, v.group, v.label) + k if len(o.kdims) else
- (type(v).__name__,) + k for k, v in o.data.items()]
+ okeys = [get_overlay_spec(o, k, v) for k, v in o.data.items()]
if len(okeys) == 1 and not okeys[0] in orderings:
orderings[okeys[0]] = []
else:
From ea97e023a90de1a6c1990518a041e76452497a06 Mon Sep 17 00:00:00 2001
From: Philipp Rudiger
Date: Fri, 18 Dec 2015 20:27:05 +0000
Subject: [PATCH 2/6] Made GenericOverlayPlot compatible with DynamicMaps
---
holoviews/plotting/plot.py | 65 +++++++++++++++++++++++++++++++++++---
1 file changed, 60 insertions(+), 5 deletions(-)
diff --git a/holoviews/plotting/plot.py b/holoviews/plotting/plot.py
index 7201b42605..47c73125cd 100644
--- a/holoviews/plotting/plot.py
+++ b/holoviews/plotting/plot.py
@@ -441,7 +441,10 @@ def __init__(self, element, keys=None, ranges=None, dimensions=None,
def _get_frame(self, key):
- if self.dynamic:
+ if isinstance(self.hmap, DynamicMap) and self.overlaid and self.current_frame:
+ self.current_key = key
+ return self.current_frame
+ elif self.dynamic:
if isinstance(key, tuple):
frame = self.hmap[key]
elif key < self.hmap.counter:
@@ -457,7 +460,7 @@ def _get_frame(self, key):
self.current_key = key
return frame
- if not self.dynamic and isinstance(key, int):
+ if isinstance(key, int):
key = self.hmap.keys()[min([key, len(self.hmap)-1])]
if key == self.current_key:
@@ -657,10 +660,10 @@ def _create_subplots(self, ranges):
continue
if self.hmap.type == Overlay:
- style_key = (vmap.type.__name__,) + key
+ style_key = (vmap.type.__name__,) + (key,)
else:
if not isinstance(key, tuple): key = (key,)
- style_key = group_fn(vmap) + key
+ style_key = group_fn(vmap) + (key,)
group_key = style_key[:length]
zorder = ordering.index(style_key) + zoffset
cyclic_index = group_counter[group_key]
@@ -688,8 +691,17 @@ def _create_subplots(self, ranges):
def get_extents(self, overlay, ranges):
extents = []
+ items = overlay.items()
for key, subplot in self.subplots.items():
- layer = overlay.data.get(key, False)
+ layer = overlay.data.get(key, None)
+ found = False
+ if isinstance(self.hmap, DynamicMap) and layer is None:
+ for i, (k, layer) in enumerate(items):
+ if isinstance(layer, subplot.hmap.type):
+ found = True
+ break
+ if not found:
+ layer = None
if layer and subplot.apply_ranges:
if isinstance(layer, CompositeOverlay):
sp_ranges = ranges
@@ -722,6 +734,49 @@ def _format_title(self, key, separator='\n'):
return separator.join([title, dim_title])
+ def dynamic_update(self, subplot, key, overlay, items):
+ """
+ Function to assign layers in an Overlay to a new plot.
+ """
+ layer = overlay.get(key, None)
+ if layer is None:
+ match_spec = util.get_overlay_spec(self.current_frame,
+ util.wrap_tuple(key),
+ subplot.current_frame)
+ specs = [(i, util.get_overlay_spec(overlay, util.wrap_tuple(k), el))
+ for i, (k, el) in enumerate(items)]
+ idx = self.closest_match(match_spec, specs)
+ k, layer = items.pop(idx)
+ return layer
+
+
+ def closest_match(self, match, specs, depth=0):
+ new_specs = []
+ match_lengths = []
+ for i, spec in specs:
+ if spec[0] == match[0]:
+ new_specs.append((i, spec[1:]))
+ else:
+ match_length = max(i for i in range(len(match[0]))
+ if (isinstance(match[0], tuple)
+ and match[0][:i] == spec[0][:i])
+ or (isinstance(match[0], util.basestring)
+ and match[0].startswith(spec[0][:i])))
+ match_lengths.append((i, match_length, spec[0]))
+ if not new_specs:
+ if depth == 0:
+ raise Exception("No plot with matching type found, ensure "
+ "the first frame of the DynamicMap initializes "
+ "all required plots.")
+ else:
+ return sorted(match_lengths, key=lambda x: -x[1])[0][0]
+ elif new_specs == 1:
+ return new_specs[0][0]
+ else:
+ depth = depth+1
+ return self.closest_match(match[1:], new_specs, depth)
+
+
class GenericCompositePlot(DimensionedPlot):
From 6ce7bf382408daafe938ac328a6e1fa8a091db23 Mon Sep 17 00:00:00 2001
From: Philipp Rudiger
Date: Fri, 18 Dec 2015 20:28:16 +0000
Subject: [PATCH 3/6] Fixed handling of dynamic Overlay in bokeh plots
---
holoviews/plotting/bokeh/element.py | 30 ++++++++++++++---------------
1 file changed, 15 insertions(+), 15 deletions(-)
diff --git a/holoviews/plotting/bokeh/element.py b/holoviews/plotting/bokeh/element.py
index 359461877c..65af8bc426 100644
--- a/holoviews/plotting/bokeh/element.py
+++ b/holoviews/plotting/bokeh/element.py
@@ -14,7 +14,7 @@
mpl = None
import param
-from ...core import Store, HoloMap, Overlay, CompositeOverlay
+from ...core import Store, HoloMap, Overlay, CompositeOverlay, DynamicMap
from ...core import util
from ...element import RGB
from ..plot import GenericElementPlot, GenericOverlayPlot
@@ -441,19 +441,14 @@ def initialize_plot(self, ranges=None, plot=None, plots=None, source=None):
return plot
- def update_frame(self, key, ranges=None, plot=None, element=None):
+ def update_frame(self, key, ranges=None, plot=None, element=None, empty=False):
"""
Updates an existing plot with data corresponding
to the key.
"""
- if element is None:
+ reused = isinstance(self.hmap, DynamicMap) and self.overlaid
+ if not reused and element is None:
element = self._get_frame(key)
- if not element:
- if self.dynamic and self.overlaid:
- self.current_key = key
- element = self.current_frame
- else:
- element = self._get_frame(key)
else:
self.current_key = key
self.current_frame = element
@@ -464,13 +459,12 @@ def update_frame(self, key, ranges=None, plot=None, element=None):
return
self.set_param(**self.lookup_options(element, 'plot').options)
- ranges = self.compute_ranges(self.hmap, key, ranges)
ranges = util.match_spec(element, ranges)
self.current_ranges = ranges
plot = self.handles['plot']
source = self.handles['source']
- empty = self.callbacks and self.callbacks.downsample
+ empty = (self.callbacks and self.callbacks.downsample) or empty
data, mapping = self.get_data(element, ranges, empty)
self._update_datasource(source, data)
@@ -689,15 +683,21 @@ def update_frame(self, key, ranges=None, element=None):
"""
if element is None:
element = self._get_frame(key)
- ranges = self.compute_ranges(element, key, ranges)
else:
self.current_frame = element
self.current_key = key
- ranges = self.compute_ranges(self.hmap, key, ranges)
+ range_obj = element if isinstance(self.hmap, DynamicMap) else self.hmap
+ ranges = self.compute_ranges(range_obj, key, ranges)
+
+ items = element.items()
for k, subplot in self.subplots.items():
- el = element.get(k, None) if isinstance(element, CompositeOverlay) else None
- subplot.update_frame(key, ranges, element=el)
+ empty = False
+ if isinstance(self.hmap, DynamicMap):
+ el = self.dynamic_update(subplot, k, element, items)
+ empty = el is None
+ subplot.update_frame(key, ranges, element=el, empty=empty)
+
if not self.overlaid and not self.tabs:
self._update_ranges(element, ranges)
self._update_plot(key, self.handles['plot'], element)
From 4c06fe3f3d76603c5a5ac72bbc0230765bd11bd8 Mon Sep 17 00:00:00 2001
From: Philipp Rudiger
Date: Fri, 18 Dec 2015 20:37:48 +0000
Subject: [PATCH 4/6] Fixed handling of dynamic Overlays in matplotlib backend
---
holoviews/plotting/mpl/element.py | 28 ++++++++++++++++------------
1 file changed, 16 insertions(+), 12 deletions(-)
diff --git a/holoviews/plotting/mpl/element.py b/holoviews/plotting/mpl/element.py
index 5838d759d9..83e689da19 100644
--- a/holoviews/plotting/mpl/element.py
+++ b/holoviews/plotting/mpl/element.py
@@ -7,7 +7,7 @@
import param
from ...core import util
-from ...core import (OrderedDict, Collator, NdOverlay, HoloMap,
+from ...core import (OrderedDict, Collator, NdOverlay, HoloMap, DynamicMap,
CompositeOverlay, Element3D, Columns, NdElement)
from ...element import Table, ItemTable, Raster
from ..plot import GenericElementPlot, GenericOverlayPlot
@@ -124,12 +124,12 @@ def _finalize_axis(self, key, title=None, ranges=None, xticks=None, yticks=None,
When the number of the frame is supplied as n, this method looks
up and computes the appropriate title, axis labels and axis bounds.
"""
-
+ element = self._get_frame(key)
+ self.current_frame = element
axis = self.handles['axis']
if self.bgcolor:
axis.set_axis_bgcolor(self.bgcolor)
- element = self._get_frame(key)
subplots = list(self.subplots.values()) if self.subplots else []
if self.zorder == 0 and key is not None:
title = None if self.zorder > 0 else self._format_title(key)
@@ -391,12 +391,9 @@ def update_frame(self, key, ranges=None, element=None):
If n is greater than the number of available frames, update
using the last available frame.
"""
- if not element:
- if self.dynamic and self.overlaid:
- self.current_key = key
- element = self.current_frame
- else:
- element = self._get_frame(key)
+ reused = isinstance(self.hmap, DynamicMap) and self.overlaid
+ if not reused and element is None:
+ element = self._get_frame(key)
else:
self.current_key = key
self.current_frame = element
@@ -679,9 +676,16 @@ def update_frame(self, key, ranges=None, element=None):
else:
self.current_frame = element
self.current_key = key
- ranges = self.compute_ranges(self.hmap, key, ranges)
- for k, plot in self.subplots.items():
- plot.update_frame(key, ranges, element.get(k, None))
+
+ range_obj = element if isinstance(self.hmap, DynamicMap) else self.hmap
+ ranges = self.compute_ranges(range_obj, key, ranges)
+
+ items = element.items()
+ for k, subplot in self.subplots.items():
+ el = element.get(k, None)
+ if isinstance(self.hmap, DynamicMap):
+ el = self.dynamic_update(subplot, k, element, items)
+ subplot.update_frame(key, ranges, el)
self._finalize_axis(key, ranges=ranges)
From 53d9f58cf1c51a2cd67cffdc5be1288e5828000a Mon Sep 17 00:00:00 2001
From: Philipp Rudiger
Date: Fri, 18 Dec 2015 23:05:49 +0000
Subject: [PATCH 5/6] Fix for matching overlay keys correctly
---
holoviews/core/util.py | 2 +-
holoviews/plotting/plot.py | 16 +++++++++-------
2 files changed, 10 insertions(+), 8 deletions(-)
diff --git a/holoviews/core/util.py b/holoviews/core/util.py
index a09806e687..5d6022bd51 100644
--- a/holoviews/core/util.py
+++ b/holoviews/core/util.py
@@ -615,7 +615,7 @@ def get_overlay_spec(o, k, v):
"""
Gets the type.group.label + key spec from an Element in an Overlay.
"""
- k = (wrap_tuple(k),)
+ k = wrap_tuple(k)
return ((type(v).__name__, v.group, v.label) + k if len(o.kdims) else
(type(v).__name__,) + k)
diff --git a/holoviews/plotting/plot.py b/holoviews/plotting/plot.py
index 47c73125cd..5c441b7fa2 100644
--- a/holoviews/plotting/plot.py
+++ b/holoviews/plotting/plot.py
@@ -660,10 +660,10 @@ def _create_subplots(self, ranges):
continue
if self.hmap.type == Overlay:
- style_key = (vmap.type.__name__,) + (key,)
+ style_key = (vmap.type.__name__,) + key
else:
if not isinstance(key, tuple): key = (key,)
- style_key = group_fn(vmap) + (key,)
+ style_key = group_fn(vmap) + key
group_key = style_key[:length]
zorder = ordering.index(style_key) + zoffset
cyclic_index = group_counter[group_key]
@@ -757,11 +757,13 @@ def closest_match(self, match, specs, depth=0):
if spec[0] == match[0]:
new_specs.append((i, spec[1:]))
else:
- match_length = max(i for i in range(len(match[0]))
- if (isinstance(match[0], tuple)
- and match[0][:i] == spec[0][:i])
- or (isinstance(match[0], util.basestring)
- and match[0].startswith(spec[0][:i])))
+ if util.isnumber(match[0]) and util.isnumber(spec[0]):
+ match_length = -abs(match[0]-spec[0])
+ elif all(isinstance(s[0], basestring) for s in [spec, match]):
+ match_length = max(i for i in range(len(match[0]))
+ if match[0].startswith(spec[0][:i]))
+ else:
+ match_length = 0
match_lengths.append((i, match_length, spec[0]))
if not new_specs:
if depth == 0:
From 3bf77dc37561cc7f97e6840cb3c541978f8d6b02 Mon Sep 17 00:00:00 2001
From: Philipp Rudiger
Date: Sat, 19 Dec 2015 01:07:00 +0000
Subject: [PATCH 6/6] Factored out functions to find closest matching overlaid
Element
---
holoviews/plotting/bokeh/element.py | 13 ++++++--
holoviews/plotting/mpl/element.py | 10 +++++-
holoviews/plotting/plot.py | 45 ---------------------------
holoviews/plotting/util.py | 48 ++++++++++++++++++++++++++++-
4 files changed, 67 insertions(+), 49 deletions(-)
diff --git a/holoviews/plotting/bokeh/element.py b/holoviews/plotting/bokeh/element.py
index 65af8bc426..b9be5f4edd 100644
--- a/holoviews/plotting/bokeh/element.py
+++ b/holoviews/plotting/bokeh/element.py
@@ -18,6 +18,7 @@
from ...core import util
from ...element import RGB
from ..plot import GenericElementPlot, GenericOverlayPlot
+from ..util import dynamic_update
from .callbacks import Callbacks
from .plot import BokehPlot
from .renderer import old_bokeh
@@ -694,10 +695,18 @@ def update_frame(self, key, ranges=None, element=None):
for k, subplot in self.subplots.items():
empty = False
if isinstance(self.hmap, DynamicMap):
- el = self.dynamic_update(subplot, k, element, items)
- empty = el is None
+ idx = dynamic_update(self, subplot, k, element, items)
+ empty = idx is None
+ if empty:
+ _, el = items.pop(idx)
subplot.update_frame(key, ranges, element=el, empty=empty)
+
+ if isinstance(self.hmap, DynamicMap) and items:
+ raise Exception("Some Elements returned by the dynamic callback "
+ "were not initialized correctly and could not be "
+ "rendered.")
+
if not self.overlaid and not self.tabs:
self._update_ranges(element, ranges)
self._update_plot(key, self.handles['plot'], element)
diff --git a/holoviews/plotting/mpl/element.py b/holoviews/plotting/mpl/element.py
index 83e689da19..357b35e944 100644
--- a/holoviews/plotting/mpl/element.py
+++ b/holoviews/plotting/mpl/element.py
@@ -11,6 +11,7 @@
CompositeOverlay, Element3D, Columns, NdElement)
from ...element import Table, ItemTable, Raster
from ..plot import GenericElementPlot, GenericOverlayPlot
+from ..util import dynamic_update
from .plot import MPLPlot
from .util import wrap_formatter
@@ -684,9 +685,16 @@ def update_frame(self, key, ranges=None, element=None):
for k, subplot in self.subplots.items():
el = element.get(k, None)
if isinstance(self.hmap, DynamicMap):
- el = self.dynamic_update(subplot, k, element, items)
+ idx = dynamic_update(self, subplot, k, element, items)
+ if idx is not None:
+ _, el = items.pop(idx)
subplot.update_frame(key, ranges, el)
+ if isinstance(self.hmap, DynamicMap) and items:
+ raise Exception("Some Elements returned by the dynamic callback "
+ "were not initialized correctly and could not be "
+ "rendered.")
+
self._finalize_axis(key, ranges=ranges)
diff --git a/holoviews/plotting/plot.py b/holoviews/plotting/plot.py
index 5c441b7fa2..6af24a05da 100644
--- a/holoviews/plotting/plot.py
+++ b/holoviews/plotting/plot.py
@@ -734,51 +734,6 @@ def _format_title(self, key, separator='\n'):
return separator.join([title, dim_title])
- def dynamic_update(self, subplot, key, overlay, items):
- """
- Function to assign layers in an Overlay to a new plot.
- """
- layer = overlay.get(key, None)
- if layer is None:
- match_spec = util.get_overlay_spec(self.current_frame,
- util.wrap_tuple(key),
- subplot.current_frame)
- specs = [(i, util.get_overlay_spec(overlay, util.wrap_tuple(k), el))
- for i, (k, el) in enumerate(items)]
- idx = self.closest_match(match_spec, specs)
- k, layer = items.pop(idx)
- return layer
-
-
- def closest_match(self, match, specs, depth=0):
- new_specs = []
- match_lengths = []
- for i, spec in specs:
- if spec[0] == match[0]:
- new_specs.append((i, spec[1:]))
- else:
- if util.isnumber(match[0]) and util.isnumber(spec[0]):
- match_length = -abs(match[0]-spec[0])
- elif all(isinstance(s[0], basestring) for s in [spec, match]):
- match_length = max(i for i in range(len(match[0]))
- if match[0].startswith(spec[0][:i]))
- else:
- match_length = 0
- match_lengths.append((i, match_length, spec[0]))
- if not new_specs:
- if depth == 0:
- raise Exception("No plot with matching type found, ensure "
- "the first frame of the DynamicMap initializes "
- "all required plots.")
- else:
- return sorted(match_lengths, key=lambda x: -x[1])[0][0]
- elif new_specs == 1:
- return new_specs[0][0]
- else:
- depth = depth+1
- return self.closest_match(match[1:], new_specs, depth)
-
-
class GenericCompositePlot(DimensionedPlot):
diff --git a/holoviews/plotting/util.py b/holoviews/plotting/util.py
index 331bc67590..82f6aa1b5e 100644
--- a/holoviews/plotting/util.py
+++ b/holoviews/plotting/util.py
@@ -2,7 +2,7 @@
from ..core import (HoloMap, DynamicMap, CompositeOverlay, Layout,
GridSpace, NdLayout, Store)
-from ..core.util import match_spec
+from ..core.util import match_spec, is_number, wrap_tuple, get_overlay_spec
def displayable(obj):
@@ -159,3 +159,49 @@ def save_frames(obj, filename, fmt=None, backend=None, options=None):
for i in range(len(plot)):
plot.update(i)
renderer.save(plot, '%s_%s' % (filename, i), fmt=fmt, options=options)
+
+
+def dynamic_update(plot, subplot, key, overlay, items):
+ """
+ Given a plot, subplot and dynamically generated (Nd)Overlay
+ find the closest matching Element for that plot.
+ """
+ match_spec = get_overlay_spec(overlay,
+ wrap_tuple(key),
+ subplot.current_frame)
+ specs = [(i, get_overlay_spec(overlay, wrap_tuple(k), el))
+ for i, (k, el) in enumerate(items)]
+ return closest_match(match_spec, specs)
+
+
+def closest_match(match, specs, depth=0):
+ """
+ Recursively iterates over type, group, label and overlay key,
+ finding the closest matching spec.
+ """
+ new_specs = []
+ match_lengths = []
+ for i, spec in specs:
+ if spec[0] == match[0]:
+ new_specs.append((i, spec[1:]))
+ else:
+ if is_number(match[0]) and is_number(spec[0]):
+ match_length = -abs(match[0]-spec[0])
+ elif all(isinstance(s[0], basestring) for s in [spec, match]):
+ match_length = max(i for i in range(len(match[0]))
+ if match[0].startswith(spec[0][:i]))
+ else:
+ match_length = 0
+ match_lengths.append((i, match_length, spec[0]))
+
+ if len(new_specs) == 1:
+ return new_specs[0][0]
+ elif new_specs:
+ depth = depth+1
+ return closest_match(match[1:], new_specs, depth)
+ else:
+ if depth == 0 or not match_lengths:
+ return None
+ else:
+ return sorted(match_lengths, key=lambda x: -x[1])[0][0]
+