Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
holoviz
GitHub Repository: holoviz/panel
Path: blob/main/doc/tutorials/basic/indicators_activity.md
2013 views

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 --dev. :::

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.

:::

:::{dropdown} 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.

:::{dropdown} 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.

:::::{dropdown} Solutions

::::{tab-set}

:::{tab-item} Basic :sync: basic

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

:::

:::{tab-item} Reactive Expressions :sync: reactive-expressions

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