I’m having some trouble figuring out how to use a more object oriented approach to panel and params. What it boils down to is that I have a dashboard, with a bunch of parameters on the left side, and several tabs on the right side.
+----+------+------+
| p1 | tab1 | tab2 |
| p2 |------+------+
| p3 | content |
| | |
| | |
| | |
+----+-------------+
Ideally I want to split this into three classes:
- The parent class which contains the parameter pane on the left and organizes the tabs.
- A class for the first tab.
- A class for the second tab.
The idea is to prevent the creation of a big god class, by putting everything into smaller parameterized classes. To get this properly working I’m currently running into two issues, which are also present in the example code below:
- I’m not really sure how to pass a parameter instance to a different class and properly register listeners there. My initial guess would be to just add it to the
param
object, for exampleself.param.other_param = other_param
in__init__
, whereother_param
is passed as an argument. However this doesn’t seem to work. - The child classes have a
view()
method that constructs the actual panel for that specific tab, however it looks like changes in this aren’t propagated/picked up by the parent class. While updating the return values of a method that has@param.depends
shows up when it’s in theParent
class, these changes don’t show up in the UI when it’s in one of theChild
classes.
It feels like I’m lacking some specific understanding of how panel/param works. I’d appreciate it if someone could point me in the right direction.
import panel as pn
import param
pn.extension()
class Child1(param.Parameterized):
# Parameter that is only relevant to this class and shouldn't
# have to be exposed to other classes.
param3 = param.ObjectSelector(default="D", objects=["D", "E", "F"])
def __init__(self, other_param):
# My goal here'd be to be able to register `other_param` so I
# can use it exactly the same as `param3` that's defined in
# this class.
#
# For example:
# self.param.other_param = other_param # ?
super().__init__()
@param.depends("param3", "other_param")
def _text(self):
# This method should also be called when `other_param` is
# changed so we can change the text
return f"Param3: {self.param3} -- placeholder"
def view(self):
# This method should construct the entire view for the tab
# that this class is responsible for.
return pn.Column(pn.Param(self.param), self._text())
class Child2(param.Parameterized):
param4 = param.ObjectSelector("4", objects=["4", "5", "6"])
param5 = param.ObjectSelector("7", objects=["7", "8", "9"])
def __init__(self, other_param1, other_param2):
# Basically all the same comments apply here as in the
# `Child1` class.
super().__init__()
@param.depends("param4", "param5", "other_param1", "other_param2")
def _text(self):
return f"{self.param4} -- {self.param5} -- placeholder -- placeholder"
def view(self):
return pn.Column(pn.Param(self.param), self._text())
class Parent(param.Parameterized):
# These two parameters are defined in the parent class as they're
# used as input for various child classes.
param1 = param.ObjectSelector(default="A", objects=["A", "B", "C"])
param2 = param.ObjectSelector(default="1", objects=["1", "2", "3"])
def __init__(self):
# Depending on what parameters the child class needs I'd like
# to be able to pass the entire parameter object, so that these
# child classes are also able to listen to changes.
self.child1 = Child1(self.param.param1)
self.child2 = Child2(self.param.param1, self.param.param2)
super().__init__()
@param.depends("param1", "param2")
def _text(self):
return f"Params: {self.param1} -- {self.param2}"
def view(self):
return pn.Row(
pn.Column(pn.Param(self.param), self._text),
pn.Tabs(
("Child1", self.child1.view()),
("Child2", self.child2.view())
)
)
Parent().view()