Skip to content

Commit

Permalink
Add responsive option for Plotly elements (#4319)
Browse files Browse the repository at this point in the history
  • Loading branch information
philippjfr committed Mar 24, 2020
1 parent c3c3310 commit d5b3532
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 19 deletions.
15 changes: 11 additions & 4 deletions holoviews/plotting/plotly/element.py
Expand Up @@ -60,6 +60,9 @@ class ElementPlot(PlotlyPlot, GenericElementPlot):
Margins in pixel values specified as a tuple of the form
(left, bottom, right, top).""")

responsive = param.Boolean(default=False, doc="""
Whether the plot should stretch to fill the available space.""")

show_legend = param.Boolean(default=False, doc="""
Whether to show legend for the plot.""")

Expand Down Expand Up @@ -184,7 +187,8 @@ def generate_plot(self, key, ranges, element=None):
self.handles['layout'] = layout

# Create figure and return it
fig = dict(data=components['traces'], layout=layout)
layout['autosize'] = self.responsive
fig = dict(data=components['traces'], layout=layout, config=dict(responsive=self.responsive))
self.handles['fig'] = fig

self._execute_hooks(element)
Expand Down Expand Up @@ -472,8 +476,11 @@ def init_layout(self, key, element, ranges):
options['yaxis'] = yaxis
options['margin'] = dict(l=l, r=r, b=b, t=t, pad=4)

return dict(width=self.width, height=self.height,
title=self._format_title(key, separator=' '),
if not self.responsive:
options['width'] = self.width
options['height'] = self.height

return dict(title=self._format_title(key, separator=' '),
plot_bgcolor=self.bgcolor, **options)

def _get_ticks(self, axis, ticker):
Expand Down Expand Up @@ -580,7 +587,7 @@ class OverlayPlot(GenericOverlayPlot, ElementPlot):
_propagate_options = [
'width', 'height', 'xaxis', 'yaxis', 'labelled', 'bgcolor',
'invert_axes', 'show_frame', 'show_grid', 'logx', 'logy',
'xticks', 'toolbar', 'yticks', 'xrotation', 'yrotation',
'xticks', 'toolbar', 'yticks', 'xrotation', 'yrotation', 'responsive',
'invert_xaxis', 'invert_yaxis', 'sizing_mode', 'title', 'title_format',
'padding', 'xlabel', 'ylabel', 'zlabel', 'xlim', 'ylim', 'zlim']

Expand Down
8 changes: 7 additions & 1 deletion holoviews/plotting/plotly/renderer.py
Expand Up @@ -28,7 +28,11 @@ def _PlotlyHoloviewsPane(fig_dict, **kwargs):
# Remove internal HoloViews properties
clean_internal_figure_properties(fig_dict)

plotly_pane = pn.pane.Plotly(fig_dict, viewport_update_policy='mouseup', **kwargs)
config = fig_dict.pop('config', {})
if config.get('responsive'):
kwargs['sizing_mode'] = 'stretch_both'
plotly_pane = pn.pane.Plotly(fig_dict, viewport_update_policy='mouseup',
config=config, **kwargs)

# Register callbacks on pane
for callback_cls in callbacks.values():
Expand Down Expand Up @@ -70,13 +74,15 @@ def get_plot_state(self_or_cls, obj, doc=None, renderer=None, **kwargs):
Allows cleaning the dictionary of any internal properties that were added
"""
fig_dict = super(PlotlyRenderer, self_or_cls).get_plot_state(obj, renderer, **kwargs)
config = fig_dict.get('config', {})

# Remove internal properties (e.g. '_id', '_dim')
clean_internal_figure_properties(fig_dict)

# Run through Figure constructor to normalize keys
# (e.g. to expand magic underscore notation)
fig_dict = go.Figure(fig_dict).to_dict()
fig_dict['config'] = config

# Remove template
fig_dict.get('layout', {}).pop('template', None)
Expand Down
52 changes: 38 additions & 14 deletions holoviews/plotting/plotly/util.py
Expand Up @@ -450,12 +450,12 @@ def _scale_translate(fig, scale_x, scale_y, translate_x, translate_y):
layout = fig.setdefault('layout', {})

def scale_translate_x(x):
return [x[0] * scale_x + translate_x,
x[1] * scale_x + translate_x]
return [min(x[0] * scale_x + translate_x, 1),
min(x[1] * scale_x + translate_x, 1)]

def scale_translate_y(y):
return [y[0] * scale_y + translate_y,
y[1] * scale_y + translate_y]
return [min(y[0] * scale_y + translate_y, 1),
min(y[1] * scale_y + translate_y, 1)]

def perform_scale_translate(obj):
domain = obj.setdefault('domain', {})
Expand Down Expand Up @@ -673,17 +673,26 @@ def figure_grid(figures_grid,
nrows = len(row_heights)
ncols = len(column_widths)

responsive = True
for r in range(nrows):
for c in range(ncols):
fig_element = figures_grid[r][c]
if not fig_element:
continue
responsive &= fig_element.get('config', {}).get('responsive', False)

w = fig_element.get('layout', {}).get('width', None)
default = None if responsive else 400
for r in range(nrows):
for c in range(ncols):
fig_element = figures_grid[r][c]
if not fig_element:
continue

w = fig_element.get('layout', {}).get('width', default)
if w:
column_widths[c] = max(w, column_widths[c])

h = fig_element.get('layout', {}).get('height', None)
h = fig_element.get('layout', {}).get('height', default)
if h:
row_heights[r] = max(h, row_heights[r])

Expand Down Expand Up @@ -730,27 +739,42 @@ def figure_grid(figures_grid,

_offset_subplot_ids(fig, subplot_offsets)

fig_height = fig['layout']['height'] * row_height_scale
fig_width = fig['layout']['width'] * column_width_scale

scale_x = (column_domain[1] - column_domain[0]) * (fig_width / column_widths[c])
scale_y = (row_domain[1] - row_domain[0]) * (fig_height / row_heights[r])
_scale_translate(fig,
scale_x, scale_y,
column_domain[0], row_domain[0])
if responsive:
scale_x = 1./ncols
scale_y = 1./nrows
px = ((0.2/(ncols) if ncols > 1 else 0))
py = ((0.2/(nrows) if nrows > 1 else 0))
sx = scale_x-px
sy = scale_y-py
_scale_translate(fig, sx, sy, scale_x*c+px/2., scale_y*r+py/2.)
else:
fig_height = fig['layout'].get('height', default) * row_height_scale
fig_width = fig['layout'].get('width', default) * column_width_scale
scale_x = (column_domain[1] - column_domain[0]) * (fig_width / column_widths[c])
scale_y = (row_domain[1] - row_domain[0]) * (fig_height / row_heights[r])
_scale_translate(
fig, scale_x, scale_y, column_domain[0], row_domain[0]
)

merge_figure(output_figure, fig)

if responsive:
output_figure['config'] = {'responsive': True}

# Set output figure width/height
if height:
output_figure['layout']['height'] = height
elif responsive:
output_figure['layout']['autosize'] = True
else:
output_figure['layout']['height'] = (
sum(row_heights) + row_spacing * (nrows - 1)
)

if width:
output_figure['layout']['width'] = width
elif responsive:
output_figure['layout']['autosize'] = True
else:
output_figure['layout']['width'] = (
sum(column_widths) + column_spacing * (ncols - 1)
Expand Down

0 comments on commit d5b3532

Please sign in to comment.