Subplot panel height not consistent
There doesn't seem to be a way to set the total figure size for subplot().
Individual panel height can be specified in the call to plot_ly and (as i understand it) that height is scaled with the number of panels to become the total figure size.
I tried using this by passing to each subplot the individual plotheight multiplied with the number of subplots (nrows) so the total height of the total figure would be plotheight * nrows. It turns out the heights aren't strictly adhered too. Comparing the total height of the first two panels you can clearly see the x axes are at different heights:

generate_subplot <- function(nrows, margin = NULL){
if(is.null(margin)) margin = c(0,0,0,0)
p <- plotly::plot_ly(mtcars,
x = ~cyl, y = ~mpg,
color = ~rownames(.data),
type = "bar",
height = 150 * nrows
) %>% plotly::layout(
showlegend = FALSE,
shapes = list(
list(
type = 'rect',
fillcolor = "gray",
line = list(color = "gray"),
opacity = 0.05,
x0 = 0, x1 = 1,
y0 = 0, y1 = 1,
xref = 'paper',
yref = 'paper'
)
)
)
plotly::subplot(
rep_len(list(p), nrows),
nrows = nrows,
margin = margin
)
}
generate_subplot(2)
generate_subplot(4)
generate_subplot(8)
I found a hack to get the size of the panels to be as i specify.
I noticed that as the number of panels increased the relative height differences became smaller.
So for different values of nrows i measured the actual height (in pixels using GIMP) of the panels in the subplot:
| nrows | specified [px] | actual [px] | deviation [px] |
|---|---|---|---|
| 1 | 150 | 86 | 64 |
| 2 | 150 | 118 | 32 |
| 4 | 150 | 134 | 16 |
| 8 | 150 | 142 | 8 |
| 16 | 150 | 146 | 4 |
Notice how the deviations from the specified value are multiples of 2, likely this may help to track down the issue.
But for now the deviation can be described by an equation of the form deviation = 64/nrows.
So in the call to plot_ly I can now set height = (150 + 64/nrows) * nrows = 150 * nrows + 64 to get panels exactly 150 px for any value of nrows. Apparantly it requires an additional 64 pixels to work.

Unfortunately when specifying margins they are also inconsistent. They are based on the panel height so with the above hack (but also without) the absolute margin size is changing with the number of panels. The same sort of hack can be applied by dividing the margins by the number of panels although this isn't exact for small number of panels. For larger number of panels the relative differences in absolute margins becomes negligible if scaled this way.
This is code accounts for presence of margins:
generate_subplot <- function(nrows, margin = 0){
# LOGIC FOR GETTING THE CORRECT SUBPLOT SPACING
# This assumes a base case for nrows = 2
# with a given REL_MARGIN AND PLOT_HEIGHT:
# BASE_TOTAL_HEIGHT = 2*PLOT_HEIGHT + ABS_MARGIN
# ABS_MARGIN = REL_MARGIN * BASE_TOTAL_HEIGHT
# from which we calculate BASE_TOTAL_HEIGHT and ABS_MARGIN
# To calculate the NEW_MARGIN for nrows > 2
# with the calculated ABS_MARGIN assuming this remains constant:
# TOTAL_HEIGHT = NROWS*PLOT_HEIGHT + ABS_MARGIN
# ABS_MARGIN = NEW_MARGIN * TOTAL_HEIGHT
# from which follows the TOTAL_HEIGHT and NEW_MARGIN
REL_MARGIN = margin
PLOT_HEIGHT <- 150
BASE_TOTAL_HEIGHT = 2*PLOT_HEIGHT/(1-REL_MARGIN)
BASE_MARGIN = REL_MARGIN * BASE_TOTAL_HEIGHT
TOTAL_HEIGHT = nrows*PLOT_HEIGHT + (nrows-1)*BASE_MARGIN
NEW_MARGIN = BASE_MARGIN / TOTAL_HEIGHT
OFFSET <- 64
p <- plotly::plot_ly(mtcars,
x = ~rownames(.data),
height = TOTAL_HEIGHT + OFFSET
) %>% plotly::add_trace(
y = ~mpg,
type = 'bar'
) %>% plotly::layout(
showlegend = TRUE,
shapes = list(
list(
type = 'rect',
fillcolor = "white",
line = list(color = "black"),
opacity = 0.2,
x0 = 0, x1 = 1,
y0 = 0, y1 = 1,
xref = 'paper',
yref = 'paper'
)
)
)
plotly::subplot(
rep_len(list(p), nrows),
nrows = nrows,
margin = c(0,0, 0, NEW_MARGIN),
shareX = TRUE,
titleX = FALSE,
titleY = FALSE
) %>%
plotly::layout(
xaxis = list(
showticklabels = FALSE
)
)
}
generate_subplot(2, 0.2)
generate_subplot(4, 0.2)
generate_subplot(8, 0.2)