Reactive Parameters#
Welcome to the world of reactive parameters in Panel! In this section, we’ll delve into the powerful capabilities offered by Parameters in the HoloViz ecosystem.
Understanding Parameters#
Panel and other projects in the HoloViz ecosystem build on the foundation provided by Param. Param offers a robust framework for adding validation, documentation, and interactivity to projects. Similar to projects like Pydantic in some aspects, but Param focuses on providing APIs that simplify the expression of complex dependencies, reactivity, and UI interactions.
In this section, we won’t delve into the inner workings of Param but rather focus on understanding the fundamentals. By the end of this section, we will:
Understand the difference between the Parameter value and the Parameter object.
Know how to use Parameter objects, bound functions, and reactive expressions as proxies or references for their current value.
import panel as pn
pn.extension('tabulator')
Exploring Parameters#
Parameters serve as objects expressing the semantics of a value of an attribute on an object, encompassing not only Python types but also broader semantics.
Let’s define a simple Parameterized
class with a value
Parameter:
import param
class Text(param.Parameterized):
value = param.String(default='', allow_None=True, doc="The text value")
Now, let’s create an instance and examine its current value:
text = Text(value="Are you a Python programmer? If so, you need Param!")
text.value
We can also inspect its Parameter instance:
text.param.value
The Utility of Parameters#
Parameters act as references to underlying values, making them invaluable for enhancing UI interactivity.
Parameters imbue validation, documentation, and interactivity into Panel, with most Panel components built as Parameterized
classes with Parameters.
For instance, consider the value
Parameter of the Panel TextInput
widget:
class TextInput(pn.widgets.Widget):
...
value = param.String(default='', allow_None=True, doc="""
Initial or entered text value updated when <enter> key is pressed.""")
To explore all parameters on a Parameterized
class, we inspect the .param
namespace:
pn.widgets.TextInput.param
Utilizing Parameters#
Let’s now delve into practical usage scenarios.
Accessing Parameter Values#
Consider working with a TextInput
widget:
text_input = pn.widgets.TextInput(value='A string!')
text_input
We can access its current value:
text_input.value
Additionally, we can access the Parameter
instance, acting as a proxy or reference for the value:
text_input.param.value
Setting Parameter Values#
We can set it to a new value:
text_input.value = 'Hello World!'
For setting multiple parameter values, use the .param.update
method:
text_input.param.update(value='Updated!', width=85);
Or use it as a context manager for temporary value setting:
from time import sleep
with text_input.param.update(value='Temporary'):
sleep(1)
Validation#
Parameters perform validation, raising errors for invalid assignments:
import traceback as tb
try:
text_input.value = 3.14
except ValueError as e:
tb.print_exc()
Traceback (most recent call last): File "<ast>", line 4, in <module> File "/Users/runner/work/panel/panel/.pixi/envs/docs/lib/python3.11/site-packages/param/parameterized.py", line 528, in _f instance_param.__set__(obj, val) File "/Users/runner/work/panel/panel/.pixi/envs/docs/lib/python3.11/site-packages/param/parameterized.py", line 530, in _f return f(self, obj, val) ^^^^^^^^^^^^^^^^^ File "/Users/runner/work/panel/panel/.pixi/envs/docs/lib/python3.11/site-packages/param/parameterized.py", line 1498, in __set__ self._validate(val) File "/Users/runner/work/panel/panel/.pixi/envs/docs/lib/python3.11/site-packages/param/parameterized.py", line 1648, in _validate self._validate_value(val, self.allow_None) File "/Users/runner/work/panel/panel/.pixi/envs/docs/lib/python3.11/site-packages/param/parameterized.py", line 1642, in _validate_value raise ValueError( ValueError: String parameter 'TextInput.value' only takes a string value, not value of <class 'float'>.
Parameters as References#
Parameters serve as proxies for underlying Parameter values, facilitating interactive declarations.
Consider a simple example using the TextInput
widget:
import panel as pn
pn.extension()
text_in = pn.widgets.TextInput(value='Hello world!')
text_out = pn.pane.Markdown(text_in.param.value)
pn.Column(text_in, text_out).servable()
Observe how changes in TextInput
are automatically reflected in the Markdown output.
This also works when using it for other parameters, e.g. we can add a switch to toggle the visibility of some component:
visible = pn.widgets.Switch(value=True)
pn.Row(visible, pn.pane.Markdown('Hello World!', visible=visible)).servable()
Many parameters that accept a container such as a dictionary
or list
can also resolve references when they are nested, e.g. if we declare a styles
dictionary one of the values can be a widget:
color = pn.widgets.ColorPicker(value='red')
md = pn.pane.Markdown('Some Text!', styles={'color': color})
pn.Row(color, md).servable()
Notice that we passed in the widget object directly instead of the .param.value
. This is possible because widgets are treated as a proxy of their value
parameter just like a Parameter
is treated as a proxy for current value.
Transforming Parameters#
Transforming parameters allows for complex value processing pipelines, offering immense flexibility.
For instance, we can create a reactive format string, filling in values based on widget input:
import panel as pn
pn.extension()
text_input = pn.widgets.TextInput(value='World')
text = pn.rx('**Hello {}!**').format(text_input)
md = pn.pane.Markdown(text)
pn.Row(text_input, md).servable()
Similarly, we can make a DataFrame reactive:
import pandas as pd
import panel as pn
pn.extension()
data_url = 'https://assets.holoviz.org/panel/tutorials/turbines.csv.gz'
df = pn.cache(pd.read_csv)(data_url)
dfrx = pn.rx(df)
dfrx.head(2)
Please explore the potential by replacing 2
with an instance of an IntSlider
.
Solution
import pandas as pd
import panel as pn
pn.extension()
data_url = 'https://assets.holoviz.org/panel/tutorials/turbines.csv.gz'
df = pn.cache(pd.read_csv)(data_url)
dfrx = pn.rx(df)
slider = pn.widgets.IntSlider(value=2, start=1, end=10)
pn.panel(dfrx.head(slider)).servable()
Now let’s get a little more complex and write a whole pipeline that selects the desired columns, samples a number of random rows, and then applies some custom styling highlighting the rows with the highest value:
import pandas as pd
import panel as pn
import numpy as np
pn.extension()
data_url = 'https://assets.holoviz.org/panel/tutorials/turbines.csv.gz'
df = pn.cache(pd.read_csv)(data_url)
dfrx = pn.rx(df)
cols = pn.widgets.MultiChoice(
options=df.columns.to_list(), value=['p_name', 't_state', 't_county', 'p_year', 'p_cap'], height=300
)
nrows = pn.widgets.IntSlider(start=5, end=20, step=5, value=15, name='Samples')
style = pn.rx('color: white; background-color: {color}')
color = pn.widgets.ColorPicker(value='darkblue', name='Highlight color')
def highlight_max(s, props=''):
if s.dtype.kind not in 'f':
return np.full_like(s, False)
return np.where(s == np.nanmax(s.values), props, '')
styled_df = dfrx[cols].sample(nrows).style.apply(highlight_max, props=style.format(color=color), axis=0)
pn.pane.DataFrame(styled_df).servable()
As you can see, the Pandas code is identical to what you might have written if you were working with a regular DataFrame. However, now you can use widgets and even complex expressions as inputs.
Try replacing pn.pane.DataFrame
with pn.panel
to display an interactive component with widgets.
Solution
import pandas as pd
import panel as pn
import numpy as np
pn.extension()
data_url = 'https://assets.holoviz.org/panel/tutorials/turbines.csv.gz'
df = pn.cache(pd.read_csv)(data_url)
dfrx = pn.rx(df)
cols = pn.widgets.MultiChoice(
options=df.columns.to_list(), value=['p_name', 't_state', 't_county', 'p_year', 'p_cap'], height=300
)
nrows = pn.widgets.IntSlider(start=5, end=20, step=5, value=15, name='Samples')
style = pn.rx('color: white; background-color: {color}')
color = pn.widgets.ColorPicker(value='darkblue', name='Highlight color')
def highlight_max(s, props=''):
if s.dtype.kind not in 'f':
return np.full_like(s, False)
return np.where(s == np.nanmax(s.values), props, '')
styled_df = dfrx[cols].sample(nrows).style.apply(highlight_max, props=style.format(color=color), axis=0)
pn.panel(styled_df).servable()
Exercise: Scale the Font Size#
Write a small app where you can scale the font-size
of a Markdown
pane with another widget, e.g. an IntSlider
. The font-size
can be set using the styles
parameter.
Hint
The
styles
parameter only accepts dictionaries of strings butIntSlider
returnsint
types.The
font-size
value should be a string value in pixels, eg."15px"
is a validfont-size
.
Solution 1: Reactive String Formatting
import panel as pn
pn.extension()
intslider = pn.widgets.IntSlider(value=10, start=5, end=100, step=10, name="Font Size")
styles_rx = {"font-size": pn.rx("{value}px").format(value=intslider)}
markdown = pn.pane.Markdown("We love dataviz!", styles=styles_rx)
pn.Column(intslider, markdown).servable()
Solution 2: Reactive Function
import panel as pn
pn.extension()
intslider = pn.widgets.IntSlider(value=10, start=5, end=100, step=10, name="Font Size")
def styles(font_size):
return {"font-size": f"{font_size}px"}
styles_rx = pn.rx(styles)(intslider)
markdown = pn.pane.Markdown("We love dataviz!", styles=styles_rx)
pn.Column(intslider, markdown).servable()
Solution 3: Bound Function
import panel as pn
pn.extension()
intslider = pn.widgets.IntSlider(value=10, start=5, end=100, step=10, name="Font Size")
def styles(font_size):
return {"font-size": f"{font_size}px"}
styles_bn = pn.bind(styles, font_size=intslider)
markdown = pn.pane.Markdown("We love dataviz!", styles=styles_bn)
pn.Column(intslider, markdown).servable()
Note
pn.bind
is the predecessor of pn.rx
. We recommend using pn.rx
over pn.bind
as it’s much more flexible and efficient. We include this example because you will find lots of examples in the Panel documentation and in the Panel community using pn.bind
.
Recommended Reading#
To harness the full potential of Param and Reactive Parameters in Panel, we recommend studying the Param User Guide and the ReactiveExpr
reference guide.
Recap#
Param serves as the backbone of Panel, providing a robust framework for adding validation, documentation, and interactivity. By grasping the fundamentals discussed here, you’ll be well-equipped to leverage Panel’s interactivity and reactivity effectively.
Now, we should be able to:
Clearly understand the distinction between Parameter values and Parameter objects.
Use Parameter objects, bound functions, and expressions as proxies or references for their current value.