Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Finalize Basic Tutorials #6425

Merged
merged 26 commits into from
Mar 19, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
31ed209
content pn_bind
MarcSkovMadsen Mar 3, 2024
6aefbd7
add more references
MarcSkovMadsen Mar 3, 2024
5858f0a
update formulation
MarcSkovMadsen Mar 3, 2024
58deff2
add pn_rx
MarcSkovMadsen Mar 3, 2024
e166fa0
add note
MarcSkovMadsen Mar 3, 2024
64ca54d
fix
MarcSkovMadsen Mar 3, 2024
963212c
use .watch
MarcSkovMadsen Mar 3, 2024
0d6b122
review feedback
MarcSkovMadsen Mar 3, 2024
000febb
try to fix video on iphone
MarcSkovMadsen Mar 4, 2024
da14926
Update Community Tutorials
MarcSkovMadsen Mar 8, 2024
ee0b8d7
remove Thu Vu because outdated
MarcSkovMadsen Mar 8, 2024
622ddff
sort
MarcSkovMadsen Mar 8, 2024
e0cfa66
reorder
MarcSkovMadsen Mar 8, 2024
50e8532
CLEAN UP
MarcSkovMadsen Mar 8, 2024
c8bde59
Merge branch 'main' of https://github.com/holoviz/panel into enhancem…
MarcSkovMadsen Mar 8, 2024
d077e53
Merge branch 'main' of https://github.com/holoviz/panel into enhancem…
MarcSkovMadsen Mar 10, 2024
2686575
Merge remote-tracking branch 'origin/main' into enhancement/finalize-…
philippjfr Mar 11, 2024
d29d18e
Update pn_bind and pn_rx tutorials
philippjfr Mar 11, 2024
7361179
change height
MarcSkovMadsen Mar 14, 2024
ecf6bd3
review
MarcSkovMadsen Mar 14, 2024
80ddbbd
Merge branch 'main' into enhancement/finalize-basic-tutorial
MarcSkovMadsen Mar 15, 2024
fc4dea8
Merge branch 'main' into enhancement/finalize-basic-tutorial
philippjfr Mar 18, 2024
ac3f5e5
Replace table with definition list
philippjfr Mar 18, 2024
7504ce1
Some improvements for basic tutorials
philippjfr Mar 18, 2024
43e1c3b
Merge branch 'main' into enhancement/finalize-basic-tutorial
MarcSkovMadsen Mar 19, 2024
c7d6c0c
Merge branch 'enhancement/finalize-basic-tutorial' of https://github.…
MarcSkovMadsen Mar 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions doc/tutorials/basic/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ Once you're comfortable, it's time to dive deeper:
|--------------------------|---------------------------|-------------------|------------------------------------------------------|
| **1. Display Content** | [`pn.panel`](pn_panel.md) | [Panes](panes.md) | [Performance Indicators](indicators_performance.md) |
| **2. Organize Content** | [Layouts](layouts.md) | [Control the Size](size.md) | [Align Content](align.md) |
| **3. Handle User Input** | [Widgets](widgets.md) | [React to User Input](pn_bind.md) | [Handle State](state.md) |
| **3. Handle User Input** | [Widgets](widgets.md) | [React to User Input](pn_bind.md) | [Reactive Expressions](pn_rx.md) |
| **4. Improve the look** | [Templates](templates.md)| [Designs](design.md) | [Styles](style.md) |
| **5. Improve the Feel** | [Caching](caching.md) | [Activity Indicators](indicators_activity.md) | [Progressive Updates](progressive_layouts.md) |

Expand Down Expand Up @@ -93,7 +93,7 @@ size
align
widgets
pn_bind
state
pn_rx
caching
indicators_activity
progressive_layouts
Expand Down
4 changes: 4 additions & 0 deletions doc/tutorials/basic/panes.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ In this tutorial, we will learn to display objects with *Panes*:
A *Pane* is a component that can display an object. It takes an `object` as an argument.
:::

:::{note}
You might notice a lot of repetition from the previous section regarding `pn.panel`. Don't worry, it's intentional! We're doing this to empower you with the ability to compare and contrast. While `pn.panel` is incredibly user-friendly and versatile, specific Panes allow you to display output with precision and efficiency. This enables you to construct more intricate and high-performing applications.
:::

:::{note}
When we ask you to *run the code* in the sections below, you may either execute the code directly in the Panel docs via the green *run* button, in a cell in a notebook, or in a file `app.py` that is served with `panel serve app.py --autoreload`.
:::
Expand Down
327 changes: 325 additions & 2 deletions doc/tutorials/basic/pn_bind.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,332 @@
# React to User Input
# Reacting to User Input

COMING UP
Welcome to the interactive world of Panel! In this section, you'll learn how to make your Panel applications come alive by reacting to user input. We'll explore how to bind functions to widgets and add side effects using the `watch=True` parameter in Panel.

## Embrace `pn.bind`

The `pn.bind` method is your gateway to interactive Panel applications. It enables you to connect widgets to functions seamlessly, triggering updates whenever the widget values change. Let's dive into an example:

