using ggsave and arrangeGrob after updating gridExtra to 2.0.0
Asked Answered
M

3

9

since I read a lot similar question on stackoverflow so far, I couldn't find a good solution without updating ggplot2 to the development version.

My problem, I have several scripts which use arrangeGrob to create combined graph out of individual graphs. I save them into a variable and print this variable and/or save it with ggsave. Since a lot of my colleagues update there packages regularly (which is a good thing I think), I always get mails my script no longer work after updating to gridExtra 2.0.0.

I am not sure how to handle this, since the new ggplot2 version where the problem is solved is still under development. I found an article on stack overflow to remove a test if the object to save is a ggplot since the new arrangeGrob function returns a gtable object, but this fails in my case:

library(ggplot2)
library(grid)
library(gridExtra)
a <- data.frame(x=c(1,2,3),
                y=c(2,3,4))
p <- ggplot(a, aes(x, y)) + geom_point()
b <- arrangeGrob(p, p)
grid.draw(b)
ggsave('test.pdf', b)
ggsave <- ggplot2::ggsave
body(ggsave) <- body(ggplot2::ggsave)[-2]
ggsave('test.pdf', b)

Some output and error on the console:

d> grid.draw(b)
d> ggsave('test.pdf', b)
Error in ggsave("test.pdf", b) : plot should be a ggplot2 plot
d> ggsave <- ggplot2::ggsave
d> body(ggsave) <- body(ggplot2::ggsave)[-2]
d> ggsave('test.pdf', b)
Saving 10.5 x 10.7 in image
TableGrob (2 x 1) "arrange": 2 grobs
  z     cells    name           grob
1 1 (1-1,1-1) arrange gtable[layout]
2 2 (2-2,1-1) arrange gtable[layout]
d> 

The test.pdf is created but it is corrupted in any way and can not be opened. Also the gtable object get printed. So I guess something is wrong here.

But, as you can see, I found in the example code, I found the grid.draw function to plot at least my combined graph but I still can not ggsave it after the modification.

I don't want to use the "old" (pdf(file = "test.pdf"); grid.draw(b); dev.off()) device saving functions as suggested in this article, since they are very uncomfortable to use.

In this question someone asked exactly how to save the object, but in the answer they just explain to use grid.darw and he accepted the answer as solving the problem and nobody answered on my comments so far.

So I am pretty lost at the moment, how to provide working scripts for those who have and have not updated to new gridExtra package. The way to remove the test within the ggsave function is I guess the best solution since I can check the gridExtra and ggplot2 version and just overwrite the ggsave function in case there version do not match, but I could not get it to run.

Looking forward to get some help.

EDIT:

maybe the sessionInfo helps

d> sessionInfo()
R version 3.2.0 (2015-04-16)
Platform: x86_64-apple-darwin13.4.0 (64-bit)
Running under: OS X 10.9.5 (Mavericks)

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

attached base packages:
[1] grid      stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] gridExtra_2.0.0 ggplot2_1.0.1  

loaded via a namespace (and not attached):
 [1] Rcpp_0.12.1      digest_0.6.8     MASS_7.3-44      plyr_1.8.3       gtable_0.1.2    
 [6] magrittr_1.5     scales_0.3.0     stringi_1.0-1    reshape2_1.4.1   devtools_1.9.1  
