diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 5a015071c8..1c08d17001 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -58,7 +58,7 @@ jobs: - name: conda setup run: | conda config --prepend channels bokeh/label/dev - conda create -n test-environment python=3.8 pyctdev + conda create -n test-environment python=3.9 pyctdev - uses: actions/setup-node@v2 with: node-version: '15' @@ -111,7 +111,7 @@ jobs: run: | eval "$(conda shell.bash hook)" conda activate test-environment - panel convert examples/gallery/streaming/*.ipynb examples/gallery/dynamic/*.ipynb examples/gallery/param/*.ipynb --to pyodide-worker --out ./builtdocs/pyodide/ + panel convert examples/gallery/**/*.ipynb --to pyodide-worker --out ./builtdocs/pyodide/ - name: git status and git diff run: | git status diff --git a/doc/user_guide/APIs.md b/doc/user_guide/APIs.md index 79c760746e..ab543f7d61 100644 --- a/doc/user_guide/APIs.md +++ b/doc/user_guide/APIs.md @@ -41,13 +41,13 @@ pn.extension() The `pn.bind` reactive programming API is very similar to the ``interact`` function but is more explicit about widget selection and layout. `pn.bind` requires the programmer to select and configure widgets explicity and to lay out components explicitly, without relying on inference of widget types and ranges and without any default layouts. Specifying those aspects explicitly provides more power and control, but does typically take a bit more code and more knowledge of widget and layout components than using `interact` does. Once widgets have been bound to a reactive function, you can lay out the bound function and the widgets in any order or combination you like, including across Jupyter notebook cells if desired. -#### Pros: +### Pros: + Very clear mapping from widgets to the arguments of the function. + Very explicit layout of each of the different components. + Like `interact`, doesn't typically require modifying existing visualization code. -#### Cons: +### Cons: - Typically requires a bit more code than `interact` @@ -109,21 +109,21 @@ This alternative way of specifying the same app lets you declare the dependency ## Interact Functions -The ``interact`` function will automatically generate a UI (including widgets) by inspecting the arguments of the function given to it, or by using additional hints you provide in the ``interact`` function call. If you have worked with the [``ipywidgets``](https://github.com/jupyter-widgets/ipywidgets) package you may already be familiar with this approach. (In fact, the Panel interact function is modeled on the one from ipywidgets, making it simpler to port code between the two platforms.) The basic idea is that given a function that returns some object, Panel will inspect the arguments to that function, try to infer appropriate widgets for those arguments, and then re-run that function to update the output whenever one of the widgets generates an event. For more detail on how interact creates widgets and other ways of using it, see the Panel [interact user guide](./interact.md). This section instead focuses on when and why to use this API, laying out its benefits and drawbacks. +The ``interact`` function will automatically generate a UI (including widgets) by inspecting the arguments of the function given to it, or by using additional hints you provide in the ``interact`` function call. If you have worked with the [``ipywidgets``](https://github.com/jupyter-widgets/ipywidgets) package you may already be familiar with this approach. (In fact, the Panel interact function is modeled on the one from ipywidgets, making it simpler to port code between the two platforms.) The basic idea is that given a function that returns some object, Panel will inspect the arguments to that function, try to infer appropriate widgets for those arguments, and then re-run that function to update the output whenever one of the widgets generates an event. For more detail on how interact creates widgets and other ways of using it, see the Panel [interact user guide](./Interact.md). This section instead focuses on when and why to use this API, laying out its benefits and drawbacks. The main benefit of this approach is convenience and ease of use. You start by writing some function that returns an object, be that a plot, a dataframe, or anything else that Panel can render. Then with a single call to `pn.interact()`, you can immediately get an interactive UI, without ever instantiating any widgets or wiring up any callbacks explicitly. Unlike ipywidgets, the ``pn.interact`` call will return a Panel. This Panel can then be further modified by laying out the widgets and output separately, or combining these components with other panes. Even though `pn.interact` itself is limited in flexibility compared to the rest of Panel, you can still unpack and reconfigure the results from it to generate fairly complex GUIs in very little code. -#### Pros: +### Pros: + Easy to use (or at least easy to get started!). + Doesn't typically require modifying existing visualization code. -#### Cons: +### Cons: - Most of the behavior is implicit, with magic happening by introspection, making it difficult to see how to modify the appearance or functionality of the resulting object. - Customizing the layout requires indexing into the panel returned by `interact`. -The simplest `interact` call can be a one-liner, but here we'll show an example of intermediate complexity so that you get a good idea of what `interact` can do in practice. In this code, ``pn.interact`` infers the initial value for `x` and `y` from the `autompg_plot` function default arguments and their widget type and range from the `columns` list provided to `interact`. `interact` wouldn't normally put up a color widget because it would have no way of knowing that this string-type argument represents an RGB color, and so here we explicitly create a color-picker widget and pass that as the value for the color so that we can control the color as well. Finally, we unpack the result from `interact` and rearrange it in a different layout with a title, to create the final app. See the Panel [interact user guide](./interact.md) for even simpler examples along with details about how to control the widgets and how to rearrange the layout. +The simplest `interact` call can be a one-liner, but here we'll show an example of intermediate complexity so that you get a good idea of what `interact` can do in practice. In this code, ``pn.interact`` infers the initial value for `x` and `y` from the `autompg_plot` function default arguments and their widget type and range from the `columns` list provided to `interact`. `interact` wouldn't normally put up a color widget because it would have no way of knowing that this string-type argument represents an RGB color, and so here we explicitly create a color-picker widget and pass that as the value for the color so that we can control the color as well. Finally, we unpack the result from `interact` and rearrange it in a different layout with a title, to create the final app. See the Panel [interact user guide](./Interact.md) for even simpler examples along with details about how to control the widgets and how to rearrange the layout. ```{pyodide} @@ -139,12 +139,12 @@ The [Param](http://param.pyviz.org) library allows expressing the parameters of The parameterized approach is a powerful way to encapsulate computation in self-contained classes, taking advantage of object-oriented programming patterns. It also makes it possible to express a problem completely independently from Panel or any other GUI code, while still getting a GUI for free as a last step. For more detail on using this approach see the [Param user guide](./Param.md). -Pros: +### Pros: + Declarative way of expressing parameters and dependencies between parameters and computation + The resulting code is not tied to any particular GUI framework and can be used in other contexts as well -Cons: +### Cons: - Requires writing classes - Less explicit about widgets to use for each parameter; can be harder to customize behavior than if widgets are instantiated explicitly @@ -176,11 +176,11 @@ The callback API in panel is the lowest-level approach, affording the greatest a For more details on defining callbacks see the [Links user guide](./Links.md). -#### Pros: +### Pros: + Complete and modular control over specific events -#### Cons: +### Cons: - Complexity grows very quickly with the number of callbacks - Have to handle initializing the plots separately diff --git a/doc/user_guide/Components.md b/doc/user_guide/Components.md index ed094f3d1d..a966ba1db6 100644 --- a/doc/user_guide/Components.md +++ b/doc/user_guide/Components.md @@ -1,4 +1,4 @@ -# Components +# Component overview Panel provides a wide range of components for easily composing panels, apps, and dashboards both in the notebook and as standalone apps. The components can be broken down into three broad classes of objects: diff --git a/doc/user_guide/Customization.md b/doc/user_guide/Customization.md index 24b7ec6c46..fed2ca4849 100644 --- a/doc/user_guide/Customization.md +++ b/doc/user_guide/Customization.md @@ -10,7 +10,7 @@ Panel objects are built on top of [Param](https://param.pyviz.org), which allows ## Styling Components -#### ``css_classes`` +### ``css_classes`` The ``css_classes`` parameter allows associating a Panel component with one or more CSS classes. CSS styles can be embedded in raw form or by referencing an external .css file by providing each to the panel extension using the ``raw_css`` and ``css_files`` arguments; both should be supplied as lists. Outside the notebook or if we want to add some CSS in an external module or library, we can simply append to the ``pn.config.raw_css`` and ``pn.config.js_files`` config parameters. @@ -40,7 +40,7 @@ pn.Column( css_classes=['panel-widget-box']) ``` -#### ``background`` +### ``background`` In case we simply want to give the component a background we can define one as a hex string: @@ -49,7 +49,7 @@ In case we simply want to give the component a background we can define one as a pn.pane.HTML(background='#f307eb', width=100, height=100) ``` -#### `loading` +### `loading` All components also have a `loading` parameter which indicates that they are currently processing some event. Setting the parameter will display the global `loading_spinner` on top of the component. To configure the loading spinner you can set the: @@ -63,7 +63,7 @@ In the notebook these should be configured before loading the `pn.extension` and pn.pane.HTML(background='#00aa41', width=100, height=100, loading=True) ``` -#### ``style`` +### ``style`` Certain components, specifically markup-related panes, expose a ``style`` parameter that allows defining CSS styles applying to the HTML container of the pane's contents, e.g. the ``Markdown`` pane: @@ -72,7 +72,7 @@ Certain components, specifically markup-related panes, expose a ``style`` parame pn.pane.Markdown('### A serif Markdown heading', style={'font-family': "serif"}) ``` -#### ``visible`` +### ``visible`` All components provide a `visible` parameter which allows toggling whether the component should be visible or not. Below we display a set of components and provide some widgets to toggle the `visible` property on or off: @@ -96,7 +96,7 @@ pn.Column(controls, layout) The size of components and their spacing is also controlled through a set of parameters shared by all components. -#### ``margin`` +### ``margin`` The ``margin`` parameter can be used to create space around an element defined as the number of pixels at the (top, right, bottom, and left). The ``margin`` can be defined in one of three ways: @@ -122,7 +122,7 @@ pn.Row( pn.Column(pn.widgets.Button(name='Run', margin=(25, 50, 75, 100)), background='#f0f0f0')) ``` -## ``align`` +### ``align`` The `align` parameter controls how components align vertically and horizontally. It supports 'start', 'center', and 'end' values and can be set for both horizontal and vertical directions at once or for each separately by passing in a tuple of the form `(horizontal, vertical)`. @@ -159,11 +159,11 @@ pn.Row( Unlike other components, the size of a plot is usually determined by the underlying plotting library, so it may be necessary to ensure that you set the size and aspect when declaring the plot. -### Responsive sizing +## Responsive sizing By default, panel objects will use a fixed size if one is provided or adapt to the size of the content. However most panel objects also support reactive sizing which adjusts depending on the size of the viewport. These responsive sizing modes can be controlled using the ``sizing_mode`` parameter. -#### ``sizing_mode`` +### ``sizing_mode`` * **"fixed"**: Component is not responsive. It will retain its original width and height regardless of any subsequent browser window resize events. @@ -216,7 +216,7 @@ pn.Column( height=400, width=500, background='#3f3f3f') ``` -### Spacers +## Spacers Spacers are a very versatile component which makes it easy to put fixed or responsive spacing between objects. Like all other components spacers support both absolute and responsive sizing modes: diff --git a/doc/user_guide/Links.md b/doc/user_guide/Links.md index 15562d0b63..c94c76b00c 100644 --- a/doc/user_guide/Links.md +++ b/doc/user_guide/Links.md @@ -79,7 +79,7 @@ selected = pn.pane.Markdown(object='') toggle = pn.widgets.ToggleGroup(options=['A', 'B']) ``` -#### Defining a callback +### Defining a callback Next we define a callback that can handle multiple parameter changes at once and uses the ``Event``'s ``name`` to figure out how to process the event. In this case it updates either the ``selections`` or the ``selected`` pane depending on whether ToggleGroup ``options`` or ``value`` changed: @@ -94,7 +94,7 @@ def callback(*events): selected.object = 'Selected: %s' % ','.join(event.new) ``` -#### Event objects +### Event objects Before going any further let us discover what these ``Event`` objects are. An ``Event`` is used to signal the change in a parameter value. Event objects provide a number of useful attributes that provides additional information about the event: @@ -106,7 +106,7 @@ Before going any further let us discover what these ``Event`` objects are. An `` * **``obj``**: The Parameterized instance that holds the parameter * **``cls``**: The Parameterized class that holds the parameter -#### Registering a watcher +### Registering a watcher Now that we know how to define a callback and make use of ``Event`` attributes, it is time to register the callback. The ``obj.param.watch`` method lets us supply the callback along with the parameters we want to watch. Additionally we can declare whether the events should only be triggered when the parameter value changes, or every time the parameter is set: @@ -141,7 +141,7 @@ Using `set_param` allows us to batch two separate changes (the options and the v Now that the widgets are visible, you can toggle the option values and see the selected pane update in response via the callback (if Python is running). -#### Unlinking +### Unlinking If for whatever reason we want to stop watching parameter changes we can unsubscribe by passing our ``watcher`` (returned in the ``watch`` call above) to the ``unwatch`` method: @@ -254,7 +254,7 @@ pn.Row(value1, operator, value2, button, result) The above examples link widgets to simple static panes, but links are probably most useful when combined with dynamic objects like plots. -#### Bokeh +### Bokeh The ``jslink`` API trivially allows us to link a parameter on a Panel widget to a Bokeh plot property. Here we create a Bokeh Figure with a simple sine curve. The ``jslink`` method allows us to pass any Bokeh model held by the Figure as the ``target``, then link the widget value to some property on it. E.g. here we link a ``FloatSlider`` value to the ``line_width`` of the ``Line`` glyph: @@ -272,7 +272,7 @@ width_slider.jslink(r.glyph, value='line_width') pn.Column(width_slider, p) ``` -#### HoloViews +### HoloViews Bokeh models allow us to directly access the underlying models and properties, but this access is more indirect when working with HoloViews objects. HoloViews makes various models available directly in the namespace so that they can be accessed for linking: diff --git a/doc/user_guide/Overview.md b/doc/user_guide/Overview.md index a5f8df787b..7e2a14688e 100644 --- a/doc/user_guide/Overview.md +++ b/doc/user_guide/Overview.md @@ -2,7 +2,7 @@ In order to get the best use out of the Panel user guide, it is important to have a grasp of some core concepts, ideas, and terminology. -### Components +## Components Panel provides three main types of component: ``Pane``, ``Widget``, and ``Panel``. These components are introduced and explained in the [Components user guide](./Components.rst), but briefly: @@ -14,133 +14,135 @@ Panel provides three main types of component: ``Pane``, ``Widget``, and ``Panel` --- -### APIs +## APIs Panel is a very flexible system that supports many different usage patterns, via multiple application programming interfaces (APIs). Each API has its own advantages and disadvantages, and is suitable for different tasks and ways of working. The [API user guide](APIs.rst) goes through each of the APIs in detail, comparing their pros and cons and providing recommendations on when to use each. -#### [Reactive functions](./APIs.rst#reactive-functions) +### [Reactive functions](./APIs.rst#reactive-functions) Defining a reactive function using the ``pn.bind`` function or ``pn.depends`` decorator provides an explicit way to link specific inputs (such as the value of a widget) to some computation in a function, reactively updating the output of the function whenever the parameter changes. This approach is a highly convenient, intuitive, and flexible way of building interactive UIs. -#### [``interact``](./Interact.rst) +### [``interact``](./Interact.rst) The ``interact`` API will be familiar to ipywidgets users; it provides a very simple API to define an interactive view of the results of a Python function. This approach works by declaring functions whose arguments will be inspected to infer a set of widgets. Changing any of the resulting widgets causes the function to be re-run, updating the displayed output. This approach makes it extremely easy to get started and also easy to rearrange and reconfigure the resulting plots and widgets, but it may not be suited to more complex scenarios. See the [Interact user guide](./Interact.rst) for more detail. -#### [``Param``](./Param.rst) +### [``Param``](./Param.rst) ``Panel`` itself is built on the [param](https://param.pyviz.org) library, which allows capturing parameters and their allowable values entirely independently of any GUI code. By using Param to declare the parameters along with methods that depend on those parameters, even very complex GUIs can be encapsulated in a tidy, well-organized, maintainable, and declarative way. Panel will automatically convert parameter definition to corresponding widgets, allowing the same codebase to support command-line, batch, server, and GUI usage. This API requires the use of the param library to express the inputs and encapsulate the computations to be performed, but once implemented this approach leads to flexible, robust, and well encapsulated code. See the Panel [Param user guide](./Param.rst) for more detail. -#### [Callback API](./Widgets.rst) +### [Callback API](./Widgets.rst) At the lowest level, you can build interactive applications using ``Pane``, ``Widget``, and ``Panel`` components and connect them using explicit callbacks. Registering callbacks on components to modify other components provides full flexibility in building interactive features, but once you have defined numerous callbacks it can be very difficult to track how they all interact. This approach affords the most amount of flexibility but can easily grow in complexity, and is not recommended as a starting point for most users. That said, it is the interface that all the other APIs are built on, so it is powerful and is a good approach for building entirely new ways of working with Panel, or when you need some specific behavior not covered by the other APIs. See the [Widgets user guide](./Widgets.rst) and [Links user guide](./Links.rst) for more detail. --- -### Display and rendering +## Display and rendering Throughout this user guide we will cover a number of ways to display Panel objects, including display in a Jupyter notebook, in a standalone server, by saving and embedding, and more. For a detailed description see the [Deploy and Export user guide](./Deploy_and_Export.rst). -#### Notebook +### Notebook All of Panel's documentation is built from Jupyter notebooks that you can explore at your own pace. Panel does not require Jupyter in any way, but it has extensive Jupyter support: -##### ``pn.extension()`` +#### ``pn.extension()`` > The Panel extension loads BokehJS, any custom models required, and optionally additional custom JS and CSS in Jupyter notebook environments. It also allows passing any [`pn.config`](#pn.config) variables -##### ``pn.ipywidget()`` +#### ``pn.ipywidget()`` > Given a Panel model `pn.ipywidget` will return an ipywidget model that renders the object in the notebook. This can be useful for including an panel widget in an ipywidget layout and deploying Panel objects using [VoilĂ ](https://github.com/voila-dashboards/voila/). -##### ``pn.io.push_notebook`` +#### ``pn.io.push_notebook`` > When working with Bokeh models directly in a Jupyter Notebook any changes to the model are not automatically sent to the frontend. Instead we have to explicitly call `pn.io.push_notebook` on the Panel component(s) wrapping the Bokeh component being updated. -##### Rich display +#### Rich display Jupyter notebooks allow the final value of a notebook cell to display itself, using a mechanism called [rich display](https://ipython.readthedocs.io/en/stable/config/integrating.html#rich-display). As long as `pn.extension()` has been called in a notebook, all Panel components (widgets, panes, and panels) will display themselves when placed on the last line of a notebook cell. -##### ``.app()`` +#### ``.app()`` > The ``.app()`` method present on all viewable Panel objects allows displaying a Panel server process inline in a notebook, which can be useful for debugging a standalone server interactively. -#### Python REPL and embedding a server +### Python REPL and embedding a server When working in a Python REPL that does not support rich-media output (e.g. in a text-based terminal) or when embedding a Panel application in another tool, a panel can be launched in a browser tab using: -##### ``.show()`` +#### ``.show()`` > The ``.show()`` method is present on all viewable Panel objects and starts a server instance then opens a browser tab to point to it. To support working remotely, a specific port on which to launch the app can be supplied. -##### ``pn.serve()`` +#### ``pn.serve()`` >Similar to .show() on a Panel object but allows serving one or more Panel apps on a single server. Supplying a dictionary mapping from the URL slugs to the individual Panel objects being served allows launching multiple apps at once. Note that to ensure that each user gets separate session state you should wrap your app in a function which returns the Panel component to render. This ensures that whenever a new user visits the application a new instance of the application can be created. -#### Command line +### Command line Panel mirrors Bokeh's command-line interface for launching and exporting apps and dashboards: -##### ``panel serve app.py`` +#### ``panel serve app.py`` > The ``panel serve`` command allows allows interactively displaying and deploying Panel web-server apps from the commandline. -##### ``panel serve app.ipynb`` +#### ``panel serve app.ipynb`` > ``panel serve`` also supports using Jupyter notebook files, where it will serve any Panel objects that were marked `.servable()` in a notebook cell. This feature allows you to maintain a notebook for exploring and analysis that provides certain elements meant for broader consumption as a standalone app. -#### Export +### Export When not working interactively, a Panel object can be exported to a static file. -##### ``.save()`` to PNG +#### ``.save()`` to PNG > The ``.save`` method present on all viewable Panel objects allows saving the visual representation of a Panel object to a PNG file. -##### ``.save()`` to HTML +#### ``.save()`` to HTML > ``.save`` to HTML allows sharing the full Panel object, including any static links ("jslink"s) between widgets and other components, but other features that depend on having a live running Python process will not work (as for many of the Panel webpages). -#### Embedding +### Embedding Panel objects can be serialized into a static JSON format that captures the widget state space and the corresponding plots or other viewable items for each combination of widget values, allowing fully usable Panel objects to be embedded into external HTML files or emails. For simple cases, this approach allows distributing or publishing Panel apps that no longer require a Python server in any way. Embedding can be enabled when using ``.save()``, using the ``.embed()`` method or globally using [Python and Environment variables](#Python and Environment variables) on ``pn.config``. -##### ``.embed()`` +#### ``.embed()`` > The ``.embed()`` method embeds the contents of the object it is being called on in the notebook. ___ -### Linking and callbacks +## Linking and callbacks One of the most important aspects of a general app and dashboarding framework is the ability to link different components in flexible ways, scheduling callbacks in response to internal and external events. Panel provides convenient lower and higher-level APIs to achieve both. For more details, see the [Links](./Links.rst) user guide. -##### ``.param.watch`` +### Methods + +#### ``.param.watch`` > The ``.param.watch`` method allows listening to parameter changes on an object using Python callbacks. It is the lowest level API and provides the most amount of control, but higher-level APIs are more appropriate for most users and most use cases. -##### ``.link()`` +#### ``.link()`` > The Python-based ``.link()`` method present on all viewable Panel objects is a convenient API to link the parameters of two objects together, uni- or bi-directionally. -##### ``.jscallback`` +#### ``.jscallback`` > The Javascript-based ``.jscallback()`` method allows defining arbitrary Javascript code to be executed when some property changes or event is triggered. -##### ``.jslink()`` +#### ``.jslink()`` > The JavaScript-based ``.jslink()`` method directly links properties of the underlying Bokeh models, making it possible to define interactivity that works even without a running Python server. ___ -### State and configuration +## State and configuration Panel provides top-level objects to hold current state and control high-level configuration variables. -##### `pn.config` +### `pn.config` The `pn.config` object allows setting various configuration variables, the config variables can also be set as environment variables or passed through the [`pn.extension`](#pn-extension): -##### Python only +#### Python only > - `css_files`: External CSS files to load. > - `js_files`: External JS files to load. Dictionary should map from exported name to the URL of the JS file. @@ -167,7 +169,7 @@ The `pn.config` object allows setting various configuration variables, the confi > - `inline` (`PANEL_INLINE`): Whether to inline JS and CSS resources. If disabled, resources are loaded from CDN if one is available. > - `npm_cdn` (`PANEL_NPM_CDN`): The CDN to load NPM packages from if resources are served from CDN. Allows switching between 'https://unpkg.com' (default) and 'https://cdn.jsdelivr.net/npm' for most resources. -##### `pn.state` +#### `pn.state` The `pn.state` object makes various global state available and provides methods to manage that state: diff --git a/doc/user_guide/Param.md b/doc/user_guide/Param.md index 6fa7132568..b42cb2d1e7 100644 --- a/doc/user_guide/Param.md +++ b/doc/user_guide/Param.md @@ -1,4 +1,4 @@ -# Parameters +# Using Param with Panel Panel supports using parameters and dependencies between parameters as expressed by ``param`` in a simple way to encapsulate dashboards as declarative, self-contained classes. diff --git a/examples/user_guide/APIs.ipynb b/examples/user_guide/APIs.ipynb deleted file mode 100644 index e2294311af..0000000000 --- a/examples/user_guide/APIs.ipynb +++ /dev/null @@ -1,323 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Panel can be used to make a first pass at an app or dashboard in minutes, while also allowing you to fully customize the app's behavior and appearance or flexibly integrate GUI support into long-term, large-scale software projects. To accommodate these different ways of using Panel, four different APIs are available:\n", - "\n", - "* **Interact functions**: Auto-generates a full UI (including widgets) given a function\n", - "* **Reactive functions**: Linking functions or methods to widgets using ``pn.bind`` or the equivalent ``pn.depends`` decorator, declaring that the function should be re-run when those widget values change\n", - "* **Parameterized class**: Declare parameters and their ranges in Parameterized classes, then get GUIs (and value checking!) for free\n", - "* **Callbacks**: Generate a UI by manually declaring callbacks that update panels or panes\n", - "\n", - "Each of these APIs has its own benefits and drawbacks, so this section will go through each one in turn, while working through an example app and pointing out the benefits and drawback along the way. For a quick overview you can also review the API gallery examples, e.g. the [stocks_hvplot](../gallery/apis/stocks_hvplot.ipynb) app.\n", - "\n", - "To start with let us define some imports, load the `autompg` dataset, and define a plotting function we will be reusing throughout this user guide." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import hvplot.pandas\n", - "from bokeh.sampledata.autompg import autompg\n", - "\n", - "def autompg_plot(x='mpg', y='hp', color='#058805'):\n", - " return autompg.hvplot.scatter(x, y, c=color, padding=0.1)\n", - "\n", - "columns = list(autompg.columns[:-2])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Given values for the x and y axes and a color, this function can be used to generate an interactive plot without needing any Panel components:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "autompg_plot()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "But if we want to let a user control the axes and the color with widgets rather than writing Python code, we can use one of the Panel APIs as shown in the rest of the notebook." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import panel as pn\n", - "pn.extension()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Reactive Functions\n", - "\n", - "The `pn.bind` reactive programming API is very similar to the ``interact`` function but is more explicit about widget selection and layout. `pn.bind` requires the programmer to select and configure widgets explicity and to lay out components explicitly, without relying on inference of widget types and ranges and without any default layouts. Specifying those aspects explicitly provides more power and control, but does typically take a bit more code and more knowledge of widget and layout components than using `interact` does. Once widgets have been bound to a reactive function, you can lay out the bound function and the widgets in any order or combination you like, including across Jupyter notebook cells if desired.\n", - "\n", - "#### Pros:\n", - "\n", - "+ Very clear mapping from widgets to the arguments of the function.\n", - "+ Very explicit layout of each of the different components.\n", - "+ Like `interact`, doesn't typically require modifying existing visualization code.\n", - "\n", - "#### Cons:\n", - "\n", - "- Typically requires a bit more code than `interact`\n", - "\n", - "In this model, we can use an existing plotting function just as for `interact`, but then need to declare each widget explicitly and then bind a widget to each of the arguments that we want to be interactive. The `pn.bind` function works much like [`functools.partial`](https://docs.python.org/3/library/functools.html#functools.partial) in that it binds regular arguments and keyword arguments to a function. `partial` can only bind specific, static arguments like `5`, but `pn.bind` can also bind parameters, widgets, and other dynamic functions with dependencies to the arguments, ensuring when the function is called the current values of the parameters are passed to the function. A bound function is then reactive, updating whenever the widget values change.\n", - "\n", - "To make the concept of binding clear, let's look at a trivial example first:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def fn(a,b): return f'Arguments: {a,b}'\n", - "slider = pn.widgets.FloatSlider(value=0.5)\n", - "\n", - "bound_fn = pn.bind(fn, a=slider, b=2)\n", - "bound_fn()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Here we can see that although `fn` required two arguments, `bound_fn` takes no arguments because `a` has been bound to the slider value and `b` to a static value. \n", - "\n", - "If we display the slider and bound function in Panel, we can see that everything will update reactively as you use the widget, reevaluating the function whenever the bound argument changes:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "pn.Row(slider, bound_fn)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Using `pn.bind`, we can easily build something like the `interact` app above if we explicitly make widgets for each argument, bind them to the plotting function, and lay out the result with the widgets:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "x = pn.widgets.Select(value='mpg', options=columns, name='x')\n", - "y = pn.widgets.Select(value='hp', options=columns, name='y')\n", - "color = pn.widgets.ColorPicker(name='Color', value='#AA0505')\n", - "\n", - "pn.Row(pn.Column('## MPG Explorer', x, y, color),\n", - " pn.bind(autompg.hvplot.scatter, x, y, c=color))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Notice how here we didn't even need the `autompg_plot` function here, because `bind` works with both methods and functions, so for this particular case the reactive API works out to the same amount of code as `interact`. In practice `interact` is shorter and simpler in the case of accepting the default behavior, making it simple to get started, while `pn.bind` requires a bit more code to get started but then allows easier customization and specification.\n", - "\n", - "If you are writing code specifically for building an app, and do not wish to keep domain and GUI code separate, the functionality of `pn.bind` is also available as a decorator `@pn.depends`:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "x = pn.widgets.Select(value='mpg', options=columns, name='x')\n", - "y = pn.widgets.Select(value='hp', options=columns, name='y')\n", - "color = pn.widgets.ColorPicker(name='Color', value='#AA0505')\n", - "\n", - "@pn.depends(x, y, color)\n", - "def plot(xval, yval, colorval):\n", - " return autompg.hvplot.scatter(xval, yval, c=colorval)\n", - "\n", - "pn.Row(\n", - " pn.Column('## MPG Explorer', x, y, color), \n", - " plot\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This alternative way of specifying the same app lets you declare the dependency between a function argument and a widget (or parameter) from the start, which can be clearer if you know the function will always and only be used in a GUI. Otherwise, the `pn.bind` version is preferred, because it allows you to keep the Panel-specific code separate (even in a different Python module or file) from the underlying analysis and plotting code." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Interact Functions\n", - "\n", - "The ``interact`` function will automatically generate a UI (including widgets) by inspecting the arguments of the function given to it, or by using additional hints you provide in the ``interact`` function call. If you have worked with the [``ipywidgets``](https://github.com/jupyter-widgets/ipywidgets) package you may already be familiar with this approach. (In fact, the Panel interact function is modeled on the one from ipywidgets, making it simpler to port code between the two platforms.) The basic idea is that given a function that returns some object, Panel will inspect the arguments to that function, try to infer appropriate widgets for those arguments, and then re-run that function to update the output whenever one of the widgets generates an event. For more detail on how interact creates widgets and other ways of using it, see the Panel [interact user guide](./interact.ipynb). This section instead focuses on when and why to use this API, laying out its benefits and drawbacks.\n", - "\n", - "The main benefit of this approach is convenience and ease of use. You start by writing some function that returns an object, be that a plot, a dataframe, or anything else that Panel can render. Then with a single call to `pn.interact()`, you can immediately get an interactive UI, without ever instantiating any widgets or wiring up any callbacks explicitly. Unlike ipywidgets, the ``pn.interact`` call will return a Panel. This Panel can then be further modified by laying out the widgets and output separately, or combining these components with other panes. Even though `pn.interact` itself is limited in flexibility compared to the rest of Panel, you can still unpack and reconfigure the results from it to generate fairly complex GUIs in very little code. \n", - "\n", - "#### Pros:\n", - "\n", - "+ Easy to use (or at least easy to get started!).\n", - "+ Doesn't typically require modifying existing visualization code.\n", - "\n", - "#### Cons:\n", - "\n", - "- Most of the behavior is implicit, with magic happening by introspection, making it difficult to see how to modify the appearance or functionality of the resulting object.\n", - "- Customizing the layout requires indexing into the panel returned by `interact`.\n", - "\n", - "The simplest `interact` call can be a one-liner, but here we'll show an example of intermediate complexity so that you get a good idea of what `interact` can do in practice. In this code, ``pn.interact`` infers the initial value for `x` and `y` from the `autompg_plot` function default arguments and their widget type and range from the `columns` list provided to `interact`. `interact` wouldn't normally put up a color widget because it would have no way of knowing that this string-type argument represents an RGB color, and so here we explicitly create a color-picker widget and pass that as the value for the color so that we can control the color as well. Finally, we unpack the result from `interact` and rearrange it in a different layout with a title, to create the final app. See the Panel [interact user guide](./interact.ipynb) for even simpler examples along with details about how to control the widgets and how to rearrange the layout." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "color = pn.widgets.ColorPicker(name='Color', value='#4f4fdf')\n", - "layout = pn.interact(autompg_plot, x=columns, y=columns, color=color)\n", - "\n", - "pn.Row(pn.Column('## MPG Explorer', layout[0]), layout[1])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Parameterized Classes\n", - "\n", - "The [Param](http://param.pyviz.org) library allows expressing the parameters of a class (or a hierarchy of classes) completely independently of a GUI implementation. Panel and other libraries can then take those parameter declarations and turn them into a GUI to control the parameters. This approach allows the parameters controlling some computation to be captured specifically and explicitly (but as abstract parameters, not as widgets). Then thanks to the ``@param.depends`` decorator (similar to `@panel.depends` but for use in Parameterized classes without any dependency on Panel), it is then possible to directly express the dependencies between the parameters and the computation defined in some method on the class, all without ever importing Panel or any other GUI library. The resulting objects can then be used in both GUI and non-GUI contexts (batch computations, scripts, servers).\n", - "\n", - "The parameterized approach is a powerful way to encapsulate computation in self-contained classes, taking advantage of object-oriented programming patterns. It also makes it possible to express a problem completely independently from Panel or any other GUI code, while still getting a GUI for free as a last step. For more detail on using this approach see the [Param user guide](./Param.ipynb).\n", - "\n", - "Pros:\n", - "\n", - "+ Declarative way of expressing parameters and dependencies between parameters and computation\n", - "+ The resulting code is not tied to any particular GUI framework and can be used in other contexts as well\n", - "\n", - "Cons:\n", - "\n", - "- Requires writing classes\n", - "- Less explicit about widgets to use for each parameter; can be harder to customize behavior than if widgets are instantiated explicitly\n", - "\n", - "In this model we declare a subclass of ``param.Parameterized``, declare the parameters we want at the class level, make an instance of the class, and finally lay out the parameters and plot method of the class." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import param\n", - "\n", - "class MPGExplorer(param.Parameterized):\n", - "\n", - " x = param.Selector(objects=columns)\n", - " y = param.Selector(default='hp', objects=columns)\n", - " color = param.Color(default='#0f0f0f')\n", - " \n", - " @param.depends('x', 'y', 'color') # optional in this case\n", - " def plot(self):\n", - " return autompg_plot(self.x, self.y, self.color)\n", - "\n", - "explorer = MPGExplorer()\n", - "\n", - "pn.Row(explorer.param, explorer.plot)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Callbacks\n", - "\n", - "The callback API in panel is the lowest-level approach, affording the greatest amount of flexibility but also quickly growing in complexity because each new interactive behavior requires additional callbacks that can interact in complex ways. Nonetheless, callbacks are important to know about, and can often be used to complement the other approaches. For instance, one specific callback could be used in addition to the more reactive approaches the other APIs provide.\n", - "\n", - "For more details on defining callbacks see the [Links user guide](./Links.ipynb).\n", - "\n", - "#### Pros:\n", - "\n", - "+ Complete and modular control over specific events\n", - "\n", - "#### Cons:\n", - "\n", - "- Complexity grows very quickly with the number of callbacks\n", - "- Have to handle initializing the plots separately\n", - "\n", - "In this approach we once again define the widgets. Unlike in other approaches we then have to define the actual layout, to ensure that the callback we define has something that it can update or replace. In this case we use a single callback to update the plot, but in many cases multiple callbacks might be required." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "x = pn.widgets.Select(value='mpg', options=columns, name='x')\n", - "y = pn.widgets.Select(value='hp', options=columns, name='y')\n", - "color = pn.widgets.ColorPicker(name='Color', value='#880588')\n", - "\n", - "layout = pn.Row(\n", - " pn.Column('## MPG Explorer', x, y, color),\n", - " autompg_plot(x.value, y.value, color.value))\n", - "\n", - "def update(event):\n", - " layout[1].object = autompg_plot(x.value, y.value, color.value)\n", - "\n", - "x.param.watch(update, 'value')\n", - "y.param.watch(update, 'value')\n", - "color.param.watch(update, 'value')\n", - "\n", - "layout" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Summary\n", - "\n", - "As we have seen, each of these four APIs allows building the same basic application. The choice of the appropriate API depends very much on the use case. To build a quick throwaway GUI the ``interact`` approach can be completely sufficient. A much more explicit, flexible, and maintainable version of that approach is to define a reactive function that is bound directly to a set of widgets using `pn.bind`. When writing libraries or other code that might be used independently of the actual GUI, a Parameterized class can be a great way to organize the code. Finally, if you need low-level control or want to complement any of the other approaches, defining explicit callbacks can be the best approach. Nearly all of the functionality of Panel can be accessed using any of the APIs, but each makes certain things much easier than others. Choosing the API is therefore a matter of considering the tradeoffs and of course also a matter of preference. If you still aren't sure after reading the above, then just go with the `pn.bind` reactive API!" - ] - } - ], - "metadata": { - "language_info": { - "name": "python", - "pygments_lexer": "ipython3" - } - }, - "nbformat": 4, - "nbformat_minor": 4 -}