From 427ed825bc3f4fb5996a13e2ae3f1069c4cbe0b3 Mon Sep 17 00:00:00 2001
From: Philipp Rudiger
Date: Mon, 10 Oct 2016 22:54:37 +0100
Subject: [PATCH 1/2] Fixed multiple calls to DynamicMap callback
---
holoviews/core/util.py | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/holoviews/core/util.py b/holoviews/core/util.py
index 59d99dfd35..bf90b7332b 100644
--- a/holoviews/core/util.py
+++ b/holoviews/core/util.py
@@ -957,7 +957,8 @@ def get_dynamic_item(map_obj, dimensions, key):
dmaps = map_obj.traverse(lambda x: x, ['DynamicMap'])
dmap = dmaps[0] if dmaps else map_obj
if key == () and (not dimensions or not dmap.kdims):
- return key, map_obj.map(lambda x: x[()], ['DynamicMap', 'HoloMap'])
+ return key, map_obj.map(lambda x: x[()], ['DynamicMap', 'HoloMap'],
+ clone=False)
elif isinstance(key, tuple):
dims = {d.name: k for d, k in zip(dimensions, key)
if d in map_obj.kdims}
@@ -967,7 +968,8 @@ def get_dynamic_item(map_obj, dimensions, key):
key_offset = max([key-map_obj.cache_size, 0])
key = map_obj.keys()[min([key-key_offset,
len(map_obj)-1])]
- el = map_obj.map(lambda x: x[key], ['DynamicMap'])
+ el = map_obj.map(lambda x: x[key], ['DynamicMap'],
+ clone=False)
elif key >= map_obj.counter:
el = next(map_obj)
key = list(map_obj.keys())[-1]
From ce0516155ab1465e910999b4144a698eee22eb81 Mon Sep 17 00:00:00 2001
From: Philipp Rudiger
Date: Mon, 10 Oct 2016 22:55:05 +0100
Subject: [PATCH 2/2] Added unit tests to test for multiple dynamicmap calls
---
tests/testplotinstantiation.py | 57 +++++++++++++++++++++++++++++++---
1 file changed, 52 insertions(+), 5 deletions(-)
diff --git a/tests/testplotinstantiation.py b/tests/testplotinstantiation.py
index b12bcd15d2..e5236599b7 100644
--- a/tests/testplotinstantiation.py
+++ b/tests/testplotinstantiation.py
@@ -2,6 +2,7 @@
Tests of plot instantiation (not display tests, just instantiation)
"""
+from collections import deque
from unittest import SkipTest
from io import BytesIO
@@ -12,7 +13,7 @@
HeatMap, QuadMesh, Spikes, ErrorBars,
Scatter3D)
from holoviews.element.comparison import ComparisonTestCase
-from holoviews.streams import PositionXY
+from holoviews.streams import PositionXY, PositionX
from holoviews.plotting import comms
# Standardize backend due to random inconsistencies
@@ -46,11 +47,11 @@ def setUp(self):
Store.current_backend = 'matplotlib'
if mpl_renderer is None:
raise SkipTest("Matplotlib required to test plot instantiation")
- self.default_comm, _ = mpl_renderer.comms['default']
+ self.default_comm = mpl_renderer.comms['default']
mpl_renderer.comms['default'] = (comms.Comm, '')
def teardown(self):
- mpl_renderer.comms['default'] = (self.default_comm, '')
+ mpl_renderer.comms['default'] = self.default_comm
Store.current_backend = self.previous_backend
def test_interleaved_overlay(self):
@@ -87,6 +88,19 @@ def test_errorbar_test(self):
plot = mpl_renderer.get_plot(errorbars)
plot.initialize_plot()
+ def test_stream_callback_single_call(self):
+ def history_callback(x, history=deque(maxlen=10)):
+ history.append(x)
+ return Curve(list(history))
+ stream = PositionX()
+ dmap = DynamicMap(history_callback, kdims=[], streams=[stream])
+ plot = mpl_renderer.get_plot(dmap)
+ mpl_renderer(plot)
+ for i in range(20):
+ stream.update(x=i)
+ x, y = plot.handles['artist'].get_data()
+ self.assertEqual(x, np.arange(10))
+ self.assertEqual(y, np.arange(10, 20))
class TestBokehPlotInstantiation(ComparisonTestCase):
@@ -97,13 +111,13 @@ def setUp(self):
raise SkipTest("Bokeh required to test plot instantiation")
Store.current_backend = 'bokeh'
Callback._comm_type = comms.Comm
- self.default_comm, _ = bokeh_renderer.comms['default']
+ self.default_comm = bokeh_renderer.comms['default']
bokeh_renderer.comms['default'] = (comms.Comm, '')
def teardown(self):
Store.current_backend = self.previous_backend
Callback._comm_type = comms.JupyterCommJS
- mpl_renderer.comms['default'] = (self.default_comm, '')
+ mpl_renderer.comms['default'] = self.default_comm
def test_batched_plot(self):
overlay = NdOverlay({i: Points(np.arange(i)) for i in range(1, 100)})
@@ -156,16 +170,35 @@ def test_stream_callback(self):
self.assertEqual(data['y'], np.array([-10]))
+ def test_stream_callback_single_call(self):
+ def history_callback(x, history=deque(maxlen=10)):
+ history.append(x)
+ return Curve(list(history))
+ stream = PositionX()
+ dmap = DynamicMap(history_callback, kdims=[], streams=[stream])
+ plot = bokeh_renderer.get_plot(dmap)
+ bokeh_renderer(plot)
+ for i in range(20):
+ stream.update(x=i)
+ data = plot.handles['source'].data
+ self.assertEqual(data['x'], np.arange(10))
+ self.assertEqual(data['y'], np.arange(10, 20))
+
+
class TestPlotlyPlotInstantiation(ComparisonTestCase):
def setUp(self):
self.previous_backend = Store.current_backend
Store.current_backend = 'plotly'
+ self.default_comm = bokeh_renderer.comms['default']
if not plotly_renderer:
raise SkipTest("Plotly required to test plot instantiation")
+ plotly_renderer.comms['default'] = (comms.Comm, '')
+
def teardown(self):
Store.current_backend = self.previous_backend
+ plotly_renderer.comms['default'] = self.default_comm
def _get_plot_state(self, element):
plot = plotly_renderer.get_plot(element)
@@ -219,3 +252,17 @@ def test_grid_state(self):
self.assertEqual(state['data'][3]['y'], np.array([1, 1]))
self.assertEqual(state['data'][3]['xaxis'], 'x2')
self.assertEqual(state['data'][3]['yaxis'], 'y2')
+
+ def test_stream_callback_single_call(self):
+ def history_callback(x, history=deque(maxlen=10)):
+ history.append(x)
+ return Curve(list(history))
+ stream = PositionX()
+ dmap = DynamicMap(history_callback, kdims=[], streams=[stream])
+ plot = plotly_renderer.get_plot(dmap)
+ plotly_renderer(plot)
+ for i in range(20):
+ stream.update(x=i)
+ state = plot.state
+ self.assertEqual(state['data'][0]['x'], np.arange(10))
+ self.assertEqual(state['data'][0]['y'], np.arange(10, 20))