Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Correctly sync shared datasources #1254

Merged
merged 1 commit into from Apr 9, 2017
Merged
Changes from all commits
Commits
File filter...
Filter file types
Jump to…
Jump to file or symbol
Failed to load files and symbols.
+110 −5
Diff settings

Always

Just for now

@@ -17,7 +17,7 @@
from ..util import get_dynamic_mode, initialize_sampled
from .renderer import BokehRenderer
from .util import (bokeh_version, layout_padding, pad_plots,
filter_toolboxes, make_axis)
filter_toolboxes, make_axis, update_shared_sources)

if bokeh_version >= '0.12':
from bokeh.layouts import gridplot
@@ -153,6 +153,8 @@ def sync_sources(self):
and 'source' in x.handles)
data_sources = self.traverse(get_sources, [filter_fn])
grouped_sources = groupby(sorted(data_sources, key=lambda x: x[0]), lambda x: x[0])
shared_sources = []
source_cols = {}
for _, group in grouped_sources:
group = list(group)
if len(group) > 1:
@@ -169,6 +171,10 @@ def sync_sources(self):
else:
renderer.update(source=new_source)
plot.handles['source'] = new_source
shared_sources.append(new_source)
source_cols[id(new_source)] = [c for c in new_source.data]
self.handles['shared_sources'] = shared_sources
self.handles['source_cols'] = source_cols



@@ -441,7 +447,7 @@ def _make_axes(self, plot):
plot = Column(*models)
return plot


@update_shared_sources
def update_frame(self, key, ranges=None):
"""
Update the internal state of the Plot to represent the given
@@ -450,7 +456,7 @@ def update_frame(self, key, ranges=None):
"""
ranges = self.compute_ranges(self.layout, key, ranges)
for coord in self.layout.keys(full_grid=True):
subplot = self.subplots.get(coord, None)
subplot = self.subplots.get(wrap_tuple(coord), None)
if subplot is not None:
subplot.update_frame(key, ranges)
title = self._get_title(key)
@@ -692,7 +698,7 @@ def initialize_plot(self, plots=None, ranges=None):

return self.handles['plot']


@update_shared_sources
def update_frame(self, key, ranges=None):
"""
Update the internal state of the Plot to represent the given
@@ -612,3 +612,29 @@ def filter_batched_data(data, mapping):
del data[v]
except:
pass


def update_shared_sources(f):
"""
Context manager to ensures data sources shared between multiple
plots are cleared and updated appropriately avoiding warnings and
allowing empty frames on subplots. Expects a list of
shared_sources and a mapping of the columns expected columns for
each source in the plots handles.
"""
def wrapper(self, *args, **kwargs):
source_cols = self.handles.get('source_cols', {})
shared_sources = self.handles.get('shared_sources', [])
for source in shared_sources:
source.data.clear()

ret = f(self, *args, **kwargs)

for source in shared_sources:
expected = source_cols[id(source)]
found = [c for c in expected if c in source.data]
empty = np.full_like(source.data[found[0]], np.NaN) if found else []
patch = {c: empty for c in expected if c not in source.data}
source.data.update(patch)
return ret
return wrapper
Copy path View file
@@ -11,7 +11,7 @@

import param
import numpy as np
from holoviews import (Dimension, Overlay, DynamicMap, Store,
from holoviews import (Dimension, Overlay, DynamicMap, Store, Dataset,
NdOverlay, GridSpace, HoloMap, Layout, Cycle)
from holoviews.core.util import pd
from holoviews.element import (Curve, Scatter, Image, VLine, Points,
@@ -1224,6 +1224,79 @@ def test_shared_axes_disable(self):
self.assertEqual((x_range.start, x_range.end), (-.5, .5))
self.assertEqual((y_range.start, y_range.end), (-.5, .5))

def test_layout_shared_source_synced_update(self):
hmap = HoloMap({i: Dataset({chr(65+j): np.random.rand(i+2)

This comment has been minimized.

Copy link
@philippjfr

philippjfr Apr 9, 2017

Author Contributor

Add comments and check source_cols is A, B, C, D

for j in range(4)}, kdims=['A', 'B', 'C', 'D'])
for i in range(3)})

# Create two holomaps of points sharing the same data source
hmap1= hmap.map(lambda x: Points(x.clone(kdims=['A', 'B'])), Dataset)
hmap2 = hmap.map(lambda x: Points(x.clone(kdims=['D', 'C'])), Dataset)

# Pop key (1,) for one of the HoloMaps and make Layout
hmap2.pop((1,))
layout = (hmap1 + hmap2)(plot=dict(shared_datasource=True))

# Get plot
plot = bokeh_renderer.get_plot(layout)

# Check plot created shared data source and recorded expected columns
sources = plot.handles.get('shared_sources', [])
source_cols = plot.handles.get('source_cols', {})
self.assertEqual(len(sources), 1)
source = sources[0]
data = source.data
cols = source_cols[id(source)]
self.assertEqual(set(cols), {'A', 'B', 'C', 'D'})

# Ensure the source contains the expected columns
self.assertEqual(set(data.keys()), {'A', 'B', 'C', 'D'})

# Update to key (1,) and check the source contains data
# corresponding to hmap1 and filled in NaNs for hmap2,
# which was popped above
plot.update((1,))
self.assertEqual(data['A'], hmap1[1].dimension_values(0))
self.assertEqual(data['B'], hmap1[1].dimension_values(1))
self.assertEqual(data['C'], np.full_like(hmap1[1].dimension_values(0), np.NaN))
self.assertEqual(data['D'], np.full_like(hmap1[1].dimension_values(0), np.NaN))

def test_grid_shared_source_synced_update(self):
hmap = HoloMap({i: Dataset({chr(65+j): np.random.rand(i+2)
for j in range(4)}, kdims=['A', 'B', 'C', 'D'])
for i in range(3)})

# Create two holomaps of points sharing the same data source
hmap1= hmap.map(lambda x: Points(x.clone(kdims=['A', 'B'])), Dataset)
hmap2 = hmap.map(lambda x: Points(x.clone(kdims=['D', 'C'])), Dataset)

# Pop key (1,) for one of the HoloMaps and make GridSpace
hmap2.pop(1)
grid = GridSpace({0: hmap1, 2: hmap2}, kdims=['X'])(plot=dict(shared_datasource=True))

# Get plot
plot = bokeh_renderer.get_plot(grid)

# Check plot created shared data source and recorded expected columns
sources = plot.handles.get('shared_sources', [])
source_cols = plot.handles.get('source_cols', {})
self.assertEqual(len(sources), 1)
source = sources[0]
data = source.data
cols = source_cols[id(source)]
self.assertEqual(set(cols), {'A', 'B', 'C', 'D'})

# Ensure the source contains the expected columns
self.assertEqual(set(data.keys()), {'A', 'B', 'C', 'D'})

# Update to key (1,) and check the source contains data
# corresponding to hmap1 and filled in NaNs for hmap2,
# which was popped above
plot.update((1,))
self.assertEqual(data['A'], hmap1[1].dimension_values(0))
self.assertEqual(data['B'], hmap1[1].dimension_values(1))
self.assertEqual(data['C'], np.full_like(hmap1[1].dimension_values(0), np.NaN))
self.assertEqual(data['D'], np.full_like(hmap1[1].dimension_values(0), np.NaN))


class TestPlotlyPlotInstantiation(ComparisonTestCase):
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.