diff --git a/holoviews/plotting/bokeh/plot.py b/holoviews/plotting/bokeh/plot.py index 6c379a8f95..d6ff6763b0 100644 --- a/holoviews/plotting/bokeh/plot.py +++ b/holoviews/plotting/bokeh/plot.py @@ -7,6 +7,7 @@ import numpy as np import param +from bokeh.io import curdoc from bokeh.layouts import gridplot from bokeh.models import (ColumnDataSource, Column, Row, Div) from bokeh.models.widgets import Panel, Tabs @@ -175,6 +176,12 @@ def _construct_callbacks(self): cbs.append(cb(self, cb_streams, source)) return cbs + def refresh(self, **kwargs): + if self.renderer.mode == 'server' and curdoc() is not self.document: + # If we do not have the Document lock, schedule refresh as callback + self.document.add_next_tick_callback(self.refresh) + else: + super(BokehPlot, self).refresh(**kwargs) def push(self): """ diff --git a/holoviews/tests/plotting/bokeh/testcallbacks.py b/holoviews/tests/plotting/bokeh/testcallbacks.py index f9cee7b2cc..1b693bda17 100644 --- a/holoviews/tests/plotting/bokeh/testcallbacks.py +++ b/holoviews/tests/plotting/bokeh/testcallbacks.py @@ -14,6 +14,7 @@ try: from bokeh.events import Tap + from bokeh.io.doc import set_curdoc from bokeh.models import Range1d, Plot, ColumnDataSource, Selection, PolyEditTool from holoviews.plotting.bokeh.callbacks import ( Callback, PointDrawCallback, PolyDrawCallback, PolyEditCallback, @@ -49,6 +50,7 @@ def test_stream_callback(self): dmap = DynamicMap(lambda x, y: Points([(x, y)]), kdims=[], streams=[PointerXY()]) plot = bokeh_server_renderer.get_plot(dmap) bokeh_server_renderer(plot) + set_curdoc(plot.document) plot.callbacks[0].on_msg({"x": 0.3, "y": 0.2}) data = plot.handles['source'].data self.assertEqual(data['x'], np.array([0.3])) @@ -58,6 +60,7 @@ def test_point_stream_callback_clip(self): dmap = DynamicMap(lambda x, y: Points([(x, y)]), kdims=[], streams=[PointerXY()]) plot = bokeh_server_renderer.get_plot(dmap) bokeh_server_renderer(plot) + set_curdoc(plot.document) plot.callbacks[0].on_msg({"x": -0.3, "y": 1.2}) data = plot.handles['source'].data self.assertEqual(data['x'], np.array([0])) @@ -68,6 +71,7 @@ def test_stream_callback_on_clone(self): stream = PointerXY(source=points) plot = bokeh_server_renderer.get_plot(points.clone()) bokeh_server_renderer(plot) + set_curdoc(plot.document) plot.callbacks[0].on_msg({"x": 0.8, "y": 0.3}) self.assertEqual(stream.x, 0.8) self.assertEqual(stream.y, 0.3) @@ -83,6 +87,7 @@ def test_stream_callback_with_ids(self): dmap = DynamicMap(lambda x, y: Points([(x, y)]), kdims=[], streams=[PointerXY()]) plot = bokeh_server_renderer.get_plot(dmap) bokeh_server_renderer(plot) + set_curdoc(plot.document) model = plot.state plot.callbacks[0].on_msg({"x": {'id': model.ref['id'], 'value': 0.5}, "y": {'id': model.ref['id'], 'value': 0.4}}) @@ -98,6 +103,7 @@ def history_callback(x, history=deque(maxlen=10)): dmap = DynamicMap(history_callback, kdims=[], streams=[stream]) plot = bokeh_server_renderer.get_plot(dmap) bokeh_server_renderer(plot) + set_curdoc(plot.document) for i in range(20): stream.event(x=i) data = plot.handles['source'].data @@ -137,6 +143,7 @@ def test_pointer_x_datetime_out_of_bounds(self): points = Points([(dt.datetime(2017, 1, 1), 1), (dt.datetime(2017, 1, 3), 3)]) PointerX(source=points) plot = bokeh_server_renderer.get_plot(points) + set_curdoc(plot.document) callback = plot.callbacks[0] self.assertIsInstance(callback, PointerXCallback) msg = callback._process_msg({'x': 1000}) @@ -148,6 +155,7 @@ def test_tap_datetime_out_of_bounds(self): points = Points([(dt.datetime(2017, 1, 1), 1), (dt.datetime(2017, 1, 3), 3)]) SingleTap(source=points) plot = bokeh_server_renderer.get_plot(points) + set_curdoc(plot.document) callback = plot.callbacks[0] self.assertIsInstance(callback, TapCallback) msg = callback._process_msg({'x': 1000, 'y': 2})