How to set height of a plot created with hvplot.scatter & panel.ReactiveExpr?

How do you set the height of a plot when using hvplot, panel.ReactiveExpr and pn.panel? I’ve tried to insert height argument to multiple places but they do not seem to have any effect.

  • The panel.ReactiveExpr mention widget_layout but I’m not sure if you can pass height there somehow
  • The .opts(height=800) I took from Holoviews Customizing Plots. I’m not sure if these are holoviews objects but it has .opts function which accepts args. That does not seem to have any effect, though.
  • I also tried giving args to pn.panel just because it is the last step which creates the graph. It also takes sizing_mode="strecth_width", but not height does not have any effect. This by the way accepts height arguments which works if the input is not ReactiveExpr but simply combined_scatter = data1.hvplot.scatter() * data2.hvplot.scatter() (where data1 and data2 are pandas Series). Could not find the docs for this one.

Code for MWE

import hvplot.pandas
import pandas as pd
import numpy as np
import panel as pn

pn.extension(sizing_mode="stretch_width", template="fast")

data1 = pn.rx(pd.DataFrame(dict(a=np.random.randn(100), b=np.random.randn(100))))
data2 = pn.rx(pd.DataFrame(dict(a=np.random.randn(100), b=np.random.randn(100))))

slider = pn.widgets.IntRangeSlider(
    name="rande for b",
    start=0,
    end=100,
    value=(0, 100),
    step=10,
).servable(area="sidebar")

data1 = data1[data1["b"].between(slider.value[0], slider.value[1])]["a"]
data2 = data2[data2["b"].between(slider.value[0], slider.value[1])]["a"]


combined_scatter = data1.hvplot.scatter() * data2.hvplot.scatter()
combined_scatter.opts(height=800)  # has no effect
combined_plot = pn.ReactiveExpr(
    combined_scatter,
    height=800,  # has no effect
)
graph_panel = pn.panel(
    combined_plot,
    sizing_mode="stretch_width",
    height=800,  # has no effect
)

graph_panel.servable(
    title="My Title",
    # does not take height argument
)

This produces

Question(s)

  • How to set the height of the plot?
  • why pn.panel does not accept, or ignores, height with interactive graphs? (but works well if the graph is not tied to any widget)

Okay, some progress. Seems that the .opts has no effect as the combined_scatter is of type param.reactive.rx (created with panel.rx). But there is a private attribute called ._current which stores the Overlay object, which has the opts() method which can be called and works.

>>> combined_scatter
:Overlay
   .Scatter.A.I  :Scatter   [index]   (a)
   .Scatter.A.II :Scatter   [index]   (a)
>>> type(combined_scatter)
param.reactive.rx
>>> combined_scatter._current
:Overlay
   .Scatter.A.I  :Scatter   [index]   (a)
   .Scatter.A.II :Scatter   [index]   (a)
>>> type(combined_scatter._current)
holoviews.core.overlay.Overlay

Therefore, using

combined_scatter._current.opts(height=800) 

instead of

combined_scatter.opts(height=800) 

works. Now I get a graph of height 800:

But I feel a bit dirty using a private attribute. What would be the correct way to accomplish this?

Adding some debugging details

Edit: I can also see that the ReactiveExpr object has a layout. It even has a height of 810 but for some reason that height is not used after passing the object to pn.panel.

>>> type(combined_plot.layout)
panel.layout.base.Row
>>> combined_plot.layout
Row(height=810, sizing_mode='stretch_width')
    [0] Row(sizing_mode='stretch_width')
        [0] ParamFunction(function, _pane=HoloViews, defer_load=False, sizing_mode='stretch_width')
>>> combined_plot.layout.height
810

What does pn.panel do?

  • It should return a displayable Panel object.
  • pn.panel does nothing to panel.param.ReactiveExpr objects, as can seen in panel/pane/base.py which starts with:
    if isinstance(obj, (Viewable, ServableMixin)):
        return obj

The ReactiveExpr is subclass of both Viewable and ServableMixin. Therefore:

>>> combined_plot is  pn.panel(
        combined_plot,
        sizing_mode="stretch_width",
       )
True

What does the height in pn.ReactiveExpr do?

  • This has some effect. It changes the outer div.bk-Row height in the html (which will be height+10). So, setting height=455 gives an outer div with height of 465 (always height+10):

  • Then there is another div.bk-Row which has always height of 310:

  • This in turn has bk-panel-models-layout-Column inside it with height of 310, and this has div.bk-Figure inside of it with always height of 300 and margin of 5.

The combined_scatter , which is of type param.reactive.rx, created in

combined_scatter = data1.hvplot.scatter() * data2.hvplot.scatter()

has also a public method called register_display_handler. I’m not sure if that’s the supposed to be used, but this seems to also do the trick:

def display_handler(overlay, **_):
    overlay.opts(height=800)
    return overlay

combined_scatter.register_display_handler(
    lambda x: isinstance(x, holoviews.core.overlay.Overlay),
    display_handler,
)

And I’m not sure if the _display_handlers are meant to be copied over to new rx instances (for example when it’s cloned). If not, the above could be also:

def display_handler(overlay, **_):
    overlay.opts(height=800)
    return overlay

combined_scatter.register_display_handler(
    lambda x: True,
    display_handler,
)

What I do not understand is that why the

combined_scatter.opts(height=800)

call does not work directly. Could someone confirm that (1) should the combined_scatter.opts(height=800) call work? (2) What is the API that is expected to be used for setting height for an interactive graph?