Skip to content

Subplot panel height not consistent #2144

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
nlooije opened this issue Jun 13, 2022 · 4 comments
Open

Subplot panel height not consistent #2144

nlooije opened this issue Jun 13, 2022 · 4 comments

Comments

@nlooije
Copy link

nlooije commented Jun 13, 2022

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:

subplot height mismatch

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)
@nlooije
Copy link
Author

nlooije commented Jun 13, 2022

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.

newplot (7)

@nlooije
Copy link
Author

nlooije commented Jun 13, 2022

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.

@nlooije
Copy link
Author

nlooije commented Dec 11, 2023

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)

newplot (14)

@mayer79
Copy link

mayer79 commented Oct 31, 2024

I had some good results with heights and width corrections.

corr_margin <- function(m, margin) {
  if (m >= 3L) {
    average_margin <- margin * (m - 2) / m
    outer_size <- 1 / m - average_margin
    inner_size <- 1 / m - average_margin + margin
    return(c(outer_size, rep(inner_size, m - 2L), outer_size))
  }
  NULL
}

E.g., if you have 3 rows and a top and bottom margin of 0.05 each, you would write

subplots(..., heights = corr_margin(3, 0.05))

Similar for left/right via width.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants