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

Tutorial 3: Interlinked Panels

" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import panel as pn\n", "\n", "pn.extension()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In the previous section we learned the very basics of working with Panel. Specifically we looked at the different types of components, how to update them and how to serve a Panel application or dashboard. However to start building actual apps with Panel we need to be able to add interactivity by linking different components together. In this section we will learn how to link widgets to outputs to start building some simple interactive applications.\n", "\n", "In this section we will once again make use of the earthquake dataset we loaded previously:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import dask.dataframe as dd\n", "\n", "df = dd.read_parquet('../data/earthquakes.parq', columns=['time', 'place', 'mag']).reset_index(drop=True).persist()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Widgets and links\n", "\n", "`pn.interact` constructs widgets automatically that can then be reconfigured, but if you want more control, you'll want to instantiate widgets explicitly. A widget is an input control that allows a user to change a ``value`` using some graphical UI. A simple example is a ColorPicker:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "color_picker = pn.widgets.ColorPicker(value='#ff0000')\n", "\n", "color_picker" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here the widget `value` is a [Parameter](https://param.pyviz.org) that is set to a string specifying a color. Parameters are an extended type of Python attribute that declare their type, range, etc. so that other code can interact with them in a consistent way. When we change the color using the widget the ``value`` parameter updates, and vice versa if you change the value parameter manually:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "color_picker.value" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# color_picker.value='#559977'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now that we have a widget, we can link its ``value`` parameter to a parameter on some other object. Here, let's link it to the ``background`` parameter on a ``HTML`` pane:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "html = pn.pane.HTML('', width=200, height=200, background=color_picker.value)\n", "color_picker.link(html, value='background')\n", "\n", "html" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Whenever the color is changed the background of the HTML pane will update, because the `link` method set up the HTML pane to \"watch\" for changes in that value and update as needed. Linking may also be done directly in Javascript, which allows building apps which do not require a live server or notebook kernel. For more detail see the [Links user guide](http://panel.pyviz.org/user_guide/Links.html)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Exercise\n", "\n", "Use tab-completion on the ``pn.widgets`` namespace to discover some other widget, construct a string pane initialized to the `value` parameter of that widget, and then link the widget's value to the string pane's ``object`` parameter. Finally display both the widget and the pane in a panel." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
Solution
\n", "\n", "```python\n", "w = pn.widgets.FloatSlider()\n", "s = pn.pane.Str(w.value)\n", "w.link(s, value=\"object\")\n", "pn.Row(w,s)\n", "```\n", " \n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Callbacks\n", "\n", "The ability to link parameters is somewhat limited, in that it can only pass values through unchanged. Panel also supports the more general approach of writing callbacks in response to changes in some parameter, e.g. the ``value`` of a widget. All parameters can be watched using the ``.param.watch`` API, which will call the provided callback with an event object containing the old and new value of the widget." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now that it is loaded we will create a slider which we will eventually use to select the row of the dataframe that we want to display:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "row_slider = pn.widgets.IntSlider(value=0, start=0, end=len(df)-1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next we create a Pane to display the current row of the dataframe with times formatted nicely:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def timeformat(df):\n", " return df.compute().style.format({\"time\": lambda t: t.strftime(\"%c\")})\n", "\n", "row_pane = pn.panel(timeformat(df.loc[row_slider.value]))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now that we have defined both the widget and the object we want to update we can declare a callback to link the two. As we learned in the previous section assigning a new value to the ``object`` of a pane will update the display. In the callback we select the row of the dataframe and then assign it to the ``pane.object``." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def df_callback(event):\n", " row_pane.object = timeformat(df.loc[event.new])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Lastly we actually have to register this callback, to do so we provide the callback and the parameter we want to trigger the event on the slider's ``.param.watch`` method:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "row_slider.param.watch(df_callback, 'value')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now that everything is connected up we can put both the widget and the pane in a panel and display them:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pn.Column(row_slider, row_pane)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As you can see, we've fairly laboriously built up what `pn.interact` gave us immediately above, but doing it in this way should help you see how everything fits together." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Declaring interactive components\n", "\n", "In between the fully automated `pn.interact` and the fully manual but quite messy and verbose callbacks above, Panel offers a very concise, powerful approach of declaring dependencies between the parameters of one object and the arguments to a function. In practice, this middle ground provides enough control for nearly any app, without the complexity of explicit chains of callbacks.\n", "\n", "As a very simple example we will declare a ``TextInput`` widget and then write a function that uses Markdown syntax to convert the text into a title. Using the ``pn.depends`` decorator we can then declare that this function depends on widget value (to be more explicit you can explicitly declare the dependency on the `text_input.param.value` object). Finally we lay out the widget and the function:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "text_input = pn.widgets.TextInput(value='A title')\n", "\n", "@pn.depends(text_input)\n", "def title_text(value):\n", " return '## ' + value\n", "\n", "app2 = pn.Row(text_input, title_text)\n", "app2" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "After entering some text into the widget (and pressing enter), the title text should update (if you have a live Python server running). In this way we can easily declare dynamic components that depend on the value of a widget (or any other component that has Parameters). Regardless of whether you use interact, callbacks, or dependency declarations, all of the apps will be some composition of panes into a panel, with links or callbacks connecting things up:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(app2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Exercise\n", "\n", "Declare two ``Spinner`` widgets with an initial value of 1, then declare a function that depends on the values of both widgets and adds them together. Finally lay out the two widgets and the function in a Panel:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
Solution
\n", "\n", "```python\n", "a = pn.widgets.Spinner(value=1, width=60)\n", "b = pn.widgets.Spinner(value=1, width=60)\n", "\n", "@pn.depends(a.param.value, b.param.value)\n", "def adder(a, b):\n", " return a + b\n", "\n", "pn.Row(a, '+', b, '=', adder)\n", "```\n", " \n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Moving onwards\n", "\n", "Now that we have learned to link parameters between displayed objects and build interactive components, we can start building actual apps and dashboards. Before we move on to plotting and visualization let us quickly use what we have learned by adding interactivity to [the dashboard we built in the previous exercise](./exercises/Building_a_Dashboard.ipynb)." ] } ], "metadata": { "language_info": { "name": "python", "pygments_lexer": "ipython3" } }, "nbformat": 4, "nbformat_minor": 4 }