diff --git a/holoviews/plotting/bokeh/renderer.py b/holoviews/plotting/bokeh/renderer.py index f1222f9c05..dd9772fc5b 100644 --- a/holoviews/plotting/bokeh/renderer.py +++ b/holoviews/plotting/bokeh/renderer.py @@ -1,18 +1,23 @@ import uuid -from ...core import Store, HoloMap -from ..renderer import Renderer, MIME_TYPES -from .widgets import BokehScrubberWidget, BokehSelectionWidget -from .util import compute_static_patch, serialize_json - +import numpy as np import param from param.parameterized import bothmethod +from bokeh.charts import Chart from bokeh.embed import notebook_div from bokeh.io import load_notebook +from bokeh.model import _ModelInDocument as add_to_document +from bokeh.models import (Row, Column, Plot, Model, ToolbarBox, + WidgetBox, Div, DataTable, Tabs) +from bokeh.plotting import Figure from bokeh.resources import CDN, INLINE -from bokeh.model import _ModelInDocument as add_to_document +from ...core import Store, HoloMap +from ..renderer import Renderer, MIME_TYPES +from .widgets import BokehScrubberWidget, BokehSelectionWidget +from .util import compute_static_patch, serialize_json + class BokehRenderer(Renderer): @@ -114,7 +119,26 @@ def get_size(self_or_cls, plot): Returns a tuple of (width, height) in pixels. """ - return (plot.state.height, plot.state.height) + if not isinstance(plot, Model): + plot = plot.state + if isinstance(plot, Div): + # Cannot compute size for Div + return 0, 0 + elif isinstance(plot, (Row, Column, ToolbarBox, WidgetBox, Tabs)): + if not plot.children: return 0, 0 + if isinstance(plot, Row) or (isinstance(plot, ToolbarBox) and plot.toolbar_location not in ['right', 'left']): + w_agg, h_agg = (np.sum, np.max) + elif isinstance(plot, Tabs): + w_agg, h_agg = (np.max, np.max) + else: + w_agg, h_agg = (np.max, np.sum) + widths, heights = zip(*[self_or_cls.get_size(child) for child in plot.children]) + width, height = w_agg(widths), h_agg(heights) + elif isinstance(plot, (Chart, Figure)): + width, height = plot.plot_width, plot.plot_height + elif isinstance(plot, (Plot, DataTable)): + width, height = plot.width, plot.height + return width, height @classmethod def load_nb(cls, inline=True): diff --git a/tests/testrenderclass.py b/tests/testrenderclass.py index 5c35421b2a..175e86852c 100644 --- a/tests/testrenderclass.py +++ b/tests/testrenderclass.py @@ -9,7 +9,7 @@ from unittest import SkipTest import numpy as np -from holoviews import HoloMap, Image, ItemTable, Store +from holoviews import HoloMap, Image, ItemTable, Store, GridSpace, Table from holoviews.core.util import unicode from holoviews.element.comparison import ComparisonTestCase from holoviews.plotting import Renderer @@ -153,3 +153,36 @@ def test_save_html(self): def test_export_widgets(self): bytesio = BytesIO() self.renderer.export_widgets(self.map1, bytesio, fmt='widgets') + + def test_get_size_single_plot(self): + plot = self.renderer.get_plot(self.image1) + w, h = self.renderer.get_size(plot) + self.assertEqual((w, h), (300, 300)) + + def test_get_size_row_plot(self): + plot = self.renderer.get_plot(self.image1+self.image2) + w, h = self.renderer.get_size(plot) + self.assertEqual((w, h), (600, 300)) + + def test_get_size_column_plot(self): + plot = self.renderer.get_plot((self.image1+self.image2).cols(1)) + w, h = self.renderer.get_size(plot) + self.assertEqual((w, h), (300, 600)) + + def test_get_size_grid_plot(self): + grid = GridSpace({(i, j): self.image1 for i in range(3) for j in range(3)}) + plot = self.renderer.get_plot(grid) + w, h = self.renderer.get_size(plot) + self.assertEqual((w, h), (360, 360)) + + def test_get_size_table(self): + table = Table(range(10), kdims=['x']) + plot = self.renderer.get_plot(table) + w, h = self.renderer.get_size(plot) + self.assertEqual((w, h), (400, 300)) + + def test_get_size_tables_in_layout(self): + table = Table(range(10), kdims=['x']) + plot = self.renderer.get_plot(table+table) + w, h = self.renderer.get_size(plot) + self.assertEqual((w, h), (680, 300))