patchwork icon indicating copy to clipboard operation
patchwork copied to clipboard

Adapt ggsave for patchwork with fixed dimensions

Open jbengler opened this issue 6 years ago • 5 comments

Dear Thomas,

I really like the plot_layout() function you came up with!

I often write something like this: p1 + p2 + p3 + plot_layout(widths = unit(50, 'mm'), heights = unit(50, 'mm')) Now, my question is, how to efficiently save this as a pdf with the correct dimensions. Is there a way to extract the width and height of the patchwork to feed it into ggsave for export? This would be really great!

Thanks a lot and keep up the great work! Broder

jbengler avatar Nov 26 '19 10:11 jbengler

I found something that works. Might not be pretty, though.

library(patchwork)
library(ggplot2)

p1 <- ggplot(mtcars) + 
  geom_point(aes(mpg, disp)) + 
  ggtitle('Plot 1')

p2 <- ggplot(mtcars) + 
  geom_boxplot(aes(gear, disp, group = gear)) + 
  ggtitle('Plot 2')

p3 <- ggplot(mtcars) + 
  geom_point(aes(hp, wt, colour = mpg)) + 
  ggtitle('Plot 3')

p4 <- ggplot(mtcars) + 
  geom_bar(aes(gear)) + 
  ggtitle('Plot 4')

patchwork <- 
  p1 + p2 + p3 + p4 + 
  plot_layout(
    ncol = 4,
    widths = unit(c(50, 25, 50, 25), "mm"),
    heights = unit(50, "mm"),
    guides = "collect"
    )

gtab <- patchwork:::plot_table(patchwork, 'auto')
overall_width <- grid::convertWidth(sum(gtab$widths) + unit(1, "mm"), unitTo = "mm", valueOnly = TRUE)
overall_height <- grid::convertHeight(sum(gtab$heights) + unit(1, "mm"), unitTo = "mm", valueOnly = TRUE)

ggsave("test_patchwork.pdf", width = overall_width, height = overall_height, unit = "mm", useDingbats = FALSE)

jbengler avatar Nov 26 '19 20:11 jbengler

Here, the solution from above wrapped in a function called bro_ggsave(). Ideas to solve this more elegantly are welcome.

devtools::install_github("thomasp85/patchwork")

library(patchwork)
library(tidyverse)

p1 <- ggplot(mtcars) + 
  geom_point(aes(mpg, disp)) + 
  ggtitle('Plot 1')

p2 <- ggplot(mtcars) + 
  geom_boxplot(aes(gear, disp, group = gear)) + 
  ggtitle('Plot 2')

p3 <- ggplot(mtcars) + 
  geom_point(aes(hp, wt, colour = mpg)) + 
  ggtitle('Plot 3')

p4 <- ggplot(mtcars) + 
  geom_bar(aes(gear)) + 
  ggtitle('Plot 4')

patchwork <- 
  p1 + p2 + p3 + p4 + 
  plot_layout(
    ncol = 4,
    widths = unit(c(50, 25, 50, 25), "mm"),
    heights = unit(50, "mm"),
    guides = "collect"
    )

bro_get_ggsize <- function(plot) {
  gtab <- patchwork:::plot_table(plot, 'auto')
  
  has_fixed_dimensions <- 
  !gtab$widths %>% map(~is.null(attr(.x, "unit"))) %>% unlist() %>% any() |
  !gtab$heights %>% map(~is.null(attr(.x, "unit"))) %>% unlist() %>% any()
  
  if (has_fixed_dimensions) {
    width <- grid::convertWidth(sum(gtab$widths) + unit(1, "mm"), unitTo = "mm", valueOnly = TRUE)
    height <- grid::convertHeight(sum(gtab$heights) + unit(1, "mm"), unitTo = "mm", valueOnly = TRUE)
    c(width = width, height = height)
  } else {
    c(width = NA, height = NA)
  }
}

bro_get_ggsize(p1)
bro_get_ggsize(patchwork)

bro_ggsave <- function(plot = last_plot(), filename, width = NA, height = NA, units = c("in", "cm", "mm"), ...) {
  width <- bro_get_ggsize(plot)[["width"]]
  height <- bro_get_ggsize(plot)[["height"]]
  if(!is.na(width)) message("Saving plot with fixed dimensions: ", round(width, 1), " x ", round(height, 1), " mm")
  ggsave(filename = filename, plot = plot, width = width, height = height, units = "mm", ...)
}

bro_ggsave(p1, "bro_test_p1.pdf")
ggsave(plot = p1, filename = "test_p1.pdf")

bro_ggsave(patchwork, "bro_test_patchwork.pdf")
ggsave(plot = patchwork, filename = "test_patchwork.pdf")

jbengler avatar Nov 27 '19 09:11 jbengler

It would be great to have an inbuilt function in the patchwork to calculate the resulting image size for these scenarios. I run in the same problem when I was trying to position correctly in an RMd a couple of “patchworked” plots where the heights was set to an equal amount.

Unfortunately the solution kindly provided by the @jbengler is not working for me. Do you have any idea how to solve it?

library(tidyverse)
library(patchwork)

p <- mtcars %>% 
  ggplot(aes(mpg, hp)) +
  geom_smooth()

