Display Activity#

Let’s bring our Panel apps to life with dynamic indicators and notifications, much like the rotating blades of wind turbines indicate activity.

  • Overlay loading indicators using the loading parameter.

  • Automatically overlay loading indicators on outputs from bound functions by enabling loading_indicator=True.

  • Enhance user experience with slowly loading applications by deferring load with defer_load=True.

  • Customize loading indicators with various components.

  • Provide notifications using pn.state.notifications.

Note

In the sections below, you can execute the code directly in the Panel documentation using the green run button, in a notebook cell, or in a file named app.py served with panel serve app.py --autoreload.

import panel as pn

pn.extension(notifications=True)

Overlay a Loading Indicator#

We can overlay a loading indicator on a Panel component by setting loading=True.

Run the code below:

import panel as pn

pn.extension()

pn.widgets.Button(name="Loading", loading=True, button_type="primary").servable()

It also works for composed components.

Run the code below:

import panel as pn

pn.extension()

pn.WidgetBox(
    pn.widgets.Checkbox(name="Checked", value=True),
    pn.widgets.Button(name="Submit", button_type="primary"),
    loading=True, margin=(10,10),
).servable()

Automatically Overlay a Loading Indicator#

We can automatically overlay a loading indicator on bound functions by setting loading_indicator=True in pn.panel.

Run the code below:

from time import sleep
import hvplot.pandas
import pandas as pd
import panel as pn

pn.extension()

data = pd.DataFrame([
    ('Monday', 7), ('Tuesday', 4), ('Wednesday', 9), ('Thursday', 4),
    ('Friday', 4), ('Saturday', 5), ('Sunday', 4)], columns=['Day', 'Wind Speed (m/s)']
)

button = pn.widgets.Button(name="Submit", button_type="primary")

def get_figure(running):
    if not running:
        return "Click Submit"

    sleep(2)
    return data.hvplot(x="Day", y="Wind Speed (m/s)", line_width=10, ylim=(0,10), title="Wind Speed")

bound_function = pn.bind(get_figure, button)
plot = pn.panel(bound_function, height=400, sizing_mode="stretch_width", loading_indicator=True)

pn.Column(button, plot).servable()

Exercise: Configure Global Loading Indicator#

Add two more plots to the previous example. Make sure a loading indicator is applied to all three plots when loading.

Hint

You can configure a global loading_indicator via one of:

  • pn.extension(..., loading_indicator=True)

  • pn.config.loading_indicator=True.

Solution
from time import sleep
import hvplot.pandas
import pandas as pd
import panel as pn

pn.extension(sizing_mode="stretch_width", loading_indicator=True)

button = pn.widgets.Button(name="Submit", button_type="primary")

data = pd.DataFrame([
    ('Monday', 7), ('Tuesday', 4), ('Wednesday', 9), ('Thursday', 4),
    ('Friday', 4), ('Saturday', 5), ('Sunday', 4)], columns=['Day', 'Wind Speed (m/s)']
)

def get_figure(running):
    if not running:
        return None

    sleep(2)
    return data.hvplot(x="Day", y="Wind Speed (m/s)", line_width=10, ylim=(0,10), title="Wind Speed")

bound_function = pn.bind(get_figure, button)
plot = pn.panel(bound_function, height=400)
bound_function2 = pn.bind(get_figure, button)
plot2 = pn.panel(bound_function2, height=400)
bound_function3 = pn.bind(get_figure, button)
plot3 = pn.panel(bound_function3, height=400)

pn.Column(button, plot, pn.Row(plot2, plot3), sizing_mode="stretch_width").servable()

Exercise: Defer the Load#

Hint

If the bound functions in the application are slow, it will take a while for the application to load.

We can improve the user experience by using defer_load=True. This can be used locally in pn.panel or globally via pn.extension(..., defer_load=True) or pn.config.defer_load=True.

Run the code below

from time import sleep
import hvplot.pandas
import pandas as pd
import panel as pn

pn.extension(sizing_mode="stretch_width")

button = pn.widgets.Button(name="Submit", button_type="primary")

data = pd.DataFrame([
    ('Monday', 7), ('Tuesday', 4), ('Wednesday', 9), ('Thursday', 4),
    ('Friday', 4), ('Saturday', 5), ('Sunday', 4)], columns=['Day', 'Wind Speed (m/s)']
)

def get_figure(running):
    sleep(2)
    return data.hvplot(x="Day", y="Wind Speed (m/s)", line_width=10, ylim=(0,10), title="Wind Speed")

bound_function = pn.bind(get_figure, button)
plot = pn.panel(bound_function, height=400)
bound_function2 = pn.bind(get_figure, button)
plot2 = pn.panel(bound_function2, height=400)
bound_function3 = pn.bind(get_figure, button)
plot3 = pn.panel(bound_function3, height=400)

pn.Column(button, plot, pn.Row(plot2, plot3), sizing_mode="stretch_width").servable()

Notice that it takes +6 seconds before the application loads.

Click the Submit button. Notice the missing loading indicator.

