Skip to content

Commit

Permalink
Refactor ggsave
Browse files Browse the repository at this point in the history
* pull out plot_dim and add tests
* save any object with grid.draw method
* require filename
  • Loading branch information
hadley committed Jun 18, 2015
1 parent e7e7566 commit 7387d39
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 127 deletions.
1 change: 1 addition & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ S3method(fortify,summary.glht)
S3method(ggplot,data.frame)
S3method(ggplot,default)
S3method(grid.draw,absoluteGrob)
S3method(grid.draw,ggplot)
S3method(grobHeight,absoluteGrob)
S3method(grobHeight,zeroGrob)
S3method(grobWidth,absoluteGrob)
Expand Down
4 changes: 4 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
ggplot2 1.0.1.9000
----------------------------------------------------------------

* `ggsave()` has been simplified a little to make it easier to maintain.
It no longer checks that you're printing a ggplot2 object (so now also
works with any grid grob) (#970), and always requires a filename.

* `facet_wrap()` more carefully checks its `nrow` and `ncol` arguments
to ensure that they're specified correctly (@richierocks, #962)

Expand Down
165 changes: 75 additions & 90 deletions R/save.r
Original file line number Diff line number Diff line change
@@ -1,127 +1,112 @@
#' Save a ggplot with sensible defaults
#' Save a ggplot (or other grid object) with sensible defaults
#'
#' ggsave is a convenient function for saving a plot. It defaults to
#' saving the last plot that you displayed, and for a default size uses
#' the size of the current graphics device. It also guesses the type of
#' graphics device from the extension. This means the only argument you
#' need to supply is the filename.
#' \code{ggsave()} is a convenient function for saving a plot. It defaults to
#' saving the last plot that you displayed, using the size of the current
#' graphics device. It also guesses the type of graphics device from the
#' extension.
#'
#' \code{ggsave} currently recognises the extensions eps/ps, tex (pictex),
#' pdf, jpeg, tiff, png, bmp, svg and wmf (windows only).
#'
#' @param filename file name/filename of plot
#' @param plot plot to save, defaults to last plot displayed
#' @param device device to use, automatically extract from file name extension
#' @param path path to save plot to (if you just want to set path and not
#' filename)
#' @param scale scaling factor
#' @param width width (defaults to the width of current plotting window)
#' @param height height (defaults to the height of current plotting window)
#' @param units units for width and height when either one is explicitly specified (in, cm, or mm)
#' @param dpi dpi to use for raster graphics
#' @param limitsize when \code{TRUE} (the default), \code{ggsave} will not
#' @param filename File name to create on disk.
#' @param plot Plot to save, defaults to last plot displayed.
#' @param device Device to use. By default, extracted from extension.
#' \code{ggsave} currently recognises eps/ps, tex (pictex), pdf, jpeg, tiff,
#' png, bmp, svg and wmf (windows only).
#' @param path Path to save plot to (combined with filename).
#' @param scale Multiplicative scaling factor.
#' @param width,height Plot dimensions, defaults to size of current graphics
#' device.
#' @param units Units for width and height when specified explicitly (in, cm,
#' or mm)
#' @param dpi Resolution used for raster outputs.
#' @param limitsize When \code{TRUE} (the default), \code{ggsave} will not
#' save images larger than 50x50 inches, to prevent the common error of
#' specifying dimensions in pixels.
#' @param ... other arguments passed to graphics device
#' @param ... Other arguments passed on to graphics device
#' @export
#' @examples
#' \dontrun{
#' ratings <- ggplot(movies, aes(rating)) +
#' geom_histogram()
#' ggplot(movies, aes(length)) +
#' geom_histogram()
#' ggsave("length-hist.pdf")
#' ggsave("length-hist.png")
#' ggsave("ratings.pdf", ratings)
#' ggsave("ratings.pdf", ratings, width=4, height=4)
#' # make twice as big as on screen
#' ggsave("ratings.pdf", ratings, scale=2)
#' ggplot(movies, aes(rating)) + geom_histogram(binwidth = 0.1)
#'
#' ggsave("ratings.pdf")
#' ggsave("ratings.pdf")
#'
#' ggsave("ratings.pdf", width = 4, height = 4)
#' ggsave("ratings.pdf", width = 20, height = 20, units = "cm")
#'
#' unlink("ratings.pdf")
#' unlink("ratings.png")
#' }
ggsave <- function(filename = default_name(plot), plot = last_plot(),
device = default_device(filename), path = NULL, scale = 1,
width = par("din")[1], height = par("din")[2], units = c("in", "cm", "mm"),
dpi = 300, limitsize = TRUE, ...) {

if (!inherits(plot, "ggplot")) stop("plot should be a ggplot2 plot")
ggsave <- function(filename, plot = last_plot(),
device = default_device(filename), path = NULL, scale = 1,
width = NA, height = NA, units = c("in", "cm", "mm"),
dpi = 300, limitsize = TRUE, ...) {

eps <- ps <- function(..., width, height)
grDevices::postscript(..., width=width, height=height, onefile=FALSE,
grDevices::postscript(..., width = width, height = height, onefile = FALSE,
horizontal = FALSE, paper = "special")
tex <- function(..., width, height)
grDevices::pictex(..., width=width, height=height)
pdf <- function(..., version="1.4")
grDevices::pdf(..., version=version)
grDevices::pictex(..., width = width, height = height)
pdf <- function(..., version = "1.4")
grDevices::pdf(..., version = version)
svg <- function(...)
grDevices::svg(...)
wmf <- function(..., width, height)
grDevices::win.metafile(..., width=width, height=height)
grDevices::win.metafile(..., width = width, height = height)
emf <- function(..., width, height)
grDevices::win.metafile(..., width=width, height=height)

grDevices::win.metafile(..., width = width, height = height)
png <- function(..., width, height)
grDevices::png(..., width=width, height=height, res = dpi, units = "in")
grDevices::png(..., width = width, height = height, res = dpi, units = "in")
jpg <- jpeg <- function(..., width, height)
grDevices::jpeg(..., width=width, height=height, res = dpi, units = "in")
grDevices::jpeg(..., width = width, height = height, res = dpi, units = "in")
bmp <- function(..., width, height)
grDevices::bmp(..., width=width, height=height, res = dpi, units = "in")
grDevices::bmp(..., width = width, height = height, res = dpi, units = "in")
tiff <- function(..., width, height)
grDevices::tiff(..., width=width, height=height, res = dpi, units = "in")

default_name <- function(plot) {
paste(digest.ggplot(plot), ".pdf", sep="")
}
grDevices::tiff(..., width = width, height = height, res = dpi, units = "in")

default_device <- function(filename) {
pieces <- strsplit(filename, "\\.")[[1]]
ext <- tolower(pieces[length(pieces)])
match.fun(ext)
}

units <- match.arg(units)
convert_to_inches <- function(x, units) {
x <- switch(units,
`in` = x,
cm = x / 2.54,
mm = x / 2.54 /10
)
}
dim <- plot_dim(c(width, height), scale = scale, units = units,
limitsize = limitsize)

convert_from_inches <- function(x, units) {
x <- switch(units,
`in` = x,
cm = x * 2.54,
mm = x * 2.54 * 10
)
if (!is.null(path)) {
filename <- file.path(path, filename)
}
device(file = filename, width = dim[1], height = dim[2], ...)
on.exit(capture.output(dev.off()))
grid.draw(plot)

# dimensions need to be in inches for all graphic devices
# convert width and height into inches when they are specified
if (!missing(width)) {
width <- convert_to_inches(width, units)
}
if (!missing(height)) {
height <- convert_to_inches(height, units)
}
# if either width or height is not specified, display an information message
# units are those specified by the user
if (missing(width) || missing(height)) {
message("Saving ", prettyNum(convert_from_inches(width * scale, units), digits=3), " x ", prettyNum(convert_from_inches(height * scale, units), digits=3), " ", units, " image")
}
invisible()
}

plot_dim <- function(dim = c(NA, NA), scale = 1, units = c("in", "cm", "mm"),
limitsize = TRUE, dim_guess = FALSE) {


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]

width <- width * scale
height <- height * scale
dim <- to_inches(dim) * scale

if (limitsize && (width >= 50 || height >= 50)) {
stop("Dimensions exceed 50 inches (height and width are specified in inches/cm/mm, not pixels).",
" If you are sure you want these dimensions, use 'limitsize=FALSE'.")
if (any(is.na(dim))) {
dim[is.na(dim)] <- par("din") * scale
dim_f <- prettyNum(from_inches(dim), digits = 3)
message("Saving ", dim_f[1], " x ", dim_f[2], " ", units, " image")
}

if (!is.null(path)) {
filename <- file.path(path, filename)
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)
}
device(file=filename, width=width, height=height, ...)
on.exit(capture.output(dev.off()))
print(plot)

invisible()
dim
}

#' @export
grid.draw.ggplot <- function(x, recording = TRUE) {
print(x)
}
22 changes: 22 additions & 0 deletions inst/tests/test-ggsave.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
context("ggsave")


# plot_dim ---------------------------------------------------------------

test_that("guesses and informs if dim not specified", {
png(width = 10, height = 10, units = "in", res = 300)
on.exit(capture.output(dev.off()))

expect_message(out <- plot_dim(), "10 x 10")
expect_equal(out, c(10, 10))
})

test_that("warned about large plot unless limitsize = FALSE", {
expect_error(plot_dim(c(50, 50)), "exceed 50 inches")
expect_equal(plot_dim(c(50, 50), limitsize = FALSE), c(50, 50))
})

test_that("scale multiplies height & width", {
expect_equal(plot_dim(c(10, 10), scale = 1), c(10, 10))
expect_equal(plot_dim(c(5, 5), scale = 2), c(10, 10))
})
69 changes: 32 additions & 37 deletions man/ggsave.Rd
Original file line number Diff line number Diff line change
Expand Up @@ -2,62 +2,57 @@
% Please edit documentation in R/save.r
\name{ggsave}
\alias{ggsave}
\title{Save a ggplot with sensible defaults}
\title{Save a ggplot (or other grid object) with sensible defaults}
\usage{
ggsave(filename = default_name(plot), plot = last_plot(),
device = default_device(filename), path = NULL, scale = 1,
width = par("din")[1], height = par("din")[2], units = c("in", "cm",
"mm"), dpi = 300, limitsize = TRUE, ...)
ggsave(filename, plot = last_plot(), device = default_device(filename),
path = NULL, scale = 1, width = NA, height = NA, units = c("in",
"cm", "mm"), dpi = 300, limitsize = TRUE, ...)
}
\arguments{
\item{filename}{file name/filename of plot}
\item{filename}{File name to create on disk.}

\item{plot}{plot to save, defaults to last plot displayed}
\item{plot}{Plot to save, defaults to last plot displayed.}

\item{device}{device to use, automatically extract from file name extension}
\item{device}{Device to use. By default, extracted from extension.
\code{ggsave} currently recognises eps/ps, tex (pictex), pdf, jpeg, tiff,
png, bmp, svg and wmf (windows only).}

\item{path}{path to save plot to (if you just want to set path and not
filename)}
\item{path}{Path to save plot to (combined with filename).}

\item{scale}{scaling factor}
\item{scale}{Multiplicative scaling factor.}

\item{width}{width (defaults to the width of current plotting window)}
\item{width,height}{Plot dimensions, defaults to size of current graphics
device.}

\item{height}{height (defaults to the height of current plotting window)}
\item{units}{Units for width and height when specified explicitly (in, cm,
or mm)}

\item{units}{units for width and height when either one is explicitly specified (in, cm, or mm)}
\item{dpi}{Resolution used for raster outputs.}

\item{dpi}{dpi to use for raster graphics}

\item{limitsize}{when \code{TRUE} (the default), \code{ggsave} will not
\item{limitsize}{When \code{TRUE} (the default), \code{ggsave} will not
save images larger than 50x50 inches, to prevent the common error of
specifying dimensions in pixels.}

\item{...}{other arguments passed to graphics device}
\item{...}{Other arguments passed on to graphics device}
}
\description{
ggsave is a convenient function for saving a plot. It defaults to
saving the last plot that you displayed, and for a default size uses
the size of the current graphics device. It also guesses the type of
graphics device from the extension. This means the only argument you
need to supply is the filename.
}
\details{
\code{ggsave} currently recognises the extensions eps/ps, tex (pictex),
pdf, jpeg, tiff, png, bmp, svg and wmf (windows only).
\code{ggsave()} is a convenient function for saving a plot. It defaults to
saving the last plot that you displayed, using the size of the current
graphics device. It also guesses the type of graphics device from the
extension.
}
\examples{
\dontrun{
ratings <- ggplot(movies, aes(rating)) +
geom_histogram()
ggplot(movies, aes(length)) +
geom_histogram()
ggsave("length-hist.pdf")
ggsave("length-hist.png")
ggsave("ratings.pdf", ratings)
ggsave("ratings.pdf", ratings, width=4, height=4)
# make twice as big as on screen
ggsave("ratings.pdf", ratings, scale=2)
ggplot(movies, aes(rating)) + geom_histogram(binwidth = 0.1)

ggsave("ratings.pdf")
ggsave("ratings.pdf")

ggsave("ratings.pdf", width = 4, height = 4)
ggsave("ratings.pdf", width = 20, height = 20, units = "cm")

unlink("ratings.pdf")
unlink("ratings.png")
}
}

0 comments on commit 7387d39

Please sign in to comment.