{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "\n", "

Tutorial 2: Building Panels

\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "\n", "Panel is designed to make it simple to add interactive controls to your existing\n", "plots and data displays, simple to build apps for your own use in a notebook,\n", "simple to deploy apps as standalone dashboards to share with colleagues, and\n", "seamlessly shift back and forth between each of these tasks as your needs\n", "evolve. If there is one thing you should take away from this tutorial, it's\n", "Panel!\n", "\n", "Throughout this tutorial we will use a wave heights dataset collected by NOAA,\n", "so will start by loading it:\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "from load_data import *\n", "\n", "df = load_data()\n", "print(df.shape)\n", "df.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Additionally before displaying anything with Panel it is always necessary to\n", "load the Panel extension:\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import panel as pn\n", "\n", "pn.extension()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## pn.interact\n", "\n", "Before we get into the details of how Panel allows you to render and lay out\n", "objects we will dive straight in and use Panel's `interact` function, modeled on\n", "the similar function in `ipywidgets`, to get a simple interactive app\n", "immediately. For instance, if you have a function that returns a row of a\n", "dataframe given an index, you can very easily make a panel with a widget to\n", "control the row displayed.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def select_row(row=0):\n", " row = df.loc[row].to_frame()\n", " return row.style.format({\"time\": lambda t: t.strftime(\"%c\")})\n", "\n", "\n", "pn.interact(select_row, row=(0, len(df) - 1))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This approach can be used for any function that returns a displayable object,\n", "calling the function whenever one of the parameters of that function has\n", "changed.\n", "\n", "In the spirit of \"shortcuts, not dead ends\", let's see what's in the object\n", "returned by `interact`:\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "app = pn.interact(select_row, row=(0, len(df) - 1))\n", "\n", "print(app)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here, `interact` has constructed a Column panel consisting of one Column of\n", "widgets (with one widget), and one Row of output (with one HTML pane). This\n", "object, once created, is a full compositional Panel object, and can be\n", "reconfigured and expanded with additional content if you wish, without breaking\n", "the connections between widgets and values:\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pn.Column(\"## Choose a row\", pn.Row(app[0], app[1]))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Hopefully from this simple example you can see the sorts of things Panel can do.\n", "In the rest of this section we'll cover some of the items you can use in a panel\n", "and how to compose them. In the subsequent section we will dive into how to set\n", "up widgets and their relationships explicitly, and then build a custom dashboard\n", "as an exercise. For now, we won't show code for any particular plotting library,\n", "but if you have a favorite one already, you should be able to use it with Panel\n", "in the exercises.\n", "\n", "## Component types\n", "\n", "Before we start building more interactive apps, we will learn about the three\n", "main types of components in Panel:\n", "\n", "- **Pane**: A Pane provides a view of an external object (text, image, plot,\n", " etc.) by wrapping it\n", "- **Panel**: A Panel lays out multiple components in a row, column, or grid.\n", "- **Widget**: A Widget provides input controls to add interactive features to\n", " your Panel.\n", "\n", "If you ever want to discover how a particular component works, see the\n", "[reference gallery](https://panel.pyviz.org/reference/index.html).\n", "\n", "## Displaying content\n", "\n", "The fundamental concept behind Panel is that it transforms the objects you give\n", "it into a viewable object that can be composed into a layout and updated\n", "dynamically. In this tutorial we will be building a dashboard visualizing a\n", "dataset of earthquake events, so let us start by displaying a title using the\n", "`pn.panel` function:\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "title = pn.panel(\"## Major Waves Dashboard\")\n", "\n", "title" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "df" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To understand how Panel rendered this string we can take a look at the textual\n", "representation of this object:\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# top 5 waves in July 2021\n", "df_sorted = df.sort_values(by=[\"wvht\"], ascending=False)\n", "df_sorted.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Panel transformed the `str` object and wrapped it in a so-called `Markdown`\n", "`Pane`. The `pn.panel` function attempts to find the most appropriate\n", "representation for different objects whether it is a string, an image, or even a\n", "plot. So if we provide the location of a PNG file instead as a path or a URL,\n", "the `panel` function will automatically infer that it should be rendered as an\n", "image:\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "noaa_logo = pn.panel(\"../assets/noaa-lrg.png\", height=130)\n", "noaa_logo" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The appropriate representation is resolved using a set of precedences, so it may\n", "sometimes be necessary to explicitly declare the type of Pane that is required.\n", "For example, if we want to display some `HTML`, which cannot easily be\n", "distinguished from Markdown, we can explicitly declare it by specifying the\n", "`HTML` Pane type from the `pn.pane` module:\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pn.pane.HTML(\n", " \"Breaking news: Major waves off coast of Rat Islands\"\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Exercise\n", "\n", "Use the `pn.panel` function to display some different types of objects, e.g. an\n", "image, a pandas dataframe or even a plot you made.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now construct the same type of object by explicitly constructing the appropriate\n", "type in the `pn.pane` module (Hint: Print the object in the previous example to\n", "find out what it is):\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Laying out content\n", "\n", "In addition to `Pane` objects, Panel provides `Panel` objects that allow laying\n", "out components. The principal layouts are by `Row` or `Column`. These components\n", "act just like a regular `list` in Python:\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "column = pn.Column(title, noaa_logo, app)\n", "\n", "column" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Panels may be nested arbitrarily to construct complex layouts. Internally, Panel\n", "will call the `pn.panel` function on any objects which are not already a known\n", "component type, making it easy to lay out objects without explicitly wrapping\n", "them in a panel component, though wrapping it explicitly can help ensure that it\n", "is the type you expect:\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "\n", "df_top5 = pd.DataFrame(df_sorted[0:10], columns=[\"station\", \"time\", \"wvht\"])\n", "\n", "row = pn.Row(column, pn.Column(\"### Top 5\", pn.panel(df_top5, width=500)))\n", "\n", "row" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Exercise\n", "\n", "Use `Row` and `Column` panels to lay out some of the objects (text, images, or\n", "plots) you rendered in the previous exercise.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Try swapping a `Row` or `Column` with a `Tabs` component:\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Updating content\n", "\n", "So far we have only seen how Panel can be used to render and lay out objects,\n", "which on its own is not too exciting. However, all Panel objects can be modified\n", "once constructed, and all associated views will automatically update in\n", "response. Let us take the `title` pane we constructed above as an example:\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This dynamic behavior also extends to Panels, whose Panes will update as they\n", "are changed:\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "column" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As we discussed above `Row` and `Column` Panels behave much like lists. Just\n", "like lists, we can use `append`, `pop`, `remove`, `insert`, and all the other\n", "standard methods (including setting with =) to modify them. E.g. we could\n", "dynamically insert a caption for the logo:\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "text = \"\"\"\n", "This dashboard displays the tallest waves\n", "\"\"\"" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "column.insert(2, text)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you uncomment and execute that cell, make sure to scroll back up to confirm\n", "that all versions of the `column` have been updated.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Exercise: Construct and display a new Panel and then replace a component in a new cell\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
Hint
\n", "\n", "Use `print()` to see how objects are indexed, then use indexing to replace a\n", "component:\n", "\n", "```python\n", "row[1] = \"../assets/panel.png\"\n", "```\n", "\n", "
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Serving Panel apps\n", "\n", "So far we have been building Panel objects entirely within a notebook, which is\n", "a convenient way to build up an app, particularly for one-time or private use.\n", "However, a large part of the power of Panel is that it allows us to seamlessly\n", "transition between an iterative process inside the notebook to a deployed app.\n", "When developing an app inside the notebook it will display inline, but you can\n", "see what it looks like as a deployed app by calling `.show()` on the object. As\n", "long you are running this notebook on a local machine this will open a new\n", "window:\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# row.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "otherwise let us simply look at what we have built here in the notebook:\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "row" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Once you are happy with the app or dashboard you have built you can annotate it\n", "with `.servable()`. In the notebook, this annotation has no effect, but it will\n", "later indicate to the `panel serve` command that you want this particular\n", "component to be served. If you then go to the command line and run\n", "`panel serve 02_Building_Panels.ipynb`, the code cells in the notebook will be\n", "executed and the resulting dashboard will be available as a web server.\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "row.servable()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that despite this deep support for switching back and forth between Jupyter\n", "and deployed servers, Jupyter is in no way required for this process -- the same\n", "deployed dashboard can be obtained by simply exporting the code cells here into\n", "their own .py file, and doing `panel serve 02_Building_Panels.py`.\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Exercise\n", "\n", "Build a Panel component (or reuse one from a previous exercise) and call\n", "`.show()` on it.\n" ] } ], "metadata": { "kernelspec": { "display_name": "Python [conda env:root] *", "language": "python", "name": "conda-root-py" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.6" } }, "nbformat": 4, "nbformat_minor": 4 }