From c1c8d020435767aecc98475d75498c176ce52cd8 Mon Sep 17 00:00:00 2001
From: Philipp Rudiger
Date: Wed, 2 Nov 2016 12:03:36 +0000
Subject: [PATCH] Optimized dynamic bokeh plots
Avoid updating unchanged data sources
---
holoviews/plotting/bokeh/element.py | 38 +++++++++++++++++------------
1 file changed, 23 insertions(+), 15 deletions(-)
diff --git a/holoviews/plotting/bokeh/element.py b/holoviews/plotting/bokeh/element.py
index 4ecb70c6fa..6e9d4003b1 100644
--- a/holoviews/plotting/bokeh/element.py
+++ b/holoviews/plotting/bokeh/element.py
@@ -169,6 +169,7 @@ def __init__(self, element, plot=None, **params):
self.handles = {} if plot is None else self.handles['plot']
self.static = len(self.hmap) == 1 and len(self.keys) == len(self.hmap)
self.callbacks = self._construct_callbacks()
+ self.static_source = False
def _construct_callbacks(self):
@@ -619,7 +620,8 @@ def update_frame(self, key, ranges=None, plot=None, element=None, empty=False):
glyph = self.handles.get('glyph', None)
if hasattr(glyph, 'visible'):
glyph.visible = bool(element)
- if not element:
+
+ if not element or (not self.dynamic and self.static):
return
style_element = element.last if self.batched else element
@@ -633,11 +635,22 @@ def update_frame(self, key, ranges=None, plot=None, element=None, empty=False):
plot = self.handles['plot']
source = self.handles['source']
empty = False
+ mapping = {}
+
+ # Cache frame object id to skip updating data if unchanged
+ previous_id = self.handles.get('previous_id', None)
if self.batched:
- data, mapping = self.get_batched_data(element, ranges, empty)
+ current_id = sum(element.traverse(lambda x: id(x.data), [Element]))
else:
- data, mapping = self.get_data(element, ranges, empty)
- self._update_datasource(source, data)
+ current_id = id(element.data)
+ self.handles['previous_id'] = current_id
+ self.static_source = self.dynamic and (current_id == previous_id)
+ if not self.static_source:
+ if self.batched:
+ data, mapping = self.get_batched_data(element, ranges, empty)
+ else:
+ data, mapping = self.get_data(element, ranges, empty)
+ self._update_datasource(source, data)
if glyph:
properties = self._glyph_properties(plot, element, source, ranges)
@@ -657,19 +670,12 @@ def current_handles(self):
if self.static and not self.dynamic:
return handles
- previous_id = self.handles.get('previous_id', None)
- current_id = id(self.current_frame.data) if self.current_frame else None
for handle in self._update_handles:
- if (handle == 'source' and self.dynamic and
- current_id == previous_id):
+ if (handle == 'source' and self.static_source):
continue
if handle in self.handles:
handles.append(self.handles[handle])
- # Cache frame object id to skip updating if unchanged
- if self.dynamic:
- self.handles['previous_id'] = current_id
-
if self.overlaid:
return handles
@@ -679,12 +685,14 @@ def current_handles(self):
handles.append(plot.title)
if self.current_frame:
- if self.framewise:
+ if not self.apply_ranges:
+ rangex, rangey = False, False
+ elif self.framewise:
rangex, rangey = True, True
elif isinstance(self.hmap, DynamicMap):
rangex, rangey = True, True
- subplots = list(self.subplots.values()) if self.subplots else []
- callbacks = [cb for p in [self]+subplots for cb in p.callbacks]
+ callbacks = [cb for cbs in self.traverse(lambda x: x.callbacks)
+ for cb in cbs]
streams = [s for cb in callbacks for s in cb.streams]
for stream in streams:
if isinstance(stream, RangeXY):