Panel Tabs as item in panel Column switches to first tab on widget interactions

In this example there is a multitab panel. When I try to use it in a pn.Column object alongside some text, the tabbed object behaves differently from stand-alone: whenever I interact with a widget in one of the tabs, in the end it jumps back to the first tab.
In the gif, the top graph is in a pn.Column, the bottom is the pn.Tabs.

I’m using:
Python: 3.7.6
panel: 0.9.5
param: 1.9.3
hvplot: 0.5.2

weighted-choices.ipynb (7.5 KB)

I think there are multiple issues. But the major one is that you have two .servable(). If you want to show together then you should just have one. Something like.

pn.Column(
    viewer.panel_full(),
    viewer.panel_tabs(),
).servable()

That works for me. But the app is still slow because you replace your layout in all the @param.depends functions. Instead you should define your layout initially and then replace the content of your Column, Holoviews pane etc.

@philippjfr. I see new users having this problem again and again. I also started out doing this. I think it is because it’s shown like that in the user guide ??

Initially this was actually pretty efficient but that implementation modified user objects potentially leading to very subtle bugs, which were hard to explain or work around. I do have a solid plan to optimize this again so it should be in the same ballpark of performance as the more manual callback approach. It’s quite unfortunate that it has been left in a fairly inefficient state for so long.

Thank you for the quick response. Applying the first suggestion, the behaviour still shows the same wrong tab update

pn.Column(
    viewer.panel_full(),
    viewer.panel_tabs(),
).servable()

I admit putting the .servable() twice is a bit of lazyness. However, it does work and can be a flexible feature in notebooks, toggling items in and out of an app on the fly without changing the code structure. Out of interest: is it to avoided or actually understood by Panel?

Thinking about your seconds option, I tried to modify the panel_full to render a second tabs object identical to that in panel_tabs and the problem is gone!

   def panel_full(self):
       description = " Selection tool\nThis tool assist users to ..."
       return pn.Column(description,
                     pn.Tabs(('single',self.pane_separate_bar),
                             ('combined',self.pane_combined_bar),
                             ('weighted',self.compute_weighted)))

So basically the Tabs object doesn’t retain it’s state? But why does it behave correctly in the panel_tabs method, where I also return a new tabs object every update?

Regarding app speed, I will take up your suggestion to update rather than output the layout. Do I understand correctly you suggest to make a dummy panel with the desired layout as a variable and at the same time changing all interactive methods with @param.depends to modify the specific element in that dummy structure instead of return the value?
Do you have any code example showing this (e.g. at Param / Holoviz / Panel docs or https://github.com/MarcSkovMadsen/awesome-panel)?

Many thanks!

Hi @mcav.

Some examples where I just replace the object of a Pane or objects of a layout are

I took a closer look at your code.

As far as I can see the problem comes from

 @param.depends(
        "component.param",
        "weights.param",
        "set_criteria_as_outer_and_tools_as_inner_label",
        watch=True,
    )
    def panel_tabs(self):
        return pn.Tabs(
            ("single", self.pane_separate_bar),
            ("combined", self.pane_combined_bar),
            ("weighted", self.compute_weighted),
        )

Here you define via @param.depends that the layout should update each time you change a parameter of component or weights.

When its added to the tab in panel_full it will be updated (i.e. reset) every time you change a parameter of component or weight.

def panel_full(self):
        description = "## Selection tool\nThis tool assist users to indicate their preferences for different components. For each component, scores between 0-5 can be given. In the weights tab each criterion can be weighted which allows aggregating all criteria to one single cost function"
        return pn.Column(description, self.panel_tabs)

Solved the problem by defining the layout in initialisation of the class with references to the methods (single_tool_pane, combined_tool_pane, weighted_tool_pane) returning the Holoviews objects.
The plot_both and plot_full now become obsolete, which is cleaner.
Also a lot faster now!

weighted-choices-solved.ipynb (7.3 KB)

Thanks @Marc

    layout = param.Parameter()
    
    def __init__(self,**params):
        super().__init__(**params)
        single_tool_pane = pn.Row(pn.Param(self.param, parameters=['component'], show_name=False, expand=True), 
                                  self.pane_separate_bar)
        combined_tool_pane = pn.Column(self.param['set_criteria_as_outer_and_tools_as_inner_label'],
                                       self.pane_combined_bar
                                      )
        weighted_tool_pane = pn.Row(self.weights.param,
                                   self.compute_weighted)
        self.layout = pn.Column(self.description,
                                pn.Tabs(('single',single_tool_pane),
                                        ('combined',combined_tool_pane),
                                        ('weighted',weighted_tool_pane)))
    

Although it works as expected, I do get an ugly warning when creating the viewer object

WARNING:param.ParamMethod: No such watcher Watcher(inst=ParamMethod(method), cls=<class 'panel.param.ParamMethod'>, fn=<bound method Reactive.param_change of ParamMethod(method)>, mode='args', onlychanged=True, parameter_names=('align', 'aspect_ratio', 'background', 'css_classes', 'width', 'height', 'min_width', 'min_height', 'max_width', 'max_height', 'margin', 'width_policy', 'height_policy', 'sizing_mode'), what='value', queued=False) to remove.
WARNING:param.ParamMethod: No such watcher Watcher(inst=ParamMethod(method), cls=<class 'panel.param.ParamMethod'>, fn=<bound method ParamMethod._update_pane of ParamMethod(method)>, mode='args', onlychanged=True, parameter_names=('object',), what='value', queued=False) to remove.
WARNING:param.Component: No such watcher Watcher(inst=Component(deployability=3, integratability=3, name='foo', scalability=3), cls=<class '__main__.Component'>, fn=<function ParamMethod._link_object_params.<locals>.update_pane at 0x000002192FB409D8>, mode='args', onlychanged=True, parameter_names=('name', 'scalability', 'deployability', 'integratability'), what='value', queued=False) to remove.
    

This seems occur because I’m linking to the Component method component.view_bar from the Viewer class

    def pane_separate_bar(self):
        return self.component.view_bar
    
1 Like