Skip to content

Commit

Permalink
Improved bokeh json patch computation
Browse files Browse the repository at this point in the history
Improved code that tidies up bokeh json patches to update plots.
Should result in an overall reduction of 30-50% in the json size
  • Loading branch information
philippjfr committed Aug 3, 2016
1 parent a6cae95 commit 36094ba
Showing 1 changed file with 37 additions and 22 deletions.
59 changes: 37 additions & 22 deletions 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:
Expand Down Expand Up @@ -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']
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -176,22 +173,40 @@ 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
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.
"""
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'])
Expand All @@ -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
Expand All @@ -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
Expand Down

0 comments on commit 36094ba

Please sign in to comment.