Nothing
has_tag <- function(x) {
UseMethod('has_tag')
}
#' Annotate the final patchwork
#'
#' The result of this function can be added to a patchwork using `+` in the same
#' way as [plot_layout()], but unlike [plot_layout()] it will only have an
#' effect on the top level plot. As the name suggests it controls different
#' aspects of the annotation of the final plot, such as titles and tags. Already
#' added annotations can be removed by setting the relevant argument to `NULL`.
#'
#' @details
#' Tagging of subplots is done automatically and following the order of the
#' plots as they are added. When the plot contains nested layouts the
#' `tag_level` argument in the nested [plot_layout] will define whether
#' enumeration should continue as usual or add a new level. The format of the
#' levels are defined with `tag_levels` argument in `plot_annotation`
#'
#' @param title,subtitle,caption Text strings to use for the various plot
#' annotations.
#'
#' @param tag_levels A character vector defining the enumeration format to use
#' at each level. Possible values are `'a'` for lowercase letters, `'A'` for
#' uppercase letters, `'1'` for numbers, `'i'` for lowercase Roman numerals, and
#' `'I'` for uppercase Roman numerals. It can also be a list containing
#' character vectors defining arbitrary tag sequences. If any element in the
#' list is a scalar and one of `'a'`, `'A'`, `'1'`, `'i`, or `'I'`, this level
#' will be expanded to the expected sequence.
#'
#' @param tag_prefix,tag_suffix Strings that should appear before or after the
#' tag.
#'
#' @param tag_sep A separator between different tag levels
#'
#' @param theme A ggplot theme specification to use for the plot. Only elements
#' related to the titles as well as plot margin and background is used.
#'
#' @return A `plot_annotation` object
#'
#' @export
#' @importFrom ggplot2 waiver
#'
#' @examples
#' library(ggplot2)
#'
#' p1 <- ggplot(mtcars) + geom_point(aes(mpg, disp))
#' p2 <- ggplot(mtcars) + geom_boxplot(aes(gear, disp, group = gear))
#' p3 <- ggplot(mtcars) + geom_bar(aes(gear)) + facet_wrap(~cyl)
#'
#' # Add title, etc. to a patchwork
#' p1 + p2 + plot_annotation('This is a title', caption = 'made with patchwork')
#'
#' # Change styling of patchwork elements
#' p1 + p2 +
#' plot_annotation(
#' title = 'This is a title',
#' caption = 'made with patchwork',
#' theme = theme(plot.title = element_text(size = 16))
#' )
#'
#' # Add tags to plots
#' p1 / (p2 | p3) +
#' plot_annotation(tag_levels = 'A')
#'
#' # Add multilevel tagging to nested layouts
#' p1 / ((p2 | p3) + plot_layout(tag_level = 'new')) +
#' plot_annotation(tag_levels = c('A', '1'))
#'
#' # Use a custom tag sequence (mixed with a standard one)
#' p1 / ((p2 | p3) + plot_layout(tag_level = 'new')) +
#' plot_annotation(tag_levels = list(c('&', '%'), '1'))
#'
plot_annotation <- function(title = waiver(), subtitle = waiver(), caption = waiver(),
tag_levels = waiver(), tag_prefix = waiver(), tag_suffix = waiver(),
tag_sep = waiver(), theme = waiver()) {
th <- if (is.null(theme)) ggplot2::theme() else theme
structure(list(
title = title,
subtitle = subtitle,
caption = caption,
tag_levels = tag_levels,
tag_prefix = tag_prefix,
tag_suffix = tag_suffix,
tag_sep = tag_sep,
theme = th
), class = 'plot_annotation')
}
default_annotation <- plot_annotation(
title = NULL,
subtitle = NULL,
caption = NULL,
tag_levels = character(),
tag_prefix = '',
tag_suffix = '',
tag_sep = '',
theme = NULL
)
#' @importFrom utils modifyList
#' @export
ggplot_add.plot_annotation <- function(object, plot, object_name) {
plot <- as_patchwork(plot)
if (is.null(object$theme)) {
plot$patches$annotation$theme <- NULL
} else if (!is_waiver(object$theme)) {
plot$patches$annotation$theme <- plot$patches$annotation$theme + object$theme
}
object$theme <- NULL
do_change <- object[!vapply(object, is_waiver, logical(1))]
plot$patches$annotation[names(do_change)] <- do_change
plot
}
#' @importFrom ggplot2 is.ggplot labs
recurse_tags <- function(x, levels, prefix, suffix, sep, offset = 1) {
if (length(levels) == 0) return(list(patches = x, tab_ind = offset))
level <- get_level(levels[1])
patches <- x$patches$plots
tag_ind <- offset
for (i in seq_along(patches)) {
this_level <- if (length(level) < tag_ind) '' else level[tag_ind]
if (is_patchwork(patches[[i]])) {
if (is_inset_patch(patches[[i]]) && !has_tag(patches[[i]])) {
next
}
tag_level <- patches[[i]]$patches$layout$tag_level
tag_level <- if (is.null(tag_level)) default_layout$tag_level else tag_level
if (tag_level == 'keep') {
new_plots <- recurse_tags(patches[[i]], levels, prefix, suffix, sep, tag_ind)
patches[[i]] <- new_plots$patches
tag_ind <- new_plots$tag_ind
} else if (length(levels) > 1) {
patches[[i]] <- recurse_tags(patches[[i]], levels[-1],
prefix = paste0(prefix, this_level, sep),
suffix, sep)$patches
tag_ind <- tag_ind + 1
}
} else if (has_tag(patches[[i]])) {
patches[[i]] <- patches[[i]] + labs(tag = paste0(prefix, this_level, suffix))
tag_ind <- tag_ind + 1
}
}
x$patches$plots <- patches
if (has_tag(x)) {
this_level <- if (length(level) < tag_ind) '' else level[tag_ind]
x <- x + labs(tag = paste0(prefix, this_level, suffix))
tag_ind <- tag_ind + 1
}
list(
patches = x,
tag_ind = tag_ind
)
}
#' @importFrom ggplot2 ggplot labs ggplotGrob
#' @importFrom gtable gtable_add_rows gtable_add_cols
#' @importFrom grid unit
#' @importFrom utils tail
annotate_table <- function(table, annotation) {
p <- ggplot() + annotation$theme + exec(labs, !!!annotation[c('title', 'subtitle', 'caption')])
p <- ggplotGrob(p)
max_z <- max(table$layout$z)
fix_respect <- is.matrix(table$respect)
if (!is.null(annotation$title) || !is.null(annotation$subtitle)) {
table <- gtable_add_rows(table, p$heights[c(1, 3, 4)], 0)
table <- gtable_add_grob(table, get_grob(p, 'title'), 2, 2, r = ncol(table) - 1,
z = max_z + 3, name = 'title', clip = 'off')
table <- gtable_add_grob(table, get_grob(p, 'subtitle'), 3, 2, r = ncol(table) - 1,
z = max_z + 2, name = 'subtitle', clip = 'off')
if (fix_respect) table$respect <- rbind(matrix(0, nrow = 3, ncol = ncol(table$respect)), table$respect)
} else {
table <- gtable_add_rows(table, p$heights[1], 0)
if (fix_respect) table$respect <- rbind(0, table$respect)
}
if (!is.null(annotation$caption)) {
table <- gtable_add_rows(table, tail(p$heights, 3)[-2])
table <- gtable_add_grob(table, get_grob(p, 'caption'), nrow(table) - 1, 2,
r = ncol(table) - 1, z = max_z + 1, name = 'caption',
clip = 'off')
if (fix_respect) table$respect <- rbind(table$respect, matrix(0, nrow = 2, ncol = ncol(table$respect)))
} else {
table <- gtable_add_rows(table, tail(p$heights, 1))
if (fix_respect) table$respect <- rbind(table$respect, 0)
}
table <- gtable_add_cols(table, p$widths[1], 0)
table <- gtable_add_cols(table, tail(p$widths, 1))
if (fix_respect) table$respect <- cbind(0, table$respect, 0)
table <- gtable_add_grob(table, get_grob(p, 'background'), 1, 1, nrow(table), ncol(table),
z = -Inf, name = 'background')
table
}
#' @importFrom utils as.roman
get_level <- function(x) {
if (is.list(x)) {
if (length(x[[1]]) == 1 && x[[1]] %in% c('a', 'A', '1', 'i', 'I')) {
x <- x[[1]]
} else {
return(x[[1]])
}
}
switch(
as.character(x),
a = letters,
A = LETTERS,
"1" = 1:100,
i = tolower(as.roman(1:100)),
I = as.roman(1:100),
cli_abort('Unknown tag type: {.val {x}}')
)
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.