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

Fix dynamic ScrubberWidget and add throttling #2748

Merged
merged 2 commits into from
May 29, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions holoviews/core/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -1550,6 +1550,27 @@ 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]
length = np.product(lengths)
if index >= length:
raise IndexError('Index %d out of bounds for cross-product of size %d'
% (index, length))
indexes = []
for i in range(1, len(values))[::-1]:
p = np.product(lengths[-i:])
indexes.append(index//p)
index -= indexes[-1] * p
indexes.append(index)
return tuple(v[i] for v, i in zip(values, indexes))


def arglexsort(arrays):
"""
Returns the indices of the lexicographical sorting
Expand Down
30 changes: 21 additions & 9 deletions holoviews/plotting/widgets/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)


Expand Down Expand Up @@ -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




Expand All @@ -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):
Expand Down
14 changes: 10 additions & 4 deletions holoviews/plotting/widgets/widgets.js
Original file line number Diff line number Diff line change
Expand Up @@ -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++) {
Expand Down
46 changes: 43 additions & 3 deletions tests/core/testutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Expand Down Expand Up @@ -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'))