From 36094ba93e457d59b300f0c5364ed2e64485d953 Mon Sep 17 00:00:00 2001
From: Philipp Rudiger
Date: Wed, 3 Aug 2016 14:48:30 +0100
Subject: [PATCH] Improved bokeh json patch computation
Improved code that tidies up bokeh json patches to update plots.
Should result in an overall reduction of 30-50% in the json size
---
holoviews/plotting/bokeh/util.py | 59 ++++++++++++++++++++------------
1 file changed, 37 insertions(+), 22 deletions(-)
diff --git a/holoviews/plotting/bokeh/util.py b/holoviews/plotting/bokeh/util.py
index d968bc4445..a223c44e94 100644
--- a/holoviews/plotting/bokeh/util.py
+++ b/holoviews/plotting/bokeh/util.py
@@ -1,6 +1,7 @@
+import itertools
from distutils.version import LooseVersion
-
from collections import defaultdict
+
import numpy as np
try:
@@ -40,13 +41,9 @@
# and can therefore be safely ignored. Axes currently fail saying
# LinearAxis.computed_bounds cannot be updated
IGNORED_MODELS = ['LinearAxis', 'LogAxis', 'DatetimeAxis',
- 'CategoricalAxis' 'BasicTicker', 'BasicTickFormatter',
+ 'CategoricalAxis', 'BasicTicker', 'BasicTickFormatter',
'FixedTicker', 'FuncTickFormatter', 'LogTickFormatter']
-# Where to look for the ignored models
-LOCATIONS = ['new', 'below', 'right', 'left',
- 'renderers', 'above', 'attributes', 'plot', 'ticker']
-
# Model priority order to ensure some types are updated before others
MODEL_PRIORITY = ['Range1d', 'Title', 'Image', 'LinearColorMapper',
'Plot', 'Range1d', 'LinearAxis', 'ColumnDataSource']
@@ -95,7 +92,7 @@ def mpl_to_bokeh(properties):
if k == 's':
new_properties['size'] = v
elif k == 'marker':
- new_properties.update(markers.get(v, {'marker': v}))
+ new_properties.updates(markers.get(v, {'marker': v}))
elif k == 'color' or k.endswith('_color'):
with abbreviated_exception():
v = colors.ColorConverter.colors.get(v, v)
@@ -176,7 +173,22 @@ def refs(json):
return result
-def compute_static_patch(document, models, json=None):
+def get_ids(obj):
+ """
+ Returns a list of all ids in the supplied object.
+ Useful for determining if a json representation contains
+ references to other objects.
+ """
+ ids = []
+ if isinstance(obj, list):
+ ids = [get_ids(o) for o in obj]
+ elif isinstance(obj, dict):
+ ids = [(v,) if k == 'id' else get_ids(v)
+ for k, v in obj.items()]
+ return list(itertools.chain(*ids))
+
+
+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
@@ -184,14 +196,17 @@ def compute_static_patch(document, models, json=None):
attributes and will break if new models have been added since
the plot was first created.
"""
- references = refs(json if json else document.to_json())
- requested_updates = [m.ref['id'] for m in models]
+ references = refs(document.to_json())
+ model_ids = [m.ref['id'] for m in models]
+ requested_updates = []
value_refs = {}
events = []
update_types = defaultdict(list)
for ref_id, obj in references.items():
- if ref_id not in requested_updates:
+ if ref_id in model_ids:
+ requested_updates += get_ids(obj)
+ else:
continue
if obj['type'] in MODEL_PRIORITY:
priority = MODEL_PRIORITY.index(obj['type'])
@@ -203,16 +218,17 @@ def compute_static_patch(document, models, json=None):
value_refs)
events.append((priority, event))
update_types[obj['type']].append(key)
- events = [delete_refs(e, LOCATIONS, IGNORED_MODELS)
+ events = [delete_refs(e, IGNORED_MODELS)
for _, e in sorted(events, key=lambda x: x[0])]
- value_refs = {ref_id: delete_refs(val, LOCATIONS, IGNORED_MODELS)
+ events = [e for e in events if all(i in requested_updates for i in get_ids(e))]
+ value_refs = {ref_id: delete_refs(val, IGNORED_MODELS)
for ref_id, val in value_refs.items()}
references = [val for val in value_refs.values()
- if val is not None]
+ if val not in [None, {}]]
return dict(events=events, references=references)
-def delete_refs(obj, locs, delete):
+def delete_refs(obj, delete):
"""
Delete all references to specific model types by recursively
traversing the object and looking for the models to be deleted in
@@ -226,15 +242,14 @@ def delete_refs(obj, locs, delete):
return None
new_obj = {}
for k, v in list(obj.items()):
- if k in locs:
- ref = delete_refs(v, locs, delete)
- if ref is not None:
- new_obj[k] = ref
- else:
- new_obj[k] = v
+ if k in ['data', 'palette'] or (k == 'attributes' and not get_ids(v)):
+ continue
+ ref = delete_refs(v, delete)
+ if ref is not None:
+ new_obj[k] = ref
return new_obj
elif isinstance(obj, list):
- objs = [delete_refs(v, locs, delete) for v in obj]
+ objs = [delete_refs(v, delete) for v in obj]
return [o for o in objs if o is not None]
else:
return obj