From 140c744d1df9a20f30aa0b04ea137764cd902f48 Mon Sep 17 00:00:00 2001 From: MarcSkovMadsen Date: Sat, 16 Dec 2023 17:04:25 +0000 Subject: [PATCH 1/9] improve HoloViews reference guide --- examples/reference/panes/HoloViews.ipynb | 459 ++++++++++++++++++++--- panel/pane/holoviews.py | 2 +- 2 files changed, 401 insertions(+), 60 deletions(-) diff --git a/examples/reference/panes/HoloViews.ipynb b/examples/reference/panes/HoloViews.ipynb index 1b423463b4..9aa14f3094 100644 --- a/examples/reference/panes/HoloViews.ipynb +++ b/examples/reference/panes/HoloViews.ipynb @@ -6,33 +6,62 @@ "metadata": {}, "outputs": [], "source": [ + "import holoviews as hva\n", "import panel as pn\n", - "pn.extension('plotly')" + "\n", + "hv.extension(\"bokeh\") # DON'T KNOW WHY THIS WAS SUDDENLY NEEDED. PLEASE HELP\n", + "pn.extension('plotly') # We add 'plotly' to be able to use the 'plotly' plot backend" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "The ``HoloViews`` pane renders HoloViews plots with one of the plotting backends supported by HoloViews. It supports the regular HoloViews widgets for exploring the key dimensions of a ``HoloMap`` or ``DynamicMap``, but is more flexible than the native HoloViews widgets since it also allows customizing widget types and their position relative to the plot.\n", + "[HoloViews](https://holoviews.org/) is a very 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", - "* **``backend``** (str): Any of the supported HoloViews backends ('bokeh', 'matplotlib', or 'plotly')\n", - "* **``center``** (boolean, default=False): Whether to center the plot\n", - "* **``linked_axes``** (boolean, default=True): Whether to link axes across plots in a panel layout\n", + "The main argument is the `object` parameter\n", + "\n", "* **``object``** (object): The HoloViews object being displayed\n", - "* **``widget_location``** (str): Where to lay out the widget relative to the plot \n", - "* **``widget_layout``** (ListPanel type): 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", - "* **``widgets``** (dict): A mapping from dimension name to a widget class, instance, or dictionary of overrides to modify the default widgets.\n", "\n", - "##### Display\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", + "* **``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", - "* **``default_layout``** (pn.layout.Panel, default=Row): Layout to wrap the plot and widgets in\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 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", "___" ] }, @@ -40,7 +69,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "The `panel` function will automatically convert any ``HoloViews`` object into a displayable panel, while keeping all of its interactive features:" + "The `pn.pane.HoloViews` pane will automatically convert any ``HoloViews`` object into a displayable panel, while keeping all of its interactive features:" ] }, { @@ -50,11 +79,11 @@ "outputs": [], "source": [ "import numpy as np\n", - "import holoviews as hv\n", "\n", - "box = hv.BoxWhisker((np.random.randint(0, 10, 100), np.random.randn(100)), 'Group').sort()\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_layout = pn.panel(box)\n", + "hv_layout = pn.pane.HoloViews(box, height=300, sizing_mode=\"stretch_width\")\n", "hv_layout" ] }, @@ -71,16 +100,18 @@ "metadata": {}, "outputs": [], "source": [ - "hv_layout.object = hv.Violin(box).opts(violin_color='Group', cmap='Category20')" + "hv_layout.object = hv.Violin(box).opts(violin_color='Group', cmap='Category20', responsive=True, height=300)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Widgets\n", + "You can display [`HoloMap`](https://holoviews.org/reference/containers/bokeh/HoloMap.html) and [`DynamicMap`](https://holoviews.org/reference/containers/bokeh/DynamicMap.html) too. \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.\n", "\n", - "HoloViews natively renders plots with widgets if a HoloMap or DynamicMap declares any key dimensions. Unlike Panel's ``interact`` functionality, this approach efficiently updates just the data inside a plot instead of replacing it entirely. Calling ``pn.panel`` on the DynamicMap will return a ``Row`` layout (configurable via the ``default_layout`` option), which is equivalent to calling ``pn.pane.HoloViews(dmap).layout``:" + "Please note that if you want to use the layout and widgets parameters, then you must use use `pn.panel` or `pn.pane.HoloViews(...).layout` instead of `pn.pane.HoloViews`. More info below." ] }, { @@ -96,21 +127,63 @@ "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()\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.panel(dmap)\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": [ + "You can use [hvPlot](https://hvplot.holoviz.org/) (and [GeoViews](https://geoviews.org/)) too." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "import hvplot.pandas\n", "\n", - "print(hv_panel)" + "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": [ - "We can see the widgets generated for each of the dimensions and arrange them any way we like, e.g. by unpacking them into a ``Row``:" + "## 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." ] }, { @@ -119,18 +192,148 @@ "metadata": {}, "outputs": [], "source": [ - "widgets = hv_panel[1]\n", + "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" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "plot = df.hvplot.scatter(x=\"group\", y=\"value\")\n", + "pn.pane.HoloViews(plot, backend='matplotlib', sizing_mode=\"stretch_width\", height=300)" + ] + }, + { + "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": [ + "You can make your matplotlib plot stretch responsively by ?? **DON'T KNOW HOW TO. PLEASE HELP**" + ] + }, + { + "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", + "pn.pane.HoloViews(plot, backend='plotly', height=300)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**DO NOT WORK!**. This plot is higher than 300px. **DON'T KNOW HOW TO SOLVE**.\n", + "\n", + "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", + "plot_pane = pn.pane.HoloViews(plot, backend='bokeh', sizing_mode=\"stretch_width\", height=300)\n", + "widget = pn.widgets.RadioButtonGroup.from_param(plot_pane.param.backend, button_type=\"primary\", button_style=\"outline\")\n", + "pn.Column(widget, plot_pane)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**DOES NOT WORK WELL!**. The matplotlib and plotly plots don't have the title. And they don't stretch their width. And the plotly plot is higher than the 300px specified. **DON'T KNOW HOW TO FIX. PLEASE HELP**" + ] + }, + { + "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.Row(*widgets),\n", - " hv_panel[0])" + " 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": [ - "However, more conveniently 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." + "## Theme\n", + "\n", + "You can change the `theme`." ] }, { @@ -139,34 +342,30 @@ "metadata": {}, "outputs": [], "source": [ - "pn.panel(dmap, center=True, widget_location='right_bottom')" + "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": [ - "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']" + "DON'T KNOW IF THIS WORKS IN JUPYTER. IN VS CODE IT DOES NOT WORK" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "#### Customizing widgets\n", + "## Layout and Widget Parameters\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", + "Please note that above we used the `Holoviews` pane to display HoloViews objects.\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", + "The parameters we explain below works with `pn.panel` and equivalently `pn.pane.HoloViews(...).layout` instead.\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:" + "### Center\n", + "\n", + "You can center your plots via the `center` parameter." ] }, { @@ -175,20 +374,39 @@ "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" + "plot = df.hvplot.scatter(x=\"group\", y=\"value\", height=300, width=400)\n", + "\n", + "pn.Column(\n", + " \"## Not Centered: `center=False`\",\n", + " pn.panel(plot, sizing_mode=\"stretch_width\"),\n", + " \"## Centered: `center=True`\",\n", + " pn.panel(plot, center=True, sizing_mode=\"stretch_width\"),\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**DO NOT WORK AS I WOULD EXPECT!**. I would have expected the NOT CENTERED plot to have been left aligned and 400px wide." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Switching backends\n", + "### HoloMap and DynamicMap with 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", - "The ``HoloViews`` pane will default to the Bokeh backend if no backend has been loaded, but you can override the backend as needed." + "You can control the layout via the parameters\n", + "\n", + "layout\n", + "widgets\n", + "widgets\n", + "widget_location\n", + "widget_layout\n", + "widget_type" ] }, { @@ -197,25 +415,110 @@ "metadata": {}, "outputs": [], "source": [ - "import holoviews.plotting.mpl\n", - "import holoviews.plotting.plotly\n", + "import pandas as pd\n", + "import hvplot.pandas\n", + "import holoviews.plotting.bokeh\n", "\n", - "hv_pane = pn.pane.HoloViews(dmap, backend='matplotlib')\n", - "hv_pane" + "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", + "pn.panel(dmap, height=500, widget_location=\"bottom\", widget_layout=pn.Row, sizing_mode=\"stretch_width\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Please note that in a *server context* you will also have to set the matplotlib backend like below\n", + "**DOES NOT WORK!**. The `default_layout` is not taken into account. And why is the frequency and amplitude widgets not aligned vertically?\n", "\n", - "```python\n", - "import matplotlib\n", - "matplotlib.use('agg')\n", - "```\n", + "Lets inspect the layout" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(hv_panel)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can see that\n", + "\n", + "- the outer `Row` contains the `HoloViews` pane and a `WidgetBox`.\n", + "- the 3 widgets corresponding to the 3 *key dimensions* (`kdims`) are laid out in the `WidgetBox`\n", + "\n", + "Lets inspect the `widgets`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(hv_panel.widgets)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Lets inspect the `widgets`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "Lets try to change the name of the `FloatSlider`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "float_slider = hv_panel[1][0]\n", + "float_slider.name=\"Frequency (updated)\"\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", - "The ``backend``, like all other parameters, can be modified after the fact. To demonstrate, we can set up a select widget to toggle between backends for the above plot:" + "pn.Column(hv_panel[0], pn.Column(*widgets))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "However, more conveniently 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." ] }, { @@ -224,9 +527,47 @@ "metadata": {}, "outputs": [], "source": [ - "backend_select = pn.widgets.RadioButtonGroup(name='Backend Selector:', options=['bokeh', 'matplotlib', 'plotly'])\n", - "backend_select.link(hv_pane[0], value='backend')\n", - "backend_select" + "pn.panel(dmap, center=True, widget_location='right_bottom')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "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": "markdown", + "metadata": {}, + "source": [ + "#### Customizing 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" ] } ], diff --git a/panel/pane/holoviews.py b/panel/pane/holoviews.py index 855de13082..a2f736c3fb 100644 --- a/panel/pane/holoviews.py +++ b/panel/pane/holoviews.py @@ -52,7 +52,7 @@ class HoloViews(PaneBase): """ backend = param.ObjectSelector( - default=None, objects=['bokeh', 'plotly', 'matplotlib'], doc=""" + default=None, objects=['bokeh', 'matplotlib', 'plotly'], doc=""" The HoloViews backend used to render the plot (if None defaults to the currently selected renderer).""") From 2b37ce251de3c7ba4fa7d45f381bc3c805a18dba Mon Sep 17 00:00:00 2001 From: MarcSkovMadsen Date: Sat, 16 Dec 2023 18:51:09 +0000 Subject: [PATCH 2/9] more work --- examples/reference/panes/HoloViews.ipynb | 182 ++++++++++++++--------- 1 file changed, 113 insertions(+), 69 deletions(-) diff --git a/examples/reference/panes/HoloViews.ipynb b/examples/reference/panes/HoloViews.ipynb index 9aa14f3094..fb73ee09e9 100644 --- a/examples/reference/panes/HoloViews.ipynb +++ b/examples/reference/panes/HoloViews.ipynb @@ -6,7 +6,7 @@ "metadata": {}, "outputs": [], "source": [ - "import holoviews as hva\n", + "import holoviews as hv\n", "import panel as pn\n", "\n", "hv.extension(\"bokeh\") # DON'T KNOW WHY THIS WAS SUDDENLY NEEDED. PLEASE HELP\n", @@ -55,7 +55,7 @@ "\n", "##### Layout and Widget Parameters\n", "\n", - "The below parameters are used to control the layout and widgets using `pn.panel` or `pn.pane.HoloViews(...).layout`.\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", @@ -83,8 +83,8 @@ "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_layout = pn.pane.HoloViews(box, height=300, sizing_mode=\"stretch_width\")\n", - "hv_layout" + "hv_pane = pn.pane.HoloViews(box, height=300, sizing_mode=\"stretch_width\")\n", + "hv_pane" ] }, { @@ -100,18 +100,37 @@ "metadata": {}, "outputs": [], "source": [ - "hv_layout.object = hv.Violin(box).opts(violin_color='Group', cmap='Category20', responsive=True, height=300)" + "hv_pane.object = hv.Violin(box).opts(violin_color='Group', cmap='Category20', responsive=True, height=300)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "You can display [`HoloMap`](https://holoviews.org/reference/containers/bokeh/HoloMap.html) and [`DynamicMap`](https://holoviews.org/reference/containers/bokeh/DynamicMap.html) too. \n", + "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", - "[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.\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 display [`HoloMap`](https://holoviews.org/reference/containers/bokeh/HoloMap.html) and [`DynamicMap`](https://holoviews.org/reference/containers/bokeh/DynamicMap.html) objects too. \n", "\n", - "Please note that if you want to use the layout and widgets parameters, then you must use use `pn.panel` or `pn.pane.HoloViews(...).layout` instead of `pn.pane.HoloViews`. More info below." + "[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." ] }, { @@ -156,21 +175,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "You can use [hvPlot](https://hvplot.holoviz.org/) (and [GeoViews](https://geoviews.org/)) too." - ] - }, - { - "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\")" + "Please note that if you want to use the layout and widgets parameters, then you must use `pn.panel` or `pn.pane.HoloViews(...).layout` instead of `pn.pane.HoloViews`. More info below." ] }, { @@ -361,7 +366,7 @@ "\n", "Please note that above we used the `Holoviews` pane to display HoloViews objects.\n", "\n", - "The parameters we explain below works with `pn.panel` and equivalently `pn.pane.HoloViews(...).layout` instead.\n", + "Below we will be using `pn.panel(...)` instead. `pn.panel(...)` is the same as `pn.pane.HoloViews(...).layout`\n", "\n", "### Center\n", "\n", @@ -395,18 +400,13 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### HoloMap and DynamicMap with Widgets\n", + "### 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", - "You can control the layout via the parameters\n", + "When rendering a `HoloMap` or `DynamicMap` object with `pn.panel`, then `pn.pane.HoloViews(...).layout` will be displayed. Not `pn.pane.HoloViews(...)`.\n", "\n", - "layout\n", - "widgets\n", - "widgets\n", - "widget_location\n", - "widget_layout\n", - "widget_type" + "The widgets in the `.layout` can be customized via the `widgets`, `widget_location`, `widget_layout` and `widget_type` parameters." ] }, { @@ -427,16 +427,15 @@ "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", - "pn.panel(dmap, height=500, widget_location=\"bottom\", widget_layout=pn.Row, sizing_mode=\"stretch_width\")" + "dmap_panel = pn.panel(dmap, height=400, sizing_mode=\"stretch_width\")\n", + "dmap_panel" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "**DOES NOT WORK!**. The `default_layout` is not taken into account. And why is the frequency and amplitude widgets not aligned vertically?\n", - "\n", - "Lets inspect the layout" + "Lets inspect the `dmap_panel`" ] }, { @@ -445,19 +444,14 @@ "metadata": {}, "outputs": [], "source": [ - "print(hv_panel)" + "print(dmap_panel)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "You can see that\n", - "\n", - "- the outer `Row` contains the `HoloViews` pane and a `WidgetBox`.\n", - "- the 3 widgets corresponding to the 3 *key dimensions* (`kdims`) are laid out in the `WidgetBox`\n", - "\n", - "Lets inspect the `widgets`" + "Lets try to change the name of the `FloatSlider`." ] }, { @@ -466,23 +460,18 @@ "metadata": {}, "outputs": [], "source": [ - "print(hv_panel.widgets)" + "float_slider = hv_panel[1][0]\n", + "float_slider.name=\"Frequency (updated)\"\n", + "float_slider" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Lets inspect the `widgets`" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "Lets try to change the name of the `FloatSlider`." + "DON'T WORK. I GET A TypeError when I change the slider.\n", + "\n", + "Lets try to reorganize the layout" ] }, { @@ -491,16 +480,23 @@ "metadata": {}, "outputs": [], "source": [ - "float_slider = hv_panel[1][0]\n", - "float_slider.name=\"Frequency (updated)\"\n", - "float_slider" + "widgets = hv_panel[1]\n", + "\n", + "pn.Row(pn.Column(*widgets), hv_panel[0])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Lets try to reorganize the layout" + "#### `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']" ] }, { @@ -509,16 +505,16 @@ "metadata": {}, "outputs": [], "source": [ - "widgets = hv_panel[1]\n", - "\n", - "pn.Column(hv_panel[0], pn.Column(*widgets))" + "pn.panel(dmap, center=True, widget_location='left')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "However, more conveniently 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." + "#### `widget_layout`\n", + "\n", + "Lets change the widget layout to a `Row`." ] }, { @@ -527,24 +523,30 @@ "metadata": {}, "outputs": [], "source": [ - "pn.panel(dmap, center=True, widget_location='right_bottom')" + "pn.panel(dmap, center=True, widget_layout=pn.Row, widget_location='top_left')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "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']" + "DON'T LOOK NICE. The widgets are not aligned. If you inspect, then you can see that the widget margins have been optimized for a vertical layout.\n", + "\n", + "```bash\n", + "Column(sizing_mode='stretch_width')\n", + " [0] Row\n", + " [0] FloatSlider(end=10, margin=(20, 20, 5, 20), name='frequency', start=0.1, value=0.1, width=250)\n", + " [1] IntSlider(end=10, margin=(0, 20, 5, 20), name='amplitude', start=1, value=1, width=250)\n", + " [2] Select(margin=(5, 20, 20, 20), name='function', options=['sin', 'cos', 'tan'], value='sin', width=250)\n", + " [1] HoloViews(DynamicMap, center=True, widget_layout= Date: Sat, 16 Dec 2023 19:05:32 +0000 Subject: [PATCH 3/9] remove comments --- examples/reference/panes/HoloViews.ipynb | 67 +++--------------------- 1 file changed, 6 insertions(+), 61 deletions(-) diff --git a/examples/reference/panes/HoloViews.ipynb b/examples/reference/panes/HoloViews.ipynb index fb73ee09e9..c34623d8f2 100644 --- a/examples/reference/panes/HoloViews.ipynb +++ b/examples/reference/panes/HoloViews.ipynb @@ -9,7 +9,7 @@ "import holoviews as hv\n", "import panel as pn\n", "\n", - "hv.extension(\"bokeh\") # DON'T KNOW WHY THIS WAS SUDDENLY NEEDED. PLEASE HELP\n", + "hv.extension(\"bokeh\")\n", "pn.extension('plotly') # We add 'plotly' to be able to use the 'plotly' plot backend" ] }, @@ -17,7 +17,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "[HoloViews](https://holoviews.org/) is a very powerful data visualization library supporting many data and plotting *backends*.\n", + "[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", @@ -128,7 +128,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "You can display [`HoloMap`](https://holoviews.org/reference/containers/bokeh/HoloMap.html) and [`DynamicMap`](https://holoviews.org/reference/containers/bokeh/DynamicMap.html) objects too. \n", + "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." ] @@ -171,13 +171,6 @@ "print(hv_panel.layout, \"\\n\\n\", hv_panel.widget_box)" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Please note that if you want to use the layout and widgets parameters, then you must use `pn.panel` or `pn.pane.HoloViews(...).layout` instead of `pn.pane.HoloViews`. More info below." - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -234,7 +227,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "You can make your matplotlib plot stretch responsively by ?? **DON'T KNOW HOW TO. PLEASE HELP**" + "You can make your matplotlib plot stretch responsively by ???? **DON'T KNOW HOW TO. PLEASE HELP**" ] }, { @@ -264,8 +257,6 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "**DO NOT WORK!**. This plot is higher than 300px. **DON'T KNOW HOW TO SOLVE**.\n", - "\n", "Lets change the default backend back to 'bokeh'" ] }, @@ -299,13 +290,6 @@ "pn.Column(widget, plot_pane)" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**DOES NOT WORK WELL!**. The matplotlib and plotly plots don't have the title. And they don't stretch their width. And the plotly plot is higher than the 300px specified. **DON'T KNOW HOW TO FIX. PLEASE HELP**" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -351,13 +335,6 @@ "pn.pane.HoloViews(plot, height=300, theme=\"night_sky\")" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "DON'T KNOW IF THIS WORKS IN JUPYTER. IN VS CODE IT DOES NOT WORK" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -389,13 +366,6 @@ ")" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**DO NOT WORK AS I WOULD EXPECT!**. I would have expected the NOT CENTERED plot to have been left aligned and 400px wide." - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -469,8 +439,6 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "DON'T WORK. I GET A TypeError when I change the slider.\n", - "\n", "Lets try to reorganize the layout" ] }, @@ -526,22 +494,6 @@ "pn.panel(dmap, center=True, widget_layout=pn.Row, widget_location='top_left')" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "DON'T LOOK NICE. The widgets are not aligned. If you inspect, then you can see that the widget margins have been optimized for a vertical layout.\n", - "\n", - "```bash\n", - "Column(sizing_mode='stretch_width')\n", - " [0] Row\n", - " [0] FloatSlider(end=10, margin=(20, 20, 5, 20), name='frequency', start=0.1, value=0.1, width=250)\n", - " [1] IntSlider(end=10, margin=(0, 20, 5, 20), name='amplitude', start=1, value=1, width=250)\n", - " [2] Select(margin=(5, 20, 20, 20), name='function', options=['sin', 'cos', 'tan'], value='sin', width=250)\n", - " [1] HoloViews(DynamicMap, center=True, widget_layout= Date: Sat, 16 Dec 2023 19:42:16 +0000 Subject: [PATCH 4/9] remove comments --- examples/reference/panes/HoloViews.ipynb | 34 +++++++++++++----------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/examples/reference/panes/HoloViews.ipynb b/examples/reference/panes/HoloViews.ipynb index c34623d8f2..336f454c00 100644 --- a/examples/reference/panes/HoloViews.ipynb +++ b/examples/reference/panes/HoloViews.ipynb @@ -100,7 +100,7 @@ "metadata": {}, "outputs": [], "source": [ - "hv_pane.object = hv.Violin(box).opts(violin_color='Group', cmap='Category20', responsive=True, height=300)" + "hv_pane.object = hv.Violin(box).opts(violin_color='Group', responsive=True, height=300)" ] }, { @@ -223,13 +223,6 @@ "```" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can make your matplotlib plot stretch responsively by ???? **DON'T KNOW HOW TO. PLEASE HELP**" - ] - }, { "cell_type": "markdown", "metadata": {}, @@ -356,13 +349,17 @@ "metadata": {}, "outputs": [], "source": [ - "plot = df.hvplot.scatter(x=\"group\", y=\"value\", height=300, width=400)\n", + "plot = df.hvplot.scatter(x=\"group\", y=\"value\", height=100, width=400)\n", "\n", "pn.Column(\n", - " \"## Not Centered: `center=False`\",\n", - " pn.panel(plot, sizing_mode=\"stretch_width\"),\n", - " \"## Centered: `center=True`\",\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", ")" ] }, @@ -374,9 +371,7 @@ "\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 `pn.pane.HoloViews(...).layout` will be displayed. Not `pn.pane.HoloViews(...)`.\n", - "\n", - "The widgets in the `.layout` can be customized via the `widgets`, `widget_location`, `widget_layout` and `widget_type` parameters." + "When rendering a `HoloMap` or `DynamicMap` object with `pn.panel`, then the `pn.pane.HoloViews(...).layout` will be displayed. Not `pn.pane.HoloViews(...)`." ] }, { @@ -453,6 +448,13 @@ "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": {}, @@ -531,7 +533,7 @@ "source": [ "### Bound Functions and DynamicMaps\n", "\n", - "When working with bounds functions (`.bind`, `.depends` or `.rx`) that returns basic HoloViews objects, then you may wrap it in a `DynamicMap` for better performance. This will replace the data only instead of the whole plot when changing some widget value." + "When working with bounds functions (`.bind`, `.depends` or `.rx`) that returns basic HoloViews objects, then you may wrap it in a `DynamicMap` for better performance. This will replace the data only instead of the whole plot when a parameter or widget value changes." ] }, { From e7beb4906528b254a3711d24cc2241744e5487e6 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Mon, 11 Mar 2024 14:35:56 +0100 Subject: [PATCH 5/9] Fix alignment issues --- panel/pane/holoviews.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/panel/pane/holoviews.py b/panel/pane/holoviews.py index a2f736c3fb..201ecf800e 100644 --- a/panel/pane/holoviews.py +++ b/panel/pane/holoviews.py @@ -276,8 +276,10 @@ def _update_widgets(self, *events): if self.object is None: widgets, values = [], [] else: + direction = getattr(self.widget_layout, '_direction', 'vertical') widgets, values = self.widgets_from_dimensions( - self.object, self.widgets, self.widget_type) + self.object, self.widgets, self.widget_type, direction + ) self._values = values # Clean up anything models listening to the previous widgets @@ -560,7 +562,7 @@ def jslink(self, target, code=None, args=None, bidirectional=False, **links): jslink.__doc__ = PaneBase.jslink.__doc__ @classmethod - def widgets_from_dimensions(cls, object, widget_types=None, widgets_type='individual'): + def widgets_from_dimensions(cls, object, widget_types=None, widgets_type='individual', direction='vertical'): from holoviews.core import Dimension, DynamicMap from holoviews.core.options import SkipRendering from holoviews.core.traversal import unique_dimkeys @@ -607,7 +609,7 @@ def widgets_from_dimensions(cls, object, widget_types=None, widgets_type='indivi for i, dim in enumerate(dims): widget_type, widget, widget_kwargs = None, None, {} - if widgets_type == 'individual': + if widgets_type == 'individual' and direction == 'vertical': if i == 0 and i == (len(dims)-1): margin = (20, 20, 20, 20) elif i == 0: From 74ee0b66371bcee7f412dce555f024a992492419 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Mon, 11 Mar 2024 14:36:52 +0100 Subject: [PATCH 6/9] Fix name change issue --- examples/reference/panes/HoloViews.ipynb | 16 +++++++++++----- panel/pane/holoviews.py | 1 + 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/examples/reference/panes/HoloViews.ipynb b/examples/reference/panes/HoloViews.ipynb index 336f454c00..a65b27542e 100644 --- a/examples/reference/panes/HoloViews.ipynb +++ b/examples/reference/panes/HoloViews.ipynb @@ -9,8 +9,7 @@ "import holoviews as hv\n", "import panel as pn\n", "\n", - "hv.extension(\"bokeh\")\n", - "pn.extension('plotly') # We add 'plotly' to be able to use the 'plotly' plot backend" + "hv.extension(\"bokeh\", \"plotly\")" ] }, { @@ -278,8 +277,13 @@ "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", - "widget = pn.widgets.RadioButtonGroup.from_param(plot_pane.param.backend, button_type=\"primary\", button_style=\"outline\")\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)" ] }, @@ -416,7 +420,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Lets try to change the name of the `FloatSlider`." + "Lets try to modify the `FloatSlider` before we render it:" ] }, { @@ -426,7 +430,9 @@ "outputs": [], "source": [ "float_slider = hv_panel[1][0]\n", - "float_slider.name=\"Frequency (updated)\"\n", + "\n", + "float_slider.styles = {'border': '2px solid red', 'padding': '10px', 'border-radius': '5px'}\n", + "\n", "float_slider" ] }, diff --git a/panel/pane/holoviews.py b/panel/pane/holoviews.py index 201ecf800e..18b284cbc8 100644 --- a/panel/pane/holoviews.py +++ b/panel/pane/holoviews.py @@ -683,6 +683,7 @@ def widgets_from_dimensions(cls, object, widget_types=None, widgets_type='indivi **widget_kwargs) widget = widget_type(**widget_kwargs) if widget is not None: + widget.param.name.constant = True widgets.append(widget) if widgets_type == 'scrubber': widgets = [Player(length=nframes, width=550)] From bcc20e4daf347189898e617a8bd40f58acbf8f75 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Mon, 11 Mar 2024 14:49:58 +0100 Subject: [PATCH 7/9] Update docs --- examples/reference/panes/HoloViews.ipynb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/reference/panes/HoloViews.ipynb b/examples/reference/panes/HoloViews.ipynb index a65b27542e..c2e759f3f6 100644 --- a/examples/reference/panes/HoloViews.ipynb +++ b/examples/reference/panes/HoloViews.ipynb @@ -539,7 +539,9 @@ "source": [ "### Bound Functions and DynamicMaps\n", "\n", - "When working with bounds functions (`.bind`, `.depends` or `.rx`) that returns basic HoloViews objects, then you may wrap it in a `DynamicMap` for better performance. This will replace the data only instead of the whole plot when a parameter or widget value changes." + "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:" ] }, { From 33000c9ff9174f1a9c935bdb211923de8e861813 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Mon, 11 Mar 2024 15:24:02 +0100 Subject: [PATCH 8/9] Allow setting format on HoloViews pane --- examples/reference/panes/HoloViews.ipynb | 49 ++++++++++++++---------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/examples/reference/panes/HoloViews.ipynb b/examples/reference/panes/HoloViews.ipynb index c2e759f3f6..a2757fe5df 100644 --- a/examples/reference/panes/HoloViews.ipynb +++ b/examples/reference/panes/HoloViews.ipynb @@ -38,29 +38,30 @@ "\n", "The main argument is the `object` parameter\n", "\n", - "* **``object``** (object): The HoloViews object being displayed\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", - "* **``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", + "* **`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", + "* **`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", + "* **`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", "___" ] }, @@ -197,7 +198,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Matplotlib" + "### 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:" ] }, { @@ -206,8 +209,11 @@ "metadata": {}, "outputs": [], "source": [ + "hvplot.extension(\"matplotlib\")\n", + "\n", "plot = df.hvplot.scatter(x=\"group\", y=\"value\")\n", - "pn.pane.HoloViews(plot, backend='matplotlib', sizing_mode=\"stretch_width\", height=300)" + "\n", + "pn.pane.HoloViews(plot, backend='matplotlib', sizing_mode=\"stretch_both\", format='svg', center=False)" ] }, { @@ -242,6 +248,7 @@ "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)" ] }, @@ -530,6 +537,7 @@ " 'function': pn.widgets.RadioButtonGroup,\n", " 'frequency': {'value': 5}\n", "}).layout\n", + "\n", "hv_panel" ] }, @@ -551,22 +559,21 @@ "outputs": [], "source": [ "def get_plot(n):\n", - " values = list(range(n))\n", - " return hv.Scatter({\n", - " \"x\": values, \"y\": values\n", - " }, kdims=\"x\", vdims=\"y\", extents=(0,0,100,100)).opts(height=300, responsive=True)\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", - "hv_pane = pn.pane.HoloViews(hv.DynamicMap(pn.bind(get_plot, widget)),)\n", + "plot = hv.DynamicMap(pn.bind(get_plot, widget))\n", "\n", - "pn.Column(widget, hv_pane)" + "pn.Column(widget, plot)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Please not the bound function may not return `Layout`s, `NdLayout`s, `GridSpace`s, `DynamicMap`s or `HoloMaps` as these are not supported by the `DynamicMap`." + "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`." ] } ], From 2e49cf69ed0e0ae3ef6dca0bf30f364f49982b6f Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Mon, 11 Mar 2024 15:34:42 +0100 Subject: [PATCH 9/9] Add support for format --- panel/pane/holoviews.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/panel/pane/holoviews.py b/panel/pane/holoviews.py index 18b284cbc8..368ac6eb82 100644 --- a/panel/pane/holoviews.py +++ b/panel/pane/holoviews.py @@ -1,5 +1,6 @@ """ HoloViews integration for Panel including a Pane to render HoloViews + objects and their widgets and support for Links """ from __future__ import annotations @@ -59,6 +60,9 @@ class HoloViews(PaneBase): center = param.Boolean(default=False, doc=""" Whether to center the plot.""") + format = param.Selector(default='png', objects=['png', 'svg'], doc=""" + The format to render Matplotlib plots with.""") + linked_axes = param.Boolean(default=True, doc=""" Whether to link the axes of bokeh plots inside this pane across a panel layout.""") @@ -116,10 +120,10 @@ class HoloViews(PaneBase): 'backend': None, 'center': None, 'linked_axes': None, 'renderer': None, 'theme': None, 'widgets': None, 'widget_layout': None, 'widget_location': None, - 'widget_type': None + 'widget_type': None, 'format': None } - _rerender_params = ['object', 'backend'] + _rerender_params = ['object', 'backend', 'format'] _skip_layoutable = ( 'background', 'css_classes', 'margin', 'name', 'sizing_mode', @@ -477,6 +481,7 @@ def _get_pane(self, backend, state, **kwargs): if isinstance(pane_type, type): if issubclass(pane_type, Matplotlib): kwargs['tight'] = True + kwargs['format'] = self.format if issubclass(pane_type, Bokeh): kwargs['autodispatch'] = False return pane_type(state, **kwargs)