thomasp85 / patchwork

The Composer of ggplots
https://patchwork.data-imaginist.com
Other
2.45k stars 162 forks source link

Adapt ggsave for patchwork with fixed dimensions #127

Closed jbengler closed 3 weeks ago

jbengler commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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")
kbzsl commented 4 years ago

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 commented 4 years ago

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).

wmoldham commented 3 years ago

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.

thomasp85 commented 3 weeks ago

I'm going to close this. While nifty, and the above function works for your use case, it is generally not possible to know how much space a plot will take up before the graphics device is opened and I can't thus provide a general solution