From bbfdfb0622e09404ed47c73d1686a814952730cf Mon Sep 17 00:00:00 2001 From: Claus Wilke Date: Fri, 13 Jul 2018 23:39:21 +0200 Subject: [PATCH 01/14] move validation code. Doesn't work yet. --- R/theme.r | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/R/theme.r b/R/theme.r index dbd4b8d4ad..9c4cd5d660 100644 --- a/R/theme.r +++ b/R/theme.r @@ -405,11 +405,6 @@ theme <- function(line, elements$legend.margin <- margin() } - # Check that all elements have the correct class (element_text, unit, etc) - if (validate) { - mapply(validate_element, elements, names(elements)) - } - # If complete theme set all non-blank elements to inherit from blanks if (complete) { elements <- lapply(elements, function(el) { @@ -433,10 +428,13 @@ is_theme_complete <- function(x) isTRUE(attr(x, "complete")) # Combine plot defaults with current theme to get complete theme for a plot plot_theme <- function(x, default = theme_get()) { theme <- x$theme - if (is_theme_complete(theme)) { - theme - } else { - defaults(theme, default) + if (!is_theme_complete(theme)) { + theme <- defaults(theme, default) + } + + # Check that all elements have the correct class (element_text, unit, etc) + if (isTRUE(theme$validate)) { + mapply(validate_element, elements, names(elements)) } } From ed8d13e945b3265309c608f84e9744acf47a4a8e Mon Sep 17 00:00:00 2001 From: Claus Wilke Date: Thu, 26 Jul 2018 09:40:14 +0200 Subject: [PATCH 02/14] move theme validation to plot construction --- R/theme-elements.r | 10 +++++----- R/theme.r | 32 ++++++++++++++++++++++++-------- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/R/theme-elements.r b/R/theme-elements.r index 3af3875fb1..2c51978929 100644 --- a/R/theme-elements.r +++ b/R/theme-elements.r @@ -146,7 +146,7 @@ element_render <- function(theme, element, ..., name = NULL) { # Get the element from the theme, calculating inheritance el <- calc_element(element, theme) if (is.null(el)) { - message("Theme element ", element, " missing") + message("Theme element `", element, "` missing") return(zeroGrob()) } @@ -367,7 +367,7 @@ validate_element <- function(el, elname) { eldef <- ggplot_global$element_tree[[elname]] if (is.null(eldef)) { - stop('"', elname, '" is not a valid theme element name.') + stop("Theme element `", elname, "` is not defined in the element hierarchy.", call. = FALSE) } # NULL values for elements are OK @@ -377,12 +377,12 @@ validate_element <- function(el, elname) { # Need to be a bit looser here since sometimes it's a string like "top" # but sometimes its a vector like c(0,0) if (!is.character(el) && !is.numeric(el)) - stop("Element ", elname, " must be a string or numeric vector.") + stop("Theme element `", elname, "` must be a string or numeric vector.", call. = FALSE) } else if (eldef$class == "margin") { if (!is.unit(el) && length(el) == 4) - stop("Element ", elname, " must be a unit vector of length 4.") + stop("Theme element `", elname, "` must be a unit vector of length 4.", call. = FALSE) } else if (!inherits(el, eldef$class) && !inherits(el, "element_blank")) { - stop("Element ", elname, " must be a ", eldef$class, " object.") + stop("Theme element `", elname, "` must be an `", eldef$class, "` object.", call. = FALSE) } invisible() } diff --git a/R/theme.r b/R/theme.r index 9c4cd5d660..601b0c7efa 100644 --- a/R/theme.r +++ b/R/theme.r @@ -422,20 +422,34 @@ theme <- function(line, ) } -is_theme_complete <- function(x) isTRUE(attr(x, "complete")) - +# check whether theme is complete +is_theme_complete <- function(x) isTRUE(attr(x, "complete", exact = TRUE)) + +# check whether theme should be validated +is_theme_validate <- function(x) { + validate <- attr(x, "validate", exact = TRUE) + if (is.null(validate)) + TRUE # we validate by default + else + isTRUE(validate) +} # Combine plot defaults with current theme to get complete theme for a plot plot_theme <- function(x, default = theme_get()) { theme <- x$theme + if (!is_theme_complete(theme)) { - theme <- defaults(theme, default) + # can't use `defaults()` here because it strips attributes + missing <- setdiff(names(default), names(theme)) + theme[missing] <- default[missing] } # Check that all elements have the correct class (element_text, unit, etc) - if (isTRUE(theme$validate)) { - mapply(validate_element, elements, names(elements)) + if (is_theme_validate(theme)) { + mapply(validate_element, theme, names(theme)) } + + theme } #' Modify properties of an element in a theme object @@ -518,10 +532,12 @@ update_theme <- function(oldtheme, newtheme) { # Turn the 'theme' list into a proper theme object first, and preserve # the 'complete' attribute. It's possible that oldtheme is an empty # list, and in that case, set complete to FALSE. - old.validate <- isTRUE(attr(oldtheme, "validate")) - new.validate <- isTRUE(attr(newtheme, "validate")) + old.validate <- is_theme_validate(oldtheme) + new.validate <- is_theme_validate(newtheme) oldtheme <- do.call(theme, c(oldtheme, - complete = isTRUE(attr(oldtheme, "complete")), + complete = is_theme_complete(oldtheme), + # if at least one part of the theme can't be validated then + # we don't validate validate = old.validate & new.validate)) oldtheme + newtheme From 1e5ca2498c319d96014c7a0cf15c24056f7dba07 Mon Sep 17 00:00:00 2001 From: Claus Wilke Date: Thu, 26 Jul 2018 21:06:05 +0200 Subject: [PATCH 03/14] Provide element tree as theme attribute --- NAMESPACE | 2 ++ R/theme-current.R | 9 ++++++ R/theme-elements.r | 34 ++++++++++++++++------- R/theme.r | 64 +++++++++++++++++++++++++++++++------------ man/el_def.Rd | 23 ++++++++++++++++ man/element_render.Rd | 22 +++++++++++++++ man/ggtheme.Rd | 49 ++++++++++++++++++++++++++------- man/scale_viridis.Rd | 2 +- man/theme.Rd | 2 +- 9 files changed, 168 insertions(+), 39 deletions(-) create mode 100644 man/el_def.Rd create mode 100644 man/element_render.Rd diff --git a/NAMESPACE b/NAMESPACE index f09568dd1c..07936960e3 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -277,10 +277,12 @@ export(draw_key_text) export(draw_key_vline) export(draw_key_vpath) export(dup_axis) +export(el_def) export(element_blank) export(element_grob) export(element_line) export(element_rect) +export(element_render) export(element_text) export(enexpr) export(enexprs) diff --git a/R/theme-current.R b/R/theme-current.R index 7bb0f061b1..51f9393508 100644 --- a/R/theme-current.R +++ b/R/theme-current.R @@ -104,5 +104,14 @@ theme_replace <- function(...) { # Can't use modifyList here since it works recursively and drops NULLs e1[names(e2)] <- e2 + + # replace element tree if provided + attr(e1, "element_tree") <- + attr(e2, "element_tree", exact = TRUE) %||% + attr(e1, "element_tree", exact = TRUE) + + # `complete` and `validate` are ignored. + # Not sure how `%+replace%` should handle them. + e1 } diff --git a/R/theme-elements.r b/R/theme-elements.r index 2c51978929..987684826d 100644 --- a/R/theme-elements.r +++ b/R/theme-elements.r @@ -140,7 +140,16 @@ print.rel <- function(x, ...) print(noquote(paste(x, " *", sep = ""))) #' @keywords internal is.rel <- function(x) inherits(x, "rel") -# Given a theme object and element name, return a grob for the element +#' Render a specified theme element into a grob +#' +#' Given a theme object and element name, returns a grob for the element. +#' Uses [`element_grob()`] to generate the grob. +#' @param theme The theme object +#' @param element The element name given as character vector +#' @param ... Other arguments provided to [`element_grob()`] +#' @param name Character vector added to the name of the grob +#' @keywords internal +#' @export element_render <- function(theme, element, ..., name = NULL) { # Get the element from the theme, calculating inheritance @@ -243,13 +252,17 @@ element_grob.element_line <- function(element, x = 0:1, y = 0:1, -# Define an element's class and what other elements it inherits from -# -# @param class The name of class (like "element_line", "element_text", -# or the reserved "character", which means a character vector (not -# "character" class) -# @param inherit A vector of strings, naming the elements that this -# element inherits from. +#' Define an element's class and what other elements it inherits from +#' +#' @param class The name of class (like "element_line", "element_text", +#' or the reserved "character", which means a character vector (not +#' "character" class) +#' @param inherit A vector of strings, naming the elements that this +#' element inherits from. +#' @param description An optional character vector providing a description +#' for the element +#' @keywords internal +#' @export el_def <- function(class = NULL, inherit = NULL, description = NULL) { list(class = class, inherit = inherit, description = description) } @@ -363,8 +376,9 @@ ggplot_global$element_tree <- list( # # @param el an element # @param elname the name of the element -validate_element <- function(el, elname) { - eldef <- ggplot_global$element_tree[[elname]] +# @param element_tree the element tree to validate against +validate_element <- function(el, elname, element_tree) { + eldef <- element_tree[[elname]] if (is.null(eldef)) { stop("Theme element `", elname, "` is not defined in the element hierarchy.", call. = FALSE) diff --git a/R/theme.r b/R/theme.r index 601b0c7efa..282a75a9e5 100644 --- a/R/theme.r +++ b/R/theme.r @@ -371,9 +371,10 @@ theme <- function(line, strip.switch.pad.wrap, ..., complete = FALSE, - validate = TRUE + validate = TRUE, + element_tree = NULL ) { - elements <- find_args(..., complete = NULL, validate = NULL) + elements <- find_args(..., complete = NULL, validate = NULL, element_tree = NULL) if (!is.null(elements$axis.ticks.margin)) { warning("`axis.ticks.margin` is deprecated. Please set `margin` property ", @@ -418,7 +419,8 @@ theme <- function(line, elements, class = c("theme", "gg"), complete = complete, - validate = validate + validate = validate, + element_tree = element_tree ) } @@ -434,6 +436,17 @@ is_theme_validate <- function(x) { isTRUE(validate) } +# obtain the full element tree from a theme, +# substituting the defaults if needed +complete_element_tree <- function(theme) { + element_tree <- attr(theme, "element_tree", exact = TRUE) + if (is.null(element_tree)) { + ggplot_global$element_tree + } else { + defaults(element_tree, ggplot_global$element_tree) + } +} + # Combine plot defaults with current theme to get complete theme for a plot plot_theme <- function(x, default = theme_get()) { theme <- x$theme @@ -446,7 +459,10 @@ plot_theme <- function(x, default = theme_get()) { # Check that all elements have the correct class (element_text, unit, etc) if (is_theme_validate(theme)) { - mapply(validate_element, theme, names(theme)) + mapply( + validate_element, theme, names(theme), + MoreArgs = list(element_tree = complete_element_tree(theme)) + ) } theme @@ -489,7 +505,19 @@ add_theme <- function(t1, t2, t2name) { } # If either theme is complete, then the combined theme is complete - attr(t1, "complete") <- is_theme_complete(t1) || is_theme_complete(t2) + attr(t1, "complete") <- + is_theme_complete(t1) || is_theme_complete(t2) + + # Only validate if both themes should be validated + attr(t1, "validate") <- + is_theme_validate(t1) && is_theme_validate(t2) + + # Merge element trees if provided + attr(t1, "element_tree") <- defaults( + attr(t2, "element_tree", exact = TRUE), + attr(t1, "element_tree", exact = TRUE) + ) + t1 } @@ -531,14 +559,15 @@ update_theme <- function(oldtheme, newtheme) { # Update the theme elements with the things from newtheme # Turn the 'theme' list into a proper theme object first, and preserve # the 'complete' attribute. It's possible that oldtheme is an empty - # list, and in that case, set complete to FALSE. - old.validate <- is_theme_validate(oldtheme) - new.validate <- is_theme_validate(newtheme) - oldtheme <- do.call(theme, c(oldtheme, - complete = is_theme_complete(oldtheme), - # if at least one part of the theme can't be validated then - # we don't validate - validate = old.validate & new.validate)) + # list, and in that case, set complete to FALSE but validate to TRUE + oldtheme <- do.call( + theme, + c( + oldtheme, + complete = is_theme_complete(oldtheme), + validate = is_theme_validate(oldtheme) + ) + ) oldtheme + newtheme } @@ -573,14 +602,15 @@ calc_element <- function(element, theme, verbose = FALSE) { } # If the element is defined (and not just inherited), check that - # it is of the class specified in .element_tree + # it is of the class specified in element_tree + element_tree <- complete_element_tree(theme) if (!is.null(theme[[element]]) && - !inherits(theme[[element]], ggplot_global$element_tree[[element]]$class)) { - stop(element, " should have class ", ggplot_global$element_tree[[element]]$class) + !inherits(theme[[element]], element_tree[[element]]$class)) { + stop(element, " should have class ", element_tree[[element]]$class) } # Get the names of parents from the inheritance tree - pnames <- ggplot_global$element_tree[[element]]$inherit + pnames <- element_tree[[element]]$inherit # If no parents, this is a "root" node. Just return this element. if (is.null(pnames)) { diff --git a/man/el_def.Rd b/man/el_def.Rd new file mode 100644 index 0000000000..6296090d45 --- /dev/null +++ b/man/el_def.Rd @@ -0,0 +1,23 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/theme-elements.r +\name{el_def} +\alias{el_def} +\title{Define an element's class and what other elements it inherits from} +\usage{ +el_def(class = NULL, inherit = NULL, description = NULL) +} +\arguments{ +\item{class}{The name of class (like "element_line", "element_text", +or the reserved "character", which means a character vector (not +"character" class)} + +\item{inherit}{A vector of strings, naming the elements that this +element inherits from.} + +\item{description}{An optional character vector providing a description +for the element} +} +\description{ +Define an element's class and what other elements it inherits from +} +\keyword{internal} diff --git a/man/element_render.Rd b/man/element_render.Rd new file mode 100644 index 0000000000..d9bd13ec56 --- /dev/null +++ b/man/element_render.Rd @@ -0,0 +1,22 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/theme-elements.r +\name{element_render} +\alias{element_render} +\title{Render a specified theme element into a grob} +\usage{ +element_render(theme, element, ..., name = NULL) +} +\arguments{ +\item{theme}{The theme object} + +\item{element}{The element name given as character vector} + +\item{...}{Other arguments provided to \code{\link[=element_grob]{element_grob()}}} + +\item{name}{Character vector added to the name of the grob} +} +\description{ +Given a theme object and element name, returns a grob for the element. +Uses \code{\link[=element_grob]{element_grob()}} to generate the grob. +} +\keyword{internal} diff --git a/man/ggtheme.Rd b/man/ggtheme.Rd index a54289cec5..2a1c1bee64 100644 --- a/man/ggtheme.Rd +++ b/man/ggtheme.Rd @@ -97,14 +97,43 @@ for new features.} } } \examples{ -p <- ggplot(mtcars) + geom_point(aes(x = wt, y = mpg, - colour = factor(gear))) + facet_wrap(~am) -p + theme_gray() # the default -p + theme_bw() -p + theme_linedraw() -p + theme_light() -p + theme_dark() -p + theme_minimal() -p + theme_classic() -p + theme_void() +mtcars2 <- within(mtcars, { + vs <- factor(vs, labels = c("V-shaped", "Straight")) + am <- factor(am, labels = c("Automatic", "Manual")) + cyl <- factor(cyl) + gear <- factor(gear) +}) + +p1 <- ggplot(mtcars2) + + geom_point(aes(x = wt, y = mpg, colour = gear)) + + labs(title = "Fuel economy declines as weight increases", + subtitle = "(1973-74)", + caption = "Data from the 1974 Motor Trend US magazine.", + tag = "Figure 1", + x = "Weight (1000 lbs)", + y = "Fuel economy (mpg)", + colour = "Gears") + +p1 + theme_gray() # the default +p1 + theme_bw() +p1 + theme_linedraw() +p1 + theme_light() +p1 + theme_dark() +p1 + theme_minimal() +p1 + theme_classic() +p1 + theme_void() + +# Theme examples with panels +\donttest{ +p2 <- p1 + facet_grid(vs ~ am) + +p2 + theme_gray() # the default +p2 + theme_bw() +p2 + theme_linedraw() +p2 + theme_light() +p2 + theme_dark() +p2 + theme_minimal() +p2 + theme_classic() +p2 + theme_void() +} } diff --git a/man/scale_viridis.Rd b/man/scale_viridis.Rd index 3a375deeb1..0066028de0 100644 --- a/man/scale_viridis.Rd +++ b/man/scale_viridis.Rd @@ -50,7 +50,7 @@ same time, via \code{aesthetics = c("colour", "fill")}.} \item{values}{if colours should not be evenly positioned along the gradient this vector gives the position (between 0 and 1) for each colour in the -\code{colours} vector. See \code{\link{rescale}} for a convience function +\code{colours} vector. See \code{\link[=rescale]{rescale()}} for a convience function to map an arbitrary range to between 0 and 1.} \item{space}{colour space in which to calculate gradient. Must be "Lab" - diff --git a/man/theme.Rd b/man/theme.Rd index bb0f543812..39c75ee5f6 100644 --- a/man/theme.Rd +++ b/man/theme.Rd @@ -24,7 +24,7 @@ theme(line, rect, text, title, aspect.ratio, axis.title, axis.title.x, plot.caption, plot.tag, plot.tag.position, plot.margin, strip.background, strip.background.x, strip.background.y, strip.placement, strip.text, strip.text.x, strip.text.y, strip.switch.pad.grid, strip.switch.pad.wrap, ..., - complete = FALSE, validate = TRUE) + complete = FALSE, validate = TRUE, element_tree = NULL) } \arguments{ \item{line}{all line elements (\code{element_line})} From 6133424ecb0df30384bd3782f6604a7f418e677b Mon Sep 17 00:00:00 2001 From: Claus Wilke Date: Sun, 29 Jul 2018 09:10:40 -0500 Subject: [PATCH 04/14] fix documentation --- R/theme-elements.r | 6 +++--- R/theme.r | 3 +++ man/el_def.Rd | 6 +++--- man/theme.Rd | 4 ++++ 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/R/theme-elements.r b/R/theme-elements.r index 987684826d..b112c0fec2 100644 --- a/R/theme-elements.r +++ b/R/theme-elements.r @@ -255,12 +255,12 @@ element_grob.element_line <- function(element, x = 0:1, y = 0:1, #' Define an element's class and what other elements it inherits from #' #' @param class The name of class (like "element_line", "element_text", -#' or the reserved "character", which means a character vector (not -#' "character" class) +#' or the reserved "character", which implies a character or numeric +#' vector, not a class called "character"). #' @param inherit A vector of strings, naming the elements that this #' element inherits from. #' @param description An optional character vector providing a description -#' for the element +#' for the element. #' @keywords internal #' @export el_def <- function(class = NULL, inherit = NULL, description = NULL) { diff --git a/R/theme.r b/R/theme.r index 282a75a9e5..3c803c08e0 100644 --- a/R/theme.r +++ b/R/theme.r @@ -194,6 +194,9 @@ #' `complete = TRUE` all elements will be set to inherit from blank #' elements. #' @param validate `TRUE` to run `validate_element()`, `FALSE` to bypass checks. +#' @param element_tree optional addition or modification to the element tree, +#' which specifies the inheritance relationship of the theme elements. See +#' [`el_def()`]. #' #' @seealso #' [+.gg()] and \code{\link{\%+replace\%}}, diff --git a/man/el_def.Rd b/man/el_def.Rd index 6296090d45..d18c8ad0ba 100644 --- a/man/el_def.Rd +++ b/man/el_def.Rd @@ -8,14 +8,14 @@ el_def(class = NULL, inherit = NULL, description = NULL) } \arguments{ \item{class}{The name of class (like "element_line", "element_text", -or the reserved "character", which means a character vector (not -"character" class)} +or the reserved "character", which implies a character or numeric +vector, not a class called "character").} \item{inherit}{A vector of strings, naming the elements that this element inherits from.} \item{description}{An optional character vector providing a description -for the element} +for the element.} } \description{ Define an element's class and what other elements it inherits from diff --git a/man/theme.Rd b/man/theme.Rd index 39c75ee5f6..a0af560b65 100644 --- a/man/theme.Rd +++ b/man/theme.Rd @@ -287,6 +287,10 @@ differently when added to a ggplot object. Also, when setting elements.} \item{validate}{\code{TRUE} to run \code{validate_element()}, \code{FALSE} to bypass checks.} + +\item{element_tree}{optional addition or modification to the element tree, +which specifies the inheritance relationship of the theme elements. See +\code{\link[=el_def]{el_def()}}.} } \description{ Use \code{theme()} to modify individual components of a theme, allowing From 97e42bb3a0db07172f50a6e20a6e7c46f2258e7d Mon Sep 17 00:00:00 2001 From: Claus Wilke Date: Sun, 13 Oct 2019 16:30:48 -0500 Subject: [PATCH 05/14] Adding news item and tests. Closes #2540. --- NEWS.md | 4 ++++ tests/testthat/test-theme.r | 25 +++++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/NEWS.md b/NEWS.md index a6daad8503..b3cd1cc139 100644 --- a/NEWS.md +++ b/NEWS.md @@ -14,6 +14,10 @@ * `Geom` now gains a `setup_params()` method in line with the other ggproto classes (@thomasp85, #3509) +* Themes can now modify the theme element tree, via the + `element_tree` argument. This allows extension packages to add functionality that + alters the element tree (@clauswilke, #2540). + * `element_text()` now issues a warning when vectorized arguments are provided, as in `colour = c("red", "green", "blue")`. Such use is discouraged and not officially supported (@clauswilke, #3492). diff --git a/tests/testthat/test-theme.r b/tests/testthat/test-theme.r index 1e2d12fcda..93844f31ca 100644 --- a/tests/testthat/test-theme.r +++ b/tests/testthat/test-theme.r @@ -196,6 +196,31 @@ test_that("theme(validate=FALSE) means do not validate_element", { expect_equal(red.before$theme$animint.width, 500) }) +test_that("theme validation happens at build stage", { + # adding a non-valid theme element to a theme is no problem + expect_silent(theme_gray() + theme(text = 0)) + + # the error occurs when we try to render the plot + p <- ggplot() + theme(text = 0) + expect_error(print(p), "must be an `element_text`") + + # without validation, the error occurs when the element is accessed + p <- ggplot() + theme(text = 0, validate = FALSE) + expect_error(print(p), "text should have class element_text") +}) + +test_that("element tree can be modified", { + # we cannot add a new theme element without modifying the element tree + p <- ggplot() + theme(blablabla = element_text()) + expect_error(print(p), "Theme element `blablabla` is not defined in the element hierarchy") + + # things work once we add a new element to the element tree + q <- p + theme( + element_tree = list(blablabla = el_def("element_text", "text")) + ) + expect_silent(print(q)) +}) + test_that("all elements in complete themes have inherit.blank=TRUE", { inherit_blanks <- function(theme) { all(vapply(theme, function(el) { From 444ea3df15322b0a5d398affa3a7498a81627f96 Mon Sep 17 00:00:00 2001 From: Claus Wilke Date: Sun, 13 Oct 2019 17:12:15 -0500 Subject: [PATCH 06/14] better comments, more tests --- R/theme-current.R | 8 ++++++-- R/theme-elements.r | 10 ++++++---- tests/testthat/test-theme.r | 12 +++++++++++- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/R/theme-current.R b/R/theme-current.R index 6af843ab19..aec54f847f 100644 --- a/R/theme-current.R +++ b/R/theme-current.R @@ -106,12 +106,16 @@ theme_replace <- function(...) { e1[names(e2)] <- e2 # replace element tree if provided + # note: this *does not* merge element trees, it + # simply overwrites the first if the second is present. attr(e1, "element_tree") <- attr(e2, "element_tree", exact = TRUE) %||% attr(e1, "element_tree", exact = TRUE) - # `complete` and `validate` are ignored. - # Not sure how `%+replace%` should handle them. + # comment by @clauswilke: + # `complete` and `validate` are currently ignored, + # which means they are taken from e1. Is this correct? + # I'm not sure how `%+replace%` should handle them. e1 } diff --git a/R/theme-elements.r b/R/theme-elements.r index e7dab9b350..a6e366bf1a 100644 --- a/R/theme-elements.r +++ b/R/theme-elements.r @@ -10,7 +10,7 @@ #' - `element_text`: text. #' #' `rel()` is used to specify sizes relative to the parent, -#' `margins()` is used to specify the margins of elements. +#' `margin()` is used to specify the margins of elements. #' #' @param fill Fill colour. #' @param colour,color Line/border colour. Color is an alias for colour. @@ -274,9 +274,11 @@ element_grob.element_line <- function(element, x = 0:1, y = 0:1, #' Define an element's class and what other elements it inherits from #' -#' @param class The name of class (like "element_line", "element_text", -#' or the reserved "character", which implies a character or numeric -#' vector, not a class called "character"). +#' @param class The name of the element class. Examples are "element_line" or +#' "element_text" or "unit", or one of the two reserved keywords "character" or +#' "margin". The reserved keyword "character" implies a character +#' or numeric vector, not a class called "character". The keyword +#' "margin" implies a unit vector of length 4, as created by [margin()]. #' @param inherit A vector of strings, naming the elements that this #' element inherits from. #' @param description An optional character vector providing a description diff --git a/tests/testthat/test-theme.r b/tests/testthat/test-theme.r index 93844f31ca..030914e9a6 100644 --- a/tests/testthat/test-theme.r +++ b/tests/testthat/test-theme.r @@ -211,7 +211,7 @@ test_that("theme validation happens at build stage", { test_that("element tree can be modified", { # we cannot add a new theme element without modifying the element tree - p <- ggplot() + theme(blablabla = element_text()) + p <- ggplot() + theme(blablabla = element_text(colour = "red")) expect_error(print(p), "Theme element `blablabla` is not defined in the element hierarchy") # things work once we add a new element to the element tree @@ -219,6 +219,16 @@ test_that("element tree can be modified", { element_tree = list(blablabla = el_def("element_text", "text")) ) expect_silent(print(q)) + + # inheritance and final calculation of novel element works + final_theme <- ggplot2:::plot_theme(q, theme_gray()) + e1 <- calc_element("blablabla", final_theme) + e2 <- calc_element("text", final_theme) + expect_identical(e1$family, e2$family) + expect_identical(e1$face, e2$face) + expect_identical(e1$size, e2$size) + expect_identical(e1$lineheight, e2$lineheight) + expect_identical(e1$colour, "red") # not inherited from element_text }) test_that("all elements in complete themes have inherit.blank=TRUE", { From d3af67dea1cb85876e429ed1e101e320416ddda7 Mon Sep 17 00:00:00 2001 From: Claus Wilke Date: Tue, 22 Oct 2019 23:28:53 -0500 Subject: [PATCH 07/14] Pull element tree first from default theme, then from internal defaults. --- R/theme.r | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/R/theme.r b/R/theme.r index 184fe33c27..aa0f3e2a3b 100644 --- a/R/theme.r +++ b/R/theme.r @@ -430,11 +430,18 @@ is_theme_validate <- function(x) { # substituting the defaults if needed complete_element_tree <- function(theme) { element_tree <- attr(theme, "element_tree", exact = TRUE) - if (is.null(element_tree)) { + + # we fill in the element tree first from the current default theme, + # and then from the internal element tree if necessary + # this makes it easy for extension packages to provide modified + # default element trees + defaults( + defaults( + element_tree, + attr(theme_get(), "element_tree", exact = TRUE) + ), ggplot_global$element_tree - } else { - defaults(element_tree, ggplot_global$element_tree) - } + ) } # Combine plot defaults with current theme to get complete theme for a plot From 2cbf62b9d4fc2cbc07cf76d2a026ab5cb691b615 Mon Sep 17 00:00:00 2001 From: Claus Wilke Date: Wed, 23 Oct 2019 00:04:49 -0500 Subject: [PATCH 08/14] always pull entirely missing elements from the default theme. --- R/theme.r | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/R/theme.r b/R/theme.r index aa0f3e2a3b..681ca754e9 100644 --- a/R/theme.r +++ b/R/theme.r @@ -533,13 +533,20 @@ add_theme <- function(t1, t2, t2name) { calc_element <- function(element, theme, verbose = FALSE) { if (verbose) message(element, " --> ", appendLF = FALSE) - # if theme is not complete, merge element with theme defaults, - # otherwise take it as is. This fills in theme defaults if no - # explicit theme is set for the plot. + # If the theme is not complete, merge element with theme defaults, + # otherwise take it as is (unless not defined at all). This fills + # in theme defaults if no explicit theme is set for the plot. if (!is_theme_complete(theme)) { el_out <- merge_element(theme[[element]], theme_get()[[element]]) } else { - el_out <- theme[[element]] + # if the element is explicitly named (even if NULL), we assume + # that it is set to its proper value. However, if it is missing + # entirely, we get it from the default theme. + if (element %in% names(theme)) { + el_out <- theme[[element]] + } else { + el_out <- theme_get()[[element]] + } } # If result is element_blank, don't inherit anything from parents From 09eefcc2d037d6b215dbee91575fd4c84e46db47 Mon Sep 17 00:00:00 2001 From: Claus Wilke Date: Wed, 23 Oct 2019 01:00:53 -0500 Subject: [PATCH 09/14] more unit tests --- R/theme.r | 18 ++++++++---------- tests/testthat/test-theme.r | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/R/theme.r b/R/theme.r index 681ca754e9..03f6963345 100644 --- a/R/theme.r +++ b/R/theme.r @@ -533,19 +533,17 @@ add_theme <- function(t1, t2, t2name) { calc_element <- function(element, theme, verbose = FALSE) { if (verbose) message(element, " --> ", appendLF = FALSE) - # If the theme is not complete, merge element with theme defaults, - # otherwise take it as is (unless not defined at all). This fills - # in theme defaults if no explicit theme is set for the plot. - if (!is_theme_complete(theme)) { - el_out <- merge_element(theme[[element]], theme_get()[[element]]) + # If the element is not at all in the theme, get it from the default theme + if (!element %in% names(theme)) { + el_out <- theme_get()[[element]] } else { - # if the element is explicitly named (even if NULL), we assume - # that it is set to its proper value. However, if it is missing - # entirely, we get it from the default theme. - if (element %in% names(theme)) { + # If the theme is complete, take the element as is, and otherwise + # merge it with theme defaults. This fills in theme defaults if no + # explicit theme is set for the plot. + if (is_theme_complete(theme)) { el_out <- theme[[element]] } else { - el_out <- theme_get()[[element]] + el_out <- merge_element(theme[[element]], theme_get()[[element]]) } } diff --git a/tests/testthat/test-theme.r b/tests/testthat/test-theme.r index c430a4b7e2..3bbb3472ec 100644 --- a/tests/testthat/test-theme.r +++ b/tests/testthat/test-theme.r @@ -322,6 +322,40 @@ test_that("complete plot themes shouldn't inherit from default", { expect_null(ptheme$axis.text.x) }) +test_that("element calculations are identical for current theme and empty theme", { + old <- theme_set(theme_grey()) + + # works for root element + expect_identical( + calc_element("text", theme_get()), + calc_element("text", theme()) + ) + + # works for derived element + expect_identical( + calc_element("axis.text.x", theme_get()), + calc_element("axis.text.x", theme()) + ) + + # theme calculation for nonexisting element returns NULL + expect_identical(calc_element("abcde", theme()), NULL) + + # element tree gets merged properly + theme_update( + abcde = element_text(color = "blue", hjust = 0, vjust = 1, inherit.blank = TRUE), + element_tree = list(abcde = el_def("element_text", "text")) + ) + + e1 <- calc_element("abcde", theme()) + e2 <- calc_element("text", theme()) + e2$colour <- "blue" + e2$hjust <- 0 + e2$vjust <- 1 + expect_identical(e1, e2) + + theme_set(old) +}) + test_that("titleGrob() and margins() work correctly", { # ascenders and descenders g1 <- titleGrob("aaaa", 0, 0, 0.5, 0.5) # lower-case letters, no ascenders or descenders From bc6bb0c89381ced223dd119b64ddd3df6bb7c1e4 Mon Sep 17 00:00:00 2001 From: Claus Wilke Date: Wed, 23 Oct 2019 14:05:57 -0500 Subject: [PATCH 10/14] %+replace% should also merge element trees --- R/theme-current.R | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/R/theme-current.R b/R/theme-current.R index ed561885ad..c26e196a2d 100644 --- a/R/theme-current.R +++ b/R/theme-current.R @@ -105,12 +105,11 @@ theme_replace <- function(...) { # Can't use modifyList here since it works recursively and drops NULLs e1[names(e2)] <- e2 - # replace element tree if provided - # note: this *does not* merge element trees, it - # simply overwrites the first if the second is present. - attr(e1, "element_tree") <- - attr(e2, "element_tree", exact = TRUE) %||% - attr(e1, "element_tree", exact = TRUE) + # Merge element trees if provided + attr(t1, "element_tree") <- defaults( + attr(t2, "element_tree", exact = TRUE), + attr(t1, "element_tree", exact = TRUE) + ) # comment by @clauswilke: # `complete` and `validate` are currently ignored, From 3566fd6cafa93aa6022915bad8bc29c986a47976 Mon Sep 17 00:00:00 2001 From: Claus Wilke Date: Wed, 23 Oct 2019 23:07:36 -0500 Subject: [PATCH 11/14] fix various bugs --- R/theme-current.R | 6 ++--- R/theme.r | 49 ++++++++++++++++++++++++++++--------- tests/testthat/test-theme.r | 26 +++++++++++--------- 3 files changed, 55 insertions(+), 26 deletions(-) diff --git a/R/theme-current.R b/R/theme-current.R index c26e196a2d..8cfb61f87e 100644 --- a/R/theme-current.R +++ b/R/theme-current.R @@ -106,9 +106,9 @@ theme_replace <- function(...) { e1[names(e2)] <- e2 # Merge element trees if provided - attr(t1, "element_tree") <- defaults( - attr(t2, "element_tree", exact = TRUE), - attr(t1, "element_tree", exact = TRUE) + attr(e1, "element_tree") <- defaults( + attr(e2, "element_tree", exact = TRUE), + attr(e1, "element_tree", exact = TRUE) ) # comment by @clauswilke: diff --git a/R/theme.r b/R/theme.r index 03f6963345..e520838e62 100644 --- a/R/theme.r +++ b/R/theme.r @@ -448,17 +448,32 @@ complete_element_tree <- function(theme) { plot_theme <- function(x, default = theme_get()) { theme <- x$theme - if (!is_theme_complete(theme)) { - # can't use `defaults()` here because it strips attributes - missing <- setdiff(names(default), names(theme)) - theme[missing] <- default[missing] + # apply theme defaults appropriately if needed + if (is_theme_complete(theme)) { + # for complete themes, we fill in missing elements but don't do any element merging + # can't use `defaults()` because it strips attributes + + ## The following two lines cause trouble when a complete theme doesn't + ## properly define all elements. This is currently the case for `theme_void()`, + ## for example, which doesn't define (among other elements) `legend_margin`, + ## and that causes unexpected interactions with `theme_test()` for two vdiffr + ## test cases. + #missing <- setdiff(names(default), names(theme)) + #theme[missing] <- default[missing] + } else { + # otherwise, we can just add the theme to the default theme + theme <- default + theme } + # complete the element tree and save back to the theme + element_tree <- complete_element_tree(theme) + attr(theme, "element_tree") <- element_tree + # Check that all elements have the correct class (element_text, unit, etc) if (is_theme_validate(theme)) { mapply( validate_element, theme, names(theme), - MoreArgs = list(element_tree = complete_element_tree(theme)) + MoreArgs = list(element_tree = element_tree) ) } @@ -473,9 +488,9 @@ plot_theme <- function(x, default = theme_get()) { #' informative error messages. #' @keywords internal add_theme <- function(t1, t2, t2name) { - if (!is.theme(t2)) { + if (!is.list(t2)) { # in various places in the code base, simple lists are used as themes stop("Don't know how to add ", t2name, " to a theme object", - call. = FALSE) + call. = FALSE) } # If t2 is a complete theme or t1 is NULL, just return t2 @@ -533,6 +548,11 @@ add_theme <- function(t1, t2, t2name) { calc_element <- function(element, theme, verbose = FALSE) { if (verbose) message(element, " --> ", appendLF = FALSE) + el_out <- theme[[element]] + + ## For efficiency, the following block of code should be replaced by + ## two lines in `plot_theme()`, but that doesn't currently work, as + ## explained there. # If the element is not at all in the theme, get it from the default theme if (!element %in% names(theme)) { el_out <- theme_get()[[element]] @@ -540,10 +560,8 @@ calc_element <- function(element, theme, verbose = FALSE) { # If the theme is complete, take the element as is, and otherwise # merge it with theme defaults. This fills in theme defaults if no # explicit theme is set for the plot. - if (is_theme_complete(theme)) { - el_out <- theme[[element]] - } else { - el_out <- merge_element(theme[[element]], theme_get()[[element]]) + if (!is_theme_complete(theme)) { + el_out <- merge_element(el_out, theme_get()[[element]]) } } @@ -553,9 +571,16 @@ calc_element <- function(element, theme, verbose = FALSE) { return(el_out) } + # Obtain the element tree and check that the element is in it + # If not, try to retrieve the complete element tree. This is + # needed for backwards compatibility and certain unit tests. + element_tree <- attr(theme, "element_tree", exact = TRUE) + if (!element %in% names(element_tree)) { + element_tree <- complete_element_tree(theme) + } + # If the element is defined (and not just inherited), check that # it is of the class specified in element_tree - element_tree <- complete_element_tree(theme) if (!is.null(el_out) && !inherits(el_out, element_tree[[element]]$class)) { stop(element, " should have class ", element_tree[[element]]$class) diff --git a/tests/testthat/test-theme.r b/tests/testthat/test-theme.r index 3bbb3472ec..4d7574bb01 100644 --- a/tests/testthat/test-theme.r +++ b/tests/testthat/test-theme.r @@ -322,32 +322,36 @@ test_that("complete plot themes shouldn't inherit from default", { expect_null(ptheme$axis.text.x) }) -test_that("element calculations are identical for current theme and empty theme", { +test_that("current theme can be updated with new elements", { old <- theme_set(theme_grey()) + b1 <- ggplot() + theme_grey() + b2 <- ggplot() + # works for root element expect_identical( - calc_element("text", theme_get()), - calc_element("text", theme()) + calc_element("text", plot_theme(b1)), + calc_element("text", plot_theme(b2)) ) # works for derived element expect_identical( - calc_element("axis.text.x", theme_get()), - calc_element("axis.text.x", theme()) + calc_element("axis.text.x", plot_theme(b1)), + calc_element("axis.text.x", plot_theme(b2)) ) # theme calculation for nonexisting element returns NULL - expect_identical(calc_element("abcde", theme()), NULL) + expect_identical(calc_element("abcde", plot_theme(b1)), NULL) # element tree gets merged properly - theme_update( - abcde = element_text(color = "blue", hjust = 0, vjust = 1, inherit.blank = TRUE), - element_tree = list(abcde = el_def("element_text", "text")) + theme_replace( + abcde = element_text(color = "blue", hjust = 0, vjust = 1), + element_tree = list(abcde = el_def("element_text", "text")), + complete = TRUE ) - e1 <- calc_element("abcde", theme()) - e2 <- calc_element("text", theme()) + e1 <- calc_element("abcde", plot_theme(b2)) + e2 <- calc_element("text", plot_theme(b2)) e2$colour <- "blue" e2$hjust <- 0 e2$vjust <- 1 From 0a173acb9ea56fec10fa0b2d796083d6a3e56c24 Mon Sep 17 00:00:00 2001 From: Claus Wilke Date: Fri, 15 Nov 2019 13:56:10 -0600 Subject: [PATCH 12/14] more efficient merging of theme defaults --- R/theme.r | 25 ++----------------------- 1 file changed, 2 insertions(+), 23 deletions(-) diff --git a/R/theme.r b/R/theme.r index c8966de6a9..c054d8e7e1 100644 --- a/R/theme.r +++ b/R/theme.r @@ -452,14 +452,8 @@ plot_theme <- function(x, default = theme_get()) { if (is_theme_complete(theme)) { # for complete themes, we fill in missing elements but don't do any element merging # can't use `defaults()` because it strips attributes - - ## The following two lines cause trouble when a complete theme doesn't - ## properly define all elements. This is currently the case for `theme_void()`, - ## for example, which doesn't define (among other elements) `legend_margin`, - ## and that causes unexpected interactions with `theme_test()` for two vdiffr - ## test cases. - #missing <- setdiff(names(default), names(theme)) - #theme[missing] <- default[missing] + missing <- setdiff(names(default), names(theme)) + theme[missing] <- default[missing] } else { # otherwise, we can just add the theme to the default theme theme <- default + theme @@ -550,21 +544,6 @@ calc_element <- function(element, theme, verbose = FALSE) { el_out <- theme[[element]] - ## For efficiency, the following block of code should be replaced by - ## two lines in `plot_theme()`, but that doesn't currently work, as - ## explained there. - # If the element is not at all in the theme, get it from the default theme - if (!element %in% names(theme)) { - el_out <- theme_get()[[element]] - } else { - # If the theme is complete, take the element as is, and otherwise - # merge it with theme defaults. This fills in theme defaults if no - # explicit theme is set for the plot. - if (!is_theme_complete(theme)) { - el_out <- merge_element(el_out, theme_get()[[element]]) - } - } - # If result is element_blank, don't inherit anything from parents if (inherits(el_out, "element_blank")) { if (verbose) message("element_blank (no inheritance)") From 47ce21d6e4d9829f458c0083c74a04e2785cc559 Mon Sep 17 00:00:00 2001 From: Claus Wilke Date: Fri, 15 Nov 2019 14:21:35 -0600 Subject: [PATCH 13/14] improve documentation --- R/theme-elements.r | 34 +++++++++++++++++++++++++++++++++- R/theme.r | 2 +- man/el_def.Rd | 43 ++++++++++++++++++++++++++++++++++++++----- man/element.Rd | 2 +- man/theme.Rd | 2 +- 5 files changed, 74 insertions(+), 9 deletions(-) diff --git a/R/theme-elements.r b/R/theme-elements.r index a6e366bf1a..29f8b708df 100644 --- a/R/theme-elements.r +++ b/R/theme-elements.r @@ -272,7 +272,11 @@ element_grob.element_line <- function(element, x = 0:1, y = 0:1, -#' Define an element's class and what other elements it inherits from +#' Define new elements for a theme's element tree +#' +#' Each theme has an element tree that defines which theme elements inherit +#' theme parameters from which other elements. The function `el_def()` can be used +#' to define new or modified elements for this tree. #' #' @param class The name of the element class. Examples are "element_line" or #' "element_text" or "unit", or one of the two reserved keywords "character" or @@ -283,6 +287,34 @@ element_grob.element_line <- function(element, x = 0:1, y = 0:1, #' element inherits from. #' @param description An optional character vector providing a description #' for the element. +#' @examples +#' # define a new coord that includes a panel annotation +#' coord_annotate <- function(label = "panel annotation") { +#' ggproto(NULL, CoordCartesian, +#' limits = list(x = NULL, y = NULL), +#' expand = TRUE, +#' default = FALSE, +#' clip = "on", +#' render_fg = function(panel_params, theme) { +#' element_render(theme, "panel.annotation", label = label) +#' } +#' ) +#' } +#' +#' # update the default theme by adding a new `panel.annotation` +#' # theme element +#' old <- theme_update( +#' panel.annotation = element_text(color = "blue", hjust = 0.95, vjust = 0.05), +#' element_tree = list(panel.annotation = el_def("element_text", "text")) +#' ) +#' +#' df <- data.frame(x = 1:3, y = 1:3) +#' ggplot(df, aes(x, y)) + +#' geom_point() + +#' coord_annotate("annotation in blue") +#' +#' # revert to original default theme +#' theme_set(old) #' @keywords internal #' @export el_def <- function(class = NULL, inherit = NULL, description = NULL) { diff --git a/R/theme.r b/R/theme.r index c054d8e7e1..adfd62c443 100644 --- a/R/theme.r +++ b/R/theme.r @@ -9,7 +9,7 @@ #' about theme inheritance below. #' #' @section Theme inheritance: -#' Theme elements inherit properties from other theme elements heirarchically. +#' Theme elements inherit properties from other theme elements hierarchically. #' For example, `axis.title.x.bottom` inherits from `axis.title.x` which inherits #' from `axis.title`, which in turn inherits from `text`. All text elements inherit #' directly or indirectly from `text`; all lines inherit from diff --git a/man/el_def.Rd b/man/el_def.Rd index d18c8ad0ba..a7592f63df 100644 --- a/man/el_def.Rd +++ b/man/el_def.Rd @@ -2,14 +2,16 @@ % Please edit documentation in R/theme-elements.r \name{el_def} \alias{el_def} -\title{Define an element's class and what other elements it inherits from} +\title{Define new elements for a theme's element tree} \usage{ el_def(class = NULL, inherit = NULL, description = NULL) } \arguments{ -\item{class}{The name of class (like "element_line", "element_text", -or the reserved "character", which implies a character or numeric -vector, not a class called "character").} +\item{class}{The name of the element class. Examples are "element_line" or +"element_text" or "unit", or one of the two reserved keywords "character" or +"margin". The reserved keyword "character" implies a character +or numeric vector, not a class called "character". The keyword +"margin" implies a unit vector of length 4, as created by \code{\link[=margin]{margin()}}.} \item{inherit}{A vector of strings, naming the elements that this element inherits from.} @@ -18,6 +20,37 @@ element inherits from.} for the element.} } \description{ -Define an element's class and what other elements it inherits from +Each theme has an element tree that defines which theme elements inherit +theme parameters from which other elements. The function \code{el_def()} can be used +to define new or modified elements for this tree. +} +\examples{ +# define a new coord that includes a panel annotation +coord_annotate <- function(label = "panel annotation") { + ggproto(NULL, CoordCartesian, + limits = list(x = NULL, y = NULL), + expand = TRUE, + default = FALSE, + clip = "on", + render_fg = function(panel_params, theme) { + element_render(theme, "panel.annotation", label = label) + } + ) +} + +# update the default theme by adding a new `panel.annotation` +# theme element +old <- theme_update( + panel.annotation = element_text(color = "blue", hjust = 0.95, vjust = 0.05), + element_tree = list(panel.annotation = el_def("element_text", "text")) +) + +df <- data.frame(x = 1:3, y = 1:3) +ggplot(df, aes(x, y)) + + geom_point() + + coord_annotate("annotation in blue") + +# revert to original default theme +theme_set(old) } \keyword{internal} diff --git a/man/element.Rd b/man/element.Rd index e7be72807c..286ae88dc4 100644 --- a/man/element.Rd +++ b/man/element.Rd @@ -90,7 +90,7 @@ specify the display of how non-data components of the plot are a drawn. } \code{rel()} is used to specify sizes relative to the parent, -\code{margins()} is used to specify the margins of elements. +\code{margin()} is used to specify the margins of elements. } \examples{ plot <- ggplot(mpg, aes(displ, hwy)) + geom_point() diff --git a/man/theme.Rd b/man/theme.Rd index 92b96feae9..34609c44c8 100644 --- a/man/theme.Rd +++ b/man/theme.Rd @@ -223,7 +223,7 @@ about theme inheritance below. } \section{Theme inheritance}{ -Theme elements inherit properties from other theme elements heirarchically. +Theme elements inherit properties from other theme elements hierarchically. For example, \code{axis.title.x.bottom} inherits from \code{axis.title.x} which inherits from \code{axis.title}, which in turn inherits from \code{text}. All text elements inherit directly or indirectly from \code{text}; all lines inherit from From cbe03d57a28da50d7f2e6d300c08ccae6d7ccd36 Mon Sep 17 00:00:00 2001 From: Claus Wilke Date: Fri, 15 Nov 2019 14:44:22 -0600 Subject: [PATCH 14/14] minor documentation tweaks --- R/theme.r | 5 +++-- man/theme.Rd | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/R/theme.r b/R/theme.r index adfd62c443..0ef578b26c 100644 --- a/R/theme.r +++ b/R/theme.r @@ -165,8 +165,9 @@ #' elements. #' @param validate `TRUE` to run `validate_element()`, `FALSE` to bypass checks. #' @param element_tree optional addition or modification to the element tree, -#' which specifies the inheritance relationship of the theme elements. See -#' [`el_def()`]. +#' which specifies the inheritance relationship of the theme elements. The element +#' tree should be provided as a list of named element definitions created with +#' [`el_def()`]. See [`el_def()`] for more details. #' #' @seealso #' [+.gg()] and \code{\link{\%+replace\%}}, diff --git a/man/theme.Rd b/man/theme.Rd index 34609c44c8..09962d32d5 100644 --- a/man/theme.Rd +++ b/man/theme.Rd @@ -209,8 +209,9 @@ elements.} \item{validate}{\code{TRUE} to run \code{validate_element()}, \code{FALSE} to bypass checks.} \item{element_tree}{optional addition or modification to the element tree, -which specifies the inheritance relationship of the theme elements. See -\code{\link[=el_def]{el_def()}}.} +which specifies the inheritance relationship of the theme elements. The element +tree should be provided as a list of named element definitions created with +\code{\link[=el_def]{el_def()}}. See \code{\link[=el_def]{el_def()}} for more details.} } \description{ Themes are a powerful way to customize the non-data components of your