[11] proto_0.3-10     tools_3.2.0      stringr_1.0.0    munsell_0.4.2    colorspace_1.2-6
[16] memoise_0.2.1  
Meehan answered 20/11, 2015 at 9:39 Comment(10)
Did you try to use pdf() instead of ggsave? Something like that: pdf(file = "test.pdf"); grid.newpage() ;print(b) ;dev.off()Bounteous
This link may help: alstatr.blogspot.co.uk/2015/02/…Bounteous
@Bounteous this is what I mentioned with the second link. I know this is working but is very unhandy. I would love to get the ggsave function working as suggested in the first link, because otherwise I have to change a lot of lines in my scripts...Meehan
I probably don't understand, but where is the error/problem with ggsave using your example?Maclaine
@Pascal I added the output and a comment directly below, forgot to mention what the problem was :-)Meehan
It seems to work for me. A plot is saved in test.pdf using your example (ggplot2 version 1.0.1.9003 and gridExtra version 2.0.0).Maclaine
@Pascal the ggplot2 1.0.1.9003 is the development version which is also not easily to install for all users since the have to use devtools and download from github. So I would like to stay with stable versions but also use ggsave and not the pdf() and dev.off() functions as I all mentioned in my post.Meehan
Your example fails with ggplot2 version 1.0.1. But it works with the development version of ggplot2, version 1.0.1.9003.Maclaine
grid.arrange doesn't work ?Clemente
grid.arrange is directly plotting. But I want to save the object without plotting or plotting it later in the script.Meehan
M
0

Pascal brought me finally to the idea to check for the differences between ggplot 1.0.1 and ggplot 1.0.1.9003, since I don't want to or force the development version of ggplot.

So my idea is a function which will be executed within each script which overwrites the default ggsave function.

I tested it now a little, if there are any bugs or so, please let me know. But the way I do it now it works so far.

repairGgsave <- function() {
  ggplot_version <- 
    compareVersion(as.character(packageVersion('ggplot2')), 
                 '1.0.1.9003')
  gridextra_version <- 
    compareVersion(as.character(packageVersion('gridExtra')), 
                   '0.9.1')
  if(gridextra_version > 0) {
    if(ggplot_version <= 0) {
      ggsave <- function(filename, plot = last_plot(),
                         device = NULL, path = NULL, scale = 1,
                         width = NA, height = NA, units = c("in", "cm", "mm"),
                         dpi = 300, limitsize = TRUE, ...) {

        dev <- plot_dev(device, filename, dpi = dpi)
        dim <- plot_dim(c(width, height), scale = scale, units = units,
                        limitsize = limitsize)

        if (!is.null(path)) {
          filename <- file.path(path, filename)
        }
        dev(file = filename, width = dim[1], height = dim[2], ...)
        on.exit(utils::capture.output(grDevices::dev.off()))
        grid.draw(plot)

        invisible()
      }
      assign("ggsave", ggsave, .GlobalEnv)
      plot_dim <<- function(dim = c(NA, NA), scale = 1, units = c("in", "cm", "mm"),
                           limitsize = TRUE) {

        units <- match.arg(units)
        to_inches <- function(x) x / c(`in` = 1, cm = 2.54, mm = 2.54 * 10)[units]
        from_inches <- function(x) x * c(`in` = 1, cm = 2.54, mm = 2.54 * 10)[units]

        dim <- to_inches(dim) * scale

        if (any(is.na(dim))) {
          if (length(grDevices::dev.list()) == 0) {
            default_dim <- c(7, 7)
          } else {
            default_dim <- dev.size() * scale
          }
          dim[is.na(dim)] <- default_dim[is.na(dim)]
          dim_f <- prettyNum(from_inches(dim), digits = 3)

          message("Saving ", dim_f[1], " x ", dim_f[2], " ", units, " image")
        }

        if (limitsize && any(dim >= 50)) {
          stop("Dimensions exceed 50 inches (height and width are specified in '",
               units, "' not pixels). If you're sure you a plot that big, use ",
               "`limitsize = FALSE`.", call. = FALSE)
        }

        dim
      }

      plot_dev <<- function(device, filename, dpi = 300) {
        if (is.function(device))
          return(device)

        eps <- function(...) {
          grDevices::postscript(..., onefile = FALSE, horizontal = FALSE,
                                paper = "special")
        }
        devices <- list(
          eps =  eps,
          ps =   eps,
          tex =  function(...) grDevices::pictex(...),
          pdf =  function(..., version = "1.4") grDevices::pdf(..., version = version),
          svg =  function(...) grDevices::svg(...),
          emf =  function(...) grDevices::win.metafile(...),
          wmf =  function(...) grDevices::win.metafile(...),
          png =  function(...) grDevices::png(..., res = dpi, units = "in"),
          jpg =  function(...) grDevices::jpeg(..., res = dpi, units = "in"),
          jpeg = function(...) grDevices::jpeg(..., res = dpi, units = "in"),
          bmp =  function(...) grDevices::bmp(..., res = dpi, units = "in"),
          tiff = function(...) grDevices::tiff(..., res = dpi, units = "in")
        )

        if (is.null(device)) {
          device <- tolower(tools::file_ext(filename))
        }

        if (!is.character(device) || length(device) != 1) {
          stop("`device` must be NULL, a string or a function.", call. = FALSE)
        }

        dev <<- devices[[device]]
        if (is.null(dev)) {
          stop("Unknown graphics device '", device, "'", call. = FALSE)
        }
        dev
      }
    }
  }
}

