Tips and Tricks

Here, I will share random tips and tricks, but note the following are not officially endorsed by the maintainers; they’re simply things that worked the best for me.

The tips can be read chronologically, or jumped around, but I do try to put the less complex ones upfront and build up to the complex ones.

Litter prints and check both your terminal + browser console for debugging

If you are on using panel serve, print statements get passed to either terminal (for panel serve) or browser console (for Jupyter after the first output). To get to the browser console on Chrome (and probably other browsers): Mouse-Right-Click -> Inspect -> Console tab, or simply F12 -> Console.

[1]:
import panel as pn

pn.extension()

button = pn.widgets.Button(name="Click")


def update(event):
    num_clicks = event.obj.clicks
    print(f"I was clicked {num_clicks} times!")


click_watch = button.param.watch(update, "clicks")
button
[1]:

It should look something like below.

[2]:
import panel as pn
pn.extension()

pn.pane.JPG("console.jpg")
[2]:

Use apply.opts and param.value for interactive updates

This is one way of doing it…

[3]:
import panel as pn
import holoviews as hv

pn.extension()


def update(alpha):
    return hv.Curve(([0, 1, 2], [3, 4, 5])).opts(alpha=alpha)


widget = pn.widgets.FloatSlider(value=1)
plot = pn.bind(update, widget.param.value)
col = pn.Column(widget, plot)

But actually there’s no need for a chunky function; take advantage of apply.opts!

[4]:
import panel as pn
import holoviews as hv

pn.extension()

widget = pn.widgets.FloatSlider(value=1)
plot = hv.Curve(([0, 1, 2], [3, 4, 5])).apply.opts(alpha=widget.param.value)
col = pn.Column(widget, plot)

Just remember the apply and param is important; without those the interactivity won’t work!

[5]:
import panel as pn
import holoviews as hv

pn.extension()

widget = pn.widgets.FloatSlider(value=1)
plot = hv.Curve(([0, 1, 2], [3, 4, 5])).opts(alpha=widget.value)  # not interactive
col = pn.Column(widget, plot)
[6]:
import panel as pn
pn.extension()
pn.pane.GIF("use_apply.opts_and_param.value_for_interactive_updates.gif")
[6]:

Just to be clear, this works with other param parameters like param.start, param.stop, param.options, etc!

Use set_param for simultaneous updates and duplicate computations

This is a poor way of updating multiple parameters simultaneously because select_update gets triggered twice.

The reason is because, when options gets updated from the click, the new options are out of range from the original value so value is forced to update, triggering select_update the first time, but then value later gets manually updated to the last option, triggering select_update again.

[7]:
import time
import panel as pn

pn.extension()

button = pn.widgets.Button(name="Click")
select = pn.widgets.Select(options=[0])
text = pn.widgets.StaticText(value=select.value)


def click_update(event):
    options = [button.clicks + 1, button.clicks + 2]
    select.options = options
    select.value = options[-1]


def select_update(event):
    time.sleep(1)  # mimic expensive computation
    text.value = str(event.new)
    print("I was updated!")


click_watch = button.param.watch(click_update, "clicks")
select_watch = select.param.watch(select_update, "value")

col = pn.Column(button, select, text)
[8]:
import panel as pn
pn.extension()
pn.pane.GIF("use_set_param_for_simultaneous_updates_and_duplicate_computations_before.gif")
[8]:

Use set_param to prevent the duplicate calls!

[9]:
import time
import panel as pn

pn.extension()

button = pn.widgets.Button(name="Click")
select = pn.widgets.Select(options=[0])
text = pn.widgets.StaticText(value=select.value)


def click_update(event):
    select.param.set_param(
        options=[button.clicks + 1, button.clicks + 2], value=button.clicks + 2
    )


def select_update(event):
    time.sleep(1)  # mimic expensive computation
    text.value = str(event.new)
    print("I was updated!")


click_watch = button.param.watch(click_update, "clicks")
select_watch = select.param.watch(select_update, "value")

pn.Column(button, select, text)
[9]:
[10]:
import panel as pn
pn.extension()
pn.pane.GIF("use_set_param_for_simultaneous_updates_and_duplicate_computations_after.gif")
[10]:

Just be sure to prepend the param namespace or else you’ll get a warning!

[11]:
import panel as pn

pn.extension()

select = pn.widgets.Select(options=[0])
select.set_param(options=[0, 1], value=1)
WARNING:param.Select: Use method 'set_param' via param namespace

Use value_throttled for updates on mouse-up and expensive computations

If you have a slider, you may want to bind updates to value_throttled instead of value so that the plot updates on mouse-up, rather than every step to the desired value. This is especially valuable if it takes long to update!

[12]:
import time
import numpy as np
import panel as pn
import holoviews as hv

pn.extension()


def update(func):
    y = getattr(np, func)([0, 1, 2])
    time.sleep(1)
    return hv.Curve(([0, 1, 2], y)).opts(title=func)


widget = pn.widgets.DiscreteSlider(value="sin", options=["sin", "cos", "tan"])
plot = pn.bind(update, widget.param.value_throttled)
col = pn.Column(widget, plot)
[13]:
import panel as pn
pn.extension()
pn.pane.GIF("use_value_throttled_for_updates_on_mouse-up_and_expensive_computations.gif")
[13]:

Add a built-in loading indicator for expensive computations

Panel has a loading_indicator built-in for panel objects; great for keeping the app look functional!

[14]:
import time
import numpy as np
import panel as pn
import holoviews as hv

pn.extension()
pn.param.ParamMethod.loading_indicator = True


def update(func):
    y = getattr(np, func)([0, 1, 2])
    time.sleep(1)
    return hv.Curve(([0, 1, 2], y))


widget = pn.widgets.DiscreteSlider(value="sin", options=["sin", "cos", "tan"])
plot = pn.bind(update, widget.param.value_throttled)
col = pn.Column(widget, plot)
[15]:
import panel as pn
pn.extension()
pn.pane.GIF("add_a_built-in_loading_indicator_for_expensive_computations.gif")
[15]: