diff --git a/DESCRIPTION b/DESCRIPTION index 978cbf725c..777d755861 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -274,6 +274,7 @@ Collate: 'theme.R' 'theme-defaults.R' 'theme-current.R' + 'theme-sub.R' 'utilities-break.R' 'utilities-grid.R' 'utilities-help.R' diff --git a/NAMESPACE b/NAMESPACE index 391f435b30..690b9fb0ed 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -725,6 +725,17 @@ export(theme_linedraw) export(theme_minimal) export(theme_replace) export(theme_set) +export(theme_sub_axis) +export(theme_sub_axis_bottom) +export(theme_sub_axis_left) +export(theme_sub_axis_right) +export(theme_sub_axis_top) +export(theme_sub_axis_x) +export(theme_sub_axis_y) +export(theme_sub_legend) +export(theme_sub_panel) +export(theme_sub_plot) +export(theme_sub_strip) export(theme_test) export(theme_transparent) export(theme_update) diff --git a/NEWS.md b/NEWS.md index 539dee258c..62a18cfd4d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,10 @@ # ggplot2 (development version) +* New function family for setting parts of a theme. For example, you can now use + `theme_sub_axis(line, text, ticks, ticks.length, line)` as a substitute for + `theme(axis.line, axis.text, axis.ticks, axis.ticks.length, axis.line)`. This + should allow slightly terser and more organised theme declarations + (@teunbrand, #5301). * `scale_{x/y}_discrete(continuous.limits)` is a new argument to control the display range of discrete scales (@teunbrand, #4174, #6259). * `geom_ribbon()` now appropriately warns about, and removes, missing values diff --git a/R/theme-sub.R b/R/theme-sub.R new file mode 100644 index 0000000000..abfb178c44 --- /dev/null +++ b/R/theme-sub.R @@ -0,0 +1,144 @@ +#' Shortcuts for theme settings +#' +#' This collection of functions serves as a shortcut for [`theme()`][theme] with +#' shorter argument names. Besides the shorter arguments, it also helps in +#' keeping theme declarations more organised. +#' +#' @eval subtheme_param_doc() +#' +#' @return A `theme`-class object that can be added to a plot. +#' @name subtheme +#' +#' @examples +#' # A standard plot +#' p <- ggplot(mtcars, aes(disp, mpg, colour = drat)) + +#' geom_point() +#' +#' red_text <- element_text(colour = "red") +#' red_line <- element_line(colour = "red") +#' +#' # The theme settings below: +#' p + theme( +#' axis.title.x.bottom = red_text, +#' axis.text.x.bottom = red_text, +#' axis.line.x.bottom = red_line, +#' axis.ticks.x.bottom = red_line +#' ) +#' +#' # Are equivalent to these less verbose theme settings +#' p + theme_sub_axis_bottom( +#' title = red_text, +#' text = red_text, +#' line = red_line, +#' ticks = red_line +#' ) +NULL + +subtheme <- function(elements, prefix = "", suffix = "", call = caller_env()) { + if (length(elements) < 1) { + return(theme()) + } + names(elements) <- paste0(prefix, names(elements), suffix) + + extra <- setdiff(names(elements), names(get_element_tree())) + if (length(extra) > 0) { + cli::cli_warn( + "Ignoring unknown {.fn theme} element{?s}: {.and {.field {extra}}}.", + call = call + ) + elements <- elements[setdiff(names(elements), extra)] + } + + exec(theme, !!!elements) +} + +#' @export +#' @describeIn subtheme Theme specification for all axes. +theme_sub_axis <- function(title, text, ticks, ticks.length, line) { + subtheme(find_args(), "axis.") +} + +#' @export +#' @describeIn subtheme Theme specification for both x axes. +theme_sub_axis_x <- function(title, text, ticks, ticks.length, line) { + subtheme(find_args(), "axis.", ".x") +} + +#' @export +#' @describeIn subtheme Theme specification for both y axes. +theme_sub_axis_y <- function(title, text, ticks, ticks.length, line) { + subtheme(find_args(), "axis.", ".y") +} + +#' @export +#' @describeIn subtheme Theme specification for the bottom x axis. +theme_sub_axis_bottom <- function(title, text, ticks, ticks.length, line) { + subtheme(find_args(), "axis.", ".x.bottom") +} + +#' @export +#' @describeIn subtheme Theme specification for the top x axis. +theme_sub_axis_top <- function(title, text, ticks, ticks.length, line) { + subtheme(find_args(), "axis.", ".x.top") +} + +#' @export +#' @describeIn subtheme Theme specification for the left y axis. +theme_sub_axis_left <- function(title, text, ticks, ticks.length, line) { + subtheme(find_args(), "axis.", ".y.left") +} + +#' @export +#' @describeIn subtheme Theme specification for the right y axis. +theme_sub_axis_right <- function(title, text, ticks, ticks.length, line) { + subtheme(find_args(), "axis.", ".y.right") +} + +#' @export +#' @describeIn subtheme Theme specification for the legend. +theme_sub_legend <- function(background, margin, spacing, spacing.x, spacing.y, + key, key.size, key.height, key.width, text, title, + position, direction, justification, box, box.just, + box.margin, box.background, box.spacing) { + subtheme(find_args(), "legend.") +} + +#' @export +#' @describeIn subtheme Theme specification for the panels. +theme_sub_panel <- function(background, border, spacing, spacing.x, spacing.y, + grid, grid.major, grid.minor, grid.major.x, + grid.major.y, grid.minor.x, grid.minor.y, ontop) { + subtheme(find_args(), "panel.") +} + +#' @export +#' @describeIn subtheme Theme specification for the whole plot. +theme_sub_plot <- function(background, title, title.position, subtitle, caption, + caption.position, tag, tag.position, tag.location, + margin) { + subtheme(find_args(), "plot.") +} + +#' @export +#' @describeIn subtheme Theme specification for facet strips. +theme_sub_strip <- function(background, background.x, background.y, clip, + placement, text, text.x, text.x.bottom, text.x.top, + text.y, text.y.left, text.y.right, + switch.pad.grid, switch.pad.wrap) { + subtheme(find_args(), "strip.") +} + +subtheme_param_doc <- function() { + funs <- list( + theme_sub_axis, theme_sub_axis_x, theme_sub_axis_y, theme_sub_axis_bottom, + theme_sub_axis_top, theme_sub_axis_left, theme_sub_axis_right, theme_sub_legend, + theme_sub_panel, theme_sub_plot, theme_sub_strip + ) + args <- sort(unique(unlist(lapply(funs, fn_fmls_names), use.names = FALSE))) + paste0( + "@param ", + paste0(args, collapse = ","), + " Arguments that are renamed and passed on to ", + "\\code{\\link[=theme]{theme()}}." + ) +} diff --git a/_pkgdown.yml b/_pkgdown.yml index 593f015590..0259312234 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -186,6 +186,7 @@ reference: - theme - theme_bw - theme_update + - subtheme - element_line - margin diff --git a/man/subtheme.Rd b/man/subtheme.Rd new file mode 100644 index 0000000000..a05a98a54c --- /dev/null +++ b/man/subtheme.Rd @@ -0,0 +1,159 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/theme-sub.R +\name{subtheme} +\alias{subtheme} +\alias{theme_sub_axis} +\alias{theme_sub_axis_x} +\alias{theme_sub_axis_y} +\alias{theme_sub_axis_bottom} +\alias{theme_sub_axis_top} +\alias{theme_sub_axis_left} +\alias{theme_sub_axis_right} +\alias{theme_sub_legend} +\alias{theme_sub_panel} +\alias{theme_sub_plot} +\alias{theme_sub_strip} +\title{Shortcuts for theme settings} +\usage{ +theme_sub_axis(title, text, ticks, ticks.length, line) + +theme_sub_axis_x(title, text, ticks, ticks.length, line) + +theme_sub_axis_y(title, text, ticks, ticks.length, line) + +theme_sub_axis_bottom(title, text, ticks, ticks.length, line) + +theme_sub_axis_top(title, text, ticks, ticks.length, line) + +theme_sub_axis_left(title, text, ticks, ticks.length, line) + +theme_sub_axis_right(title, text, ticks, ticks.length, line) + +theme_sub_legend( + background, + margin, + spacing, + spacing.x, + spacing.y, + key, + key.size, + key.height, + key.width, + text, + title, + position, + direction, + justification, + box, + box.just, + box.margin, + box.background, + box.spacing +) + +theme_sub_panel( + background, + border, + spacing, + spacing.x, + spacing.y, + grid, + grid.major, + grid.minor, + grid.major.x, + grid.major.y, + grid.minor.x, + grid.minor.y, + ontop +) + +theme_sub_plot( + background, + title, + title.position, + subtitle, + caption, + caption.position, + tag, + tag.position, + tag.location, + margin +) + +theme_sub_strip( + background, + background.x, + background.y, + clip, + placement, + text, + text.x, + text.x.bottom, + text.x.top, + text.y, + text.y.left, + text.y.right, + switch.pad.grid, + switch.pad.wrap +) +} +\arguments{ +\item{background, background.x, background.y, border, box, box.background, box.just, box.margin, box.spacing, caption, caption.position, clip, direction, grid, grid.major, grid.major.x, grid.major.y, grid.minor, grid.minor.x, grid.minor.y, justification, key, key.height, key.size, key.width, line, margin, ontop, placement, position, spacing, spacing.x, spacing.y, subtitle, switch.pad.grid, switch.pad.wrap, tag, tag.location, tag.position, text, text.x, text.x.bottom, text.x.top, text.y, text.y.left, text.y.right, ticks, ticks.length, title, title.position}{Arguments that are renamed and passed on to \code{\link[=theme]{theme()}}.} +} +\value{ +A \code{theme}-class object that can be added to a plot. +} +\description{ +This collection of functions serves as a shortcut for \code{\link[=theme]{theme()}} with +shorter argument names. Besides the shorter arguments, it also helps in +keeping theme declarations more organised. +} +\section{Functions}{ +\itemize{ +\item \code{theme_sub_axis()}: Theme specification for all axes. + +\item \code{theme_sub_axis_x()}: Theme specification for both x axes. + +\item \code{theme_sub_axis_y()}: Theme specification for both y axes. + +\item \code{theme_sub_axis_bottom()}: Theme specification for the bottom x axis. + +\item \code{theme_sub_axis_top()}: Theme specification for the top x axis. + +\item \code{theme_sub_axis_left()}: Theme specification for the left y axis. + +\item \code{theme_sub_axis_right()}: Theme specification for the right y axis. + +\item \code{theme_sub_legend()}: Theme specification for the legend. + +\item \code{theme_sub_panel()}: Theme specification for the panels. + +\item \code{theme_sub_plot()}: Theme specification for the whole plot. + +\item \code{theme_sub_strip()}: Theme specification for facet strips. + +}} +\examples{ +# A standard plot +p <- ggplot(mtcars, aes(disp, mpg, colour = drat)) + + geom_point() + +red_text <- element_text(colour = "red") +red_line <- element_line(colour = "red") + +# The theme settings below: +p + theme( + axis.title.x.bottom = red_text, + axis.text.x.bottom = red_text, + axis.line.x.bottom = red_line, + axis.ticks.x.bottom = red_line +) + +# Are equivalent to these less verbose theme settings +p + theme_sub_axis_bottom( + title = red_text, + text = red_text, + line = red_line, + ticks = red_line +) +} diff --git a/tests/testthat/_snaps/theme.md b/tests/testthat/_snaps/theme.md index fa7237d37d..322cce92b7 100644 --- a/tests/testthat/_snaps/theme.md +++ b/tests/testthat/_snaps/theme.md @@ -77,6 +77,10 @@ `plot.tag.position` must be one of "topleft", "top", "topright", "left", "right", "bottomleft", "bottom", or "bottomright", not "test". i Did you mean "left"? +# subtheme functions rename arguments as intended + + Ignoring unknown `theme()` elements: foo and bar. + # Theme validation behaves as expected The `aspect.ratio` theme element must be a object. diff --git a/tests/testthat/test-theme.R b/tests/testthat/test-theme.R index ef358b10b6..b6248acaa3 100644 --- a/tests/testthat/test-theme.R +++ b/tests/testthat/test-theme.R @@ -514,6 +514,32 @@ test_that("Theme elements are checked during build", { expect_snapshot_error(ggplotGrob(p)) }) +test_that("subtheme functions rename arguments as intended", { + + line <- element_line(colour = "red") + rect <- element_rect(colour = "red") + + expect_equal(theme_sub_axis(ticks = line), theme(axis.ticks = line)) + expect_equal(theme_sub_axis_x(ticks = line), theme(axis.ticks.x = line)) + expect_equal(theme_sub_axis_y(ticks = line), theme(axis.ticks.y = line)) + expect_equal(theme_sub_axis_top(ticks = line), theme(axis.ticks.x.top = line)) + expect_equal(theme_sub_axis_bottom(ticks = line), theme(axis.ticks.x.bottom = line)) + expect_equal(theme_sub_axis_left(ticks = line), theme(axis.ticks.y.left = line)) + expect_equal(theme_sub_axis_right(ticks = line), theme(axis.ticks.y.right = line)) + expect_equal(theme_sub_legend(key = rect), theme(legend.key = rect)) + expect_equal(theme_sub_panel(border = rect), theme(panel.border = rect)) + expect_equal(theme_sub_plot(background = rect), theme(plot.background = rect)) + expect_equal(theme_sub_strip(background = rect), theme(strip.background = rect)) + + # Test rejection of unknown theme elements + expect_snapshot_warning( + expect_equal( + subtheme(list(foo = 1, bar = 2, axis.line = line)), + theme(axis.line = line) + ) + ) +}) + test_that("Theme validation behaves as expected", { tree <- get_element_tree() expect_silent(validate_element(1, "aspect.ratio", tree))