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

Tutorial 12. Parameters and Widgets

" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In previous sections we have mostly been learning about using HoloViews to build visualizations. There is only so much information that can be packed on to the screen at once, and in practice it's often necessary to supply interactive widgets so the user can further select what is shown. HoloViews can provide widgets automatically for dimensions that have been declared on the data, but often you will want to express other types of user-settable parameters to control your plots. Here we will discover how to leverage the [Panel](https://panel.pyviz.org) layout/widget/app/dashboard/web-ui library, along with the [Param](https://param.pyviz.org) library to declare your own custom parameters in a GUI-independent way. Param and Panel both work with nearly any plotting library, not just HoloViews, but we'll mainly focus on HoloViews examples because those should be familiar by now and because they make it simple to express even quite complex interactive behavior.\n", "\n", "
\n", "\n", "\n", "\n", "\n", "\n", "
" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import param\n", "import panel as pn\n", "import numpy as np\n", "import holoviews as hv\n", "\n", "hv.extension('bokeh')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Declaring parameters" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As a first step, let's declare a so called ``Parameterized`` class with a number of parameters, which we can use to control our visualization (or any Python code!). In this example we will stick with something simple and we will simply expose some styling options for a Curve plot, declaring ``alpha``, ``color``, and ``line_width`` parameters." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "class StyleOptions(param.Parameterized):\n", " alpha = param.Magnitude(default=0.8)\n", " color = param.ObjectSelector(default='red', objects=['red', 'green', 'blue'])\n", " line_width = param.Number(default=1, bounds=(0, 10)) " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Displaying parameters" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can automatically get a UI to control these parameters using Panel. Simply pass the ``Parameterized`` instance to a `pn.Row` or `pn.Column` layout object, and it will display a set of widgets that originally came from the Bokeh library:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "opts = StyleOptions(name=\"\")\n", "opts" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pn.Row(opts)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Panel provides layout and two-way communication between Python and browser-based widgets, either in a Jupyter Notebook or in a separate widely shareable deployment. In this way it's similar to ipywidgets in a Jupyter notebook or Plotly Dash on a standalone server, but allows you to develop in Jupyter and deploy separately because the same code works in both contexts (unlike for ipywidgets or Dash). \n", "\n", "Panel is fully reactive, which makes it simple to use from Python. For instance, if you change one of the widgets above and then read it from python, you'll get whatever value you set:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "opts.line_width" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Similarly, if you set the value from Python you should see the widget update to the new value (an example of reactive programming):" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "opts.line_width=7" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In addition to allowing you to add widgets when needed, Param also provides documentation and optional bounds and type checking for your parameters, making it much simpler to write safe and clear code:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "try:\n", " opts.line_width=12\n", "except ValueError as e:\n", " print(e)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "try:\n", " opts.line_width=\"very wide\"\n", "except ValueError as e:\n", " print(e)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In this way, you can declare parameters using the GUI-independent pure-Python toolkit Param, which has no dependencies and can thus be used throughout your code, while also being able to display browser-based widgets for interactive setting of those objects when needed.\n", "\n", "\n", "## Linking parameters as streams\n", "\n", "As a final step we will link the Parameterized class and the widgets to an actual plot. To do this, we will declare a HoloViews ``DynamicMap``, which sets the style options on an existing ``Curve``. The only thing we need to declare is that the ``DynamicMap`` should listen to the `options` object for changes, which will make the plot update when the widgets change the parameters:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "options = StyleOptions(name=\"\")\n", "curve = hv.Curve(np.sin(np.linspace(0, np.pi*3, 100)))\n", "dmap = hv.DynamicMap(lambda **kw: curve.opts(**kw), streams=[options])\n", "\n", "pn.Row(options, dmap)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Of course, you can still also change the parameter by hand in Python, with the widgets and the plot updating automatically, because both the plot (and the widgets) is watching the *parameters*, not the widget state." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "options.alpha=0.2" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "## Exercise: Subclass the StyleOptions class and add a parameter to control the line_dash style option\n", "\n", "# Hint: The valid options for line_dash are: 'solid', 'dashed', 'dotted', 'dotdash', 'dashdot'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "## Exercise: Make a simple dashboard using some of the many, many types of objects\n", "## that Panel can lay out (https://github.com/pyviz/panel/issues/2)\n", "\n", "# Hint: The valid options for line_dash are: 'solid', 'dashed', 'dotted', 'dotdash', 'dashdot'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "## Exercise: Make a simple dashboard using some of the [many, many types of objects](https://github.com/pyviz/panel/issues/2)\n", "\n", "# Hint: The valid options for line_dash are: 'solid', 'dashed', 'dotted', 'dotdash', 'dashdot'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n", "# Onwards\n", "\n", "The following section shows how to deploy your visualizations using [Bokeh server](./13_Deploying_Bokeh_Apps.ipynb)." ] } ], "metadata": { "language_info": { "name": "python", "pygments_lexer": "ipython3" } }, "nbformat": 4, "nbformat_minor": 2 }