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'))