{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", " Note: This guide is written for an interactive environment such as Jupyter notebooks. The interactive widgets will not work in a static version of this documentation. Instructions for installing Panel and the example notebooks can be found in the Installation Guide\n", "
\n", "\n", "Panel lets you add interactive controls for just about anything you can display in Python. Panel can help you build simple interactive apps, complex multi-page dashboards, or anything in between. As a simple example, let's say we have loaded the [UCI ML dataset measuring the environment in a meeting room](http://archive.ics.uci.edu/ml/datasets/Occupancy+Detection+):" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import pandas as pd; import numpy as np; import matplotlib.pyplot as plt\n", "\n", "data = pd.read_csv('https://raw.githubusercontent.com/holoviz/panel/master/examples/assets/occupancy.csv')\n", "data['date'] = data.date.astype('datetime64[ns]')\n", "data = data.set_index('date')\n", "\n", "data.tail()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And we've written some code that smooths a time series and plots it using Matplotlib with outliers highlighted:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from matplotlib.figure import Figure\n", "from matplotlib.backends.backend_agg import FigureCanvas\n", "\n", "%matplotlib inline\n", "\n", "def mpl_plot(avg, highlight):\n", " fig = Figure()\n", " FigureCanvas(fig) # not needed in mpl >= 3.1\n", " ax = fig.add_subplot()\n", " avg.plot(ax=ax)\n", " if len(highlight): highlight.plot(style='o', ax=ax)\n", " return fig\n", "\n", "def find_outliers(variable='Temperature', window=30, sigma=10, view_fn=mpl_plot):\n", " avg = data[variable].rolling(window=window).mean()\n", " residual = data[variable] - avg\n", " std = residual.rolling(window=window).std()\n", " outliers = (np.abs(residual) > std * sigma)\n", " return view_fn(avg, avg[outliers])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can call the function with parameters and get a plot:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "find_outliers(variable='Temperature', window=20, sigma=10)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "It works! But exploring all these parameters by typing Python is slow and tedious. Plus we want our boss, or the boss's boss, to be able to try it out." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If we wanted to try out lots of combinations of these values to understand how the window and sigma affect the plot, we could reevaluate the above cell lots of times, but that would be a slow and painful process, and is only really appropriate for users who are comfortable with editing Python code. In the next few examples we will demonstrate how to use Panel to quickly add some interactive controls to some object and make a simple app. \n", "\n", "To see an overview of the different APIs Panel offers see the [API user guide](../user_guide/APIs.ipynb) and for a quick reference for various Panel functionality see the [overview](../user_guide/Overview.ipynb).\n", "\n", "## Interactive Panels\n", "\n", "Instead of editing code, it's much quicker and more straightforward to use sliders to adjust the values interactively. You can easily make a Panel app to explore a function's parameters using `pn.interact`, which is similar to the [ipywidgets interact function](https://ipywidgets.readthedocs.io/en/stable/examples/Using%20Interact.html):" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import panel as pn\n", "pn.extension()\n", "\n", "pn.interact(find_outliers)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As long as you have a live Python process running, dragging these widgets will trigger a call to the `find_outliers` callback function, evaluating it for whatever combination of parameter values you select and displaying the results. A Panel like this makes it very easy to explore any function that produces a visual result of a [supported type](https://github.com/pyviz/panel/issues/2), such as Matplotlib (as above), Bokeh, Plotly, Altair, or various text and image types.\n", "\n", "## Components of Panels\n", "\n", "`interact` is convenient, but what if you want more control over how it looks or works? First, let's see what `interact` actually creates, by grabbing that object and displaying its representation:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "kw = dict(window=(1, 60), variable=sorted(list(data.columns)), sigma=(1, 20))\n", "i = pn.interact(find_outliers, **kw)\n", "i.pprint()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As you can see, the `interact` call created a `pn.Column` object consisting of a WidgetBox (with 3 widgets) and a `pn.Row` with one Matplotlib figure object. Panel is compositional, so you can mix and match these components any way you like, adding other objects as needed:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "text = \"
\\n# Room Occupancy\\nSelect the variable, and the time window for smoothing\"\n", "\n", "p = pn.Row(i[1][0], pn.Column(text, i[0][0], i[0][1]))\n", "p" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that the widgets stay linked to their plot even if they are in a different notebook cell:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "i[0][2]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Also note that Panel widgets are reactive, so they will update even if you set the values by hand:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "i[0][2].value = 5" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Composing new Panels\n", "\n", "You can use this compositional approach to combine different components such as widgets, plots, text, and other elements needed for an app or dashboard in arbitrary ways. The ``interact`` example builds on a reactive programming model, where an input to the function changes and Panel reactively updates the output of the function. ``interact`` is a convenient way to create widgets from the arguments to your function automatically, but Panel also provides a more explicit reactive API letting you specifically define connections between widgets and function arguments, and then lets you compose the resulting dashboard manually from scratch.\n", "\n", "In the example below we explicitly declare each of the components of an app: widgets, a function to return the plot, column and row containers, and the completed `occupancy` Panel app. Widget objects have multiple \"parameters\" (current value, allowed ranges, and so on), and here we will use Panel's ``bind`` function to declare that function's input values should come from the widgets' ``value`` parameters. Now when the function and the widgets are displayed, Panel will automatically update the displayed output whenever any of the inputs change:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import panel.widgets as pnw\n", "\n", "variable = pnw.RadioButtonGroup(name='variable', value='Temperature', \n", " options=list(data.columns))\n", "window = pnw.IntSlider(name='window', value=10, start=1, end=60)\n", "\n", "reactive_outliers = pn.bind(find_outliers, variable, window, 10)\n", "\n", "widgets = pn.Column(\"
\\n# Room occupancy\", variable, window)\n", "occupancy = pn.Row(reactive_outliers, widgets)\n", "occupancy" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Deploying Panels\n", "\n", "The above panels all work in the notebook cell (if you have a live Jupyter kernel running), but unlike other approaches such as ipywidgets, Panel apps work just the same in a standalone server. For instance, the app above can be launched as its own web server on your machine by uncommenting and running the following cell:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#occupancy.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Or, you can simply mark whatever you want to be in the separate web page with `.servable()`, and then run the shell command `panel serve --show Introduction.ipynb` to launch a server containing that object. (Here, we've also added a semicolon to avoid getting another copy of the occupancy app here in the notebook.)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "occupancy.servable();" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "During development, particularly when working with a raw script using `panel serve --show --autoreload` can be very useful as the application will automatically update whenever the script or notebook or any of its imports change." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Declarative Panels\n", "\n", "The above compositional approach is very flexible, but it ties your domain-specific code (the parts about sine waves) with your widget display code. That's fine for small, quick projects or projects dominated by visualization code, but what about large-scale, long-lived projects, where the code is used in many different contexts over time, such as in large batch runs, one-off command-line usage, notebooks, and deployed dashboards? For larger projects like that, it's important to be able to separate the parts of the code that are about the underlying domain (i.e. application or research area) from those that are tied to specific display technologies (such as Jupyter notebooks or web servers). \n", "\n", "For such usages, Panel supports objects declared with the separate [Param](http://param.pyviz.org) library, which provides a GUI-independent way of capturing and declaring the parameters of your objects (and dependencies between your code and those parameters), in a way that's independent of any particular application or dashboard technology. For instance, the above code can be captured in an object that declares the ranges and values of all parameters, as well as how to generate the plot, independently of the Panel library or any other way of interacting with the object:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import param\n", "\n", "class RoomOccupancy(param.Parameterized):\n", " variable = param.Selector(objects=list(data.columns))\n", " window = param.Integer(default=10, bounds=(1, 20))\n", " sigma = param.Number(default=10, bounds=(0, 20))\n", "\n", " def view(self):\n", " return find_outliers(self.variable, self.window, self.sigma)\n", " \n", "obj = RoomOccupancy()\n", "obj" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `RoomOccupancy` class and the `obj` instance have no dependency on Panel, Jupyter, or any other GUI or web toolkit; they simply declare facts about a certain domain (such as that smoothing requires window and sigma parameters, and that window is an integer greater than 0 and sigma is a positive real number). This information is then enough for Panel to create an editable and viewable representation for this object without having to specify anything that depends on the domain-specific details encapsulated in `obj`:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pn.Row(obj.param, obj.view)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To support a particular domain, you can create hierarchies of such classes encapsulating all the parameters and functionality you need across different families of objects, with both parameters and code inheriting across the classes as appropriate, all without any dependency on a particular GUI library or even the presence of a GUI at all. This approach makes it practical to maintain a large codebase, all fully displayable and editable with Panel, in a way that can be maintained and adapted over time." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Linking plots and actions between panes\n", "\n", "The above approaches each work with a very wide variety of displayable objects, including images, equations, tables, and plots. In each case, Panel provides interactive functionality using widgets and updates the displayed objects accordingly, while making very few assumptions about what actually is being displayed. Panel also supports richer, more dynamic interactivity where the displayed object is itself interactive, such as the JavaScript-based plots from Bokeh and Plotly.\n", "\n", "For instance, if we substitute the [Bokeh](http://bokeh.pydata.org) wrapper [hvPlot](http://hvplot.pyviz.org) for the Matplotlib wrapper provided with Pandas, we automatically get interactive plots that allow zooming, panning and hovering:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import hvplot.pandas\n", "\n", "def hvplot(avg, highlight):\n", " return avg.hvplot(height=200) * highlight.hvplot.scatter(color='orange', padding=0.1)\n", "\n", "text2 = \"## Room Occupancy\\nSelect the variable and the smoothing values\"\n", "hvp = pn.interact(find_outliers, view_fn=hvplot, **kw)\n", "pn.Column(pn.Row(pn.panel(text2, width=400), hvp[0]), hvp[1]).servable(\"Occupancy\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "These interactive actions can be combined with more complex interactions with a plot (e.g. tap, hover) to make it easy to explore data more deeply and uncover connections. For instance, we can use HoloViews to make a more full-featured version of the hvPlot example that displays a table of the current measurement values at the hover position on the plot:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import holoviews as hv\n", "\n", "tap = hv.streams.PointerX(x=data.index.min())\n", "\n", "def hvplot2(avg, highlight):\n", " line = avg.hvplot(height=300, width=500)\n", " outliers = highlight.hvplot.scatter(color='orange', padding=0.1)\n", " tap.source = line\n", " return (line * outliers).opts(legend_position='top_right')\n", "\n", "@pn.depends(tap.param.x)\n", "def table(x):\n", " index = np.abs((data.index - x).astype(int)).argmin()\n", " return data.iloc[index]\n", "\n", "app = pn.interact(find_outliers, view_fn=hvplot2, **kw)\n", "\n", "pn.Row(\n", " pn.Column(\"## Room Occupancy\\nHover over the plot for more information.\", app[0]),\n", " pn.Row(app[1], table)\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Exploring further\n", "\n", "For a quick reference of different Panel functionality refer to the [overview](../user_guide/Overview.ipynb). If you want a more detailed description of different ways of using Panel, each appropriate for different applications see the following materials:\n", "\n", "- [APIs](../user_guide/APIs.ipynb): An overview of the different APIs offered by Panel.\n", "- [Interact](../user_guide/Interact.ipynb): Instant GUI, given a function with arguments\n", "- [Widgets](../user_guide/Widgets.ipynb): Explicitly instantiating widgets and linking them to actions\n", "- [Parameters](../user_guide/Param.ipynb): Capturing parameters and their links to actions declaratively\n", "\n", "Just pick the style that seems most appropriate for the task you want to do, then study that section of the user guide. Regardless of which approach you take, you'll want to learn more about Panel's panes and layouts:\n", "\n", "- [Components](../user_guide/Components.ipynb): An overview of the core components of Panel including Panes, Widgets and Layouts\n", "- [Customization](../user_guide/Customization.ipynb): How to set styles and sizes of Panel components\n", "- [Deploy & Export](../user_guide/Deploy_and_Export.ipynb): An overview on how to display, export and deploy Panel apps and dashboards\n", "\n", "\n", "Finally, if you are building a complex multi-stage application, you can consider our support for organizing workflows consisting of multiple stages:\n", "\n", "- [Pipelines](../user_guide/Pipelines.ipynb): Making multi-stage processing pipelines in notebooks and as deployed apps\n", "\n", "Or for more polished apps you can make use of Templates to achieve exactly the look and feel you want:\n", "\n", "- [Templates](../user_guide/Templates.ipynb): Composing one or more Panel objects into jinja2 template with full control over layout and styling." ] } ], "metadata": { "language_info": { "name": "python", "pygments_lexer": "ipython3" } }, "nbformat": 4, "nbformat_minor": 2 }