mtcars_10 <- p + p + p + p + p + p + p + p + p + p +
  plot_layout(widths = unit(3, "in"), heights = unit(2, "in"))

bro_get_ggsize <- function(plot) {
  gtab <- patchwork:::plot_table(plot, 'auto')
  
  has_fixed_dimensions <- 
    !gtab$widths %>% map(~is.null(attr(.x, "unit"))) %>% unlist() %>% any() |
    !gtab$heights %>% map(~is.null(attr(.x, "unit"))) %>% unlist() %>% any()
  
  if (has_fixed_dimensions) {
    width <- grid::convertWidth(sum(gtab$widths) + unit(1, "mm"), unitTo = "mm", valueOnly = TRUE)
    height <- grid::convertHeight(sum(gtab$heights) + unit(1, "mm"), unitTo = "mm", valueOnly = TRUE)
    c(width = width, height = height)
  } else {
    c(width = NA, height = NA)
  }
}

bro_get_ggsize(mtcars_10)
#> `geom_smooth()` using method = 'loess' and formula 'y ~ x'
#> `geom_smooth()` using method = 'loess' and formula 'y ~ x'
#> `geom_smooth()` using method = 'loess' and formula 'y ~ x'
#> `geom_smooth()` using method = 'loess' and formula 'y ~ x'
#> `geom_smooth()` using method = 'loess' and formula 'y ~ x'
#> `geom_smooth()` using method = 'loess' and formula 'y ~ x'
#> `geom_smooth()` using method = 'loess' and formula 'y ~ x'
#> `geom_smooth()` using method = 'loess' and formula 'y ~ x'
#> `geom_smooth()` using method = 'loess' and formula 'y ~ x'
#> `geom_smooth()` using method = 'loess' and formula 'y ~ x'
#>  width height 
#>     NA     NA

gtab <- patchwork:::plot_table(mtcars_10, 'auto')
#> `geom_smooth()` using method = 'loess' and formula 'y ~ x'
#> `geom_smooth()` using method = 'loess' and formula 'y ~ x'
#> `geom_smooth()` using method = 'loess' and formula 'y ~ x'
#> `geom_smooth()` using method = 'loess' and formula 'y ~ x'
#> `geom_smooth()` using method = 'loess' and formula 'y ~ x'
#> `geom_smooth()` using method = 'loess' and formula 'y ~ x'
#> `geom_smooth()` using method = 'loess' and formula 'y ~ x'
#> `geom_smooth()` using method = 'loess' and formula 'y ~ x'
#> `geom_smooth()` using method = 'loess' and formula 'y ~ x'
#> `geom_smooth()` using method = 'loess' and formula 'y ~ x'
gtab$widths
#>  [1] 1.93302891933029mm 0mm                0mm                0mm               
#>  [5] 4.93526445966514mm 7.29597602739726mm 0mm                3inches           
#>  [9] 0mm                0mm                0mm                0mm               
#> [13] 0mm                0mm                1.93302891933029mm 1.93302891933029mm
#> [17] 0mm                0mm                0mm                4.93526445966514mm
#> [21] 7.29597602739726mm 0mm                3inches            0mm               
#> [25] 0mm                0mm                0mm                0mm               
#> [29] 0mm                1.93302891933029mm 1.93302891933029mm 0mm               
#> [33] 0mm                0mm                4.93526445966514mm 7.29597602739726mm
#> [37] 0mm                3inches            0mm                0mm               
#> [41] 0mm                0mm                0mm                0mm               
#> [45] 1.93302891933029mm 1.93302891933029mm 0mm                0mm               
#> [49] 0mm                4.93526445966514mm 7.29597602739726mm 0mm               
#> [53] 3inches            0mm                0mm                0mm               
#> [57] 0mm                0mm                0mm                1.93302891933029mm
gtab$heights
#>  [1] 1.93302891933029mm 0mm                0mm                0mm               
#>  [5] 0mm                0mm                0mm                0mm               
#>  [9] 0mm                2inches            0mm                4.91472602739726mm
#> [13] 4.93526445966514mm 0mm                0mm                0mm               
#> [17] 0mm                1.93302891933029mm 1.93302891933029mm 0mm               
#> [21] 0mm                0mm                0mm                0mm               
#> [25] 0mm                0mm                0mm                2inches           
#> [29] 0mm                4.91472602739726mm 4.93526445966514mm 0mm               
#> [33] 0mm                0mm                0mm                1.93302891933029mm
#> [37] 1.93302891933029mm 0mm                0mm                0mm               
#> [41] 0mm                0mm                0mm                0mm               
#> [45] 0mm                2inches            0mm                4.91472602739726mm
#> [49] 4.93526445966514mm 0mm                0mm                0mm               
#> [53] 0mm   

kbzsl avatar Aug 11 '20 05:08 kbzsl

It's working when removing the check for the has_fixed_dimensions, just I have to add a bit more extra space to the width (10 mm instead 1 mm, but this can be case by case).

kbzsl avatar Aug 11 '20 06:08 kbzsl

For anyone roaming here in the future, can replace gtab <- patchwork:::plot_table(plot, 'auto') with gtab <- patchwork::patchworkGrob(plot) which also seems to account for overhangs related to plot tags and so it's not necessary to add an extra mm of padding around the patchwork.

wmoldham avatar Apr 09 '21 16:04 wmoldham