From e258f789803ac27330de45d50214f84242bba3de Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Sat, 17 Jun 2017 17:56:01 +0100 Subject: [PATCH] Added support for Empty adjoint plots in bokeh (#1561) --- holoviews/core/layout.py | 2 +- holoviews/plotting/bokeh/plot.py | 27 ++++++++++++++++++--------- holoviews/plotting/plot.py | 2 ++ tests/testplotinstantiation.py | 14 +++++++++++++- 4 files changed, 34 insertions(+), 11 deletions(-) diff --git a/holoviews/core/layout.py b/holoviews/core/layout.py index 3cee742b84..1ad9743e43 100644 --- a/holoviews/core/layout.py +++ b/holoviews/core/layout.py @@ -31,7 +31,7 @@ def __add__(self, obj): def __lshift__(self, other): - if isinstance(other, (ViewableElement, NdMapping)): + if isinstance(other, (ViewableElement, NdMapping, Empty)): return AdjointLayout([self, other]) elif isinstance(other, AdjointLayout): return AdjointLayout(other.data.values()+[self]) diff --git a/holoviews/plotting/bokeh/plot.py b/holoviews/plotting/bokeh/plot.py index 028d508931..975d4a135c 100644 --- a/holoviews/plotting/bokeh/plot.py +++ b/holoviews/plotting/bokeh/plot.py @@ -7,7 +7,8 @@ from bokeh.models.widgets import Panel, Tabs from ...core import (OrderedDict, CompositeOverlay, Store, Layout, GridMatrix, - AdjointLayout, NdLayout, Empty, GridSpace, HoloMap, Element) + AdjointLayout, NdLayout, Empty, GridSpace, HoloMap, Element, + Empty) from ...core.options import Compositor from ...core.util import basestring, wrap_tuple, unique_iterator from ...element import Histogram @@ -61,7 +62,8 @@ def document(self, doc): self._document = doc if self.subplots: for plot in self.subplots.values(): - plot.document = doc + if plot is not None: + plot.document = doc def __init__(self, *args, **params): @@ -561,7 +563,7 @@ def _create_subplots(self, layout, positions, layout_dimensions, ranges, num=0): for pos in positions: # Pos will be one of 'main', 'top' or 'right' or None element = layout.get(pos, None) - if element is None or not element.traverse(lambda x: x, [Element]): + if element is None or not element.traverse(lambda x: x, [Element, Empty]): continue subplot_opts = dict(adjoined=main_plot) @@ -573,14 +575,16 @@ def _create_subplots(self, layout, positions, layout_dimensions, ranges, num=0): if pos != 'main': plot_type = AdjointLayoutPlot.registry.get(vtype, plot_type) if pos == 'right': - yaxis = 'right-bare' if 'bare' in plot_type.yaxis else 'right' + yaxis = 'right-bare' if plot_type and 'bare' in plot_type.yaxis else 'right' + width = plot_type.width if plot_type else 0 side_opts = dict(height=main_plot.height, yaxis=yaxis, - width=plot_type.width, invert_axes=True, + width=width, invert_axes=True, labelled=['y'], xticks=1, xaxis=main_plot.xaxis) else: - xaxis = 'top-bare' if 'bare' in plot_type.xaxis else 'top' + xaxis = 'top-bare' if plot_type and 'bare' in plot_type.xaxis else 'top' + height = plot_type.height if plot_type else 0 side_opts = dict(width=main_plot.width, xaxis=xaxis, - height=plot_type.height, labelled=['x'], + height=height, labelled=['x'], yticks=1, yaxis=main_plot.yaxis) # Override the plotopts as required @@ -588,7 +592,10 @@ def _create_subplots(self, layout, positions, layout_dimensions, ranges, num=0): plotopts = dict(side_opts, **plotopts) plotopts.update(subplot_opts) - if plot_type is None: + if vtype is Empty: + subplots[pos] = None + continue + elif plot_type is None: self.warning("Bokeh plotting class for %s type not found, object will " "not be rendered." % vtype.__name__) continue @@ -754,13 +761,15 @@ def initialize_plot(self, ranges=None, plots=[]): """ if plots is None: plots = [] adjoined_plots = [] - for pos in ['main', 'right', 'top']: + for pos in self.view_positions: # Pos will be one of 'main', 'top' or 'right' or None subplot = self.subplots.get(pos, None) # If no view object or empty position, disable the axis if subplot: passed_plots = plots + adjoined_plots adjoined_plots.append(subplot.initialize_plot(ranges=ranges, plots=passed_plots)) + else: + adjoined_plots.append(empty_plot(0, 0)) self.drawn = True if not adjoined_plots: adjoined_plots = [None] return adjoined_plots diff --git a/holoviews/plotting/plot.py b/holoviews/plotting/plot.py index ad59cf107a..b1567ed18e 100644 --- a/holoviews/plotting/plot.py +++ b/holoviews/plotting/plot.py @@ -262,6 +262,8 @@ def traverse(self, fn=None, specs=None, full_breadth=True): # Assumes composite objects are iterables if hasattr(self, 'subplots') and self.subplots: for el in self.subplots.values(): + if el is None: + continue accumulator += el.traverse(fn, specs, full_breadth) if not full_breadth: break return accumulator diff --git a/tests/testplotinstantiation.py b/tests/testplotinstantiation.py index 3516166698..5cfa06b8bc 100644 --- a/tests/testplotinstantiation.py +++ b/tests/testplotinstantiation.py @@ -14,7 +14,7 @@ import numpy as np from holoviews import (Dimension, Overlay, DynamicMap, Store, Dataset, NdOverlay, GridSpace, HoloMap, Layout, Cycle, - Palette, Element) + Palette, Element, Empty) from holoviews.core.util import pd from holoviews.element import (Curve, Scatter, Image, VLine, Points, HeatMap, QuadMesh, Spikes, ErrorBars, @@ -1479,6 +1479,18 @@ 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_empty_adjoint_plot(self): + adjoint = Curve([0,1,1,2,3]) << Empty() << Curve([0,1,1,0,1]) + plot = bokeh_renderer.get_plot(adjoint) + adjoint_plot = plot.subplots[(0, 0)] + self.assertEqual(len(adjoint_plot.subplots), 3) + column = plot.state.children[1] + row1, row2 = column.children + self.assertEqual(row1.children[0].plot_height, row1.children[1].plot_height) + self.assertEqual(row1.children[1].plot_width, 0) + self.assertEqual(row2.children[1].plot_width, 0) + self.assertEqual(row2.children[0].plot_height, row2.children[1].plot_height) + def test_layout_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'])