From 049cb304b82a2be2cdb77f4b391fa55cc5ba3b03 Mon Sep 17 00:00:00 2001
From: Philipp Rudiger
Date: Tue, 21 Jun 2016 11:52:53 +0100
Subject: [PATCH] Unified bokeh plot patching approach dropping 0.10
compatibility
---
holoviews/plotting/bokeh/bokehwidgets.js | 16 +------
holoviews/plotting/bokeh/callbacks.py | 34 ++++----------
holoviews/plotting/bokeh/element.py | 11 ++---
holoviews/plotting/bokeh/plot.py | 4 +-
holoviews/plotting/bokeh/renderer.py | 38 +++++++--------
holoviews/plotting/bokeh/util.py | 60 ++++++++++++++++++------
holoviews/plotting/bokeh/widgets.py | 18 +++----
7 files changed, 86 insertions(+), 95 deletions(-)
diff --git a/holoviews/plotting/bokeh/bokehwidgets.js b/holoviews/plotting/bokeh/bokehwidgets.js
index df0c39469f..eec4065e2e 100644
--- a/holoviews/plotting/bokeh/bokehwidgets.js
+++ b/holoviews/plotting/bokeh/bokehwidgets.js
@@ -25,20 +25,8 @@ var BokehMethods = {
var data = this.frames[current];
}
if (data !== undefined) {
- if (data.root !== undefined) {
- var doc = Bokeh.index[data.root].model.document;
- }
- $.each(data.data, function(i, value) {
- if (data.root !== undefined) {
- var ds = doc.get_model_by_id(value.id);
- } else {
- var ds = Bokeh.Collections(value.type).get(value.id);
- }
- if (ds != undefined) {
- ds.set(value.data);
- ds.trigger('change');
- }
- });
+ var doc = Bokeh.index[data.root].model.document;
+ doc.apply_json_patch(data.patch);
}
},
dynamic_update : function(current){
diff --git a/holoviews/plotting/bokeh/callbacks.py b/holoviews/plotting/bokeh/callbacks.py
index b2fd77f440..d3d847ea5c 100644
--- a/holoviews/plotting/bokeh/callbacks.py
+++ b/holoviews/plotting/bokeh/callbacks.py
@@ -4,14 +4,10 @@
import param
from ...core.data import ArrayColumns
-from .renderer import bokeh_version
-from .util import models_to_json, bokeh_version
+from .util import compute_static_patch, models_to_json
from bokeh.models import CustomJS, TapTool, ColumnDataSource
-if bokeh_version < '0.11':
- from bokeh.protocol import serialize_json
-else:
- from bokeh.core.json_encoder import serialize_json
+from bokeh.core.json_encoder import serialize_json
class Callback(param.ParameterizedFunction):
@@ -71,20 +67,11 @@ class Callback(param.ParameterizedFunction):
function callback(msg){
if (msg.msg_type == "execute_result") {
var data = JSON.parse(msg.content.data['text/plain'].slice(1, -1));
- if (data.root !== undefined) {
- var doc = Bokeh.index[data.root].model.document;
+ if (data !== undefined) {
+ console.log(data.root)
+ var doc = Bokeh.index[data.root].model.document;
+ doc.apply_json_patch(data.patch);
}
- $.each(data.data, function(i, value) {
- if (data.root !== undefined) {
- var ds = doc.get_model_by_id(value.id);
- } else {
- var ds = Bokeh.Collections(value.type).get(value.id);
- }
- if (ds != undefined) {
- ds.set(value.data);
- ds.trigger('change');
- }
- });
} else {
console.log("Python callback returned unexpected message:", msg)
}
@@ -145,14 +132,13 @@ def update(self, data, chained=False):
return self.serialize(objects)
- def serialize(self, objects):
+ def serialize(self, models):
"""
Serializes any Bokeh plot objects passed to it as a list.
"""
- data = dict(data=models_to_json(objects))
- if bokeh_version >= '0.11':
- plot = self.plots[0]
- data['root'] = plot.state._id
+ plot = self.plots[0]
+ patch = compute_static_patch(plot.document, models)
+ data = dict(root=plot.state._id, patch=patch)
return serialize_json(data)
diff --git a/holoviews/plotting/bokeh/element.py b/holoviews/plotting/bokeh/element.py
index 44ad4a147a..1ab310d164 100644
--- a/holoviews/plotting/bokeh/element.py
+++ b/holoviews/plotting/bokeh/element.py
@@ -23,7 +23,7 @@
from ..util import dynamic_update
from .callbacks import Callbacks
from .plot import BokehPlot
-from .util import bokeh_version, mpl_to_bokeh, convert_datetime, update_plot
+from .util import mpl_to_bokeh, convert_datetime, update_plot
# Define shared style properties for bokeh plots
@@ -280,9 +280,7 @@ def _plot_properties(self, key, plot, element):
if self.show_title:
plot_props['title'] = self._format_title(key, separator=' ')
if self.bgcolor:
- bg_attr = 'background_fill'
- if bokeh_version > '0.11': bg_attr += '_color'
- plot_props[bg_attr] = self.bgcolor
+ plot_props['background_fill_color'] = self.bgcolor
if self.border is not None:
for p in ['left', 'right', 'top', 'bottom']:
plot_props['min_border_'+p] = self.border
@@ -675,10 +673,7 @@ def _process_legend(self):
if legend_fontsize:
plot.legend[0].label_text_font_size = legend_fontsize
- if bokeh_version < '0.11':
- plot.legend.orientation = self.legend_position
- else:
- plot.legend.location = self.legend_position
+ plot.legend.location = self.legend_position
legends = plot.legend[0].legends
new_legends = []
for label, l in legends:
diff --git a/holoviews/plotting/bokeh/plot.py b/holoviews/plotting/bokeh/plot.py
index 12793554f6..895773cc04 100644
--- a/holoviews/plotting/bokeh/plot.py
+++ b/holoviews/plotting/bokeh/plot.py
@@ -10,7 +10,7 @@
AdjointLayout, NdLayout, Empty, GridSpace, HoloMap)
from ...core import traversal
from ...core.options import Compositor
-from ...core.util import basestring
+from ...core.util import basestring, wrap_tuple
from ...element import Histogram
from ..plot import DimensionedPlot, GenericCompositePlot, GenericLayoutPlot
from ..util import get_dynamic_mode, initialize_sampled
@@ -257,7 +257,7 @@ def initialize_plot(self, ranges=None, plots=[]):
passed_plots = list(plots)
for i, coord in enumerate(self.layout.keys(full_grid=True)):
r = i % self.cols
- subplot = self.subplots.get(coord, None)
+ subplot = self.subplots.get(wrap_tuple(coord), None)
if subplot is not None:
plot = subplot.initialize_plot(ranges=ranges, plots=passed_plots)
plots[r].append(plot)
diff --git a/holoviews/plotting/bokeh/renderer.py b/holoviews/plotting/bokeh/renderer.py
index 17a883bd37..a748427f97 100644
--- a/holoviews/plotting/bokeh/renderer.py
+++ b/holoviews/plotting/bokeh/renderer.py
@@ -3,7 +3,7 @@
from ...core import Store, HoloMap
from ..renderer import Renderer, MIME_TYPES
from .widgets import BokehScrubberWidget, BokehSelectionWidget
-from .util import bokeh_version, models_to_json
+from .util import compute_static_patch
import param
from param.parameterized import bothmethod
@@ -12,12 +12,12 @@
from bokeh.embed import notebook_div
from bokeh.io import load_notebook
from bokeh.resources import CDN, INLINE
+from bokeh.io import _CommsHandle
+from bokeh.util.notebook import get_comms
-if bokeh_version < '0.11':
- from bokeh.protocol import serialize_json
-else:
- from bokeh.core.json_encoder import serialize_json
- from bokeh.model import _ModelInDocument as add_to_document
+from bokeh.core.json_encoder import serialize_json
+from bokeh.model import _ModelInDocument as add_to_document
+from bokeh.document import Document
class BokehRenderer(Renderer):
@@ -60,25 +60,21 @@ def __call__(self, obj, fmt=None):
elif fmt == 'json':
plotobjects = [h for handles in plot.traverse(lambda x: x.current_handles)
for h in handles]
- data = dict(data=[])
- if bokeh_version >= '0.11':
- data['root'] = plot.state._id
- data['data'] = models_to_json(plotobjects)
+ patch = compute_static_patch(plot.document, plotobjects)
+ data = dict(root=plot.state._id, patch=patch)
return self._apply_post_render_hooks(serialize_json(data), obj, fmt), info
def figure_data(self, plot, fmt='html', **kwargs):
- if bokeh_version >= '0.11':
- doc_handler = add_to_document(plot.state)
- with doc_handler:
- doc = doc_handler._doc
- comms_target = str(uuid.uuid4())
- doc.last_comms_target = comms_target
- div = notebook_div(plot.state, comms_target)
- plot.document = doc
- return div
- else:
- return notebook_div(plot.state)
+ doc_handler = add_to_document(plot.state)
+ with doc_handler:
+ doc = doc_handler._doc
+ comms_target = str(uuid.uuid4())
+ doc.last_comms_target = comms_target
+ div = notebook_div(plot.state, comms_target)
+ plot.document = doc
+ doc.add_root(plot.state)
+ return div
@classmethod
diff --git a/holoviews/plotting/bokeh/util.py b/holoviews/plotting/bokeh/util.py
index e237f72cb7..1597ee3d6b 100644
--- a/holoviews/plotting/bokeh/util.py
+++ b/holoviews/plotting/bokeh/util.py
@@ -11,12 +11,9 @@
import bokeh
bokeh_version = LooseVersion(bokeh.__version__)
-if bokeh_version < '0.11':
- from bokeh.enums import Palette
- from bokeh.plotting import Plot
-else:
- from bokeh.core.enums import Palette
- from bokeh.models.plots import Plot
+from bokeh.core.enums import Palette
+from bokeh.document import Document
+from bokeh.models.plots import Plot
from bokeh.models import GlyphRenderer
from bokeh.plotting import Figure
@@ -139,18 +136,51 @@ def models_to_json(models):
continue
else:
ids.append(plotobj.ref['id'])
- if bokeh_version < '0.11':
- json = plotobj.vm_serialize(changed_only=True)
- else:
- json = plotobj.to_json(False)
- json.pop('tool_events', None)
- json.pop('renderers', None)
- json_data.append({'id': plotobj.ref['id'],
- 'type': plotobj.ref['type'],
- 'data': json})
+ json = plotobj.to_json(False)
+ json.pop('tool_events', None)
+ json.pop('renderers', None)
+ json_data.append({'id': plotobj.ref['id'],
+ 'type': plotobj.ref['type'],
+ 'data': json})
return json_data
+def refs(json):
+ """
+ Finds all the references to other objects in the json
+ representation of a bokeh Document.
+ """
+ result = {}
+ for obj in json['roots']['references']:
+ result[obj['id']] = obj
+ return result
+
+
+def compute_static_patch(document, models):
+ """
+ Computes a patch to update an existing document without
+ diffing the json first, making it suitable for static updates
+ between arbitrary frames. Note that this only supports changed
+ attributes and will break if new models have been added since
+ the plot was first created.
+ """
+ json = document.to_json()
+ references = refs(json)
+ requested_updates = [m.ref['id'] for m in models]
+
+ value_refs = {}
+ events = []
+ for ref_id, obj in references.items():
+ if ref_id not in requested_updates:
+ continue
+ for key, val in obj['attributes'].items():
+ event = Document._event_for_attribute_change(references,
+ obj, key, val,
+ value_refs)
+ events.append(event)
+ return dict(events=events, references=list(value_refs.values()))
+
+
def hsv_to_rgb(hsv):
"""
Vectorized HSV to RGB conversion, adapted from:
diff --git a/holoviews/plotting/bokeh/widgets.py b/holoviews/plotting/bokeh/widgets.py
index e743220fa2..470fdad079 100644
--- a/holoviews/plotting/bokeh/widgets.py
+++ b/holoviews/plotting/bokeh/widgets.py
@@ -1,14 +1,12 @@
import json
-from .util import bokeh_version
-from ..widgets import NdWidget, SelectionWidget, ScrubberWidget
-
import param
import bokeh
from bokeh.io import Document
-if bokeh_version >= '0.11':
- from bokeh.io import _CommsHandle
- from bokeh.util.notebook import get_comms
+from bokeh.io import _CommsHandle
+from bokeh.util.notebook import get_comms
+
+from ..widgets import NdWidget, SelectionWidget, ScrubberWidget
class BokehWidget(NdWidget):
@@ -39,15 +37,13 @@ def _plot_figure(self, idx, fig_format='json'):
first call and
"""
self.plot.update(idx)
- if self.embed or fig_format == 'html' or bokeh_version < '0.11':
- return self.renderer.html(self.plot, fig_format)
+ if self.embed or fig_format == 'html':
+ html = self.renderer.html(self.plot, fig_format)
+ return html
else:
- doc = self.plot.document
-
if hasattr(doc, 'last_comms_handle'):
handle = doc.last_comms_handle
else:
- doc.add_root(self.plot.state)
handle = _CommsHandle(get_comms(doc.last_comms_target),
doc, doc.to_json())
doc.last_comms_handle = handle