diff --git a/examples/reference/panes/HoloViews.ipynb b/examples/reference/panes/HoloViews.ipynb index 1b423463b4..a2757fe5df 100644 --- a/examples/reference/panes/HoloViews.ipynb +++ b/examples/reference/panes/HoloViews.ipynb @@ -6,33 +6,62 @@ "metadata": {}, "outputs": [], "source": [ + "import holoviews as hv\n", "import panel as pn\n", - "pn.extension('plotly')" + "\n", + "hv.extension(\"bokeh\", \"plotly\")" ] }, { "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 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", - "* **``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", - "* **``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", + "The main argument is the `object` parameter\n", "\n", - "##### Display\n", + "* **`object`** (object): The HoloViews object being displayed\n", "\n", - "* **``default_layout``** (pn.layout.Panel, default=Row): Layout to wrap the plot and widgets in\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", "___" ] }, @@ -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,12 +79,12 @@ "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" + "hv_pane = pn.pane.HoloViews(box, height=300, sizing_mode=\"stretch_width\")\n", + "hv_pane" ] }, { @@ -71,16 +100,37 @@ "metadata": {}, "outputs": [], "source": [ - "hv_layout.object = hv.Violin(box).opts(violin_color='Group', cmap='Category20')" + "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": [ - "### Widgets\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 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``:" + "[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." ] }, { @@ -96,21 +146,61 @@ "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": [ + "## 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", - "print(hv_panel)" + "### 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": [ - "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``:" + "### 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:" ] }, { @@ -119,18 +209,245 @@ "metadata": {}, "outputs": [], "source": [ - "widgets = hv_panel[1]\n", + "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.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`." + ] + }, + { + "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" ] }, { @@ -139,24 +456,64 @@ "metadata": {}, "outputs": [], "source": [ - "pn.panel(dmap, center=True, widget_location='right_bottom')" + "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": [ - "#### Customizing widgets\n", + "#### `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", @@ -179,16 +536,20 @@ " 'amplitude': pn.widgets.LiteralInput(value=1., type=(float, int)),\n", " 'function': pn.widgets.RadioButtonGroup,\n", " 'frequency': {'value': 5}\n", - "}).layout" + "}).layout\n", + "\n", + "hv_panel" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Switching backends\n", + "### 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", - "The ``HoloViews`` pane will default to the Bokeh backend if no backend has been loaded, but you can override the backend as needed." + "Instead of re-rendering the entire plot each time a parameter changes this will delegate the update to HoloViews and update the data inplace:" ] }, { @@ -197,36 +558,22 @@ "metadata": {}, "outputs": [], "source": [ - "import holoviews.plotting.mpl\n", - "import holoviews.plotting.plotly\n", + "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", - "hv_pane = pn.pane.HoloViews(dmap, backend='matplotlib')\n", - "hv_pane" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Please note that in a *server context* you will also have to set the matplotlib backend like below\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", - "```python\n", - "import matplotlib\n", - "matplotlib.use('agg')\n", - "```\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(widget, plot)" ] }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "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" + "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`." ] } ], diff --git a/panel/pane/holoviews.py b/panel/pane/holoviews.py index 855de13082..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 @@ -52,13 +53,16 @@ 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).""") 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', @@ -276,8 +280,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 @@ -475,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) @@ -560,7 +567,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 +614,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: @@ -681,6 +688,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)]