mirror of
https://github.com/rstudio/shiny.git
synced 2026-01-13 17:08:05 -05:00
* Close #1409: don't supply width/height to the device if they aren't defined * Update news * Update unit tests to reflect that plotPNG()/startPNG() now handles NULL dimensions * Add a note about NULL dimensions on plotPNG() help page * Update news
133 lines
4.7 KiB
R
133 lines
4.7 KiB
R
startPNG <- function(filename, width, height, res, ...) {
|
|
pngfun <- if ((getOption('shiny.useragg') %||% TRUE) && is_installed("ragg")) {
|
|
ragg::agg_png
|
|
} else if (capabilities("aqua")) {
|
|
# i.e., png(type = 'quartz')
|
|
grDevices::png
|
|
} else if ((getOption('shiny.usecairo') %||% TRUE) && is_installed("Cairo")) {
|
|
Cairo::CairoPNG
|
|
} else {
|
|
# i.e., png(type = 'cairo')
|
|
grDevices::png
|
|
}
|
|
|
|
args <- list2(filename = filename, width = width, height = height, res = res, ...)
|
|
|
|
# It's possible for width/height to be NULL/numeric(0) (e.g., when using
|
|
# suspendWhenHidden=F w/ tabsetPanel(), see rstudio/shiny#1409), so when
|
|
# this happens let the device determine what the default size should be.
|
|
if (length(args$width) == 0) args$width <- NULL
|
|
if (length(args$height) == 0) args$height <- NULL
|
|
|
|
# Set a smarter default for the device's bg argument (based on thematic's global state).
|
|
# Note that, technically, this is really only needed for CairoPNG, since the other
|
|
# devices allow their bg arg to be overridden by par(bg=...), which thematic does prior
|
|
# to plot-time, but it shouldn't hurt to inform other the device directly as well
|
|
if (is.null(args$bg) && isNamespaceLoaded("thematic")) {
|
|
args$bg <- getThematicOption("bg", "white")
|
|
# auto vals aren't resolved until plot time, so if we see one, resolve it
|
|
if (isTRUE("auto" == args$bg)) {
|
|
args$bg <- getCurrentOutputInfo()[["bg"]]()
|
|
}
|
|
}
|
|
|
|
# Handle both bg and background device arg
|
|
# https://github.com/r-lib/ragg/issues/35
|
|
fmls <- names(formals(pngfun))
|
|
if (("background" %in% fmls) && (!"bg" %in% fmls)) {
|
|
if (is.null(args$background)) {
|
|
args$background <- args$bg
|
|
}
|
|
args$bg <- NULL
|
|
}
|
|
|
|
do.call(pngfun, args)
|
|
# Call plot.new() so that even if no plotting operations are performed at
|
|
# least we have a blank background. N.B. we need to set the margin to 0
|
|
# temporarily before plot.new() because when the plot size is small (e.g.
|
|
# 200x50), we will get an error "figure margin too large", which is triggered
|
|
# by plot.new() with the default (large) margin. However, this does not
|
|
# guarantee user's code in func() will not trigger the error -- they may have
|
|
# to set par(mar = smaller_value) before they draw base graphics.
|
|
op <- graphics::par(mar = rep(0, 4))
|
|
tryCatch(
|
|
graphics::plot.new(),
|
|
finally = graphics::par(op)
|
|
)
|
|
|
|
grDevices::dev.cur()
|
|
}
|
|
|
|
#' Capture a plot as a PNG file.
|
|
#'
|
|
#' The PNG graphics device used is determined in the following order:
|
|
#' * If the ragg package is installed (and the `shiny.useragg` is not
|
|
#' set to `FALSE`), then use [ragg::agg_png()].
|
|
#' * If a quartz device is available (i.e., `capabilities("aqua")` is
|
|
#' `TRUE`), then use `png(type = "quartz")`.
|
|
#' * If the Cairo package is installed (and the `shiny.usecairo` option
|
|
#' is not set to `FALSE`), then use [Cairo::CairoPNG()].
|
|
#' * Otherwise, use [grDevices::png()]. In this case, Linux and Windows
|
|
#' may not antialias some point shapes, resulting in poor quality output.
|
|
#'
|
|
#' @details
|
|
#' A `NULL` value provided to `width` or `height` is ignored (i.e., the
|
|
#' default `width` or `height` of the graphics device is used).
|
|
#'
|
|
#' @param func A function that generates a plot.
|
|
#' @param filename The name of the output file. Defaults to a temp file with
|
|
#' extension `.png`.
|
|
#' @param width Width in pixels.
|
|
#' @param height Height in pixels.
|
|
#' @param res Resolution in pixels per inch. This value is passed to the
|
|
#' graphics device. Note that this affects the resolution of PNG rendering in
|
|
#' R; it won't change the actual ppi of the browser.
|
|
#' @param ... Arguments to be passed through to the graphics device. These can
|
|
#' be used to set the width, height, background color, etc.
|
|
#'
|
|
#' @return A path to the newly generated PNG file.
|
|
#'
|
|
#' @export
|
|
plotPNG <- function(func, filename=tempfile(fileext='.png'),
|
|
width=400, height=400, res=72, ...) {
|
|
dv <- startPNG(filename, width, height, res, ...)
|
|
on.exit(grDevices::dev.off(dv), add = TRUE)
|
|
func()
|
|
|
|
filename
|
|
}
|
|
|
|
createGraphicsDevicePromiseDomain <- function(which = dev.cur()) {
|
|
force(which)
|
|
|
|
promises::new_promise_domain(
|
|
wrapOnFulfilled = function(onFulfilled) {
|
|
force(onFulfilled)
|
|
function(...) {
|
|
old <- dev.cur()
|
|
dev.set(which)
|
|
on.exit(dev.set(old))
|
|
|
|
onFulfilled(...)
|
|
}
|
|
},
|
|
wrapOnRejected = function(onRejected) {
|
|
force(onRejected)
|
|
function(...) {
|
|
old <- dev.cur()
|
|
dev.set(which)
|
|
on.exit(dev.set(old))
|
|
|
|
onRejected(...)
|
|
}
|
|
},
|
|
wrapSync = function(expr) {
|
|
old <- dev.cur()
|
|
dev.set(which)
|
|
on.exit(dev.set(old))
|
|
|
|
force(expr)
|
|
}
|
|
)
|
|
}
|