Skip to content

Commit

Permalink
Improved robustness of converting plots to grobs and of calling ggsave
Browse files Browse the repository at this point in the history
in various unusual environments.
  • Loading branch information
clauswilke committed Dec 9, 2017
1 parent 89b795c commit a2fda96
Show file tree
Hide file tree
Showing 9 changed files with 1,550 additions and 566 deletions.
33 changes: 18 additions & 15 deletions R/plot_to_gtable.R
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,12 @@ plot_to_gtable <- function(plot){
gt
}
else {
# we convert the captured plot or output plot into a grob
# to be safe, we have to save and restore the current graphics device
cur_dev <- dev.cur()
tree <- grid::grid.grabExpr(gridGraphics::grid.echo(plot))
dev.set(cur_dev)

u <- grid::unit(1, "null")
gt <- gtable::gtable_col(NULL, list(tree), u, u)
# fix gtable clip setting
Expand All @@ -29,23 +34,21 @@ plot_to_gtable <- function(plot){
}
}
else if (methods::is(plot, "ggplot")){
## ggplotGrob must open a device and when a multiple page capable device (e.g. PDF) is open this will save a blank page
## in order to avoid saving this blank page to the final target device a NULL device is opened and closed here to *absorb* the blank plot

# catch_blank <- identical(names(grDevices::dev.cur()), 'pdf') # is the current device `pdf()`

## this problem arises even when the current device is not pdf, e.g. in R studio notebooks. Let's try to set
## catch_blank to TRUE always and see if this causes other issues.

catch_blank <- TRUE

if (catch_blank)
grDevices::pdf(NULL)
## ggplotGrob must open a device and when a multiple page capable device (e.g. PDF) is open it will
## save a blank page. To avoid this blank page, we open a NULL pdf device that will *absorb* the
## blank plot. However, as part of this procedure, the wrong device can end up being the current one,
## so to be absolutely sure, we also save the previous device and then restore it.

# save currently active device
cur_dev <- dev.cur()
# open NULL pdf device
grDevices::pdf(NULL)
# convert plot to grob
plot <- ggplot2::ggplotGrob(plot)

if (catch_blank)
grDevices::dev.off()
# close pdf device
grDevices::dev.off()
# restore previously active device
dev.set(cur_dev)

plot
}
Expand Down
54 changes: 44 additions & 10 deletions R/save.R
Original file line number Diff line number Diff line change
@@ -1,16 +1,9 @@
# Much of the code in this file was copied verbatim from the ggplot2 source. Ugly, but
# necessary to fix the dingbats issue.

# *************************************************
# Output
# *************************************************

#' Cowplot reimplementation of ggsave.
#'
#' This function should behave just like \code{ggsave} from ggplot2,
#' with the main difference being that by default it doesn't use the Dingbats
#' font for pdf output. If you ever have trouble with this function, you can
#' try using \code{ggplot2::ggsave()} instead.
#' use \code{ggplot2::ggsave()} instead.
#' @param filename Filename of plot
#' @param plot Plot to save, defaults to last plot displayed.
#' @param device Device to use, automatically extract from file name extension.
Expand All @@ -26,7 +19,47 @@
#' specifying dimensions in pixels.
#' @param ... Other arguments to be handed to the plot device.
#' @export
ggsave <- function(filename, plot = ggplot2::last_plot(),
ggsave <- function(filename, plot = ggplot2::last_plot(), device = NULL, path = NULL, scale = 1,
width = NA, height = NA, units = c("in", "cm", "mm"), dpi = 300, limitsize = TRUE, ...) {

# arguments we want to hand off to ggplot2::ggsave only if explicitly provided
ggsave_args <- c("plot", "path", "scale", "width", "height", "units", "dpi", "limitsize")

# match ggsave_args to args provided
args <- as.list(match.call())
args[[1]] <- NULL # remove the function call
args <- args[na.omit(match(ggsave_args, names(args)))] # remove other args

args_dotdotdot <- list(...)

# if device isn't provided, try to infer from filename
if (is.null(device)) {
device <- tolower(tools::file_ext(filename))
}

if (identical(device, "pdf") || identical(device, grDevices::pdf)) {
# pdf device specified either by filename extension or by character string
# set useDingbats option unless provided

if (!"useDingbats" %in% names(args_dotdotdot))
args_dotdotdot <- append(args_dotdotdot, list(useDingbats = FALSE))

}

# combine all the args
args <- c(list(filename = filename), args, device = device, args_dotdotdot)

# call ggplot2::ggsave while saving and afterwards restoring the current graphics device
cur_dev <- grDevices::dev.cur()
x <- do.call(ggplot2::ggsave, args, envir = parent.frame())
grDevices::dev.set(cur_dev)
invisible(x)
}


# This was the previous implementation copying much of the ggplot2::ggsave
# code over. This seems unnecessary.
ggsave_old <- function(filename, plot = ggplot2::last_plot(),
device = NULL, path = NULL, scale = 1,
width = NA, height = NA, units = c("in", "cm", "mm"),
dpi = 300, limitsize = TRUE, ...) {
Expand Down Expand Up @@ -200,6 +233,7 @@ save_plot <- function(filename, plot, ncol = 1, nrow = 1,
base_width <- base_height * base_aspect_ratio
}

ggsave(filename = filename, plot = plot, width = base_width*cols, height = base_height*rows, ...)
# make clear we're using the cowplot function, not the ggplot2 one
cowplot::ggsave(filename = filename, plot = plot, width = base_width*cols, height = base_height*rows, ...)
}

Loading

0 comments on commit a2fda96

Please sign in to comment.