How to use param to return a figure with control widgets

Hi

I am recently learning param, I am trying to writing a class when i call it, it return a control widget with a bar chart with interactive widget value to change the bar value.

The minimize panel code is here, Is anyone could please help me to change this code to a param class

import panel as pn
pn.extension()
import holoviews as hv
hv.extension('bokeh')

widget = pn.widgets.RadioButtonGroup(
    options = [10,100,1000,10000],
    button_type='primary'
)

@pn.depends(widget)
def Bar(value):
    return hv.Bars([('a',value)])

pn.Row(widget,Bar)

Kind regards
Victor

import param
import panel as pn
pn.extension()
import holoviews as hv
hv.extension('bokeh')

class App(param.Parameterized):
    value = param.Selector([10,100,1000,10000])
    
    @param.depends('value')
    def bar(self):
        return hv.Bars([('a',self.value)])

app = App()

pn.Row(
    pn.Param(
        app.param,
        widgets = {
            'value': {
                'widget_type' : pn.widgets.RadioButtonGroup,
                'button_type' :'primary'
            }
        }
    ),
    app.bar
)
2 Likes

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()
1 Like

I actually generally try to create a parameterized base class without dependency on panel so if necessary I can use it in other contexts outside of a Panel dashboard. That said, another Dane once told me that brevity is the soul of wit…

2 Likes

You are totally right @riziles . An example of separation of Param and Panel would be the first example in a tutorial on Parameterized classes and components. Thanks.

Honestly, I just couldn’t resist the Hamlet joke. Your example + walkthrough is fantastic. Appreciate your help as always.

1 Like

@riziles @Marc @mrileysamc
Thanks for your reply and demos.
That help me a lot :grinning:

1 Like

I updated my “blog post” with a standalone Parameterized component example.

2 Likes