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
.
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 setnotifications=True
to support notifications in Panel.pn.state.notifications.info(..., duration=3000)
: We send aninfo
notification. The notification is shown for3000
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
.