From d336e111bf255a990e4475c09a00f8a7cf65f8ad Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Mon, 5 Jun 2023 18:59:08 +0200 Subject: [PATCH] Implement bokeh3 compatible layout (#1076) --- hvplot/interactive.py | 50 ++++++++++++++++++++++++++++++++- hvplot/tests/testinteractive.py | 27 ++++++++++++++++++ hvplot/util.py | 3 ++ 3 files changed, 79 insertions(+), 1 deletion(-) diff --git a/hvplot/interactive.py b/hvplot/interactive.py index 0a228d3f1..baabfd574 100644 --- a/hvplot/interactive.py +++ b/hvplot/interactive.py @@ -112,7 +112,7 @@ from .converter import HoloViewsConverter from .util import ( - _flatten, is_tabular, is_xarray, is_xarray_dataarray, + _flatten, bokeh3, is_tabular, is_xarray, is_xarray_dataarray, _convert_col_names_to_str, ) @@ -709,6 +709,11 @@ def layout(self, **kwargs): to the center and widget location specified in the interactive call. """ + if bokeh3: + return self._layout_bk3(**kwargs) + return self._layout_bk2(**kwargs) + + def _layout_bk2(self, **kwargs): widget_box = self.widgets() panel = self.output() loc = self._loc @@ -751,6 +756,49 @@ def layout(self, **kwargs): components = [Column(panel, widgets)] return Row(*components, **kwargs) + def _layout_bk3(self, **kwargs): + widget_box = self.widgets() + panel = self.output() + loc = self._loc + center = self._center + alignments = { + 'left': (Row, ('start', 'center'), True), + 'right': (Row, ('end', 'center'), False), + 'top': (Column, ('center', 'start'), True), + 'bottom': (Column, ('center', 'end'), False), + 'top_left': (Column, 'start', True), + 'top_right': (Column, ('end', 'start'), True), + 'bottom_left': (Column, ('start', 'end'), False), + 'bottom_right': (Column, 'end', False), + 'left_top': (Row, 'start', True), + 'left_bottom': (Row, ('start', 'end'), True), + 'right_top': (Row, ('end', 'start'), False), + 'right_bottom': (Row, 'end', False) + } + layout, align, widget_first = alignments[loc] + widget_box.align = align + if not len(widget_box): + if center: + components = [HSpacer(), panel, HSpacer()] + else: + components = [panel] + return Row(*components, **kwargs) + + items = (widget_box, panel) if widget_first else (panel, widget_box) + sizing_mode = kwargs.get('sizing_mode') + if not center: + if layout is Row: + components = list(items) + else: + components = [layout(*items, sizing_mode=sizing_mode)] + elif layout is Column: + components = [HSpacer(), layout(*items, sizing_mode=sizing_mode), HSpacer()] + elif loc.startswith('left'): + components = [widget_box, HSpacer(), panel, HSpacer()] + else: + components = [HSpacer(), panel, HSpacer(), widget_box] + return Row(*components, **kwargs) + def holoviews(self): """ Returns a HoloViews object to render the output of this diff --git a/hvplot/tests/testinteractive.py b/hvplot/tests/testinteractive.py index 36e393200..25607ff87 100644 --- a/hvplot/tests/testinteractive.py +++ b/hvplot/tests/testinteractive.py @@ -14,6 +14,10 @@ from hvplot import bind from hvplot.interactive import Interactive from hvplot.xarray import XArrayInteractive +from hvplot.util import bokeh3 + +is_bokeh2 = pytest.mark.skipif(bokeh3, reason="requires bokeh 2.x") +is_bokeh3 = pytest.mark.skipif(not bokeh3, reason="requires bokeh 3.x") @pytest.fixture(scope='module') @@ -1247,6 +1251,7 @@ def test_interactive_pandas_layout_default_no_widgets_kwargs(df): assert layout.width == 200 +@is_bokeh2 def test_interactive_pandas_layout_default_with_widgets(df): w = pn.widgets.IntSlider(value=2, start=1, end=5) dfi = Interactive(df) @@ -1270,6 +1275,27 @@ def test_interactive_pandas_layout_default_with_widgets(df): assert isinstance(layout[0][0][1], pn.layout.HSpacer) +@is_bokeh3 +def test_interactive_pandas_layout_default_with_widgets_bk3(df): + w = pn.widgets.IntSlider(value=2, start=1, end=5) + dfi = Interactive(df) + dfi = dfi.head(w) + + assert dfi._center is False + assert dfi._loc == 'top_left' + + layout = dfi.layout() + + assert isinstance(layout, pn.Row) + assert len(layout) == 1 + assert isinstance(layout[0], pn.Column) + assert len(layout[0]) == 2 + assert isinstance(layout[0][0], pn.Column) + assert isinstance(layout[0][1], pn.pane.PaneBase) + assert len(layout[0][0]) == 1 + assert isinstance(layout[0][0][0], pn.widgets.IntSlider) + +@is_bokeh2 def test_interactive_pandas_layout_center_with_widgets(df): w = pn.widgets.IntSlider(value=2, start=1, end=5) dfi = df.interactive(center=True) @@ -1298,6 +1324,7 @@ def test_interactive_pandas_layout_center_with_widgets(df): assert isinstance(layout[1][1][2], pn.layout.HSpacer) +@is_bokeh2 def test_interactive_pandas_layout_loc_with_widgets(df): w = pn.widgets.IntSlider(value=2, start=1, end=5) dfi = df.interactive(loc='top_right') diff --git a/hvplot/util.py b/hvplot/util.py index 2e5914176..90497af16 100644 --- a/hvplot/util.py +++ b/hvplot/util.py @@ -10,6 +10,7 @@ from packaging.version import Version from types import FunctionType +import bokeh import numpy as np import pandas as pd import param @@ -21,6 +22,8 @@ panel_available = False hv_version = Version(hv.__version__) +bokeh_version = Version(bokeh.__version__) +bokeh3 = bokeh_version >= Version("3.0") def with_hv_extension(func, extension='bokeh', logo=False):