From ffec7f7556d79d60be11822c24dcba8a4fa8e5d7 Mon Sep 17 00:00:00 2001
From: Philipp Rudiger
Date: Mon, 20 Jun 2016 21:44:39 +0100
Subject: [PATCH 1/7] Initial fixes for bokeh 0.12 compatibility
---
holoviews/plotting/bokeh/callbacks.py | 20 +++++++++-----------
holoviews/plotting/bokeh/element.py | 11 ++++-------
holoviews/plotting/bokeh/plot.py | 15 ++++++++++++---
holoviews/plotting/bokeh/renderer.py | 16 ++++++++--------
holoviews/plotting/bokeh/util.py | 16 +++++++++-------
holoviews/plotting/bokeh/widgets.py | 14 +++++---------
6 files changed, 47 insertions(+), 45 deletions(-)
diff --git a/holoviews/plotting/bokeh/callbacks.py b/holoviews/plotting/bokeh/callbacks.py
index 24028a031f..b2fd77f440 100644
--- a/holoviews/plotting/bokeh/callbacks.py
+++ b/holoviews/plotting/bokeh/callbacks.py
@@ -1,19 +1,17 @@
from collections import defaultdict
import numpy as np
-from bokeh.models import CustomJS, TapTool, ColumnDataSource
-
-try:
- from bokeh.protocol import serialize_json
- bokeh_lt_011 = True
-except ImportError:
- from bokeh.core.json_encoder import serialize_json
- bokeh_lt_011 = False
-
import param
from ...core.data import ArrayColumns
-from .util import models_to_json
+from .renderer import bokeh_version
+from .util import models_to_json, bokeh_version
+
+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
class Callback(param.ParameterizedFunction):
@@ -152,7 +150,7 @@ def serialize(self, objects):
Serializes any Bokeh plot objects passed to it as a list.
"""
data = dict(data=models_to_json(objects))
- if not bokeh_lt_011:
+ if bokeh_version >= '0.11':
plot = self.plots[0]
data['root'] = plot.state._id
return serialize_json(data)
diff --git a/holoviews/plotting/bokeh/element.py b/holoviews/plotting/bokeh/element.py
index d87e8e0378..44ad4a147a 100644
--- a/holoviews/plotting/bokeh/element.py
+++ b/holoviews/plotting/bokeh/element.py
@@ -23,8 +23,7 @@
from ..util import dynamic_update
from .callbacks import Callbacks
from .plot import BokehPlot
-from .renderer import bokeh_lt_011
-from .util import mpl_to_bokeh, convert_datetime, update_plot
+from .util import bokeh_version, mpl_to_bokeh, convert_datetime, update_plot
# Define shared style properties for bokeh plots
@@ -277,14 +276,12 @@ def _plot_properties(self, key, plot, element):
"""
Returns a dictionary of plot properties.
"""
- title_font = self._fontsize('title', 'title_text_font_size')
- plot_props = dict(plot_height=self.height, plot_width=self.width,
- title_text_color='black', **title_font)
+ plot_props = dict(plot_height=self.height, plot_width=self.width)
if self.show_title:
plot_props['title'] = self._format_title(key, separator=' ')
if self.bgcolor:
bg_attr = 'background_fill'
- if not bokeh_lt_011: bg_attr += '_color'
+ if bokeh_version > '0.11': bg_attr += '_color'
plot_props[bg_attr] = self.bgcolor
if self.border is not None:
for p in ['left', 'right', 'top', 'bottom']:
@@ -678,7 +675,7 @@ def _process_legend(self):
if legend_fontsize:
plot.legend[0].label_text_font_size = legend_fontsize
- if bokeh_lt_011:
+ if bokeh_version < '0.11':
plot.legend.orientation = self.legend_position
else:
plot.legend.location = self.legend_position
diff --git a/holoviews/plotting/bokeh/plot.py b/holoviews/plotting/bokeh/plot.py
index 2afcc09d6c..12793554f6 100644
--- a/holoviews/plotting/bokeh/plot.py
+++ b/holoviews/plotting/bokeh/plot.py
@@ -15,7 +15,11 @@
from ..plot import DimensionedPlot, GenericCompositePlot, GenericLayoutPlot
from ..util import get_dynamic_mode, initialize_sampled
from .renderer import BokehRenderer
-from .util import layout_padding
+from .util import bokeh_version, layout_padding
+
+if bokeh_version >= '0.12':
+ from bokeh.layouts import gridplot
+
class BokehPlot(DimensionedPlot):
"""
@@ -261,7 +265,10 @@ def initialize_plot(self, ranges=None, plots=[]):
else:
plots[r].append(None)
passed_plots.append(None)
- self.handles['plot'] = BokehGridPlot(children=plots[::-1])
+ if bokeh_version < '0.12':
+ self.handles['plot'] = BokehGridPlot(children=plots[::-1])
+ else:
+ self.handles['plot'] = gridplot(plots[::-1])
self.handles['plots'] = plots
if self.shared_datasource:
self.sync_sources()
@@ -481,7 +488,7 @@ def initialize_plot(self, ranges=None):
# Replace None types with empty plots
# to avoid bokeh bug
- if adjoined:
+ if adjoined and bokeh_version < '0.12':
plots = layout_padding(plots)
# Determine the most appropriate composite plot type
@@ -496,6 +503,8 @@ def initialize_plot(self, ranges=None):
for c, child in enumerate(row)
if child is not None]
layout_plot = Tabs(tabs=panels)
+ elif bokeh_version >= '0.12':
+ layout_plot = gridplot(children=plots)
elif len(plots) == 1 and not adjoined:
layout_plot = VBox(children=[HBox(children=plots[0])])
elif len(plots[0]) == 1:
diff --git a/holoviews/plotting/bokeh/renderer.py b/holoviews/plotting/bokeh/renderer.py
index 0bb288a9f2..17a883bd37 100644
--- a/holoviews/plotting/bokeh/renderer.py
+++ b/holoviews/plotting/bokeh/renderer.py
@@ -1,23 +1,23 @@
import uuid
+
from ...core import Store, HoloMap
from ..renderer import Renderer, MIME_TYPES
from .widgets import BokehScrubberWidget, BokehSelectionWidget
-from .util import models_to_json
+from .util import bokeh_version, models_to_json
import param
from param.parameterized import bothmethod
+import bokeh
from bokeh.embed import notebook_div
from bokeh.io import load_notebook
from bokeh.resources import CDN, INLINE
-try:
+if bokeh_version < '0.11':
from bokeh.protocol import serialize_json
- bokeh_lt_011 = True
-except ImportError:
+else:
from bokeh.core.json_encoder import serialize_json
from bokeh.model import _ModelInDocument as add_to_document
- bokeh_lt_011 = False
class BokehRenderer(Renderer):
@@ -55,20 +55,20 @@ def __call__(self, obj, fmt=None):
return plot(), info
elif fmt == 'html':
html = self.figure_data(plot)
- html = '%s' % html
+ html = "%s
" % html
return self._apply_post_render_hooks(html, obj, fmt), info
elif fmt == 'json':
plotobjects = [h for handles in plot.traverse(lambda x: x.current_handles)
for h in handles]
data = dict(data=[])
- if not bokeh_lt_011:
+ if bokeh_version >= '0.11':
data['root'] = plot.state._id
data['data'] = models_to_json(plotobjects)
return self._apply_post_render_hooks(serialize_json(data), obj, fmt), info
def figure_data(self, plot, fmt='html', **kwargs):
- if not bokeh_lt_011:
+ if bokeh_version >= '0.11':
doc_handler = add_to_document(plot.state)
with doc_handler:
doc = doc_handler._doc
diff --git a/holoviews/plotting/bokeh/util.py b/holoviews/plotting/bokeh/util.py
index bd5795ca3f..e237f72cb7 100644
--- a/holoviews/plotting/bokeh/util.py
+++ b/holoviews/plotting/bokeh/util.py
@@ -1,6 +1,7 @@
+from distutils.version import LooseVersion
+
from collections import defaultdict
import numpy as np
-from ...core.options import abbreviated_exception
try:
from matplotlib import colors
@@ -8,18 +9,19 @@
except ImportError:
cm, colors = None, None
-try:
+import bokeh
+bokeh_version = LooseVersion(bokeh.__version__)
+if bokeh_version < '0.11':
from bokeh.enums import Palette
from bokeh.plotting import Plot
- bokeh_lt_011 = True
-except:
+else:
from bokeh.core.enums import Palette
from bokeh.models.plots import Plot
- bokeh_lt_011 = False
-
from bokeh.models import GlyphRenderer
from bokeh.plotting import Figure
+from ...core.options import abbreviated_exception
+
# Conversion between matplotlib and bokeh markers
markers = {'s': {'marker': 'square'},
'd': {'marker': 'diamond'},
@@ -137,7 +139,7 @@ def models_to_json(models):
continue
else:
ids.append(plotobj.ref['id'])
- if bokeh_lt_011:
+ if bokeh_version < '0.11':
json = plotobj.vm_serialize(changed_only=True)
else:
json = plotobj.to_json(False)
diff --git a/holoviews/plotting/bokeh/widgets.py b/holoviews/plotting/bokeh/widgets.py
index 7e9329f538..e743220fa2 100644
--- a/holoviews/plotting/bokeh/widgets.py
+++ b/holoviews/plotting/bokeh/widgets.py
@@ -1,18 +1,14 @@
import json
-from distutils.version import LooseVersion
+
+from .util import bokeh_version
+from ..widgets import NdWidget, SelectionWidget, ScrubberWidget
import param
import bokeh
from bokeh.io import Document
-
-if LooseVersion(bokeh.__version__) >= LooseVersion('0.11'):
- bokeh_lt_011 = False
+if bokeh_version >= '0.11':
from bokeh.io import _CommsHandle
from bokeh.util.notebook import get_comms
-else:
- bokeh_lt_011 = True
-
-from ..widgets import NdWidget, SelectionWidget, ScrubberWidget
class BokehWidget(NdWidget):
@@ -43,7 +39,7 @@ def _plot_figure(self, idx, fig_format='json'):
first call and
"""
self.plot.update(idx)
- if self.embed or fig_format == 'html' or bokeh_lt_011:
+ if self.embed or fig_format == 'html' or bokeh_version < '0.11':
return self.renderer.html(self.plot, fig_format)
else:
doc = self.plot.document
From 049cb304b82a2be2cdb77f4b391fa55cc5ba3b03 Mon Sep 17 00:00:00 2001
From: Philipp Rudiger
Date: Tue, 21 Jun 2016 11:52:53 +0100
Subject: [PATCH 2/7] 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
From ff9091b15dc9277c69167cd4d0d1ffa27b703d77 Mon Sep 17 00:00:00 2001
From: Philipp Rudiger
Date: Tue, 21 Jun 2016 12:13:57 +0100
Subject: [PATCH 3/7] Small adjustments to bokeh adjoined Spikes
---
holoviews/plotting/bokeh/chart.py | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/holoviews/plotting/bokeh/chart.py b/holoviews/plotting/bokeh/chart.py
index 8d3b95d969..5d638bfcde 100644
--- a/holoviews/plotting/bokeh/chart.py
+++ b/holoviews/plotting/bokeh/chart.py
@@ -15,7 +15,7 @@
from ..util import compute_sizes, get_sideplot_ranges, match_spec, map_colors
from .element import ElementPlot, line_properties, fill_properties
from .path import PathPlot, PolygonPlot
-from .util import get_cmap, mpl_to_bokeh, update_plot, rgb2hex
+from .util import get_cmap, mpl_to_bokeh, update_plot, rgb2hex, bokeh_version
class PointPlot(ElementPlot):
@@ -377,11 +377,14 @@ class SideSpikesPlot(SpikesPlot):
all axis labels including ticks and ylabel. Valid options are 'left',
'right', 'bare' 'left-bare' and 'right-bare'.""")
- border = param.Integer(default=30, doc="Default borders on plot")
+ border = param.Integer(default=30 if bokeh_version < '0.12' else 5,
+ doc="Default borders on plot")
- height = param.Integer(default=100, doc="Height of plot")
+ height = param.Integer(default=100 if bokeh_version < '0.12' else 50,
+ doc="Height of plot")
- width = param.Integer(default=100, doc="Width of plot")
+ width = param.Integer(default=100 if bokeh_version < '0.12' else 50,
+ doc="Width of plot")
From f1541596f01ed5676d6d8af3da0d49cc83ad633a Mon Sep 17 00:00:00 2001
From: Philipp Rudiger
Date: Tue, 21 Jun 2016 12:50:38 +0100
Subject: [PATCH 4/7] Small fixes for updating bokeh plots
---
holoviews/plotting/bokeh/util.py | 3 +--
holoviews/plotting/bokeh/widgets.py | 15 +++++----------
2 files changed, 6 insertions(+), 12 deletions(-)
diff --git a/holoviews/plotting/bokeh/util.py b/holoviews/plotting/bokeh/util.py
index 1597ee3d6b..f3222f2b88 100644
--- a/holoviews/plotting/bokeh/util.py
+++ b/holoviews/plotting/bokeh/util.py
@@ -164,8 +164,7 @@ def compute_static_patch(document, models):
attributes and will break if new models have been added since
the plot was first created.
"""
- json = document.to_json()
- references = refs(json)
+ references = refs(document.to_json())
requested_updates = [m.ref['id'] for m in models]
value_refs = {}
diff --git a/holoviews/plotting/bokeh/widgets.py b/holoviews/plotting/bokeh/widgets.py
index 470fdad079..e017d521a9 100644
--- a/holoviews/plotting/bokeh/widgets.py
+++ b/holoviews/plotting/bokeh/widgets.py
@@ -7,7 +7,7 @@
from bokeh.util.notebook import get_comms
from ..widgets import NdWidget, SelectionWidget, ScrubberWidget
-
+from .util import compute_static_patch
class BokehWidget(NdWidget):
@@ -41,6 +41,7 @@ def _plot_figure(self, idx, fig_format='json'):
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:
@@ -48,15 +49,9 @@ def _plot_figure(self, idx, fig_format='json'):
doc, doc.to_json())
doc.last_comms_handle = handle
- to_json = doc.to_json()
- if handle.doc is not doc:
- msg = dict(doc=to_json)
- else:
- msg = Document._compute_patch_between_json(handle.json, to_json)
- if isinstance(handle._json, dict):
- handle._json[doc] = to_json
- else:
- handle._json = to_json
+ plotobjects = [h for handles in self.plot.traverse(lambda x: x.current_handles)
+ for h in handles]
+ msg = compute_static_patch(doc, plotobjects)
handle.comms.send(json.dumps(msg))
return 'Complete'
From 0b5b52610e0ae761234937fe41cea7d66c04a8eb Mon Sep 17 00:00:00 2001
From: Philipp Rudiger
Date: Tue, 21 Jun 2016 13:43:19 +0100
Subject: [PATCH 5/7] Added ignored list of models to avoid bokeh errors
---
holoviews/plotting/bokeh/util.py | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/holoviews/plotting/bokeh/util.py b/holoviews/plotting/bokeh/util.py
index f3222f2b88..0d3250c2e4 100644
--- a/holoviews/plotting/bokeh/util.py
+++ b/holoviews/plotting/bokeh/util.py
@@ -31,6 +31,10 @@
'3': {'marker': 'triangle', 'orientation': np.pi},
'4': {'marker': 'triangle', 'orientation': -np.pi/2}}
+# List of models that do not update correctly and must be ignored
+# Should only include models that have no direct effect on the display
+# and can therefore be safely ignored.
+IGNORED_MODELS = ['LinearAxis']
def rgb2hex(rgb):
"""
@@ -169,14 +173,18 @@ def compute_static_patch(document, models):
value_refs = {}
events = []
+ update_types = defaultdict(list)
for ref_id, obj in references.items():
- if ref_id not in requested_updates:
+ if ref_id not in requested_updates and not obj['type'] in IGNORED_MODELS:
continue
for key, val in obj['attributes'].items():
event = Document._event_for_attribute_change(references,
obj, key, val,
value_refs)
events.append(event)
+ update_types[obj['type']].append(key)
+ value_refs = {ref_id: val for ref_id, val in value_refs.items()
+ if val['type'] not in IGNORED_MODELS}
return dict(events=events, references=list(value_refs.values()))
From cc84835aab0bceb94f850885cb66700075785446 Mon Sep 17 00:00:00 2001
From: Philipp Rudiger
Date: Tue, 21 Jun 2016 14:12:37 +0100
Subject: [PATCH 6/7] Fixed handling of titles in bokeh 0.12
---
holoviews/plotting/bokeh/element.py | 33 +++++++++++++++++++++++++----
1 file changed, 29 insertions(+), 4 deletions(-)
diff --git a/holoviews/plotting/bokeh/element.py b/holoviews/plotting/bokeh/element.py
index 1ab310d164..827b881891 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 mpl_to_bokeh, convert_datetime, update_plot
+from .util import mpl_to_bokeh, convert_datetime, update_plot, bokeh_version
# Define shared style properties for bokeh plots
@@ -265,10 +265,15 @@ def _init_plot(self, key, element, plots, ranges=None):
properties['x_axis_label'] = xlabel if 'x' in self.show_labels else ' '
properties['y_axis_label'] = ylabel if 'y' in self.show_labels else ' '
+ if self.show_title:
+ title = self._format_title(key, separator=' ')
+ else:
+ title = ''
+
if LooseVersion(bokeh.__version__) >= LooseVersion('0.10'):
properties['webgl'] = self.renderer.webgl
return bokeh.plotting.Figure(x_axis_type=x_axis_type,
- y_axis_type=y_axis_type,
+ y_axis_type=y_axis_type, title=title,
tools=tools, **properties)
@@ -277,8 +282,8 @@ def _plot_properties(self, key, plot, element):
Returns a dictionary of plot properties.
"""
plot_props = dict(plot_height=self.height, plot_width=self.width)
- if self.show_title:
- plot_props['title'] = self._format_title(key, separator=' ')
+ if bokeh_version < '0.12':
+ plot_props.update(self._title_properties(key, plot, element))
if self.bgcolor:
plot_props['background_fill_color'] = self.bgcolor
if self.border is not None:
@@ -290,6 +295,20 @@ def _plot_properties(self, key, plot, element):
return plot_props
+ def _title_properties(self, key, plot, element):
+ if self.show_title:
+ title = self._format_title(key, separator=' ')
+ else:
+ title = ''
+
+ if bokeh_version < '0.12':
+ title_font = self._fontsize('title', 'title_text_font_size')
+ return dict(title=title, title_text_color='black', **title_font)
+ else:
+ title_font = self._fontsize('title', 'text_font_size')
+ return dict(text=title, text_color='black', **title_font)
+
+
def _init_axes(self, plot):
if self.xaxis is None:
plot.xaxis.visible = False
@@ -345,6 +364,9 @@ def _update_plot(self, key, plot, element=None):
plot.xaxis[0].set(**props['x'])
plot.yaxis[0].set(**props['y'])
+ if bokeh_version >= '0.12':
+ plot.title.set(**self._title_properties(key, plot, element))
+
if not self.show_grid:
plot.xgrid.grid_line_color = None
plot.ygrid.grid_line_color = None
@@ -528,6 +550,9 @@ def current_handles(self):
plot = self.state
handles.append(plot)
+ if bokeh_version >= '0.12':
+ handles.append(plot.title)
+
if self.current_frame:
framewise = self.lookup_options(self.current_frame, 'norm').options.get('framewise')
if framewise or isinstance(self.hmap, DynamicMap):
From ffa91cf2ed78c2d078c927babc702ebe4a634030 Mon Sep 17 00:00:00 2001
From: Philipp Rudiger
Date: Tue, 21 Jun 2016 14:48:29 +0100
Subject: [PATCH 7/7] Correctly detect if bokeh OverlayPlot ranges need
updating
---
holoviews/plotting/bokeh/element.py | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/holoviews/plotting/bokeh/element.py b/holoviews/plotting/bokeh/element.py
index 827b881891..a4b0f9989f 100644
--- a/holoviews/plotting/bokeh/element.py
+++ b/holoviews/plotting/bokeh/element.py
@@ -554,7 +554,16 @@ def current_handles(self):
handles.append(plot.title)
if self.current_frame:
- framewise = self.lookup_options(self.current_frame, 'norm').options.get('framewise')
+ if self.subplots:
+ current_frames = [(sp.current_frame if isinstance(sp.current_frame, Element)
+ else sp.current_frame.values()[0])
+ for sp in self.subplots.values()
+ if sp.current_frame]
+ framewise = any(self.lookup_options(frame, 'norm').options.get('framewise')
+ for frame in current_frames)
+ else:
+ opts = self.lookup_options(self.current_frame, 'norm')
+ framewise = opts.options.get('framewise')
if framewise or isinstance(self.hmap, DynamicMap):
handles += [plot.x_range, plot.y_range]
return handles