1

I have been trying to solve a sizing issue with 3x3 grid of plots with ggplot, cowplot and tidyterra. The plots are spatial rasters, that need a shared legend for each row, a title in the first row, and a y-label in the first column.

I think there is a partial solution to my issue here, referring to the legend: R: Aligning/Sizing for plot_grid in cowplot?

A comment in the link above by the developer of cowplot, Claus Wilke, addresses the legend incorporation (but currently has a broken link), so following this should help with the legend https://wilkelab.org/cowplot/articles/shared_legends.html

But the added difficulty of the y-label and titles still gives me trouble with the sizing.

So in my reproducible example below 2 levels of plot_grid are needed. But note that I need the plots to remain very close to each other (for space limitations and publication purposes), so I reduce the margins to zero.

I started with including the legend within the last column of plots, but looking at Chris's comment, I now have tried to use his approach on this (adding it as object in the plot_grid rather than keeping the legend in the plots for the last column) but I think I get the same issue with random sizing when trying to keep the plots tightly close to each other.

Here is my reproducible example of only a 2 x 3 grid (the last row for my 3x3 is just another addition like the 2nd row, which is not a problem in itself). Not very elegant, but each of the three plots per row need slightly different parameters.

library(terra)
library(ggplot2)
library(cowplot)
library(tidyterra)

#get a sample raster
f <- system.file("ex/elev.tif", package="terra")
r <- terra::rast(f)


#plots for first row, will need titles for all plots, and y-label on first column plot, aand extract legend for last column plot

p <- ggplot2::ggplot()+
  tidyterra::geom_spatraster(data=r)+
  ggplot2::ggtitle("title")+
  ggplot2::ylab("Label")+
  ggplot2::theme(
    plot.margin = ggplot2::margin(0,0,0,0, "cm"),
    
  axis.text= ggplot2::element_blank(), axis.ticks= ggplot2::element_blank(),
  text = ggplot2::element_text(size=20))

#extracting legend
legend <- cowplot::get_legend(
  # create some space to the left of the legend
  p + ggplot2::theme(legend.box.margin = ggplot2::margin(0, 0, 0, 12))
)


p2 <- ggplot2::ggplot()+
  tidyterra::geom_spatraster(data=r)+
  ggplot2::ggtitle("title")+
  ggplot2::theme(legend.position = "none")+
  ggplot2::theme(
    plot.margin = ggplot2::margin(0,0,0,0, "cm"),
    
  axis.text= ggplot2::element_blank(), axis.ticks= ggplot2::element_blank(),
  text = ggplot2::element_text(size=20))

p3 <-ggplot2::ggplot()+
  tidyterra::geom_spatraster(data=r)+
  ggplot2::ggtitle("title")+
  ggplot2::theme(legend.position = "none")+
  ggplot2::theme(
    plot.margin = ggplot2::margin(0,0,0,0, "cm"),
    
  axis.text= ggplot2::element_blank(), axis.ticks= ggplot2::element_blank(),
  text = ggplot2::element_text(size=20))


# add the legend to the row we made earlier. Give it one-third of 
# the width of one plot (via rel_widths).
set1<-cowplot::plot_grid(p+ggplot2::theme(legend.position = "none"), p2, p3, legend,  axis = "bt", align = 'vh',
    rel_widths = c(1,1,1,0.35),#align = 'vh',
                        nrow = 1)

#plots for Second row, no titles, y-label on first column plot, and extract legend for last column plot
p4 <- ggplot2::ggplot()+
  tidyterra::geom_spatraster(data=r)+
  ggplot2::ylab("Label")+
  ggplot2::theme(
    plot.margin = ggplot2::margin(0,0,0,0, "cm"),
    
  axis.text= ggplot2::element_blank(), axis.ticks= ggplot2::element_blank(),
  text = ggplot2::element_text(size=20))

legend2 <- cowplot::get_legend(
  # create some space to the left of the legend
  p4 + ggplot2::theme(legend.box.margin = ggplot2::margin(0, 0, 0, 12))
)


p5 <- ggplot2::ggplot()+
  tidyterra::geom_spatraster(data=r)+
  ggplot2::theme(legend.position = "none")+
  ggplot2::theme(
    plot.margin = ggplot2::margin(0,0,0,0, "cm"),
  axis.text= ggplot2::element_blank(), axis.ticks= ggplot2::element_blank())

p6 <-ggplot2::ggplot()+
  tidyterra::geom_spatraster(data=r)+
  ggplot2::theme(legend.position = "none")+
  ggplot2::theme(
    plot.margin = ggplot2::margin(0,0,0,0, "cm"),
    
  axis.text= ggplot2::element_blank(), axis.ticks= ggplot2::element_blank())

set2<-cowplot::plot_grid( p4+ggplot2::theme(legend.position = "none"), p5, p6, legend2, axis = "bt", align = 'vh',
nrow = 1,  rel_widths = c(1,1,1,0.35))

