diff --git a/NAMESPACE b/NAMESPACE index ab86e3a..9362810 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -117,6 +117,7 @@ importFrom(ggplot2,set_last_plot) importFrom(ggplot2,theme) importFrom(ggplot2,theme_get) importFrom(ggplot2,theme_void) +importFrom(ggplot2,waiver) importFrom(ggplot2,wrap_dims) importFrom(ggplot2,zeroGrob) importFrom(grDevices,is.raster) diff --git a/NEWS.md b/NEWS.md index f50f423..e0474b4 100644 --- a/NEWS.md +++ b/NEWS.md @@ -17,6 +17,8 @@ * `as.list()` is now provided for patchwork objects to get the plots in a patchwork as a list. This also allows the use of `lapply()` and friends on patchwork objects (#381) +* The default arguments in `plot_annotation()` and `plot_layout()` are now + `waiver()` allowing the use of `NULL` to remove an already set value (#198) # patchwork 1.2.0 diff --git a/R/aaa.R b/R/aaa.R index 83d8d24..9c5362b 100644 --- a/R/aaa.R +++ b/R/aaa.R @@ -44,3 +44,5 @@ check_object <- function(x, call = call ) } + +is_waiver <- function(x) inherits(x, "waiver") diff --git a/R/add_plot.R b/R/add_plot.R index 686f3a5..fcbcffc 100644 --- a/R/add_plot.R +++ b/R/add_plot.R @@ -86,8 +86,29 @@ add_patches.patchwork <- function(plot, patches) { new_patchwork <- function() { list( plots = list(), - layout = plot_layout(), - annotation = plot_annotation() + # We need to initialise layout and annotation with NULL values rather than waivers + layout = plot_layout( + ncol = NULL, + nrow = NULL, + byrow = NULL, + widths = NULL, + heights = NULL, + guides = NULL, + tag_level = NULL, + design = NULL, + axes = NULL, + axis_titles = NULL + ), + annotation = plot_annotation( + title = NULL, + subtitle = NULL, + caption = NULL, + tag_levels = NULL, + tag_prefix = NULL, + tag_suffix = NULL, + tag_sep = NULL, + theme = NULL + ) ) } #' @importFrom ggplot2 ggplot diff --git a/R/plot_annotation.R b/R/plot_annotation.R index 10a2a5e..6f53f2f 100644 --- a/R/plot_annotation.R +++ b/R/plot_annotation.R @@ -7,7 +7,8 @@ has_tag <- function(x) { #' 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. +#' 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 @@ -38,6 +39,7 @@ has_tag <- function(x) { #' @return A `plot_annotation` object #' #' @export +#' @importFrom ggplot2 waiver #' #' @examples #' library(ggplot2) @@ -69,9 +71,9 @@ has_tag <- function(x) { #' p1 / ((p2 | p3) + plot_layout(tag_level = 'new')) + #' plot_annotation(tag_levels = list(c('&', '%'), '1')) #' -plot_annotation <- function(title = NULL, subtitle = NULL, caption = NULL, - tag_levels = NULL, tag_prefix = NULL, tag_suffix = NULL, - tag_sep = NULL, theme = NULL) { +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, @@ -84,14 +86,28 @@ plot_annotation <- function(title = NULL, subtitle = NULL, caption = NULL, theme = th ), class = 'plot_annotation') } -default_annotation <- plot_annotation(tag_levels = character(), tag_prefix = '', tag_suffix = '', tag_sep = '') +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) - plot$patches$annotation$theme <- plot$patches$annotation$theme + object$theme + 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 - plot$patches$annotation <- modifyList(plot$patches$annotation, object[!vapply(object, is.null, logical(1))]) + do_change <- object[!vapply(object, is_waiver, logical(1))] + plot$patches$annotation[names(do_change)] <- do_change plot } #' @importFrom ggplot2 is.ggplot labs diff --git a/R/plot_layout.R b/R/plot_layout.R index 02cc56b..26637a4 100644 --- a/R/plot_layout.R +++ b/R/plot_layout.R @@ -2,7 +2,8 @@ #' #' To control how different plots are laid out, you need to add a #' layout specification. If you are nesting grids, the layout is scoped to the -#' current nesting level. +#' current nesting level. An already set value can be removed by setting it to +#' `NULL`. #' #' @param ncol,nrow The dimensions of the grid to create - if both are `NULL` it #' will use the same logic as [facet_wrap()][ggplot2::facet_wrap] to set the @@ -106,18 +107,22 @@ #' # Guide position must be applied to entire patchwork #' p6 + p7 + plot_layout(guides='collect') & #' theme(legend.position='bottom') - -plot_layout <- function(ncol = NULL, nrow = NULL, byrow = NULL, widths = NULL, - heights = NULL, guides = NULL, tag_level = NULL, - design = NULL, axes = NULL, axis_titles = axes) { - if (!is.null(guides)) guides <- match.arg(guides, c('auto', 'collect', 'keep')) - if (!is.null(tag_level)) tag_level <- match.arg(tag_level, c('keep', 'new')) - if (!is.null(axes)) axes <- match.arg( - axes, c('keep', 'collect', 'collect_x', 'collect_y') - ) - if (!is.null(axis_titles)) collect_titles <- match.arg( - axis_titles, c('keep', 'collect', 'collect_x', 'collect_y') - ) +plot_layout <- function(ncol = waiver(), nrow = waiver(), byrow = waiver(), + widths = waiver(), heights = waiver(), guides = waiver(), + tag_level = waiver(), design = waiver(), axes = waiver(), + axis_titles = axes) { + if (!is.null(guides) && !is_waiver(guides)) { + guides <- arg_match0(guides, c('auto', 'collect', 'keep')) + } + if (!is.null(tag_level) && !is_waiver(tag_level)) { + tag_level <- arg_match0(tag_level, c('keep', 'new')) + } + if (!is.null(axes) && !is_waiver(axes)) { + axes <- arg_match0(axes, c('keep', 'collect', 'collect_x', 'collect_y')) + } + if (!is.null(axis_titles) && !is_waiver(axis_titles)) { + collect_titles <- arg_match0(axis_titles, c('keep', 'collect', 'collect_x', 'collect_y')) + } structure(list( ncol = ncol, nrow = nrow, @@ -128,7 +133,7 @@ plot_layout <- function(ncol = NULL, nrow = NULL, byrow = NULL, widths = NULL, tag_level = tag_level, axes = axes, axis_titles = axis_titles, - design = as_areas(design) + design = if (is_waiver(design)) design else as_areas(design) ), class = 'plot_layout') } #' Specify a plotting area in a layout @@ -304,13 +309,15 @@ c.patch_area <- function(..., recursive = FALSE) { area } default_layout <- plot_layout( - byrow = TRUE, widths = NA, heights = NA, guides = 'auto', tag_level = 'keep', + ncol = NULL, nrow = NULL, byrow = TRUE, widths = NA, heights = NA, + guides = 'auto', tag_level = 'keep', design = NULL, axes = 'keep', axis_titles = 'keep' ) #' @importFrom utils modifyList #' @export ggplot_add.plot_layout <- function(object, plot, object_name) { plot <- as_patchwork(plot) - plot$patches$layout <- modifyList(plot$patches$layout, object[!vapply(object, is.null, logical(1))]) + do_change <- object[!vapply(object, is_waiver, logical(1))] + plot$patches$layout[names(do_change)] <- do_change plot } diff --git a/man/plot_annotation.Rd b/man/plot_annotation.Rd index 7bc1ee9..15b68cf 100644 --- a/man/plot_annotation.Rd +++ b/man/plot_annotation.Rd @@ -5,14 +5,14 @@ \title{Annotate the final patchwork} \usage{ plot_annotation( - title = NULL, - subtitle = NULL, - caption = NULL, - tag_levels = NULL, - tag_prefix = NULL, - tag_suffix = NULL, - tag_sep = NULL, - theme = NULL + title = waiver(), + subtitle = waiver(), + caption = waiver(), + tag_levels = waiver(), + tag_prefix = waiver(), + tag_suffix = waiver(), + tag_sep = waiver(), + theme = waiver() ) } \arguments{ @@ -42,7 +42,8 @@ A \code{plot_annotation} object The result of this function can be added to a patchwork using \code{+} in the same way as \code{\link[=plot_layout]{plot_layout()}}, but unlike \code{\link[=plot_layout]{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. +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 \code{NULL}. } \details{ Tagging of subplots is done automatically and following the order of the