Hi @CongTang and @riziles
This can be done in so many ways depending on preferences and use cases. Here are my thoughts
Parameterized Component
Here we create a standalone component that we use Panel to build a UI for.
import holoviews as hv
import param
class MyComponent(param.Parameterized):
value = param.Selector(objects=[10,100,1000,10000])
@param.depends("value")
def plot(self):
return hv.Bars([('a',self.value)])
import panel as pn
pn.extension()
hv.extension('bokeh')
def view(component: MyComponent):
widget = pn.widgets.RadioButtonGroup.from_param(component.param.value, button_type="primary")
return pn.Row(widget,component.plot)
component = MyComponent(value=100)
view(component).servable()
Panel Parameterized Component
The way I think is to analyze my app and determine the inputs (value
) and outputs (plot
). Then I layout the component in a function (view
)
import panel as pn
import holoviews as hv
import param
pn.extension()
hv.extension('bokeh')
class MyComponent(param.Parameterized):
value = param.Selector(objects=[10,100,1000,10000])
@pn.depends("value")
def plot(self):
return hv.Bars([('a',self.value)])
def view(self):
widget = pn.widgets.RadioButtonGroup.from_param(self.param.value, button_type="primary")
return pn.Row(widget,self.plot)
MyComponent(value=100).view().servable()
Panel Viewer Component
If you want to make this a reuseable component that you and your users can use directly in a notebook cell or data app, you might want to get rid of having to specify the .view()
when using the component. This can be done by by implementing a custom Viewer component.
import panel as pn
import holoviews as hv
import param
pn.extension()
hv.extension('bokeh')
class MyComponent(pn.viewable.Viewer):
value = param.Selector(objects=[10,100,1000,10000])
def __init__(self, **params):
super().__init__(**params)
self._create_view()
@pn.depends("value")
def plot(self):
return hv.Bars([('a',self.value)])
@pn.depends(on_init=True)
def _create_view(self):
widget = pn.widgets.RadioButtonGroup.from_param(self.param.value, button_type="primary")
self._view = pn.Row(widget,self.plot)
def __panel__(self):
return self._view
pn.panel(MyComponent(value=100)).servable()
Panel Input-Data-Plot Viewer Component
Over time I’ve learned that it is nice to seperate out inputs, data and plots because the app might have to display data in several ways or he/ she wants to be able to downloead it etc. This leads to the below refinement of the Viewer
component.
Input Change → Data is extracted and transformed → Plots are updated
import panel as pn
import holoviews as hv
import param
pn.extension()
hv.extension('bokeh')
class MyComponent(pn.viewable.Viewer):
value = param.Selector(objects=[10,100,1000,10000])
data = param.Parameter()
def __init__(self, **params):
super().__init__(**params)
self._create_view()
@pn.depends("value", watch=True, on_init=True)
def _update_data(self):
self.data = [('a',self.value)]
@pn.depends("data")
def plot(self):
return hv.Bars(self.data)
def _create_view(self):
widget = pn.widgets.RadioButtonGroup.from_param(self.param.value, button_type="primary")
self._view = pn.Row(widget,self.plot)
def __panel__(self):
return self._view
pn.panel(MyComponent(value=100)).servable()