{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import holoviews as hv\n", "import panel as pn\n", "\n", "hv.extension(\"bokeh\", \"plotly\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "[HoloViews](https://holoviews.org/) is a popular and powerful data visualization library supporting many data and plotting *backends*.\n", "\n", "[hvPlot](https://hvplot.holoviz.org/index.html) (quick viz) and [GeoViews](https://holoviz.org/assets/geoviews.png) (spatial viz) are built on top of HoloViews and produces `HoloViews` objects.\n", "\n", "**Panel, HoloViews, hvPlot and GeoViews are all members of the [HoloViz](https://holoviz.org) ecosystem and you can expect them to work perfectly together**.\n", "\n", " \n", "\n", "In this reference notebook we will assume a basic level of understanding of HoloViews and optionally hvPlot or Geoviews if you want to use them with Panel.\n", "\n", "---\n", "\n", "The `HoloViews` pane renders [HoloViews](https://holoviews.org/) objects with one of the *plotting backends* supported by HoloViews. This includes objects produced by [hvPlot](https://hvplot.holoviz.org/index.html) and [GeoViews](https://holoviz.org/assets/geoviews.png).\n", "\n", "The `HoloViews` pane supports displaying interactive [`HoloMap`](https://holoviews.org/reference/containers/bokeh/HoloMap.html) and [`DynamicMap`](https://holoviews.org/reference/containers/bokeh/DynamicMap.html) objects containing widgets. The `HoloViews` pane even allows customizing the widget types and their position relative to the plot.\n", "\n", "#### Parameters:\n", "\n", "For details on other options for customizing the component see the [layout](../../how_to/layout/index.md) and [styling](../../how_to/styling/index.md) how-to guides.\n", "\n", "The main argument is the `object` parameter\n", "\n", "* **`object`** (object): The HoloViews object being displayed\n", "\n", "You can control the way the `object` is rendered and sharing axes with other plots via the parameters\n", "\n", "* **`backend`** (str): Any of the supported HoloViews backends ('bokeh', 'matplotlib', or 'plotly'). If none is specified defaults to the active holoviews renderer. This is by default 'bokeh'.\n", "* **`linked_axes`** (boolean, default=True): Whether to link axes across plots in a panel layout\n", "* **`format`** (str, default='png'): The output format to use when plotting a Matplotlib plot. \n", "* **`renderer`**: Explicit [HoloViews Renderer](https://holoviews.org/user_guide/Plots_and_Renderers.html#renderers) instance to use for rendering the HoloViews plot. Overrides the` backend` parameter.\n", "* **`theme` (str, Theme)**: [Bokeh theme](https://docs.bokeh.org/en/latest/docs/reference/themes.html) to apply to the HoloViews plot.\n", "\n", "You can access the layout and (optional) widgets via\n", "\n", "* **`layout`** (pn.layout.Panel): The layout containing the plot pane and (optionally) the `widget_box` layout.\n", "* **`widget_box`** (ListPanel): The layout containing the widgets\n", "\n", "##### Layout and Widget Parameters\n", "\n", "The below parameters are used to control the layout and widgets when using `pn.panel` or `pn.pane.HoloViews(...).layout`.\n", "\n", "* **`center`** (boolean, default=False): Whether to center the plot or not.\n", "* **`widgets`** (dict, argument): A mapping from dimension name to a widget class, instance, or dictionary of overrides to modify the default widgets. Provided as an argument.\n", "* **`widget_location`** (str): Where to lay out the widget relative to the plot \n", "* **`widget_layout`** (ListPanel): The object to lay the widgets out in, one of `Row`, `Column` or `WidgetBox`\n", "* **`widget_type`** (str): Whether to generate individual widgets for each dimension, or to use a global linear scrubber with dimensions concatenated.\n", "___" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `pn.pane.HoloViews` pane will automatically convert any ``HoloViews`` object into a displayable panel, while keeping all of its interactive features:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "\n", "data = {\"group\": np.random.randint(0, 10, 100), \"value\": np.random.randn(100)}\n", "box = hv.Scatter(data, kdims=\"group\", vdims=\"value\").sort().opts()\n", "\n", "hv_pane = pn.pane.HoloViews(box, height=300, sizing_mode=\"stretch_width\")\n", "hv_pane" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "By setting the pane's ``object`` the plot can be updated like all other pane objects:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "hv_pane.object = hv.Violin(box).opts(violin_color='Group', responsive=True, height=300)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can display [hvPlot](https://hvplot.holoviz.org/) (and [GeoViews](https://geoviews.org/)) objects too because they are `HoloViews` objects." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "import hvplot.pandas\n", "\n", "df = pd.DataFrame(data)\n", "plot = df.hvplot.box(by=\"group\", y=\"value\", responsive=True, height=300)\n", "pn.pane.HoloViews(plot, height=300, sizing_mode=\"stretch_width\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can also display [`HoloMap`](https://holoviews.org/reference/containers/bokeh/HoloMap.html) and [`DynamicMap`](https://holoviews.org/reference/containers/bokeh/DynamicMap.html) objects.\n", "\n", "[HoloViews](https://holoviews.org/) (the framework) natively renders plots with widgets if a [`HoloMap`](https://holoviews.org/reference/containers/bokeh/HoloMap.html) or [DynamicMap](https://holoviews.org/reference/containers/bokeh/DynamicMap.html) declares any *key dimensions*. This approach efficiently updates just the data inside a plot instead of replacing the plot entirely." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "import hvplot.pandas\n", "import holoviews.plotting.bokeh\n", "\n", "def sine(frequency=1.0, amplitude=1.0, function='sin'):\n", " xs = np.arange(200)/200*20.0\n", " ys = amplitude*getattr(np, function)(frequency*xs)\n", " return pd.DataFrame(dict(y=ys), index=xs).hvplot(height=250, responsive=True)\n", "\n", "dmap = hv.DynamicMap(sine, kdims=['frequency', 'amplitude', 'function']).redim.range(\n", " frequency=(0.1, 10), amplitude=(1, 10)).redim.values(function=['sin', 'cos', 'tan'])\n", "\n", "hv_panel = pn.pane.HoloViews(dmap)\n", "hv_panel" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `layout` parameter contains the `HoloViews` pane and the `widget_box`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(hv_panel.layout, \"\\n\\n\", hv_panel.widget_box)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Backend\n", "\n", "The ``HoloViews`` pane will default to the 'bokeh' plotting backend if no backend has been loaded via `holoviews`, but you can change the backend to any of the 'bokeh', 'matplotlib' and 'plotly' backends as needed.\n", "\n", "### Bokeh\n", "\n", "Bokeh is the default plotting backend, so normally you don't have to specify it. But lets do it here to show how it works." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plot = df.hvplot.scatter(x=\"group\", y=\"value\")\n", "pn.pane.HoloViews(plot, backend='bokeh', sizing_mode=\"stretch_width\", height=300)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Matplotlib\n", "\n", "The Matplotlib backend allows generating figures for print and publication. If you want to allow for responsive sizing you can set `format='svg'` and then use the standard responsive `sizing_mode` settings:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "hvplot.extension(\"matplotlib\")\n", "\n", "plot = df.hvplot.scatter(x=\"group\", y=\"value\")\n", "\n", "pn.pane.HoloViews(plot, backend='matplotlib', sizing_mode=\"stretch_both\", format='svg', center=False)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Please note that in a *server context* you might have to set the matplotlib backend like below depending on your setup.\n", "\n", "```python\n", "import matplotlib\n", "matplotlib.use('agg')\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Plotly\n", "\n", "To use the 'plotly' plotting backend you will need to run `hv.extension(\"plotly\")` to configure the 'plotly' backend.\n", "\n", "If you are using `hvPlot` you can use `hvplot.extension(\"plotly\")` instead." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "hvplot.extension(\"plotly\")\n", "\n", "plot = df.hvplot.scatter(x=\"group\", y=\"value\", height=300, responsive=True)\n", "\n", "pn.pane.HoloViews(plot, backend='plotly', height=300)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Lets change the default backend back to 'bokeh'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "hvplot.extension(\"bokeh\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Dynamic\n", "\n", "You can change the plotting backend dynamically via a widget too." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plot = df.hvplot.scatter(x=\"group\", y=\"value\", height=300, responsive=True, title=\"Try changing the backend\")\n", "\n", "plot_pane = pn.pane.HoloViews(plot, backend='bokeh', sizing_mode=\"stretch_width\", height=300)\n", "\n", "widget = pn.widgets.RadioButtonGroup.from_param(\n", " plot_pane.param.backend, button_type=\"primary\", button_style=\"outline\"\n", ")\n", "\n", "pn.Column(widget, plot_pane)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Linked Axes\n", "\n", "By default the axes of plots with shared key or value dimensions are linked. You can remove the link by setting the `linked_axes` parameter to `False`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "not_linked_plot = df.hvplot.scatter(x=\"group\", y=\"value\", responsive=True, title=\"Not Linked Axes\").opts(active_tools=['box_zoom'])\n", "linked_plot = df.hvplot.scatter(x=\"group\", y=\"value\", responsive=True, title=\"Linked Axes\").opts(active_tools=['box_zoom'])\n", "\n", "\n", "pn.Column(\n", " pn.pane.HoloViews(not_linked_plot, backend='bokeh', sizing_mode=\"stretch_width\", height=200, linked_axes=False),\n", " pn.pane.HoloViews(linked_plot, backend='bokeh', sizing_mode=\"stretch_width\", height=200),\n", " pn.pane.HoloViews(linked_plot, backend='bokeh', sizing_mode=\"stretch_width\", height=200),\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Theme\n", "\n", "You can change the `theme`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plot = df.hvplot.scatter(x=\"group\", y=\"value\", height=300, responsive=True)\n", "pn.pane.HoloViews(plot, height=300, theme=\"night_sky\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Layout and Widget Parameters\n", "\n", "Please note that above we used the `Holoviews` pane to display HoloViews objects.\n", "\n", "Below we will be using `pn.panel(...)` instead. `pn.panel(...)` is the same as `pn.pane.HoloViews(...).layout`\n", "\n", "### Center\n", "\n", "You can center your plots via the `center` parameter." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "plot = df.hvplot.scatter(x=\"group\", y=\"value\", height=100, width=400)\n", "\n", "pn.Column(\n", " \"`center=True`, `sizing_mode='fixed'`\",\n", " pn.panel(plot, center=True, sizing_mode=\"fixed\"),\n", " \"`center=True`, `sizing_mode='stretch_width'`\",\n", " pn.panel(plot, center=True, sizing_mode=\"stretch_width\"),\n", " \"`center=False`, `sizing_mode='fixed'`\",\n", " pn.panel(plot),\n", " \"`center=False`, `sizing_mode='stretch_width'`\",\n", " pn.panel(plot, sizing_mode=\"stretch_width\"),\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### HoloMap and DynamicMap Widgets\n", "\n", "[HoloViews](https://holoviews.org/) (the framework) natively renders plots with widgets if a [`HoloMap`](https://holoviews.org/reference/containers/bokeh/HoloMap.html) or [DynamicMap](https://holoviews.org/reference/containers/bokeh/DynamicMap.html) declares any *key dimensions*. This approach efficiently updates just the data inside a plot instead of replacing it entirely.\n", "\n", "When rendering a `HoloMap` or `DynamicMap` object with `pn.panel`, then the `pn.pane.HoloViews(...).layout` will be displayed. Not `pn.pane.HoloViews(...)`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "import hvplot.pandas\n", "import holoviews.plotting.bokeh\n", "\n", "def sine(frequency=1.0, amplitude=1.0, function='sin'):\n", " xs = np.arange(200)/200*20.0\n", " ys = amplitude*getattr(np, function)(frequency*xs)\n", " return pd.DataFrame(dict(y=ys), index=xs).hvplot(height=250, responsive=True)\n", "\n", "dmap = hv.DynamicMap(sine, kdims=['frequency', 'amplitude', 'function']).redim.range(\n", " frequency=(0.1, 10), amplitude=(1, 10)).redim.values(function=['sin', 'cos', 'tan'])\n", "\n", "dmap_panel = pn.panel(dmap, height=400, sizing_mode=\"stretch_width\")\n", "dmap_panel" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Lets inspect the `dmap_panel`" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(dmap_panel)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Lets try to modify the `FloatSlider` before we render it:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "float_slider = hv_panel[1][0]\n", "\n", "float_slider.styles = {'border': '2px solid red', 'padding': '10px', 'border-radius': '5px'}\n", "\n", "float_slider" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Lets try to reorganize the layout" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "widgets = hv_panel[1]\n", "\n", "pn.Row(pn.Column(*widgets), hv_panel[0])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The widgets in the `.layout` can be customized via the `widgets`, `widget_location`, `widget_layout` and `widget_type` parameters." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### `widget_location`\n", "\n", "The HoloViews pane offers options to lay out the plot and widgets in a number of preconfigured arrangements using the ``center`` and ``widget_location`` parameters.\n", "\n", "The ``widget_location`` parameter accepts all of the following options:\n", " \n", " ['left', 'bottom', 'right', 'top', 'top_left', 'top_right', 'bottom_left',\n", " 'bottom_right', 'left_top', 'left_bottom', 'right_top', 'right_bottom']" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pn.panel(dmap, center=True, widget_location='left')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### `widget_layout`\n", "\n", "Lets change the widget layout to a `Row`." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pn.panel(dmap, center=True, widget_layout=pn.Row, widget_location='top_left')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### `widgets`\n", "\n", "As we saw above, the HoloViews pane will automatically try to generate appropriate widgets for the type of data, usually defaulting to ``DiscreteSlider`` and ``Select`` widgets. This behavior can be modified by providing a dictionary of ``widgets`` by dimension name. The values of this dictionary can override the default widget in one of three ways:\n", "\n", "* Supplying a ``Widget`` instance\n", "* Supplying a compatible ``Widget`` type\n", "* Supplying a dictionary of ``Widget`` parameter overrides\n", " \n", "``Widget`` instances will be used as they are supplied and are expected to provide values matching compatible with the values defined on HoloMap/DynamicMap. Similarly if a ``Widget`` type is supplied it should be discrete if the parameter space defines a discrete set of values. If the defined parameter space is continuous, on the other hand, it may supply any valid value.\n", "\n", "In the example below the 'amplitude' dimension is overridden with an explicit ``Widget`` instance, the 'function' dimension is overridden with a RadioButtonGroup letting us toggle between the different functions, and lastly the 'value' parameter on the 'frequency' widget is overridden to change the initial value:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "hv_panel = pn.pane.HoloViews(dmap, widgets={\n", " 'amplitude': pn.widgets.LiteralInput(value=1., type=(float, int)),\n", " 'function': pn.widgets.RadioButtonGroup,\n", " 'frequency': {'value': 5}\n", "}).layout\n", "\n", "hv_panel" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Bound Functions and DynamicMaps\n", "\n", "When working with reactive references and functions (such as those returned by `.bind`, `.depends` or `.rx`) that return HoloViews elements and overlays it is recommended that you wrap them in a `DynamicMap`. This allows us to construct plots that depend on the values of widgets or other parameters or expressions in a straightforward way. Note that this differs from auto-generating widgets from dimensions as we've seen so far. Internally HoloViews will inspect the function and create a so called [`Stream`](https://holoviews.org/user_guide/Responding_to_Events.html) that updates the `DynamicMap` when the dependencies change. This also means that any widget you depend on will not appear alongside the other widgets, derived from a dimension.\n", "\n", "Instead of re-rendering the entire plot each time a parameter changes this will delegate the update to HoloViews and update the data inplace:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def get_plot(n):\n", " return hv.Scatter(range(n), kdims=\"x\", vdims=\"y\").opts(\n", " height=300, responsive=True, xlim=(0, 100), ylim=(0, 100)\n", " )\n", "\n", "widget = pn.widgets.IntSlider(value=50, start=1, end=100, name=\"Number of points\")\n", "plot = hv.DynamicMap(pn.bind(get_plot, widget))\n", "\n", "pn.Column(widget, plot)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Please note the bound function may not return `Layout`s, `NdLayout`s, `GridSpace`s, `DynamicMap`s or `HoloMaps` as these are not supported by the `DynamicMap`." ] } ], "metadata": { "language_info": { "name": "python", "pygments_lexer": "ipython3" } }, "nbformat": 4, "nbformat_minor": 4 }