From d8e29d64e9812e31743a3e5469fc4b0390ab6943 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Tue, 29 May 2018 03:52:17 +0100 Subject: [PATCH 1/2] Fixed dynamic ScrubberWidget --- holoviews/core/util.py | 20 +++++++++++++++++ holoviews/plotting/widgets/__init__.py | 30 ++++++++++++++++++-------- 2 files changed, 41 insertions(+), 9 deletions(-) diff --git a/holoviews/core/util.py b/holoviews/core/util.py index 4c84ff8336..9bd3ce82a7 100644 --- a/holoviews/core/util.py +++ b/holoviews/core/util.py @@ -1550,6 +1550,26 @@ def cartesian_product(arrays, flat=True, copy=False): return tuple(arr.copy() if copy else arr for arr in arrays) +def cross_index(values, index): + """ + Allows efficiently indexing into a cartesian product without + expanding it. The values should be defined as a list of iterables + making up the cartesian product and a linear index, returning + the cross product of the values at the supplied index. + """ + lengths = [len(v) for v in values][::-1] + partials = [np.product(lengths[:i]) for i in range(1, len(values)+1)][::-1] + length = partials.pop(0) + if index >= length: + raise ValueError('Index out of bounds') + indexes = [] + for p in partials: + indexes.append(index//p) + index -= indexes[-1] * p + indexes.append(index%lengths[0]) + return tuple(v[i] for v, i in zip(values, indexes)) + + def arglexsort(arrays): """ Returns the indices of the lexicographical sorting diff --git a/holoviews/plotting/widgets/__init__.py b/holoviews/plotting/widgets/__init__.py index bdbb59e067..e28b901285 100644 --- a/holoviews/plotting/widgets/__init__.py +++ b/holoviews/plotting/widgets/__init__.py @@ -8,9 +8,10 @@ from ...core import OrderedDict, NdMapping from ...core.options import Store from ...core.ndmapping import item_check -from ...core.util import (dimension_sanitizer, bytes_to_unicode, - unique_array, unicode, isnumeric, - wrap_tuple_streams, drop_streams) +from ...core.util import ( + dimension_sanitizer, bytes_to_unicode, unique_array, unicode, + isnumeric, cross_index, wrap_tuple_streams, drop_streams +) from ...core.traversal import hierarchical def escape_vals(vals, escape_numerics=True): @@ -134,6 +135,12 @@ def __init__(self, plot, renderer=None, **params): if dim.values and all(isnumeric(v) for v in dim.values): dim = dim.clone(values=sorted(dim.values)) sorted_dims.append(dim) + + if self.plot.dynamic: + self.length = np.product([len(d.values) for d in sorted_dims if d.values]) + else: + self.length = len(self.plot) + with item_check(False): self.mock_obj = NdMapping([(k, None) for k in self.keys], kdims=sorted_dims, sort=False) @@ -192,7 +199,7 @@ def _get_data(self): dynamic = json.dumps(self.plot.dynamic) if self.plot.dynamic else 'false' return dict(CDN=CDN, frames=self.get_frames(), delay=delay, cached=cached, load_json=load_json, mode=mode, id=self.id, - Nframes=len(self.plot), widget_name=name, json_path=json_path, + Nframes=self.length, widget_name=name, json_path=json_path, dynamic=dynamic, plot_id=self.plot_id) @@ -245,11 +252,8 @@ def _plot_figure(self, idx): def update(self, key): - if not self.plot.dimensions: - self.plot.refresh() - else: - self.plot.update(key) - self.plot.push() + pass + @@ -271,6 +275,14 @@ class ScrubberWidget(NdWidget): js_template = param.String('jsscrubber.jinja', doc=""" The jinja2 template used to generate the html output.""") + def update(self, key): + if not self.plot.dimensions: + self.plot.refresh() + else: + if self.plot.dynamic: + key = cross_index([d.values for d in self.mock_obj.kdims], key) + self.plot.update(key) + self.plot.push() class SelectionWidget(NdWidget): From dd6bd2e50afb2aba3a7ab83513a97324a7960b00 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Tue, 29 May 2018 03:52:37 +0100 Subject: [PATCH 2/2] Implemented throttling for ScrubberWidget --- holoviews/core/util.py | 13 ++++---- holoviews/plotting/widgets/widgets.js | 14 +++++--- tests/core/testutils.py | 46 +++++++++++++++++++++++++-- 3 files changed, 60 insertions(+), 13 deletions(-) diff --git a/holoviews/core/util.py b/holoviews/core/util.py index 9bd3ce82a7..aee1a66375 100644 --- a/holoviews/core/util.py +++ b/holoviews/core/util.py @@ -1557,16 +1557,17 @@ def cross_index(values, index): making up the cartesian product and a linear index, returning the cross product of the values at the supplied index. """ - lengths = [len(v) for v in values][::-1] - partials = [np.product(lengths[:i]) for i in range(1, len(values)+1)][::-1] - length = partials.pop(0) + lengths = [len(v) for v in values] + length = np.product(lengths) if index >= length: - raise ValueError('Index out of bounds') + raise IndexError('Index %d out of bounds for cross-product of size %d' + % (index, length)) indexes = [] - for p in partials: + for i in range(1, len(values))[::-1]: + p = np.product(lengths[-i:]) indexes.append(index//p) index -= indexes[-1] * p - indexes.append(index%lengths[0]) + indexes.append(index) return tuple(v[i] for v, i in zip(values, indexes)) diff --git a/holoviews/plotting/widgets/widgets.js b/holoviews/plotting/widgets/widgets.js index 0429b747d5..3367312dcf 100644 --- a/holoviews/plotting/widgets/widgets.js +++ b/holoviews/plotting/widgets/widgets.js @@ -201,14 +201,20 @@ ScrubberWidget.prototype.set_frame = function(frame){ return } widget.value = this.current_frame; - if(this.cached) { - this.update(frame) - } else { + if (this.dynamic || !this.cached) { + if ((this.time !== undefined) && ((this.wait) && ((this.time + 10000) > Date.now()))) { + this.queue.push(frame); + return + } + this.queue = []; + this.time = Date.now(); + this.wait = true; this.dynamic_update(frame) + } else { + this.update(frame) } } - ScrubberWidget.prototype.get_loop_state = function(){ var button_group = document[this.loop_select_id].state; for (var i = 0; i < button_group.length; i++) { diff --git a/tests/core/testutils.py b/tests/core/testutils.py index f94db4ea8e..b750ec840f 100644 --- a/tests/core/testutils.py +++ b/tests/core/testutils.py @@ -4,11 +4,12 @@ """ import sys, math import unittest +import datetime from unittest import SkipTest +from itertools import product +from collections import OrderedDict -import datetime import numpy as np -from collections import OrderedDict try: import pandas as pd except: @@ -17,7 +18,7 @@ from holoviews.core.util import ( sanitize_identifier_fn, find_range, max_range, wrap_tuple_streams, deephash, merge_dimensions, get_path, make_path_unique, compute_density, - date_range, dt_to_int, compute_edges, isfinite + date_range, dt_to_int, compute_edges, isfinite, cross_index ) from holoviews import Dimension, Element from holoviews.streams import PointerXY @@ -731,3 +732,42 @@ def test_close_edges(self): def test_uneven_edges(self): self.assertEqual(compute_edges(self.array3), np.array([0.5, 1.5, 3.0, 5.0])) + + +class TestCrossIndex(ComparisonTestCase): + + def setUp(self): + self.values1 = ['A', 'B', 'C'] + self.values2 = [1, 2, 3, 4] + self.values3 = ['?', '!'] + self.values4 = ['x'] + + def test_cross_index_full_product(self): + values = [self.values1, self.values2, self.values3, self.values4] + cross_product = list(product(*values)) + for i, p in enumerate(cross_product): + self.assertEqual(cross_index(values, i), p) + + def test_cross_index_depth_1(self): + values = [self.values1] + cross_product = list(product(*values)) + for i, p in enumerate(cross_product): + self.assertEqual(cross_index(values, i), p) + + def test_cross_index_depth_2(self): + values = [self.values1, self.values2] + cross_product = list(product(*values)) + for i, p in enumerate(cross_product): + self.assertEqual(cross_index(values, i), p) + + def test_cross_index_depth_3(self): + values = [self.values1, self.values2, self.values3] + cross_product = list(product(*values)) + for i, p in enumerate(cross_product): + self.assertEqual(cross_index(values, i), p) + + def test_cross_index_large(self): + values = [[chr(65+i) for i in range(26)], list(range(500)), + [chr(97+i) for i in range(26)], [chr(48+i) for i in range(10)]] + self.assertEqual(cross_index(values, 50001), ('A', 192, 'i', '1')) + self.assertEqual(cross_index(values, 500001), ('D', 423, 'c', '1'))