diff --git a/examples/user_guide/16-Streaming_Data.ipynb b/examples/user_guide/16-Streaming_Data.ipynb index 9e22d9c7c7..46683979bf 100644 --- a/examples/user_guide/16-Streaming_Data.ipynb +++ b/examples/user_guide/16-Streaming_Data.ipynb @@ -199,6 +199,13 @@ "dfstream.clear()" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Note that when using the ``Buffer`` stream the view will always follow the current range of the data by default, by setting ``buffer.following=False`` or passing following as an argument to the constructor this behavior may be disabled." + ] + }, { "cell_type": "markdown", "metadata": {}, diff --git a/holoviews/plotting/bokeh/element.py b/holoviews/plotting/bokeh/element.py index f83becc4c1..55bc766061 100644 --- a/holoviews/plotting/bokeh/element.py +++ b/holoviews/plotting/bokeh/element.py @@ -751,7 +751,8 @@ def _update_ranges(self, element, ranges): if any(isinstance(ax_range, FactorRange) for ax_range in [x_range, y_range]): xfactors, yfactors = self._get_factors(element, ranges) framewise = self.framewise - streaming = (self.streaming and any(stream._triggering for stream in self.streaming)) + streaming = (self.streaming and any(stream._triggering and stream.following + for stream in self.streaming)) xupdate = ((not (self.model_changed(x_range) or self.model_changed(plot)) and (framewise or streaming)) or xfactors is not None) diff --git a/holoviews/streams.py b/holoviews/streams.py index 7e45607864..6e6bad3fa2 100644 --- a/holoviews/streams.py +++ b/holoviews/streams.py @@ -473,9 +473,14 @@ class Buffer(Pipe): When streaming a DataFrame will reset the DataFrame index by default making it available to HoloViews elements as dimensions, this may be disabled by setting index=False. + + The ``following`` argument determines whether any plot which is + subscribed to this stream will update the axis ranges when an + update is pushed. This makes it possible to control whether zooming + is allowed while streaming. """ - def __init__(self, data, length=1000, index=True, **params): + def __init__(self, data, length=1000, index=True, following=True, **params): if (util.pd and isinstance(data, util.pd.DataFrame)): example = data elif isinstance(data, np.ndarray): @@ -512,6 +517,7 @@ def __init__(self, data, length=1000, index=True, **params): params['data'] = example super(Buffer, self).__init__(**params) self.length = length + self.following = following self._chunk_length = 0 self._count = 0 self._index = index diff --git a/holoviews/tests/plotting/bokeh/teststreaming.py b/holoviews/tests/plotting/bokeh/teststreaming.py new file mode 100644 index 0000000000..b74ab2ecd8 --- /dev/null +++ b/holoviews/tests/plotting/bokeh/teststreaming.py @@ -0,0 +1,40 @@ +import numpy as np + +from holoviews.core import DynamicMap +from holoviews.element import Curve +from holoviews.streams import Buffer + +from .testplot import TestBokehPlot, bokeh_renderer + + +class TestBufferStreamPlot(TestBokehPlot): + + def test_buffer_stream_following(self): + stream = Buffer(data={'x': np.array([1]), 'y': np.array([1])}, following=True) + dmap = DynamicMap(Curve, streams=[stream]) + + plot = bokeh_renderer.get_plot(dmap) + + x_range = plot.handles['x_range'] + y_range = plot.handles['y_range'] + + self.assertEqual(x_range.start, 0) + self.assertEqual(x_range.end, 2) + self.assertEqual(y_range.start, 0) + self.assertEqual(y_range.end, 2) + + stream.send({'x': np.array([2]), 'y': np.array([-1])}) + + self.assertEqual(x_range.start, 1) + self.assertEqual(x_range.end, 2) + self.assertEqual(y_range.start, -1) + self.assertEqual(y_range.end, 1) + + stream.following = False + + stream.send({'x': np.array([3]), 'y': np.array([3])}) + + self.assertEqual(x_range.start, 1) + self.assertEqual(x_range.end, 2) + self.assertEqual(y_range.start, -1) + self.assertEqual(y_range.end, 1)