gjabel gjabel - 1 year ago 117
R Question

Fixing plot area width when using layout_matrix in grid.arrange

I am combining facet plots of tiles. I want each tile to be square, or at least take the same height and width.

So far I have managed to give equal height to each row of tiles using

. I am stuck when trying to fix an equal width to each column of tiles (across the plots).

Some code based on
to try and illustrate the layout of my plot (actual data way more complicated):


df0 <- mtcars %>%
group_by(cyl) %>%

df1 <- mtcars %>%
rownames_to_column("car") %>%
mutate(man = gsub("([A-Za-z]+).*", "\\1", car))

g <- list()
for(i in 1:nrow(df0)){
g[[i]] <- ggplot(data = df1 %>% filter(cyl == df0$cyl[i]),
mapping = aes(x = "", y = car, fill = qsec)) +
geom_tile() +
facet_grid( man ~ ., scales = "free_y", space = "free") +
labs(x = "", y = "") +
guides(fill = FALSE) +
theme(strip.text.y = element_text(angle=0)) +

m0 <- cbind(c(rep(1, df0$n[1]), rep(NA, max(df0$n) - df0$n[1])),
c(rep(2, df0$n[2]), rep(NA, max(df0$n) - df0$n[2])),
c(rep(3, df0$n[3]), rep(NA, max(df0$n) - df0$n[3])))
grid.arrange(grobs = g, layout_matrix = m0)

Which produces this plot (minus my MS Paint skills):

enter image description here

Presumably the different lengths of the labels in the strip text and y axis lead to the different widths for the plotting area. Not sure how I can avoid this behavior though? I thought I could create on big
but I could not get anywhere near the layout of the plot above.

Answer Source

Turns out this is a rather tricky thing to do. Luckily, cowplot::plot_grid can already do the alignment that results in equal sizes of the columns. I just took that function and removed the fluff, and decoupled the heights from the grid pattern it normally uses. We end up with a little custom function that does the job (all credits to Claus Wilke):

plot_grid_gjabel <- function(plots, heights) {
  grobs <- lapply(plots, function(x) {
    if (!is.null(x)) 
    else NULL
  num_plots <- length(plots)
  num_widths <- unique(lapply(grobs, function(x) {
  num_widths[num_widths == 0] <- NULL
  max_widths <- do.call(grid::unit.pmax, 
                        lapply(grobs, function(x) { x$widths }))
  for (i in 1:num_plots) {
    grobs[[i]]$widths <- max_widths
  width <- 1 / num_plots
  height <- heights / max(heights)
  x <- cumsum(width[rep(1, num_plots)]) - width
  p <- cowplot::ggdraw() 
  for (i in seq_along(plots)) {
    p <- p + cowplot::draw_grob(grid::grobTree(grobs[[i]]), x[i], 1 - height[i], 
                                width, height[i])

We can simply call this like so:

plot_grid_gjabel(g, df0$n)

Resulting in:

enter image description here