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