Path: blob/main/doc/tutorials/intermediate/interactivity.md
2012 views
Interactivity
In the previous sections, we delved into Param, which not only forms the core architecture of Panel but also serves as the foundation for adding interactivity to your applications. This section explores how to leverage Parameters and their dependencies to incorporate interactivity. We'll focus on implementing interactivity through reactivity, departing from the more imperative style of programming commonly found in other UI frameworks.
By the end of this tutorial, you'll learn:
How to utilize both declarative and imperative APIs for interactivity
How to develop both functional and class-based interactive apps
Imperative vs Declarative Programming
To create an interactive component in Panel, we have two approaches: defining callbacks for explicit actions or declaring reactive functions, methods, or expressions that automatically manage state changes.
:::{tip}
For users of all skill levels, we recommend employing the Declarative Programming approach as it enhances code maintainability and efficiency.
:::
Let's explore these approaches by building a simple app that enables us to select a subset of columns to display in a table.
Imperative
We begin by loading our data and defining a widget for interacting with it:
In the imperative approach, we use .param.watch
to set up a callback that updates the data when the widget changes:
Declarative
The declarative and reactive approach involves declaring what we want to display, leaving Panel to handle the mechanics of updating the table:
Note how we pass the reactive DataFrame dfrx
to the Tabulator
widget. This aligns with the concept of passing references, which Param and Panel resolve. Valid references include:
param.Parameter()
param.rx(...)
pn.bind(...)
/@pn.depends
pn.widgets.Widget()
Exercise: Add more Widgets
Enhance the app by adding widgets to filter the data by year (p_year
) and capacity (p_cap
):
:::{hint}
You can filter a reactive DataFrame in the same way as a regular DataFrame.
:::
:::{dropdown} Solution: Declarative (Recommended)
:::
:::{dropdown} Solution: Imperative (Not recommended)
:::
Function vs. Class-based
Reactive functions and expressions based on pn.rx
or pn.bind
provide an excellent entry point for writing dynamic UIs. However, when we need to track state or have many consumers of the output, it can be challenging to manage. This is where Parameterized
classes come in handy.
If you recall the Reactive Parameters Section, a Parameterized
class enables you to encapsulate state as parameters, which can then be passed around to set up interactivity.
:::{tip}
The class-based approach is recommended for larger, more complex applications.
:::
Making the Class-Based Approach Efficient
Let's revisit our DataExplorer
class from the previous lesson and see how we can structure a filtering application like before:
As you can see, param.depends
allows us to set up a method that depends on specific parameters of the class (much like pn.bind
) and then use that method as a proxy for the filtered data. If you observe the execution flow, you'll notice that the filtered_data
method is executed twice whenever one of data
, columns
, year
, or capacity
is changed. You can avoid this inefficiency by using pn.cache
.
An alternative and slightly more efficient approach would be to create a parameter to store the filtered data and update it every time one of the dependencies changes.
Storing the filtered_data
has the benefit that multiple consumers can now access it without recalculating it.
We can also combine the class-based approach with pn.rx
to achieve an efficient solution:
:::{tip}
If your dependent function (
@pn.depends
) will only be executed once per update, the first approach is recommended as it's simple to implement and reason about.If not, we recommend using the second approach (
@pn.depends
withwatch=True
) or the third approach (pn.rx
) because it's much easier to implement efficiently.
:::
Exercise: Add a Plot
Write an app that allows filtering the DataFrame and displays both a table and a plot, caching the data on an intermediate parameter. You can use any plotting library you want.
:::{hint}
:::
:::{dropdown} Solution
:::
Recap
In this tutorial, we explored how to build an efficient filtering application using Panel. The focus was on optimizing the class-based approach to handle filtering operations on a DataFrame efficiently.
Key Concepts Covered
Class-Based Approach:
We started by revisiting a class-based approach for building interactive apps in Panel. This involved creating a
DataExplorer
class to handle filtering operations on a DataFrame.
Parameter Dependencies:
Utilizing
param.depends
, we established dependencies between different parameters and methods within theDataExplorer
class. This allowed us to trigger updates to specific methods whenever the parameters changed.
Imperative vs Declarative Programming:
We discussed two programming paradigms for building interactive components in Panel:
Imperative: Defining explicit callbacks to perform actions based on widget changes.
Declarative: Using reactive functions or expressions to automatically manage state updates based on input changes. We recommended the declarative approach for its maintainability and efficiency.
Efficiency Considerations:
We explored the importance of efficiency when dealing with large datasets or complex filtering operations. Inefficient code can lead to unnecessary recalculations and decreased performance.
Improving Efficiency:
To enhance efficiency, we explored three approaches:
Using
pn.cache
to cache the filtered data.Storing the filtered data as a parameter and updating it when dependencies change.
Combining the class-based approach with
pn.rx
for reactive programming.
Key Takeaways
Imperative vs Declarative Programming: Understanding the difference between imperative and declarative programming paradigms helps in choosing the most suitable approach for building interactive components in Panel.
Parameter Dependencies: Establishing dependencies between parameters and methods using
param.depends
is fundamental for reactive updates in Panel apps.Efficiency is Crucial: When building interactive applications, especially with large datasets, prioritizing efficiency is essential to ensure optimal performance.
Caching and Storing Data: Techniques like caching filtered data and storing it as a parameter can significantly improve efficiency by minimizing redundant computations.
Flexibility with Panel: Panel provides flexibility in designing interactive web apps, allowing developers to integrate various visualization components seamlessly.
By applying these concepts and techniques, developers can create efficient and responsive filtering applications using Panel, tailored to specific data exploration needs.