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