How can I make plotly subplots the same size when converting from facets with ggplotly?

Viewed 1095

In ggplot2 I believe the facets (subplots) are all the same size. I think this is very important for making comparisons between the subplots. However, when I convert my plots to plotly objects using ggplotly() the subplots are not always the same size. In particular, the rows seem to be different heights. I am not sure if this is the intended functionality of ggplotly(), a bug, or just user error on my part. I am using ggplot2 version 2.2.1.9000 and plotly 4.7.1.

How can I convert my ggplots to plotly objects while ensuring the subplots are all the same size?

Note: I originally posted this question in the context of using Shiny and plotly. However, the issue seems to be independent of Shiny so I am deleting my original question and reposting.

In the example below, when I use facet_wrap(sex ~ day) the second row is slightly shorter than top and bottom rows. When I use facet_grid(sex ~ day) the rows are equal in height. When I use facet_grid(sex ~ day) the two middle rows are slightly shorter.

library(plotly)
library(reshape2)

# An example plot
myPlot <- ggplot(tips, aes(x = total_bill, y = tip / total_bill)) +
  geom_point(shape = 1) 

# Facet wrap with sex ~ day
pWrap <- myPlot + facet_wrap(sex ~ day)
qWrap <- ggplotly(pWrap)

q <- qWrap
d <- rbind(q$x$layout$yaxis4$domain, q$x$layout$yaxis3$domain, q$x$layout$yaxis2$domain, q$x$layout$yaxis$domain)
dfWrap <- data.frame(lb = d[,1], ub = d[,2])
dfWrap["range"] <- dfWrap["ub"] - dfWrap["lb"]
dfWrap["facetType"] <- "facet_wrap(sex ~ day)"

# Facet grid with sex ~ day
pGrid1 <- myPlot + facet_grid(sex ~ day)
qGrid1 <- ggplotly(pGrid1)

q <- qGrid1
d <- rbind(q$x$layout$yaxis4$domain, q$x$layout$yaxis3$domain, q$x$layout$yaxis2$domain, q$x$layout$yaxis$domain)
dfGrid1 <- data.frame(lb = d[,1], ub = d[,2])
dfGrid1["range"] <- dfGrid1["ub"] - dfGrid1["lb"]
dfGrid1["facetType"] <- "facet_grid(sex ~ day)"

# Facet grid with day ~ sex
pGrid2 <- myPlot + facet_grid(day ~ sex)
qGrid2 <- ggplotly(pGrid2)

q <- qGrid2
d <- rbind(q$x$layout$yaxis4$domain, q$x$layout$yaxis3$domain, q$x$layout$yaxis2$domain, q$x$layout$yaxis$domain)

dfGrid2 <- data.frame(lb = d[,1], ub = d[,2])
dfGrid2["range"] <- dfGrid2["ub"] - dfGrid2["lb"]
dfGrid2["facetType"] <- "facet_grid(day ~ sex)"

EDIT

After some more digging I found that the plotly uses the get_domains() function to define the layout. Inside this function there is a line for defining the heights of the plots

ys[[i]] <- c(ystart = 1 - (heights[j]) - if (j == 1) 0 else margins[3],
             yend = 1 - (heights[j + 1]) + if (j == nrows) 0 else margins[4])

such that the top and bottom rows are higher by the width of the margins. It appears the margins include not only the spacing between the panels (panel.spacing) but also room for axis labels, ticks, panel titles, etc. Therefore, we can find a test case that works reasonably well with ggplot() but totally fails with ggplotly()

p <- ggplot(data = tips %>% 
              group_by(time, sex, day) %>%
              summarize(total_bill = mean(total_bill)),
            aes(x = time, y = total_bill)) +
  geom_bar(stat = "identity") +
  facet_wrap(sex ~ day, scales = "free_x") +
  theme(axis.text.x = element_text(angle = 90))

ggplotly(p)
0 Answers
Related