Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed scrubber bug and add support for discrete DynamicMap scrubber #1832

Merged
merged 10 commits into from
Aug 31, 2017
6 changes: 5 additions & 1 deletion holoviews/core/spaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,10 @@ def __call__(self, *args, **kwargs):

try:
ret = self.callable(*args, **kwargs)
except KeyError:
# KeyError is caught separately because it is used to signal
# invalid keys on DynamicMap and should not warn
raise
except:
posstr = ', '.join(['%r' % el for el in self.args]) if self.args else ''
kwstr = ', '.join('%s=%r' % (k,v) for k,v in self.kwargs.items())
Expand Down Expand Up @@ -1055,7 +1059,7 @@ def _cache(self, key, val):
if len(self) >= cache_size:
first_key = next(k for k in self.data)
self.data.pop(first_key)
self.data[key] = val
self[key] = val


def map(self, map_fn, specs=None, clone=True):
Expand Down
7 changes: 6 additions & 1 deletion holoviews/core/traversal.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from operator import itemgetter

from .dimension import Dimension
from .util import merge_dimensions
from .util import merge_dimensions, cartesian_product

try:
import itertools.izip as zip
Expand Down Expand Up @@ -89,6 +89,11 @@ def unique_dimkeys(obj, default_dim='Frame'):
if not matches:
unique_keys.append(padded_key)

# Add cartesian product of DynamicMap values to keys
values = [d.values for d in all_dims]
if obj.traverse(lambda x: x, ['DynamicMap']) and values and all(values):
unique_keys += list(zip(*cartesian_product(values)))

with item_check(False):
sorted_keys = NdMapping({key: None for key in unique_keys},
kdims=all_dims).data.keys()
Copy link
Contributor

@jlstevens jlstevens Aug 31, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we do this? Is this more useful than taking the set of keys then sorting them?

Copy link
Member Author

@philippjfr philippjfr Aug 31, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There used to be at least, not sure its the case anymore though. Might leave a note to simplify this, but it's
used in a few places.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually I'll open an issue because this is related to sorting issues.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Opened the issue here: #1835

Expand Down
14 changes: 7 additions & 7 deletions holoviews/plotting/plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from ..core.options import Store, Compositor, SkipRendering
from ..core.overlay import NdOverlay
from ..core.spaces import HoloMap, DynamicMap
from ..core.util import stream_parameters
from ..core.util import stream_parameters, cartesian_product
from ..element import Table
from .util import (get_dynamic_mode, initialize_unbounded, dim_axis_label,
attach_streams, traverse_setter, get_nested_streams,
Expand Down Expand Up @@ -587,10 +587,15 @@ def __init__(self, element, keys=None, ranges=None, dimensions=None,
if self.batched and not isinstance(self, GenericOverlayPlot):
plot_element = plot_element.last

dynamic = isinstance(element, DynamicMap) and not element.unbounded
self.top_level = keys is None
if self.top_level:
dimensions = self.hmap.kdims
keys = list(self.hmap.data.keys())
values = [d.values for d in dimensions]
if dynamic and values and all(values):
keys = list(zip(*cartesian_product(values)))
else:
keys = list(self.hmap.data.keys())

self.style = self.lookup_options(plot_element, 'style') if style is None else style
plot_opts = self.lookup_options(plot_element, 'plot').options
Expand All @@ -600,7 +605,6 @@ def __init__(self, element, keys=None, ranges=None, dimensions=None,
defaults=False)
plot_opts.update(**{k: v[0] for k, v in inherited.items()})

dynamic = isinstance(element, DynamicMap) and not element.unbounded
super(GenericElementPlot, self).__init__(keys=keys, dimensions=dimensions,
dynamic=dynamic,
**dict(params, **plot_opts))
Expand Down Expand Up @@ -997,10 +1001,6 @@ def _get_frame(self, key):
return layout_frame


def __len__(self):
return len(self.keys)


def _format_title(self, key, dimensions=True, separator='\n'):
dim_title = self._frame_title(key, 3, separator) if dimensions else ''
layout = self.layout
Expand Down
7 changes: 4 additions & 3 deletions holoviews/plotting/renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -301,15 +301,16 @@ def get_widget(self_or_cls, plot, widget_type, **kwargs):
if not isinstance(plot, Plot):
plot = self_or_cls.get_plot(plot)
dynamic = plot.dynamic
# Whether dimensions define discrete space
discrete = all(d.values for d in plot.dimensions)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be the basic condition that determines when scrubber can be used (with DynamicMap).

if widget_type == 'auto':
isuniform = plot.uniform
if not isuniform:
widget_type = 'scrubber'
else:
widget_type = 'widgets'
elif dynamic: widget_type = 'widgets'
elif widget_type == 'scrubber' and dynamic:
raise ValueError('DynamicMap do not support scrubber widget')
elif dynamic and not discrete:
widget_type = 'widgets'

if widget_type in [None, 'auto']:
holomap_formats = self_or_cls.mode_formats['holomap'][self_or_cls.mode]
Expand Down
19 changes: 4 additions & 15 deletions holoviews/plotting/widgets/widgets.js
Original file line number Diff line number Diff line change
Expand Up @@ -257,17 +257,7 @@ ScrubberWidget.prototype.get_loop_state = function(){


ScrubberWidget.prototype.next_frame = function() {
if (this.dynamic || !this.cached) {
if (this.wait) {
return
}
this.wait = true;
}
if (this.dynamic && this.current_frame + 1 >= this.length) {
this.length += 1;
document.getElementById(this.slider_id).max = this.length-1;
}
this.set_frame(Math.min(this.length - 1, this.current_frame + 1));
this.set_frame(Math.min(this.length - 1, this.current_frame + 1));
}

ScrubberWidget.prototype.previous_frame = function() {
Expand Down Expand Up @@ -295,7 +285,7 @@ ScrubberWidget.prototype.faster = function() {
}

ScrubberWidget.prototype.anim_step_forward = function() {
if(this.current_frame < this.length || (this.dynamic && !this.stopped)){
if(this.current_frame < this.length - 1){
this.next_frame();
}else{
var loop_state = this.get_loop_state();
Expand All @@ -312,9 +302,8 @@ ScrubberWidget.prototype.anim_step_forward = function() {
}

ScrubberWidget.prototype.anim_step_reverse = function() {
this.current_frame -= 1;
if(this.current_frame >= 0){
this.set_frame(this.current_frame);
if(this.current_frame > 0){
this.previous_frame();
} else {
var loop_state = this.get_loop_state();
if(loop_state == "loop"){
Expand Down
21 changes: 21 additions & 0 deletions tests/testtraversal.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,27 @@ def test_unique_keys_no_overlap_exception(self):
with self.assertRaisesRegexp(Exception, exception):
dims, keys = unique_dimkeys(hmap1+hmap2)

def test_unique_keys_dmap_complete_overlap(self):
hmap1 = DynamicMap(lambda x: Curve(range(10)), kdims=['x']).redim.values(x=[1, 2, 3])
hmap2 = DynamicMap(lambda x: Curve(range(10)), kdims=['x']).redim.values(x=[1, 2, 3])
dims, keys = unique_dimkeys(hmap1+hmap2)
self.assertEqual(hmap1.kdims, dims)
self.assertEqual(keys, [(i,) for i in range(1, 4)])

def test_unique_keys_dmap_partial_overlap(self):
hmap1 = DynamicMap(lambda x: Curve(range(10)), kdims=['x']).redim.values(x=[1, 2, 3])
hmap2 = DynamicMap(lambda x: Curve(range(10)), kdims=['x']).redim.values(x=[1, 2, 3, 4])
dims, keys = unique_dimkeys(hmap1+hmap2)
self.assertEqual(hmap2.kdims, dims)
self.assertEqual(keys, [(i,) for i in range(1, 5)])

def test_unique_keys_dmap_cartesian_product(self):
hmap1 = DynamicMap(lambda x, y: Curve(range(10)), kdims=['x', 'y']).redim.values(x=[1, 2, 3])
hmap2 = DynamicMap(lambda x, y: Curve(range(10)), kdims=['x', 'y']).redim.values(y=[1, 2, 3])
dims, keys = unique_dimkeys(hmap1+hmap2)
self.assertEqual(hmap1.kdims[:1]+hmap2.kdims[1:], dims)
self.assertEqual(keys, [(x, y) for x in range(1, 4) for y in range(1, 4)])

def test_unique_keys_no_overlap_dynamicmap_uninitialized(self):
dmap1 = DynamicMap(lambda A: Curve(range(10)), kdims=['A'])
dmap2 = DynamicMap(lambda B: Curve(range(10)), kdims=['B'])
Expand Down