Improve the user experience by deferring the load and adding a loading indicator to each plot.

Solution
from time import sleep
import hvplot.pandas
import pandas as pd
import panel as pn

pn.extension(sizing_mode="stretch_width", defer_load=True, loading_indicator=True)

button = pn.widgets.Button(name="Submit", button_type="primary")

data = pd.DataFrame([
    ('Monday', 7), ('Tuesday', 4), ('Wednesday', 9), ('Thursday', 4),
    ('Friday', 4), ('Saturday', 5), ('Sunday', 4)], columns=['Day', 'Wind Speed (m/s)']
)

def get_figure(running):
    sleep(2)
    return data.hvplot(x="Day", y="Wind Speed (m/s)", line_width=10, ylim=(0,10), title="Wind Speed")

bound_function = pn.bind(get_figure, button)
plot = pn.panel(bound_function, height=400)
bound_function2 = pn.bind(get_figure, button)
plot2 = pn.panel(bound_function2, height=400)
bound_function3 = pn.bind(get_figure, button)
plot3 = pn.panel(bound_function3, height=400)

pn.Column(button, plot, pn.Row(plot2, plot3), sizing_mode="stretch_width").servable()

Loading Indicators#

Panel provides a selection of loading indicators, such as the LoadingSpinner and Progress indicator.

Run the code below

import panel as pn

pn.extension()

pn.Row(
    pn.Column(
        "## Loading Spinner",
        pn.Column(
            pn.indicators.LoadingSpinner(value=False, height=25, width=25),
            pn.indicators.LoadingSpinner(
                value=True, height=25, width=25, color="secondary"
            ),
        ),
    ),
    pn.Column(
        "## Progress",
        pn.Column(
            pn.indicators.Progress(
                name="Progress", value=20, width=150, bar_color="dark"
            ),
            pn.indicators.Progress(
                name="Progress", active=True, width=150, bar_color="dark"
            ),
        ),
    )
).servable()

Exercise: Loading Spinner#

Run the code below

from time import sleep

import panel as pn

pn.extension()

button = pn.widgets.Button(name="Submit", button_type="primary")

running_indicator = pn.indicators.LoadingSpinner(
    value=False, height=25, width=25, color="secondary", visible=True
)


def predict(running):
    if not running:
        return "Click Submit"

    sleep(2)
    return "It's a Wind Turbine!"


prediction = pn.bind(predict, button)

pn.Column(
    button,
    running_indicator,
    prediction,
).servable()

Notice the poor user experience.

Now fix the issues:

  • Show LoadingSpinner only when the prediction is running.

  • Make the LoadingSpinner spin when the prediction is running.

Solutions
from time import sleep

import panel as pn

pn.extension()

button = pn.widgets.Button(name="Submit", button_type="primary")

running_indicator = pn.indicators.LoadingSpinner(
    value=False, height=25, width=25, color="secondary", visible=False
)


def predict(running):
    if not running:
        return "Click Submit"

    running_indicator.value = running_indicator.visible = True

    sleep(2)
    running_indicator.value = running_indicator.visible = False
    return "It's a Wind Turbine!"


prediction = pn.bind(predict, button)

pn.Column(button, running_indicator, prediction).servable()
from time import sleep

import panel as pn

pn.extension()

is_running = pn.rx(False)

button = pn.widgets.Button(
    name="Submit", button_type="primary", disabled=is_running, loading=is_running
)

running_indicator = pn.indicators.LoadingSpinner(
    value=is_running, height=25, width=25, color="secondary", visible=is_running
)


def predict(running):

    if not running:
        return "Click Submit"

    is_running.rx.value=True
    sleep(2)
    is_running.rx.value=False
    return "It's a Wind Turbine!"


prediction = pn.bind(predict, button)

pn.Column(button, running_indicator, prediction).servable()

Notifications#

Let’s display an info notification.

import panel as pn

pn.extension(notifications=True)

def send_notification(event):
    pn.state.notifications.info("This is a notification", duration=3000)

pn.widgets.Button(name="Send", on_click=send_notification).servable()

Try clicking the Button. You should see a notification pop up in the lower left corner of the app.

Note

The code refers to:

  • pn.extension(notifications=True): We need to set notifications=True to support notifications in Panel.

  • pn.state.notifications.info(..., duration=3000): We send an info notification. The notification is shown for 3000 milliseconds.

Try changing pn.state.notifications.info to pn.state.notifications.warning. Then click the Button. What happens?

Try changing duration=3000 to duration=0. Then click the Button. What happens?

See the Notifications reference guide for more detail.

Recap#

We’ve explored how to show activity with indicators, much like the rotating blades of wind turbines:

  • Overlay a loading indicator with the loading parameter.

  • Automatically overlay a loading indicator on output from bound functions by setting loading_indicator=True.

  • Improve the user experience of slowly loading applications by setting defer_load=True.

  • Customize the loading indication with various components.

  • Provide notifications using pn.state.notifications.

Resources#

How-to#

Reference Guides#