It basically overwrites the ggsave and creates two new functions from the development version.

After executing the function everything seems to work.

Meehan answered 20/11, 2015 at 11:51 Comment(2)
This seems relevant to me. Just one question: how should I apply it? Wrap around some ggsave-action? repairGgsave(ggsave(paste0(PLOT_loc,"/",Today,"_ObsCoxQuint_CVD.eps"), plot = multiplot(ObsQuintPlot_CVD + guides(colour = FALSE), CoxQuintPlot_CVD + guides(colour = FALSE), cols = 2), width = 12, height = 12))Boehike
@SanderW.vanderLaan I actually never got it to work in a solid way. To be honest, I never really checked babtists solution above. I implemented the solution with grid.new();grid.draw() in my scripts. Thought it might be the best solution.Meehan
A
2

As an temporary workaround for this unfortunate transition period, you could re-implement the class hack that used to be in gridExtra,

class(b) <- c("arrange","ggplot", class(b))
print.arrange <- function(x) grid.draw(x)
ggsave('test.pdf', b)
Antonina answered 21/11, 2015 at 21:55 Comment(3)
This arrange workaround has to be applied on plot I create with arrangeGrob. Is there anything against the solution I suggested? In my case I only overwrite the ggsave function in case the versions of gridExtra and ggplot are not compatible.Meehan
it's good to have different options, I wouldn't say one is better in this case. Personally I prefer not to duplicate large chunks of code for temporary workarounds because it tends to confuse my future self, but everyone has a different workflow and/or capacity to remember the past.Antonina
I just realised, if I use my function it no longer works for normal ggplot plots. It says no applicable method for 'grid.draw' applied to an object of class "c('gg', 'ggplot')" I wonder how this happens.Meehan
M
0

Pascal brought me finally to the idea to check for the differences between ggplot 1.0.1 and ggplot 1.0.1.9003, since I don't want to or force the development version of ggplot.

So my idea is a function which will be executed within each script which overwrites the default ggsave function.

I tested it now a little, if there are any bugs or so, please let me know. But the way I do it now it works so far.

repairGgsave <- function() {
  ggplot_version <- 
    compareVersion(as.character(packageVersion('ggplot2')), 
                 '1.0.1.9003')
  gridextra_version <- 
    compareVersion(as.character(packageVersion('gridExtra')), 
                   '0.9.1')
  if(gridextra_version > 0) {
    if(ggplot_version <= 0) {
      ggsave <- function(filename, plot = last_plot(),
                         device = NULL, path = NULL, scale = 1,
                         width = NA, height = NA, units = c("in", "cm", "mm"),
                         dpi = 300, limitsize = TRUE, ...) {

        dev <- plot_dev(device, filename, dpi = dpi)
        dim <- plot_dim(c(width, height), scale = scale, units = units,
                        limitsize = limitsize)

        if (!is.null(path)) {
          filename <- file.path(path, filename)
        }
        dev(file = filename, width = dim[1], height = dim[2], ...)
        on.exit(utils::capture.output(grDevices::dev.off()))
        grid.draw(plot)

        invisible()
      }
      assign("ggsave", ggsave, .GlobalEnv)
      plot_dim <<- function(dim = c(NA, NA), scale = 1, units = c("in", "cm", "mm"),
                           limitsize = TRUE) {

        units <- match.arg(units)
        to_inches <- function(x) x / c(`in` = 1, cm = 2.54, mm = 2.54 * 10)[units]
        from_inches <- function(x) x * c(`in` = 1, cm = 2.54, mm = 2.54 * 10)[units]

        dim <- to_inches(dim) * scale

        if (any(is.na(dim))) {
          if (length(grDevices::dev.list()) == 0) {
            default_dim <- c(7, 7)
          } else {
            default_dim <- dev.size() * scale
          }
          dim[is.na(dim)] <- default_dim[is.na(dim)]
          dim_f <- prettyNum(from_inches(dim), digits = 3)

          message("Saving ", dim_f[1], " x ", dim_f[2], " ", units, " image")
        }

        if (limitsize && any(dim >= 50)) {
          stop("Dimensions exceed 50 inches (height and width are specified in '",
               units, "' not pixels). If you're sure you a plot that big, use ",
               "`limitsize = FALSE`.", call. = FALSE)
        }

        dim
      }

      plot_dev <<- function(device, filename, dpi = 300) {
        if (is.function(device))
          return(device)

        eps <- function(...) {
          grDevices::postscript(..., onefile = FALSE, horizontal = FALSE,
                                paper = "special")
        }
        devices <- list(
          eps =  eps,
          ps =   eps,
          tex =  function(...) grDevices::pictex(...),
          pdf =  function(..., version = "1.4") grDevices::pdf(..., version = version),
          svg =  function(...) grDevices::svg(...),
          emf =  function(...) grDevices::win.metafile(...),
          wmf =  function(...) grDevices::win.metafile(...),
          png =  function(...) grDevices::png(..., res = dpi, units = "in"),
          jpg =  function(...) grDevices::jpeg(..., res = dpi, units = "in"),
          jpeg = function(...) grDevices::jpeg(..., res = dpi, units = "in"),
          bmp =  function(...) grDevices::bmp(..., res = dpi, units = "in"),
          tiff = function(...) grDevices::tiff(..., res = dpi, units = "in")
        )

        if (is.null(device)) {
          device <- tolower(tools::file_ext(filename))
        }

        if (!is.character(device) || length(device) != 1) {
          stop("`device` must be NULL, a string or a function.", call. = FALSE)
        }

        dev <<- devices[[device]]
        if (is.null(dev)) {
          stop("Unknown graphics device '", device, "'", call. = FALSE)
        }
        dev
      }
    }
  }
}

It basically overwrites the ggsave and creates two new functions from the development version.

After executing the function everything seems to work.

Meehan answered 20/11, 2015 at 11:51 Comment(2)
This seems relevant to me. Just one question: how should I apply it? Wrap around some ggsave-action? repairGgsave(ggsave(paste0(PLOT_loc,"/",Today,"_ObsCoxQuint_CVD.eps"), plot = multiplot(ObsQuintPlot_CVD + guides(colour = FALSE), CoxQuintPlot_CVD + guides(colour = FALSE), cols = 2), width = 12, height = 12))Boehike
@SanderW.vanderLaan I actually never got it to work in a solid way. To be honest, I never really checked babtists solution above. I implemented the solution with grid.new();grid.draw() in my scripts. Thought it might be the best solution.Meehan
T
0

Fix for me was explicitly defining the file:

ggsave(file='test.pdf', b)

Talion answered 13/3, 2019 at 22:9 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.