I’m trying to create tabbed pane with Tabulator and widgets that will be filters for the table.
Currently this is what I have:
import io
import pandas as pd
import panel as pn
import param
editors = {
'index': {'type': 'number'},
'entity': {'type': 'autocomplete', 'values': True},
'key': {'type': 'autocomplete', 'values': True},
'value': {'type': 'input'},
'special': {'type': 'input'},
'issue': {'type': 'input'},
}
COLUMNS = {
'General': ['index', 'description', 'suite', 'enable', 'issue', 'tech', 'freq', 'bw', 'duplex', 'carriers'],
'Scenarios': ['index', 'entity', 'key', 'value', 'special', 'issue'],
'Verdicts': ['index', 'channel', 'section', 'enable', 'key', 'metric', 'value', 'special', 'issue']
}
pn.extension()
pn.config.sizing_mode = 'stretch_width'
def contains_filter(df, pattern, column):
if not pattern:
return df
return df[df[column].str.contains(pattern)]
class FilterBase(param.Parameterized):
index = param.ObjectSelector()
common_index = param.Boolean(default=True)
def __init__(self, **params):
self.param.index.default = pn.widgets.MultiChoice(name='Index')
super().__init__(**params)
def view(self):
return self.index
class GeneralFilter(FilterBase):
index = param.ObjectSelector()
def __init__(self, **params):
super().__init__(**params)
class ScenariosFilter(FilterBase):
index = param.ObjectSelector()
entity = param.ObjectSelector()
key = param.String()
def __init__(self, **params):
self.param.entity.default = pn.widgets.MultiChoice(name='Entity')
super().__init__(**params)
def view(self):
return pn.Column(
super(ScenariosFilter, self).view(),
self.entity,
self.param.key,
)
class VerdictsFilter(FilterBase):
index = param.ObjectSelector()
def __init__(self, **params):
super().__init__(**params)
class InputDataBase(param.Parameterized):
tabulator = param.Parameter()
def __init__(self, group_by_index, name, filter_obj, **params):
self.param.tabulator.default = pn.widgets.Tabulator(
value=pd.DataFrame(columns=COLUMNS[name]),
selectable=False, # 'checkbox',
show_index=False,
groupby=['index'] if group_by_index else [],
# layout='fit_data_stretch',
# width=1000,
# height=800,
configuration={
'groupStartOpen': [False],
'groupToggleElement': "header",
},
# editors=editors,
theme='site',
sizing_mode="stretch_both"
)
super().__init__(**params)
self._filter = filter_obj
self._name = name
self._add_filters()
def _add_filters(self):
self.tabulator.add_filter(self._filter.index, column='index')
@pn.depends("tabulator.value", watch=True)
def update_filters(self):
self._filter.index.options = list(self.tabulator.value['index'].unique())
def view(self):
return pn.Row(
pn.Column(self.tabulator, width=1400, height=880),
pn.Card(self._filter.view(), title='Filters'),
name=self._name
)
class GeneralData(InputDataBase):
tabulator = param.Parameter()
def __init__(self, **params):
super(GeneralData, self).__init__(
group_by_index=False,
name='General',
filter_obj=GeneralFilter(),
**params
)
def _add_filters(self):
super(GeneralData, self)._add_filters()
class ScenariosData(InputDataBase):
tabulator = param.Parameter()
def __init__(self, **params):
super(ScenariosData, self).__init__(
group_by_index=True,
name='Scenarios',
filter_obj=ScenariosFilter(),
**params
)
def _add_filters(self):
super(ScenariosData, self)._add_filters()
self.tabulator.add_filter(self._filter.entity, column='entity')
self.tabulator.add_filter(pn.bind(contains_filter, pattern=self._filter.param.key, column='key'))
@pn.depends("tabulator.value", watch=True)
def update_filters(self):
super(ScenariosData, self).update_filters()
self._filter.entity.options = list(self.tabulator.value['entity'].unique())
class VerdictsData(InputDataBase):
tabulator = param.Parameter()
def __init__(self, **params):
super(VerdictsData, self).__init__(
group_by_index=True,
name='Verdicts',
filter_obj=VerdictsFilter(),
**params
)
def _add_filters(self):
super(VerdictsData, self)._add_filters()
class DataTemplate(param.Parameterized):
load_data = param.Action(label="Load Data")
file_input = param.Parameter()
def __init__(self, **params):
self.param.file_input.default = pn.widgets.FileInput(accept='.csv', multiple=True)
super().__init__(**params)
self._input_data = {
'general.csv': GeneralData(),
'scenarios.csv': ScenariosData(),
'verdicts.csv': VerdictsData()
}
self.load_data = self._load_data
self.tabs_pane = pn.Tabs(*[v.view() for v in self._input_data.values()])
self.template = pn.template.MaterialTemplate(title='Simplenario', theme=pn.template.DarkTheme)
self.template.sidebar.append(pn.Column('## Data Load', self.file_input, self.param.load_data, width=270))
self.template.main.append(self.tabs_pane)
def _load_data(self, event):
values = self.file_input.value
files = self.file_input.filename
if values is not None and files is not None:
self.tabs_pane.loading = True
for file, value in zip(files, values):
string_io = io.StringIO(value.decode("utf8"))
self._input_data[file].tabulator.value = pd.read_csv(string_io, keep_default_na=False, index_col=False)
self.tabs_pane.loading = False
def serve(self):
self.template.servable()
data_template = DataTemplate()
data_template.serve()
It’s working but I feel that I’m doing something wrong.
I think I need to construct FilterBase
class differently, but I tried many options to create widgets from params like “from_param” and param.ClassSelector
but still I can’t figure out how to make “FilterBase view” method as pn.Param(self)
.
One more thing is when I tried to create pn.Param(self)
and using widgets
attribute, I can’t make it work with Tabulator’s add_filter
method because it need to be a widget.
This is how it looks:
Thanks in advance.