diff --git a/panel/layout/tabs.py b/panel/layout/tabs.py index 84a0df72d1..afa0a1e33c 100644 --- a/panel/layout/tabs.py +++ b/panel/layout/tabs.py @@ -142,15 +142,23 @@ 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) 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 - ((hidden and isinstance(panels[id(pane)].child, BkSpacer)) or - (not hidden and not isinstance(panels[id(pane)].child, BkSpacer)))): - panel = panels[id(pane)] + (not (hidden ^ prev_hidden) or not (self.dynamic or prev_hidden))): 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}) + child.tags = ['hidden'] else: try: child = pane._get_model(doc, root, model, comm) 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) 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():