```{pyodide}
import panel as pn

pn.extension()

def calculate_power_generation(wind_speed, efficiency):
power_generation = wind_speed * efficiency
return f"Wind Speed: {wind_speed} m/s, Efficiency: {efficiency}, Power Generation: {power_generation:.1f} kW"

wind_speed = pn.widgets.FloatSlider(
value=5, start=0, end=20, step=1, name="Wind Speed (m/s)"
)

efficiency = 0.3

calculate_power_fn = pn.bind(
calculate_power_generation, wind_speed=wind_speed, efficiency=efficiency
)

pn.Column(
wind_speed, calculate_power_fn
).servable()
```

As you interact with the slider, notice how the displayed power generation dynamically updates, reflecting changes in wind speed.

You can of course bind to multiple widgets Lets make the `efficiency` a widget:

```{pyodide}
import panel as pn

pn.extension()


def calculate_power_generation(wind_speed, efficiency):
power_generation = wind_speed * efficiency
return f"Wind Speed: {wind_speed} m/s, Efficiency: {efficiency}, Power Generation: {power_generation:.1f} kW"


wind_speed = pn.widgets.FloatSlider(
value=5, start=0, end=20, step=1, name="Wind Speed (m/s)"
)
efficiency = pn.widgets.FloatInput(value=0.3, start=0.0, end=1.0, name="Efficiency (kW/(m/s))")

calculate_power_fn = pn.bind(
calculate_power_generation, wind_speed=wind_speed, efficiency=efficiency
)

pn.Column(
wind_speed, efficiency, calculate_power_fn
).servable()
```

## Enhancing Displays with References

Sometimes, you might want to customize how your bound function's output is presented. You can achieve this by referencing the bound function within a `Pane`, like so:

```{pyodide}
import panel as pn

pn.extension()


def calculate_power_generation(wind_speed, efficiency):
power_generation = wind_speed * efficiency
return f"Wind Speed: {wind_speed} m/s, Efficiency: {efficiency}, Power Generation: {power_generation:.1f} kW"


wind_speed = pn.widgets.FloatSlider(
value=5, start=0, end=20, step=1, name="Wind Speed (m/s)"
)
efficiency = pn.widgets.FloatInput(value=0.3, start=0.0, end=1.0, name="Efficiency (kW/(m/s))")

calculate_power_fn = pn.bind(
calculate_power_generation, wind_speed=wind_speed, efficiency=efficiency
)

pn.Column(
wind_speed, efficiency, calculate_power_fn
).servable()
```

## Crafting Interactive Forms

Forms are powerful tools for collecting user inputs. With Panel, you can easily create forms and process their submissions:

```{pyodide}
import panel as pn

pn.extension()


def calculate_power_generation(wind_speed, efficiency):
power_generation = wind_speed * efficiency
return f"Wind Speed: {wind_speed} m/s, Efficiency: {efficiency}, Power Generation: {power_generation:.1f} kW"


wind_speed = pn.widgets.FloatSlider(
value=5, start=0, end=20, step=1, name="Wind Speed (m/s)"
)
efficiency = pn.widgets.FloatInput(value=0.3, start=0.0, end=1.0, name="Efficiency (kW/(m/s))")

calculate_power_fn = pn.bind(
calculate_power_generation, wind_speed=wind_speed, efficiency=efficiency
)

submit = pn.widgets.Button(name="Submit")

def result(clicked):
if clicked:
return calculate_power_fn()
return "Click Submit"

result_fn = pn.bind(result, submit)

pn.Column(
wind_speed, efficiency, submit, result_fn
).servable()
```

## Harnessing Throttling for Performance

To prevent excessive updates and ensure smoother performance, you can apply throttling. This limits the rate at which certain actions or events occur, maintaining a balanced user experience:

```{pyodide}
import panel as pn
from time import sleep

pn.extension()


def calculate_power_generation(wind_speed, efficiency):
print("calculate", wind_speed)
sleep(1)
power_generation = wind_speed * efficiency
return f"Wind Speed: {wind_speed} m/s, Efficiency: {efficiency}, Power Generation: {power_generation:.1f} kW"


wind_speed = pn.widgets.FloatSlider(
value=5, start=0, end=20, step=1, name="Wind Speed (m/s)"
)

efficiency = 0.3

calculate_power_fn = pn.bind(
calculate_power_generation, wind_speed=wind_speed.param.value_throttled, efficiency=efficiency
)

pn.Column(
wind_speed, calculate_power_fn
).servable()
```

Try dragging the slider. Notice that the `calculate_power_generation` function is only run when you release the mouse.

### Binding to bound functions

You may also `bind` to bound functions. This can help you break down you reactivity into smaller, reusable steps.

