Add Interactivity with pn.bind
Both Streamlit and Panel are reactive frameworks that respond dynamically to user interactions within your application. However, their operational mechanics differ significantly:
In Streamlit:
In Panel:
The script runs once when a user visits the page.
Only specific, bound functions are rerun based on user interactions.
Panel's interactivity architecture enables you to develop and maintain larger and more complex apps efficiently.
Introduction
Panel's pn.bind
function allows you to bind functions to widgets, creating what we refer to as bound functions.
To use pn.bind
, follow these steps:
def my_func(value):
return ...
slider = pn.widgets.IntSlider(...)
my_bound_func = pn.bind(my_func, value=slider)
pn.Column(slider, my_bound_func)
Once you've set up your functions and widgets as described, they will automatically update in response to user interactions.
Migration Steps
Consider the following strategies for effective migration:
Transition your core business logic into functions. This includes data loading, transformations, calculations, plot creations, complex computations like galaxy mass estimations, model training, and inference tasks.
Enhance interactivity by using pn.bind
to associate your functions with widgets.
Use visual feedback to indicate activity, as detailed in the Show Activity Section.
Examples
Basic Interactivity Example
Learn how to adapt your code for a straightforward result update that occurs once the execution completes.
Streamlit Basic Interactivity Example
import matplotlib.pyplot as plt
import numpy as np
import streamlit as st
from matplotlib.figure import Figure
data = np.random.normal(1, 1, size=100)
fig = Figure(figsize=(8,4))
ax = fig.subplots()
bins = st.slider(value=20, min_value=10, max_value=30, step=1, label="Bins")
ax.hist(data, bins=bins)
st.pyplot(fig)

In Streamlit, the entire script is rerun from top to bottom when the bins
slider is adjusted.
Panel Basic Interactivity Example
import panel as pn
import numpy as np
from matplotlib.figure import Figure
pn.extension(sizing_mode="stretch_width", template="bootstrap")
def plot(data, bins):
fig = Figure(figsize=(8,4))
ax = fig.subplots()
ax.hist(data, bins=bins)
return fig
data = np.random.normal(1, 1, size=100)
bins_input = pn.widgets.IntSlider(value=20, start=10, end=30, step=1, name="Bins")
bplot = pn.bind(plot, data=data, bins=bins_input)
pn.Column(bins_input, bplot).servable()

Panel updates more swiftly and smoothly by only rerunning the plot
function when the bins
slider is changed.
Multiple Updates Example
Discover how to handle scenarios where a function needs to update the UI multiple times during its execution.
Streamlit Multiple Updates Example
import random
import time
import streamlit as st
def calculation_a():
time.sleep(1.5)
return random.randint(0, 100)
def calculation_b():
time.sleep(3.5)
return random.randint(-100, 0)
st.write("# Calculation Runner")
option = st.radio("Which calculation would you like to perform?", ("A", "B"))
st.write("You chose: ", option)
if st.button("Press to run calculation"):
with st.spinner("Running... Please wait!"):
time_start = time.perf_counter()
result = calculation_a() if option == "A" else calculation_b()
time_end = time.perf_counter()
st.write(f"""
Done!
Result: {result}
The function took {time_end - time_start:1.1f} seconds to complete
"""
)
else:
st.write(f"Calculation {option} did not run yet")

Panel Multiple Updates Example
In Panel, utilize a generator function to provide incremental updates during the function's execution.
import time
import random
import panel as pn
pn.extension(sizing_mode="stretch_width", template="bootstrap")
pn.state.template.param.update(site="Panel", title="Calculation Runner")
def notify_choice(calculation):
return f"You chose: {calculation}"
def calculation_a():
time.sleep(2)
return random.randint(0, 100)
def calculation_b():
time.sleep(1)
return random.randint(0, 100)
def run_calculation(running, calculation):
if not running:
yield "Calculation did not run yet"
return
calc = calculation_a if calculation == "A" else calculation_b
with run_input.param.update(loading=True):
yield pn.indicators.LoadingSpinner(
value=True, size=50, name='Running... Please Wait!'
)
time_start = time.perf_counter()
result = calc()
time_end = time.perf_counter()
yield f"""
Done!
Result: {result}
The function took {time_end - time_start:1.1f} seconds to complete
"""
calculation_input = pn.widgets.RadioBoxGroup(name="Calculation", options=["A", "B"])
run_input = pn.widgets.Button(
name="Press to run calculation",
icon="caret-right",
button_type="primary",
width=250,
)
pn.Column(
"Which calculation would you like to perform?",
calculation_input,
pn.bind(notify_choice, calculation_input),
run_input,
pn.bind(run_calculation, run_input, calculation_input),
).servable()

The use of pn.indicators.LoadingSpinner
effectively signals ongoing activity during the calculation process.
Panel Multiple Updates Alternative Indicator Example
Instead of using a visual indicator, another approach involves modifying the .disabled
and .loading
parameters of the calculation_input
and run_input
to reflect the current state of the operation dynamically.
import time
import random
import panel as pn
pn.extension(template="bootstrap")
pn.state.template.param.update(site="Panel", title="Calculation Runner")
def notify_choice(calculation):
return f"You chose: {calculation}"
def calculation_a():
time.sleep(2)
return random.randint(0, 100)
def calculation_b():
time.sleep(1)
return random.randint(0, 100)
def run_calculation(running, calculation):
if not running:
return "Calculation did not run yet"
calc = calculation_a if calculation == "A" else calculation_b
with run_input.param.update(loading=True), calculation_input.param.update(disabled=True):
time_start = time.perf_counter()
result = calc()
time_end = time.perf_counter()
return f"""
Done!
Result: {result}
The function took {time_end - time_start:1.1f} seconds to complete.
"""
calculation_input = pn.widgets.RadioBoxGroup(name="Calculation", options=["A", "B"])
run_input = pn.widgets.Button(
name="Press to run calculation",
icon="caret-right",
button_type="primary",
width=250,
)
pn.Column(
"Which calculation would you like to perform?",
calculation_input,
pn.bind(notify_choice, calculation_input),
run_input,
pn.bind(run_calculation, run_input, calculation_input),
).servable()

Multiple Results Example
In some scenarios, such as with Large (AI) Language Models, you may need to output multiple results sequentially as they become available.
Streamlit Multiple Results Example
import random
import time
import streamlit as st
run = st.button("Run model")
def model():
time.sleep(1)
return random.randint(0, 100)
if not run:
st.write("The model has not run yet")
else:
with st.spinner("Running..."):
for i in range(10):
result = model()
st.write(f"Result {i}: {result}")

Panel Multiple Results Example
Panel utilizes a generator function to dynamically display multiple results from a single execution process as they become ready.
import random
import time
import panel as pn
pn.extension(sizing_mode="stretch_width", template="bootstrap")
def model():
time.sleep(1)
return random.randint(0, 100)
def results(running):
if not running:
yield layout
return
layout.clear()
for i in range(10):
layout.append(loading)
yield layout
result = model()
result = pn.panel(f"Result {i}: {result}")
layout[-1] = result
yield layout
run_input = pn.widgets.Button(name="Run model")
loading = pn.widgets.LoadingSpinner(value=True, size=50, name="Running... Please Wait!")
layout = pn.Column("Calculation did not run yet")
pn.Column(run_input, pn.bind(results, run_input)).servable()
