From 272482c0f176a11d330e38d0681c7294b7989e3a Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Fri, 5 Nov 2021 20:40:30 +0100 Subject: [PATCH 1/5] Fix handling of Plotly pane in Tabs --- panel/layout/tabs.py | 9 +++++---- panel/models/plotly.py | 3 ++- panel/models/plotly.ts | 10 ++++++++-- panel/pane/plotly.py | 4 ++-- 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/panel/layout/tabs.py b/panel/layout/tabs.py index 84a0df72d1..6fab54b9a4 100644 --- a/panel/layout/tabs.py +++ b/panel/layout/tabs.py @@ -142,13 +142,14 @@ def _get_objects(self, model, old_objects, doc, root, comm=None): panels = self._panels[root.ref['id']] for i, (name, pane) in enumerate(zip(self._names, self)): hidden = self.dynamic and i != self.active + panel = panels.get(id(pane)) + prev_hidden = hasattr(panel, 'child') and isinstance(panel.child, BkSpacer) if (pane in old_objects and id(pane) in panels and - ((hidden and isinstance(panels[id(pane)].child, BkSpacer)) or - (not hidden and not isinstance(panels[id(pane)].child, BkSpacer)))): - panel = panels[id(pane)] + ((hidden + prev_hidden) in (0, 2) or not dynamic)): new_models.append(panel) continue - elif self.dynamic and i != self.active: + + if hidden: child = BkSpacer(**{k: v for k, v in pane.param.values().items() if k in Layoutable.param and v is not None}) else: diff --git a/panel/models/plotly.py b/panel/models/plotly.py index c12d5e526f..8c53048ba2 100644 --- a/panel/models/plotly.py +++ b/panel/models/plotly.py @@ -2,7 +2,7 @@ Defines a custom PlotlyPlot bokeh model to render Plotly plots. """ from bokeh.core.properties import ( - Any, Dict, Either, Enum, Int, Instance, List, Null, Nullable, String + Any, Bool, Dict, Either, Enum, Int, Instance, List, Null, Nullable, String ) from bokeh.models import LayoutDOM, ColumnDataSource @@ -58,4 +58,5 @@ def __js_skip__(cls): viewport = Either(Dict(String, Any), Null) viewport_update_policy = Enum( "mouseup", "continuous", "throttle") viewport_update_throttle = Int() + visibility = Bool(True) _render_count = Int() diff --git a/panel/models/plotly.ts b/panel/models/plotly.ts index 5a3e316bf3..8ade6da8a9 100644 --- a/panel/models/plotly.ts +++ b/panel/models/plotly.ts @@ -161,11 +161,15 @@ export class PlotlyPlotView extends PanelHTMLBoxView { this.plot() }); this.connect(this.model.properties.viewport.change, () => this._updateViewportFromProperty()); + this.connect(this.model.properties.visibility.change, () => { + this.el.style.visibility = this.model.visibility ? 'visible' : 'hidden' + }) } async render(): Promise { super.render() - this.el.appendChild(this._layout_wrapper); + this.el.style.visibility = this.model.visibility ? 'visible' : 'hidden' + this.el.appendChild(this._layout_wrapper) await this.plot(); (window as any).Plotly.relayout(this._layout_wrapper, this.model.relayout) } @@ -389,6 +393,7 @@ export namespace PlotlyPlot { viewport: p.Property viewport_update_policy: p.Property viewport_update_throttle: p.Property + visibility: p.Property _render_count: p.Property } } @@ -407,7 +412,7 @@ export class PlotlyPlot extends HTMLBox { static init_PlotlyPlot(): void { this.prototype.default_view = PlotlyPlotView - this.define(({Array, Any, Ref, String, Nullable, Number}) => ({ + this.define(({Array, Any, Boolean, Ref, String, Nullable, Number}) => ({ data: [ Array(Any), [] ], layout: [ Any, {} ], config: [ Any, {} ], @@ -423,6 +428,7 @@ export class PlotlyPlot extends HTMLBox { viewport: [ Any, {} ], viewport_update_policy: [ String, "mouseup" ], viewport_update_throttle: [ Number, 200 ], + visibility: [ Boolean, true], _render_count: [ Number, 0 ], })) } diff --git a/panel/pane/plotly.py b/panel/pane/plotly.py index d3dfc37fe9..4cc3897c63 100644 --- a/panel/pane/plotly.py +++ b/panel/pane/plotly.py @@ -375,8 +375,8 @@ def _patch_tabs_plotly(viewable, root): args.update({f'tabs_{tabs.id}': tabs}) active &= tabs.active == i - model.visible = active - code = f'try {{ model.visible = {condition}; }} catch {{ }}' + model.visibility = active + code = f'try {{ model.visibility = {condition}; }} catch {{ }}' for tabs in parent_tabs: tab_key = f'tabs_{tabs.id}' cb_args = dict(args) From 6c0eb9160bd3a045b75d394728bc9d19597a7b1a Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Sat, 6 Nov 2021 13:03:40 +0100 Subject: [PATCH 2/5] Fix flake --- panel/layout/tabs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/panel/layout/tabs.py b/panel/layout/tabs.py index 6fab54b9a4..07c6a78bd0 100644 --- a/panel/layout/tabs.py +++ b/panel/layout/tabs.py @@ -145,7 +145,7 @@ def _get_objects(self, model, old_objects, doc, root, comm=None): panel = panels.get(id(pane)) prev_hidden = hasattr(panel, 'child') and isinstance(panel.child, BkSpacer) if (pane in old_objects and id(pane) in panels and - ((hidden + prev_hidden) in (0, 2) or not dynamic)): + ((hidden + prev_hidden) in (0, 2) or not self.dynamic)): new_models.append(panel) continue From 82a9e3eac0fb554fd051ffa638c24fccdad3b00d Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Sat, 6 Nov 2021 13:07:36 +0100 Subject: [PATCH 3/5] Fix logic --- panel/layout/tabs.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/panel/layout/tabs.py b/panel/layout/tabs.py index 07c6a78bd0..174321258d 100644 --- a/panel/layout/tabs.py +++ b/panel/layout/tabs.py @@ -143,15 +143,19 @@ def _get_objects(self, model, old_objects, doc, root, comm=None): for i, (name, pane) in enumerate(zip(self._names, self)): hidden = self.dynamic and i != self.active panel = panels.get(id(pane)) - prev_hidden = hasattr(panel, 'child') and isinstance(panel.child, BkSpacer) + prev_hidden = ( + hasattr(panel, 'child') and isinstance(panel.child, BkSpacer) and + panel.child.tags == ['hidden'] + ) if (pane in old_objects and id(pane) in panels and - ((hidden + prev_hidden) in (0, 2) or not self.dynamic)): + (not (hidden ^ prev_hidden) or not self.dynamic)): new_models.append(panel) continue if hidden: child = BkSpacer(**{k: v for k, v in pane.param.values().items() if k in Layoutable.param and v is not None}) + child.tags = ['hidden'] else: try: child = pane._get_model(doc, root, model, comm) From cf1e62be134f117114a8fd08c79eea71a1c0bde4 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Sat, 6 Nov 2021 13:35:41 +0100 Subject: [PATCH 4/5] Add comment --- panel/layout/tabs.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/panel/layout/tabs.py b/panel/layout/tabs.py index 174321258d..afa0a1e33c 100644 --- a/panel/layout/tabs.py +++ b/panel/layout/tabs.py @@ -147,8 +147,11 @@ def _get_objects(self, model, old_objects, doc, root, comm=None): hasattr(panel, 'child') and isinstance(panel.child, BkSpacer) and panel.child.tags == ['hidden'] ) + # If object has not changed, we have not toggled between + # hidden and unhidden state or the tabs are not + # dynamic then reuse the panel if (pane in old_objects and id(pane) in panels and - (not (hidden ^ prev_hidden) or not self.dynamic)): + (not (hidden ^ prev_hidden) or not (self.dynamic or prev_hidden))): new_models.append(panel) continue From 53c6c40ccd889e6f2302780403f31cfb816777c4 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Sat, 6 Nov 2021 14:07:44 +0100 Subject: [PATCH 5/5] Update plotly test --- panel/tests/pane/test_plotly.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/panel/tests/pane/test_plotly.py b/panel/tests/pane/test_plotly.py index 098ef9bb95..1dde691523 100644 --- a/panel/tests/pane/test_plotly.py +++ b/panel/tests/pane/test_plotly.py @@ -201,16 +201,16 @@ def test_plotly_tabs(document, comm): cb1, cb2 = root.js_property_callbacks['change:active'] if cb1.args['model'] is model2: cb1, cb2 = cb2, cb1 - assert model1.visible + assert model1.visibility assert cb1.args['model'] is model1 - assert cb1.code == 'try { model.visible = (cb_obj.active == 0); } catch { }' - assert not model2.visible + assert cb1.code == 'try { model.visibility = (cb_obj.active == 0); } catch { }' + assert not model2.visibility assert cb2.args['model'] is model2 - assert cb2.code == 'try { model.visible = (cb_obj.active == 1); } catch { }' + assert cb2.code == 'try { model.visibility = (cb_obj.active == 1); } catch { }' tabs.insert(0, 'Blah') - assert cb1.code == 'try { model.visible = (cb_obj.active == 1); } catch { }' - assert cb2.code == 'try { model.visible = (cb_obj.active == 2); } catch { }' + assert cb1.code == 'try { model.visibility = (cb_obj.active == 1); } catch { }' + assert cb2.code == 'try { model.visibility = (cb_obj.active == 2); } catch { }' @plotly_available def test_clean_relayout_data():