How to group settings within a panel

I have built a complex control system for a scientific instrument. Settings are implemented as Param objects, with one Parameterized class for each component of the instrument. Settings are displayed in a UI using Panel, which serialises and sends settings to the instrument.

Some components now have dozens of settings and I would like to make the UI easier to use by grouping the settings for one component into UI group-boxes, or sections with titles, or into an accordion.

I know I can use precedence to order the settings. I know I could break each component’s settings into multiple child Parameterized objects, but this would be a huge change and would give me other problems with my custom serialisation.

I found that I could add any Python object to a Parameterized, so I added a dictionary of groups. I then tried to add the settings in each group to the UI, but this didn’t work because that subset of settings was not a Parameterized.

I also tried adding all the settings multiple times to the UI, each time setting the precedence to -1 for those that were hidden in each group. But this just showed only the last group, because the precedence was being overwritten each time.

Please does anyone know of a way to group the settings in a Parameterized class in the UI, without breaking it into a hierarchy of settings?

1 Like

Can you share a bit of code as to how you’re currently doing it?

I’m not currently doing it, because I can’t find a way that works. Hence the question…!

Hi @richardwhitehead

What I we need is some code to start from to understand you specific challenges. What you describe in text can mean so many things.

For example I interpret what you write as

import panel as pn
import param

pn.extension()


class Component(pn.viewable.Viewer):
    setting1 = param.String()
    setting2 = param.String()

    def __panel__(self):
        return pn.Param(self)


class ControlSystem(pn.viewable.Viewer):
    component1 = param.ClassSelector(class_=Component)
    component2 = param.ClassSelector(class_=Component)

    def __panel__(self):
        return pn.Param(self)

    def __init__(self, **params):
        super().__init__(**params)
        self.component1 = Component(name="Component 1")
        self.component2 = Component(name="Component 2")


ControlSystem().servable()

Which looks like

This is not so great. So I believe you are asking how to make this look and feel better. Especially as the number of controls and settings start growing?

Is that correct?

Hi @Marc. I have already organised the UI so the different modules have their own pages, selected at the top level of the UI - so this level of detail was perhaps confusing for me to have included. The issue is that each module now has scores of settings so the user just sees a sea of controls with no organisation to them; but the settings often have groups of controls which are logically similar or connected, e.g. ‘voltage limits’, ‘temperature limits’, ‘calibration settings’.
What I would like is to be able to group those settings easily, into groups of controls - each group with a heading (a “group box”).
There might be only one or two controls in each group; I still want to see all the settings at once on a page, and I definitely don’t want to split my settings classes into hundreds of small child classes.
This is a purely visual thing, not a very solid logical separation, just a visual indicator to help the user.
On a previous project, using PyQt and not using Holoviz, when I created my own settings objects I added a ‘group’ parameter to each setting; the page then grouped settings with the same group name into a GroupBox. I tried to build such a system on top of Panel but could not get it to work because I could not find a way to have a widget that showed only some settings within a Parameterized object, while another widget showed a different choice of settings.

Would an Accordion help?

1 Like

Being able to separate my settings into an accordion would be one nice solution, but I can’t see how to do it without splitting my settings class. Say I have one Parameterized with 50 parameters within it; how do I persuade panel to show say five settings in one accordion page and another five on another accordion page?

1 Like

I think you could do multiple pn.Accordion(*pn.Param(self, params=...) Param — Panel v1.5.2

1 Like

You would have to instruct how to split via code.

That’s the problem.
You might think that to split the view of one parameterized object into several views, such as several accordion pages, in the first page you could set the precedence of all but a few settings to -1; and in the next page, a different selection of settings would be hidden. But this does not work; the settings are shared between all the pages so you cannot make them different for one page from another.
This is such a basic aspect of an industrial-strength professional (as opposed to research or small toy) UI that I’m amazed it is absent.

But this does not work; the settings are shared between all the pages so you cannot make them different for one page from another.

Can you elaborate on this?

Sure. Say I have:

class MySettings(pn.Parameterized):
    setting1 = param.Number()
    setting2 = param.Number()

Then you might think you could create a UI with something like:

settings = MySettings()
settings.setting1.precedence = 1
settings.setting2.precedence = -1
row1 = pn.Param(settings)

settings.setting1.precedence = -1
settings.setting2.precedence = 1
row2 = pn.Param(settings)
panel = pn.Accordion([row1, row2])

But this shows setting2 in both entries of the accordion, because the same settings object is in both views and so changing the precedence affects them both the same. Note that I only want to instantiate one MySettings object, because this object is what will be saved and will be serialised to the instrument.

Hmm, I was thinking there was a clone method for param, but this seems to work:

from copy import deepcopy
import panel as pn
import param


class MySettings(param.Parameterized):
    setting1 = param.Number()
    setting2 = param.Number()


settings = MySettings()
settings.param["setting1"].precedence = 1
settings.param["setting2"].precedence = -1
row1 = pn.Param(settings)

second_settings = deepcopy(settings)
second_settings.param["setting1"].precedence = -1
second_settings.param["setting2"].precedence = 1
row2 = pn.Param(second_settings).clone()
panel = pn.Accordion(*[row1, row2])

panel

That may succeed in creating the UI, but now we have several objects of type MySettings, which will have different and conflicting values. To save or serialise the settings, I would then have to combine them using knowledge of which setting was in which accordion.
I could also have some serious complexity when validating changes since some settings need to be considered together, although probably all dependent settings would be in the same accordion page.
I think this may be a possible solution, thank you.
I consider it more of a kludge and work-around, considering the complexity I will have to add on top; but I should be able to create a custom “accordion Param page” to put the split settings back together. It’s a pity Param doesn’t support this very basic requirement.

Feel free to submit an issue on Param’s GitHub.