# add the legend to the row we made earlier. Give it one-third of 
# the width of one plot (via rel_widths).
#set2<-cowplot::plot_grid(p4, p5, p6, legend2, nrow = 1)

plotset<-cowplot::plot_grid(set1, set2, nrow = 2)
ggplot2::ggsave(plotset, 
                filename = "testplot.png", 
                path = "plots/", 
                units = "in", width = 21, height = 18, dpi = 300, bg = "white")

I have attempted modifying the width and height in the ggsave and the closer I get to one nice grid, some other plot is off. If I increase the width or heigth too much, the plots start to spread out too much, and there is too much white space in between.... or even the legend is very spread out too.

I have previously attempted to play with rel_with and rel_height but never get them right either, same as with width and height in the ggsave. I have tried this with all the plots (eg. increase rel_width like c(1.15, 1, 1.25) but again for hours, a not getting anywhere, and feels like eyeballing it too much, rather than a consistent solution).

If I try align = 'vh' as suggested here, and added axis = "bt to avoid warnings, but nothing changes: How to resize plots to same size in a grid of multiple plots

I honestly have spent hours (if not days) trying to size them, but nothing gets me to the perfect consistent sizing (unless I remove the y-label and titles, as per the example below). Many question/solutions do not make reference to my specific case of y-label in 1 column and titles in 1 row only.

What I get so far with the above code: enter image description here

Note that the first column plots are off in both rows. And the space between plots in the first row is also off and even different (not really sure why the space between col1 and col2 differs with the space between col2 and col3).

this is an example of what I get without the y-labels and titles, and what I want in matters of sizing (still with a tad of blank space between rows, but negligible at this point of my struggle): enter image description here

I don't think saving the png with no titles and y-labels and then adding them with an image editor is the way to go. I hope there is way to do this in R programmatically, but I just haven't figured it out or found it. Any help or guidance would be greatly appreciated.

Cheers!

5
  • This would be much easier if you use facets. Commented Mar 13, 2024 at 16:04
  • The problem seems to stem from the fact that you set text = element_text(size=20) in plots 1-4 but not in p5 or p6. Is this intentional or necessary? Otherwise agree with @M-- that facet_grid() may be a better solution. Commented Mar 13, 2024 at 16:07
  • Thanks @M-- I have attempted using facets, but this are raster data and my original data are large high res rasters. I have converted them into dataframes for the facets, but it was getting very very long to run, compared to ploting the rasters directly as in my example (ten times longer or more). I may try that if the patchwork asnwer does work. @zphryl thanks but the text =element_text(size=20) is not affecting the plots missing it, because they are blank anyway with axis.text= ggplot2::element_blank(). just re-run it to test, and same result. Commented Mar 13, 2024 at 17:14
  • You don't need to convert to dataframe for facets. Raster datasets can be combined directly (stackoverflow.com/questions/15876591/…) You only need to add a unique grouping variable for each raster so it will be then used in the facet_wrap/grid. That said, the option with the patchwork seems nice. Commented Mar 13, 2024 at 17:35
  • 1
    I would need to think about using facets with the rasters directly. Maybe I can work on this next first. The link you share in the comment is about merging rasters. Do you suggest to create a stack with terra and then faceting the stack? I will need to play around with such an idea for a bit. The patchwork option will require my small and tired brain to learn its ways and also some get familiar with purr. Will report back soon. Commented Mar 13, 2024 at 17:43

1 Answer 1

1

As always when it comes to combining multiple plots one might consider patchwork as an option.

library(terra)
library(ggplot2)
library(patchwork)
library(tidyterra)

f <- system.file("ex/elev.tif", package = "terra")
r <- terra::rast(f)

p <- ggplot2::ggplot() +
  tidyterra::geom_spatraster(data = r) +
  ggplot2::ggtitle("title") +
  ggplot2::ylab("Label") +
  ggplot2::theme(
    plot.margin = ggplot2::margin(0, 0, 0, 0, "cm"),
    axis.text = ggplot2::element_blank(), 
    axis.ticks = ggplot2::element_blank(),
    text = ggplot2::element_text(size = 20)
  )

library(patchwork)

patch1 <- list(p, p, p) |> 
  # Remove the y axis title and the plot title except for the first plot
  purrr::imap(\(x, y) if (!y == 1) x + labs(y = NULL) else x) |> 
  wrap_plots(guides = "collect")

patch2 <- list(p, p, p) |> 
  purrr::imap(\(x, y) if (!y == 1) x + labs(y = NULL) else x) |> 
  wrap_plots(guides = "collect") &
  # Get rif of the titles
  labs(title = NULL)

patch1 / patch2

enter image description here

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks, I am not familiar at all with the purr functions and syntax. But I will give it go. I wanted to be parsimonious as I basically use cowplot for all my combined plots... but I guess I can try patchwork and purr. I would love to hear if there is acutally a solution with cowplot though! May mark the aswer as solved as soon as I try it with my own data too!
this work nicely with my own data basically as is! thanks a lot!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.