```{pyodide}
import panel as pn

pn.extension()

def power_generation(wind_speed, efficiency):
print(wind_speed, efficiency)
return wind_speed * efficiency

def power_generation_text(wind_speed, efficiency, power):
return f"Wind Speed: {wind_speed} m/s, Efficiency: {efficiency}, Power Generation: {power:.1f} kW"

def to_html(text):
return f"<strong>{text}<strong>"

wind_speed = pn.widgets.FloatSlider(
value=5, start=0, end=20, step=1, name="Wind Speed (m/s)"
)

efficiency = 0.3

b_power_generation = pn.bind(power_generation, wind_speed=wind_speed, efficiency=efficiency)

b_power_generation_text = pn.bind(power_generation_text, wind_speed, efficiency, b_power_generation)

pn.Column(
wind_speed, b_power_generation, b_power_generation_text
).servable()
```

```{warning}
Binding to bound functions can help you to quickly explore your data but its highly inefficient as the results are calculated from scratch for each call.
```

Try changing the `power_generation` function to:

```python
def power_generation(wind_speed, efficiency):
print(wind_speed, efficiency)
return wind_speed * efficiency
```

Try dragging the `wind_speed` slider. Notice that the `power_generation` function is called twice every time you change the `wind_speed` `value`.

To solve this problem you should add *caching* or use *reactive expressions* (`pn.rx`). You will learn about *reactive expressions* in the next section.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we ask the user to add caching as an exercise?

## Triggering Side Effects with `watch`

philippjfr marked this conversation as resolved.
Show resolved Hide resolved
When you need to trigger additional tasks in response to user actions, setting `watch` comes in handy:

```{pyodide}
import panel as pn

pn.extension()

submit = pn.widgets.Button(name="Start the wind turbine")

def start_stop_wind_turbine(clicked):
if bool(submit.clicks%2):
submit.name="Start the wind turbine"
else:
submit.name="Stop the wind turbine"

b_stop_wind_turbine = pn.bind(start_stop_wind_turbine, submit, watch=True)

pn.Column(submit).servable()
```

```{warning}
In the example above our sideeffect updated the UI directly by changing the name of the Button. This is a sign of bad architecture.

We recommend sideeffects not to update the UI directly. Instead you should be updating the *state* and the UI should be updating automatically in response to the state change. You will learn more about state in the next section.
```

If your task is long running your might want to disable the Button while the task is running.

```{pyodide}
import panel as pn
from time import sleep

pn.extension()

submit = pn.widgets.Button(name="Start the wind turbine")

def start_stop_wind_turbine(clicked):
with submit.param.update(loading=True, disabled=True):
sleep(2)
if bool(submit.clicks%2):
submit.name="Start the wind turbine"
else:
submit.name="Stop the wind turbine"

b_stop_wind_turbine = pn.bind(start_stop_wind_turbine, submit, watch=True)


pn.Column(submit).servable()
```

### Keep the UI responsive with threads or processes

philippjfr marked this conversation as resolved.
Show resolved Hide resolved
To keep your UI and server responsive while the long running, blocking task is running you might want to run it asyncrounously in a separate thread:

```python
import asyncio
import concurrent.futures
from time import sleep

import panel as pn

pn.extension()

submit = pn.widgets.Button(name="Start the wind turbine")


async def start_stop_wind_turbine(clicked):
with submit.param.update(loading=True, disabled=True):
with concurrent.futures.ThreadPoolExecutor() as executor:
future = executor.submit(sleep, 5)
result = await asyncio.wrap_future(future)

if bool(submit.clicks % 2):
submit.name = "Start the wind turbine"
else:
submit.name = "Stop the wind turbine"


b_stop_wind_turbine = pn.bind(start_stop_wind_turbine, submit, watch=True)


pn.Column(submit).servable()
```

:::{note}
In the example we use a `ThreadPoolExecutor` this should work great if your blocking task releases the GIL while running. Tasks that request data from the web or read data from files typically do this. Some computational methods from Numpy, Pandas etc. also release the GIL. If your long running task does not release the GIL you might want to replace the `ThreadPoolExecutor` with a `ProcessPoolExecutor`. This introduces some overhead though.
:::

## Recap

You've now unlocked the power of interactivity in your Panel applications:

- `pn.bind(some_function, widget_1, widget_2)`: for seamless updates based on widget values.
- `pn.bind(some_task, some_widget, watch=True)`: for triggering tasks in response to user actions.
- Throttling ensures smoother performance by limiting update frequency.
- Utilizing async and threading keeps your UI responsive during long-running tasks.

Now, let your imagination run wild and craft dynamic, engaging Panel applications!

## Resources

### How-to

- [Add interactivity to a function](../../how_to/interactivity/bind_function.md)
- [Add Interactivity with `pn.bind` | Migrate from Streamlit](../../how_to/streamlit_migration/interactivity.md)
- [Enable Throttling](../../how_to/performance/throttling.md)
- [Run synchronous functions asynchronously](../../how_to/concurrency/sync_to_async.md)
- [Setup Manual Threading](../../how_to/concurrency/manual_threading.md)
- [Use Asynchronous Callbacks](../../how_to/callbacks/async.md)

### Explanation

- [Reactive API](../../explanation/api/reactive.md)

### External

- [Param: Parameters and `param.bind`](https://param.holoviz.org/user_guide/Reactive_Expressions.html#parameters-and-param-bind)
Loading
Loading