Skip to content

Commit

Permalink
Merge 3bf77dc into 017ffd7
Browse files Browse the repository at this point in the history
  • Loading branch information
philippjfr committed Dec 19, 2015
2 parents 017ffd7 + 3bf77dc commit 8b5a684
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 33 deletions.
11 changes: 9 additions & 2 deletions holoviews/core/util.py
Expand Up @@ -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):
"""
Expand All @@ -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:
Expand Down
39 changes: 24 additions & 15 deletions holoviews/plotting/bokeh/element.py
Expand Up @@ -14,10 +14,11 @@
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
from ..util import dynamic_update
from .callbacks import Callbacks
from .plot import BokehPlot
from .renderer import old_bokeh
Expand Down Expand Up @@ -441,19 +442,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
Expand All @@ -464,13 +460,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)

Expand Down Expand Up @@ -689,15 +684,29 @@ 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):
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)
36 changes: 24 additions & 12 deletions holoviews/plotting/mpl/element.py
Expand Up @@ -7,10 +7,11 @@
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
from ..util import dynamic_update
from .plot import MPLPlot
from .util import wrap_formatter

Expand Down Expand Up @@ -124,12 +125,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)
Expand Down Expand Up @@ -391,12 +392,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
Expand Down Expand Up @@ -679,9 +677,23 @@ 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):
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)

Expand Down
18 changes: 15 additions & 3 deletions holoviews/plotting/plot.py
Expand Up @@ -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:
Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand Down
48 changes: 47 additions & 1 deletion holoviews/plotting/util.py
Expand Up @@ -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):
Expand Down Expand Up @@ -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]

0 comments on commit 8b5a684

Please sign in to comment.