I am building a Panel app and I have been using Param to create a parameterized DataHolder class.
One of the features of this class I would like is for users to be able to define group_name : members_list which will be used to filter a param.DataFrame attribute of my class.
The members list is made up of a selection from the unique values on one of the dataframe columns and the group_name is user defined with text input.
I have read lots of the conversations on this topic via #598 but I still can’t decipher what is the correct way to write an updater function such that users can define and edit the available options for groups.
Its approach should work for yours as well.
I personally would go with a different approach though, wrapping the group name and the list in a param class and add a list of these objects to your class via classselector.
May sketch something up when I’m back in front of a computer,but may take a few days.
Johann
DG,
It’s raining, … so thought about this a little more.
On a high level you seem to want the user to be able to do 2 separate things via widgets on a UI:
create Groups (having a group_name and a list of column values to filter for)
select one of the Group filters defined and apply it to the DF
I tweaked your example along these lines so you can play a little bit more.
The main challenge i think you have is that although the param.Selector accepts a dict for the list of objects it does so only at init (internally it then populates .names and .objects attributes). So not easy to update later
Hence I splitted your self.groups into an internal self._groups (that holds the actual group_name/members) and a self.use_group that allows the user to select a group.
import param
import panel as pn
pn.extension()
class Data(param.Parameterized):
# dataframe and user-defined filters (empty at start)
df = param.DataFrame(precedence=-1)
_groups = param.Dict(precedence=-1, default={}, doc='Holds available Groups details')
# parameters and buttons to allow user to create filter groups
group_name = param.String()
group_members = param.ListSelector(
default=[], # empty by default, letting user to select some
objects=['A', 'B', 'C', 'D']) # unique values of the DF column
create_group = param.Action(lambda self: self._cb_create_group())
# parameter to allow user to select one of the existing Groups and apply
use_group = param.Selector(default=None, doc='Group to apply')
def _cb_create_group(self):
''' Will set a new group or overwrite an existing one.
It will also trigger a filtering of the data with the new/updated filter
'''
if not self.group_name or not self.group_members:
print('Specify group_name and group_members')
return
if self.group_name in self._groups:
print(f'group_name "{self.group_name}" already exists. Ignored')
return
print(f'Create group: {self.group_name}, {self.group_members}')
# update currently defined lists with the user input
self._groups[self.group_name] = self.group_members
# update the use_groups available list of groups and select the newly added
# one (which will trigger a rerun of the DF filtering)
# doing it in the sequence below will ensure the widget gets updated as well
# (doing value first and then objects will update the actual use_group parameter
# but not the widget :-()
self.param.use_group.objects = list(self._groups.keys())
self.use_group = self.group_name
print(f'updated use_group parameter/widget: {self.use_group}, {self.param.use_group.objects}')
@param.depends('use_group', watch=True)
def filter_df(self):
''' filters DF based on filter selected in groups '''
print(f'Filter with: {self.use_group}: {self._groups[self.use_group]}')
def view(self):
''' Show widgets to allow user to update filters and select a filter '''
return pn.Row(
pn.Param(self, parameters=['group_name', 'group_members', 'create_group'],
name='Create Filter Group'),
pn.widgets.Select.from_param(self.param.use_group))
data_ui = Data()
data_ui.view()