Hi,
The issue I have is that a Column panel of plots, which are dynamically updated upon some user interaction, do not resize to the window dimensions even though they are set responsive.
I have created a minimal example below for illustration. A SimplePlot class creates a basic sinusoid with an adjustable amplitude. A PlotViewer class combines the SimplePlot objects into a single panel. A checkbox tied to the parameter link_y_axes is used to trigger the PlotViewer.view() method to change the plot y-labels for linking/unlinking the y-axes.
In the panel() method, two options are shown for plotting. The first (preferable because the full application needs to update datashaded plots) passes the view callable to the panel, and works properly other than resizing. The second passes the output of the view method and resizes, but does not react to the checkbox interaction.
Minimal code example
python=3.7.7
bokeh=2.0.1
holoviews=1.13.2
panel=0.9.3
param=1.9.3
import param, panel as pn, numpy as np, holoviews as hv
pn.extension()
num_plots = 2
# Class to create simple holoviews curve plot of a sinusoid
class SimplePlot(param.Parameterized):
amplitude = param.Number(default=1.0, bounds=(0, 1))
@param.depends('amplitude')
def plot(self):
points = [(0.1 * i, self.amplitude * np.sin(0.1 * i)) for i in range(100)]
return hv.Curve(points).redim.range(y=(-1,1))
def view(self, suffix=""):
return hv.DynamicMap(self.plot).redim(y="y"+suffix).opts(responsive=True, ylabel='y', min_width=400, min_height=200)
# Class to combine multiple SimplePlot objects into a single panel
class PlotViewer(param.Parameterized):
link_y_axes = param.Boolean(True)
plot_list = param.List([], precedence=-1)
num_plots = param.Integer(default=2, bounds=(1,None), precedence=-1)
def __init__(self, **params):
super().__init__(**params)
self.plot_list = [SimplePlot() for i in range(self.num_plots)]
# Whenever the link_y_axes checkbox is changed, replot the Column so that the
# plot y-axis labels change and are linked/unlinked appropriately
@param.depends('link_y_axes')
def view(self):
col = pn.Column(width_policy='max', height_policy='max')
for i, plot in enumerate(self.plot_list):
y_label_suffix = "" if self.link_y_axes else str(i)
col.append(plot.view(y_label_suffix))
return col
def panel(self):
plot_params = pn.Column(width_policy='fit')
plot_params.append(pn.Param(self.param))
plot_params.extend([pn.Param(plot.param) for plot in self.plot_list])
# # Option 1: Dynamic sizing to window does not work, but plots update correctly
# return pn.Row(self.view, plot_params, width_policy='max', height_policy='max')
# # Option 2: Dynamic sizing works, but does not update plots
return pn.Row(self.view(), plot_params, width_policy='max', height_policy='max')
viewer = PlotViewer(num_plots=num_plots)
viewer.panel().servable()
Sorry, as a new user I couldn’t just upload two gifs…
Demonstration: First Option 1 which allows dynamic linking but not resizing then (after a pause) Option 2 which allows resizing but not dynamic linking
Ideally I want to pass the callable self.view (Option 1) which is more amenable to my ultimate application of plot axes linking and datashader, but plot resizing is needed too. Any help would be appreciated! Thanks!