#' Wrap arbitrary graphics in a patchwork-compliant patch
#'
#' In order to add non-ggplot2 element to a patchwork they can be
#' converted to a compliant representation using the `wrap_elements()` function.
#' This allows you to position either grobs, ggplot objects, patchwork
#' objects, or even base graphics (if passed as a formula) in either the full
#' area, the full plotting area (anything between and
#' including the axis label), or the panel area (only the actual area where data
#' is drawn). Further you can still add title, subtitle, tag, and caption using
#' the same approach as with normal ggplots (using
#' [ggtitle()][ggplot2::ggtitle] and [labs()][ggplot2::labs]) as well as styling
#' using [theme()][ggplot2::theme]. For the latter, only the theme elements
#' targeting plot margins and background as well as title, subtitle, etc styling
#' will have an effect. If a patchwork or ggplot object is wrapped, it will be
#' fixated in its state and will no longer respond to addition of styling,
#' geoms, etc.. When grobs and formulas are added directly, they will implicitly
#' be converted to `wrap_elements(full = x)`.
#'
#' @param panel,plot,full A grob, ggplot, patchwork, formula, raster,
#' nativeRaster, or gt object to add to the respective area.
#'
#' @param clip Should the grobs be clipped if expanding outside its area
#'
#' @param ignore_tag Should tags be ignored for this patch. This is relevant
#' when using automatic tagging of plots and the content of the patch does not
#' qualify for a tag.
#'
#' @return A wrapped_patch object
#'
#' @export
#'
#' @examples
#' library(ggplot2)
#' library(grid)
#'
#' # Combine grobs with each other
#' wrap_elements(panel = textGrob('Here are some text')) +
#' wrap_elements(
#' panel = rectGrob(gp = gpar(fill = 'steelblue')),
#' full = rectGrob(gp = gpar(fill = 'goldenrod'))
#' )
#'
#' # wrapped elements can still get titles etc like ggplots
#' wrap_elements(panel = textGrob('Here are some text')) +
#' wrap_elements(
#' panel = rectGrob(gp = gpar(fill = 'steelblue')),
#' full = rectGrob(gp = gpar(fill = 'goldenrod'))
#' ) +
#' ggtitle('Title for the amazing rectangles')
#'
#' # You can also pass in ggplots or patchworks to e.g. have it fill out the
#' # panel area
#' p1 <- ggplot(mtcars) + geom_point(aes(mpg, disp))
#' p1 + wrap_elements(panel = p1 + ggtitle('Look at me shrink'))
#'
#' # You can even add base graphics if you pass it as a formula (requires gridGraphics package)
#' if (requireNamespace("gridGraphics", quietly = TRUE)) {
#' p1 + wrap_elements(full = ~ plot(mtcars$mpg, mtcars$disp))
#'
#' # Adding a grob or formula directly is equivalent to placing it in `full`
#' p1 + ~ plot(mtcars$mpg, mtcars$disp)
#' }
#'
wrap_elements <- function(panel = NULL, plot = NULL, full = NULL, clip = TRUE, ignore_tag = FALSE) {
clip <- if (clip) 'on' else 'off'
table <- make_patch()
attr(table, 'grobs') <- list(panel = panel, plot = plot, full = full)
attr(table, 'patch_settings') <- list(clip = clip, ignore_tag = ignore_tag)
class(table) <- c('wrapped_patch', class(table))
table
}
is_wrapped_patch <- function(x) inherits(x, 'wrapped_patch')
#' @importFrom ggplot2 ggplotGrob theme_get
#' @importFrom gtable gtable_add_grob
#' @importFrom grid grobHeight convertHeight
#' @export
patchGrob.wrapped_patch <- function(x, guides = 'auto') {
gt <- ggplotGrob(x)
table <- patch_table(x, gt)
settings <- attr(x, 'patch_settings')
grobs <- attr(x, 'grobs')
if (!is.null(grobs$full)) {
table <- gtable_add_grob(table, list(as_patch(grobs$full)), 1, 1, nrow(table),
ncol(table), clip = settings$clip, name = 'full')
}
if (!is.null(grobs$plot)) {
table <- gtable_add_grob(table, list(as_patch(grobs$plot)), PLOT_TOP,
PLOT_LEFT, PLOT_BOTTOM, PLOT_RIGHT,
clip = settings$clip, name = 'plot')
}
if (!is.null(grobs$panel)) {
table <- gtable_add_grob(table, list(as_patch(grobs$panel)), PANEL_ROW,
PANEL_COL, clip = settings$clip, name = 'panel')
}
title <- get_grob(gt, 'title')
table <- gtable_add_grob(table, list(title), TITLE_ROW, PANEL_COL,
clip = settings$clip, name = 'title')
table$heights[TITLE_ROW] <- convertHeight(grobHeight(title), 'mm')
subtitle <- get_grob(gt, 'subtitle')
table <- gtable_add_grob(table, list(subtitle), SUBTITLE_ROW, PANEL_COL,
clip = settings$clip, name = 'subtitle')
table$heights[SUBTITLE_ROW] <- convertHeight(grobHeight(subtitle), 'mm')
caption <- get_grob(gt, 'caption')
table <- gtable_add_grob(table, list(caption), CAPTION_ROW, PANEL_COL,
clip = settings$clip, name = 'caption')
table$heights[CAPTION_ROW] <- convertHeight(grobHeight(caption), 'mm')
if (!settings$ignore_tag) {
table$widths[c(2, ncol(table)-1)] <- gt$widths[c(2, ncol(gt)-1)]
table$heights[c(2, nrow(table)-1)] <- gt$heights[c(2, nrow(gt)-1)]
tag <- get_grob(gt, 'tag')
tag_pos <- calc_element("plot.tag.position", x$theme %||% theme_get())
if (is.null(tag_pos)) tag_pos <- theme_get()$plot.tag.position
if (!is.character(tag_pos)) tag_pos <- 'manual'
table <- switch(
tag_pos,
topleft = gtable_add_grob(table, tag, name = "tag", t = 2, l = 2, clip = "off"),
top = gtable_add_grob(table, tag, name = "tag", t = 2, l = 2, r = ncol(table)-1, clip = "off"),
topright = gtable_add_grob(table, tag, name = "tag", t = 2, l = ncol(table)-1, clip = "off"),
left = gtable_add_grob(table, tag, name = "tag", t = 2, b = nrow(table)-1, l = 2, clip = "off"),
right = gtable_add_grob(table, tag, name = "tag", t = 2, b = nrow(table)-1, l = ncol(table)-1, clip = "off"),
bottomleft = gtable_add_grob(table, tag, name = "tag", t = nrow(table)-1, l = 2, clip = "off"),
bottom = gtable_add_grob(table, tag, name = "tag", t = nrow(table)-1, l = 2, r = ncol(table)-1, clip = "off"),
bottomright = gtable_add_grob(table, tag, name = "tag", t = nrow(table)-1, l = ncol(table)-1, clip = "off"),
manual = gtable_add_grob(table, tag, name = 'tag', t = 2, l = 2, b = nrow(table)-1, r = ncol(table)-1, clip = "off")
)
}
table
}
as_patch <- function(x, ...) {
UseMethod('as_patch')
}
#' @export
as_patch.grob <- function(x, ...) {
x
}
#' @importFrom grid gTree
#' @export
as_patch.gList <- function(x, ...) {
gTree(children = x)
}
#' @importFrom ggplot2 ggplotGrob
#' @export
as_patch.ggplot <- function(x, ...) {
ggplotGrob(x)
}
#' @export
as_patch.patchwork <- function(x, ...) {
patchworkGrob(x)
}
#' @export
as_patch.formula <- function(x, ...) {
rlang::check_installed('gridGraphics', 'to add base plots to patchworks')
gp <- graphics::par(no.readonly = TRUE)
plot_call <- function() {
old_gp <- graphics::par(no.readonly = TRUE)
graphics::par(gp)
on.exit(try(graphics::par(old_gp)))
res <- suppressMessages(eval(x[[2]], attr(x, '.Environment')))
invisible(NULL)
}
gridGraphics::echoGrob(plot_call, name = 'patchwork_base', device = offscreen_dev())
}
#' @export
#' @importFrom grid rasterGrob
as_patch.raster <- function(x, ...) {
as_patch(rasterGrob(x), ...)
}
#' @export
as_patch.nativeRaster <- as_patch.raster
#' @importFrom ggplot2 ggplotGrob
get_grob <- function(x, name) {
ind <- grep(paste0('^', name, '$'), x$layout$name)
if (length(ind) == 0) return(ggplot2::zeroGrob())
x$grobs[[grep(paste0('^', name, '$'), x$layout$name)]]
}
offscreen_dev <- function() {
if (requireNamespace('ragg', quietly = TRUE)) {
function(width, height) {
ragg::agg_capture(width = width, height = height, units = 'in')
grDevices::dev.control("enable")
}
} else {
function(width, height) {
grDevices::pdf(NULL, width = width, height = height)
grDevices::dev.control("enable")
}
}
}
#' @export
has_tag.wrapped_patch <- function(x) !attr(x, 'patch_settings')$ignore_tag
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.