{ "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 make all these different ways of using Panel possible, 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 the ``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": [ "## 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 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 implementation is modeled on that reference implementation). 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. Unlike ipywidgets, the ``pn.interact`` call will return a Panel, which can then be further modified by laying out the widgets and output separately or combining these components with other panes if you wish. Thus even though `pn.interact` itself is limited in flexibility, you can unpack and reconfigure the results from it to generate fairly complex GUIs in very little code. \n", "\n", "#### Pros:\n", "\n", "+ Easy to use.\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", "- Layouts can be customized, but requires indexing into the panel returned by `interact`.\n", "\n", "In the example below, ``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` would have no way of knowing that a color picker would be useful for the `color` argument (as each color is simply a string), 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 details of 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": [ "## Reactive Functions\n", "\n", "The reactive programming API is very similar to the ``interact`` function but makes it possible to explicitly declare the inputs to the function using the ``depends`` decorator and also makes the layout of the different components more explicit. The ``pn.depends`` decorator is a powerful way to declare the parameters a function depends on. By decorating a function with `pn.depends`, we declare that when those parameters change the function should be called with the new values of those parameters. This approach makes it very explicit which parameters the function depends on and ties it directly to the objects that control it. Once a function has been annotated in this way, it can be laid out alongside the widgets.\n", "\n", "#### Pros:\n", "\n", "+ Very clear mapping from the inputs to the arguments of the function.\n", "+ Very explicit layout of each of the different components.\n", "\n", "#### Cons:\n", "\n", "- Mixes the definition of the function with the GUI elements it depends on.\n", "\n", "In this model, we declare all the widgets we will need first, then declare a function linked to those widgets using the ``pn.depends`` decorator, and finally lay out the widgets and the ``autompg_plot`` function explicitly." ] }, { "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.param.value, y.param.value, color.param.value)\n", "def autompg_plot(x, y, color):\n", " return autompg.hvplot.scatter(x, y, c=color)\n", "\n", "pn.Row(\n", " pn.Column('## MPG Explorer', x, y, color),\n", " autompg_plot)" ] }, { "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, 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](./Parameters.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 more more explicit, flexible, and maintainable version of that approach is to define a reactive function that links directly to a set of widgets. 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." ] } ], "metadata": { "language_info": { "name": "python", "pygments_lexer": "ipython3" } }, "nbformat": 4, "nbformat_minor": 2 }