From 7a259c4d9dc41d3617bda7f05230325c49d9ea73 Mon Sep 17 00:00:00 2001 From: Grant McDermott Date: Wed, 3 Jun 2026 21:16:59 -0700 Subject: [PATCH 01/10] tinytheme_register --- R/environment.R | 3 +- R/tinyplot.R | 5 +- R/tinytheme.R | 201 +++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 193 insertions(+), 16 deletions(-) diff --git a/R/environment.R b/R/environment.R index e147caa3..f6d66482 100644 --- a/R/environment.R +++ b/R/environment.R @@ -35,5 +35,6 @@ set_environment_variable( .saved_par_after = NULL, .saved_par_first = NULL, .last_call = NULL, - .tpar_hooks = NULL + .tpar_hooks = NULL, + .registered_themes = NULL ) diff --git a/R/tinyplot.R b/R/tinyplot.R index e6a936cf..988bfd0e 100644 --- a/R/tinyplot.R +++ b/R/tinyplot.R @@ -1028,9 +1028,8 @@ tinyplot.default = function( # definition so dynmar_side uses theme mgp/tcl/las (which aren't in # par() yet since the before.plot.new hook hasn't fired). .tinytheme = get_tpar("tinytheme", default = "default") - .theme_def = if (!is.null(.tinytheme) && .tinytheme != "default") { - get(paste0("theme_", .tinytheme), envir = asNamespace("tinyplot")) - } else NULL + .theme_def = get_theme_def(.tinytheme) + if (identical(.theme_def, theme_default)) .theme_def = NULL .theme_mar = if (!is.null(.theme_def[["mar"]])) .theme_def[["mar"]] else par("mar") .tpars = if (!is.null(.theme_def)) modifyList(.theme_def, tpar()) else tpar() # Merge pending before.plot.new hook values into .tpars so user diff --git a/R/tinytheme.R b/R/tinytheme.R index b3c6cf7f..0a0cb8ea 100644 --- a/R/tinytheme.R +++ b/R/tinytheme.R @@ -111,7 +111,8 @@ #' #' @return The function returns nothing. It is called for its side effects. #' -#' @seealso [`tpar`] which does the heavy lifting under the hood. +#' @seealso [`tpar`] which does the heavy lifting under the hood; +#' [tinytheme_register()] for adding custom named themes. #' #' @examples #' # Reusable plot function @@ -195,22 +196,15 @@ tinytheme = function( ... ) { - theme = match.arg(theme) + if (length(theme) > 1) theme = theme[1] + + registered = names(get_environment_variable(".registered_themes")) + assert_choice(theme, c(builtin_themes, registered)) # in notebooks, we don't want to close the device because no image. # init_tpar() tries to be smart, but may fail. init_tpar(rm_hook = TRUE) - assert_choice( - theme, - c( - "default", - sort(c("basic", "broadsheet", "bw", "classic", "clean", "clean2", "dark", - "dynamic", "float", "ipsum", "ipsum2", "linedraw", "minimal", - "nber", "ridge", "ridge2", "socviz", "tufte", "void", "web")) - ) - ) - settings = switch(theme, "default" = theme_default, "basic" = theme_basic, @@ -233,6 +227,7 @@ tinytheme = function( "float" = theme_float, "void" = theme_void, "web" = theme_web, + get_environment_variable(".registered_themes")[[theme]] ) dots = list(...) @@ -282,6 +277,15 @@ tinytheme = function( # ## Themes (these are read and set at initial load time) +builtin_themes = c( + "default", "basic", "dynamic", + "clean", "clean2", "bw", "linedraw", "classic", + "minimal", "ipsum", "ipsum2", "dark", + "socviz", "broadsheet", "nber", "web", + "ridge", "ridge2", + "tufte", "float", "void" +) + # theme_default = list() theme_default = list( @@ -645,3 +649,176 @@ theme_void = modifyList(theme_dynamic, list( xaxt = "none", yaxt = "none" )) + + +# +## Theme registry helpers +# + +# Internal: unified theme lookup (registered first, then built-in) +get_theme_def = function(name) { + if (is.null(name) || name == "default") return(theme_default) + registry = get_environment_variable(".registered_themes") + if (!is.null(registry[[name]])) return(registry[[name]]) + obj_name = paste0("theme_", name) + if (exists(obj_name, envir = asNamespace("tinyplot"), inherits = FALSE)) { + return(get(obj_name, envir = asNamespace("tinyplot"))) + } + NULL +} + + +#' Register a Custom Named Theme +#' +#' @md +#' @description +#' Register a custom theme so it can be used by name with `tinytheme()` +#' or `tinyplot(..., theme = )`. Custom themes inherit from a base theme +#' and apply user-specified overrides. +#' +#' Registered themes are session-scoped: they persist across plots but not +#' across R sessions. To make a custom theme available automatically, register +#' it in your `.Rprofile`. +#' +#' @param name Character string. The name for your custom theme. Cannot clash +#' with or overwrite a built-in theme name (`"default"`, `"clean"`, etc.) +#' @param theme Character string or list. The base theme to inherit from. +#' If a string, it must reference a built-in or previously-registered theme. +#' If a list, it is used directly as the base definition. Default is +#' `"default"`. +#' @param ... Named arguments to override specific theme settings. These are +#' the same parameters accepted by `tpar()`. +#' +#' @return Returns the theme definition list (invisibly). +#' +#' @seealso [tinytheme()], [tinytheme_list()], [tinytheme_unregister()] +#' +#' @examples +#' # Register a simple custom theme, based on "float" but now with a grid +#' tinytheme_register("float2", theme = "float", grid = TRUE) +#' +#' # Use it persistently +#' tinytheme("float2") +#' tinyplot(1:5) +#' tinytheme() +#' +#' # Or ephemerally +#' tinyplot(1:5, theme = "float2") +#' +#' # Optional: unregister the theme +#' tinytheme_unregister("float2") +#' +#' # A more elaborate, pirate-themed example +#' tinytheme_register( +#' "pirate", +#' theme = "clean", +#' family = "HersheyScript", +#' bg = "#f5e6c8", fg = "#3b2209", +#' cex.lab = 1.5, cex.main = 1.5, cex.sub = 1.2, cex.cap = 1.2, +#' col = "#3b2209", col.axis = "#5c3a1e", col.cap = "#7a5230", +#' col.lab = "#3b2209", col.main = "#1a0f04", col.sub = "#7a5230", +#' grid = TRUE, grid.col = "#c9a96e", grid.lty = "dotted", +#' facet.bg = "#e8d4a8", facet.border = "#5c3a1e", +#' pch = 4, +#' palette.qualitative = c( +#' "#8b0000", "#1a5276", "#196f3d", "#7d6608", +#' "#6c3483", "#a04000", "#1b4f72", "#145a32" +#' ) +#' ) +#' tinyplot( +#' Sepal.Length ~ Petal.Length | Species, iris, +#' main = "Avast, me hearties!", +#' sub = "x marks the petal", +#' cap = "Ye Olde Iris Data", +#' theme = "pirate" +#' ) +#' tinytheme_unregister("pirate") +#' +#' @export +tinytheme_register = function(name, theme = "default", ...) { + if (!is.character(name) || length(name) != 1 || nchar(name) == 0) { + stop("`name` must be a single non-empty character string.", call. = FALSE) + } + builtins = builtin_themes + if (name %in% builtins) { + stop( + sprintf("'%s' is a built-in theme and cannot be overridden.", name), + call. = FALSE + ) + } + + if (is.character(theme) && length(theme) == 1) { + base_theme = get_theme_def(theme) + if (is.null(base_theme)) { + stop(sprintf("Base theme '%s' not found.", theme), call. = FALSE) + } + } else if (is.list(theme)) { + base_theme = theme + } else { + stop("`theme` must be a theme name (string) or a list.", call. = FALSE) + } + + dots = list(...) + new_theme = if (length(dots) > 0) modifyList(base_theme, dots) else base_theme + new_theme[["tinytheme"]] = name + + registry = get_environment_variable(".registered_themes") %||% list() + registry[[name]] = new_theme + set_environment_variable(.registered_themes = registry) + invisible(new_theme) +} + + +#' List Available Themes +#' +#' @md +#' @description +#' Returns the names of all available themes, both built-in and user-registered. +#' +#' @return A named list with two character vectors: `builtin` and `registered`. +#' +#' @seealso [tinytheme()], [tinytheme_register()] +#' +#' @examples +#' tinytheme_list() +#' +#' @export +tinytheme_list = function() { + builtins = builtin_themes + registered = names(get_environment_variable(".registered_themes")) + list(builtin = builtins, registered = registered) +} + + +#' Unregister a Custom Theme +#' +#' @md +#' @description +#' Remove a previously registered custom theme. Does not reset an active theme; +#' it only removes the theme from the registry so it can no longer be selected +#' by name. +#' +#' @param name Character string. The name of the registered theme to remove. +#' +#' @return Returns `NULL` (invisibly). +#' +#' @seealso [tinytheme_register()], [tinytheme_list()] +#' +#' @examples +#' tinytheme_register("my_theme", .base = "clean", grid = FALSE) +#' tinytheme_unregister("my_theme") +#' +#' @export +tinytheme_unregister = function(name) { + registry = get_environment_variable(".registered_themes") + if (!name %in% names(registry)) { + warning( + sprintf("Theme '%s' is not registered. Nothing to remove.", name), + call. = FALSE + ) + return(invisible(NULL)) + } + registry[[name]] = NULL + set_environment_variable(.registered_themes = registry) + invisible(NULL) +} From 58c5687ad845ba5cc57bf164ed77529924c730a1 Mon Sep 17 00:00:00 2001 From: Grant McDermott Date: Wed, 3 Jun 2026 21:17:20 -0700 Subject: [PATCH 02/10] docs --- NAMESPACE | 3 ++ man/tinytheme.Rd | 3 +- man/tinytheme_list.Rd | 21 ++++++++++ man/tinytheme_register.Rd | 77 +++++++++++++++++++++++++++++++++++++ man/tinytheme_unregister.Rd | 27 +++++++++++++ 5 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 man/tinytheme_list.Rd create mode 100644 man/tinytheme_register.Rd create mode 100644 man/tinytheme_unregister.Rd diff --git a/NAMESPACE b/NAMESPACE index cd450501..605b5173 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -11,6 +11,9 @@ export(tinylabel) export(tinyplot) export(tinyplot_add) export(tinytheme) +export(tinytheme_list) +export(tinytheme_register) +export(tinytheme_unregister) export(tpar) export(type_abline) export(type_area) diff --git a/man/tinytheme.Rd b/man/tinytheme.Rd index 8bcf8782..8e272714 100644 --- a/man/tinytheme.Rd +++ b/man/tinytheme.Rd @@ -208,5 +208,6 @@ tinytheme() } \seealso{ -\code{\link{tpar}} which does the heavy lifting under the hood. +\code{\link{tpar}} which does the heavy lifting under the hood; +\code{\link[=tinytheme_register]{tinytheme_register()}} for adding custom named themes. } diff --git a/man/tinytheme_list.Rd b/man/tinytheme_list.Rd new file mode 100644 index 00000000..6a4ead39 --- /dev/null +++ b/man/tinytheme_list.Rd @@ -0,0 +1,21 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/tinytheme.R +\name{tinytheme_list} +\alias{tinytheme_list} +\title{List Available Themes} +\usage{ +tinytheme_list() +} +\value{ +A named list with two character vectors: \code{builtin} and \code{registered}. +} +\description{ +Returns the names of all available themes, both built-in and user-registered. +} +\examples{ +tinytheme_list() + +} +\seealso{ +\code{\link[=tinytheme]{tinytheme()}}, \code{\link[=tinytheme_register]{tinytheme_register()}} +} diff --git a/man/tinytheme_register.Rd b/man/tinytheme_register.Rd new file mode 100644 index 00000000..5734856d --- /dev/null +++ b/man/tinytheme_register.Rd @@ -0,0 +1,77 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/tinytheme.R +\name{tinytheme_register} +\alias{tinytheme_register} +\title{Register a Custom Named Theme} +\usage{ +tinytheme_register(name, theme = "default", ...) +} +\arguments{ +\item{name}{Character string. The name for your custom theme. Cannot clash +with or overwrite a built-in theme name (\code{"default"}, \code{"clean"}, etc.)} + +\item{theme}{Character string or list. The base theme to inherit from. +If a string, it must reference a built-in or previously-registered theme. +If a list, it is used directly as the base definition. Default is +\code{"default"}.} + +\item{...}{Named arguments to override specific theme settings. These are +the same parameters accepted by \code{tpar()}.} +} +\value{ +Returns the theme definition list (invisibly). +} +\description{ +Register a custom theme so it can be used by name with \verb{tinytheme()} +or \verb{tinyplot(..., theme = )}. Custom themes inherit from a base theme +and apply user-specified overrides. + +Registered themes are session-scoped: they persist across plots but not +across R sessions. To make a custom theme available automatically, register +it in your \code{.Rprofile}. +} +\examples{ +# Register a simple custom theme, based on "float" but now with a grid +tinytheme_register("float2", theme = "float", grid = TRUE) + +# Use it persistently +tinytheme("float2") +tinyplot(1:5) +tinytheme() + +# Or ephemerally +tinyplot(1:5, theme = "float2") + +# Optional: unregister the theme +tinytheme_unregister("float2") + +# A more elaborate, pirate-themed example +tinytheme_register( + "pirate", + theme = "clean", + family = "HersheyScript", + bg = "#f5e6c8", fg = "#3b2209", + cex.lab = 1.5, cex.main = 1.5, cex.sub = 1.2, cex.cap = 1.2, + col = "#3b2209", col.axis = "#5c3a1e", col.cap = "#7a5230", + col.lab = "#3b2209", col.main = "#1a0f04", col.sub = "#7a5230", + grid = TRUE, grid.col = "#c9a96e", grid.lty = "dotted", + facet.bg = "#e8d4a8", facet.border = "#5c3a1e", + pch = 4, + palette.qualitative = c( + "#8b0000", "#1a5276", "#196f3d", "#7d6608", + "#6c3483", "#a04000", "#1b4f72", "#145a32" + ) +) +tinyplot( + Sepal.Length ~ Petal.Length | Species, iris, + main = "Avast, me hearties!", + sub = "x marks the petal", + cap = "Ye Olde Iris Data", + theme = "pirate" +) +tinytheme_unregister("pirate") + +} +\seealso{ +\code{\link[=tinytheme]{tinytheme()}}, \code{\link[=tinytheme_list]{tinytheme_list()}}, \code{\link[=tinytheme_unregister]{tinytheme_unregister()}} +} diff --git a/man/tinytheme_unregister.Rd b/man/tinytheme_unregister.Rd new file mode 100644 index 00000000..6ec2e50d --- /dev/null +++ b/man/tinytheme_unregister.Rd @@ -0,0 +1,27 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/tinytheme.R +\name{tinytheme_unregister} +\alias{tinytheme_unregister} +\title{Unregister a Custom Theme} +\usage{ +tinytheme_unregister(name) +} +\arguments{ +\item{name}{Character string. The name of the registered theme to remove.} +} +\value{ +Returns \code{NULL} (invisibly). +} +\description{ +Remove a previously registered custom theme. Does not reset an active theme; +it only removes the theme from the registry so it can no longer be selected +by name. +} +\examples{ +tinytheme_register("my_theme", .base = "clean", grid = FALSE) +tinytheme_unregister("my_theme") + +} +\seealso{ +\code{\link[=tinytheme_register]{tinytheme_register()}}, \code{\link[=tinytheme_list]{tinytheme_list()}} +} From 3ff20611a7bc619e5195d89c5d19282efbb9721c Mon Sep 17 00:00:00 2001 From: Grant McDermott Date: Wed, 3 Jun 2026 21:17:48 -0700 Subject: [PATCH 03/10] tests --- .../tinytheme_register_float2.svg | 79 +++++++++++++++++++ inst/tinytest/test-tinytheme_register.R | 24 ++++++ 2 files changed, 103 insertions(+) create mode 100644 inst/tinytest/_tinysnapshot/tinytheme_register_float2.svg create mode 100644 inst/tinytest/test-tinytheme_register.R diff --git a/inst/tinytest/_tinysnapshot/tinytheme_register_float2.svg b/inst/tinytest/_tinysnapshot/tinytheme_register_float2.svg new file mode 100644 index 00000000..0f7523e6 --- /dev/null +++ b/inst/tinytest/_tinysnapshot/tinytheme_register_float2.svg @@ -0,0 +1,79 @@ + + + + + + + + + + + + + +theme = "float2" +Registered theme test +Index +1:5 + + + + + + +1 +2 +3 +4 +5 + + + + + + +1 +2 +3 +4 +5 + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/inst/tinytest/test-tinytheme_register.R b/inst/tinytest/test-tinytheme_register.R new file mode 100644 index 00000000..17dae0ea --- /dev/null +++ b/inst/tinytest/test-tinytheme_register.R @@ -0,0 +1,24 @@ +source("helpers.R") +using("tinysnapshot") + +# register a custom theme +tinytheme_register("float2", theme = "float", grid = TRUE, bg = "#f5e6c8") + +# snapshot: registered theme used ephemerally +f = function() tinyplot( + 1:5, + main = "Registered theme test", + sub = 'theme = "float2"', + theme = "float2" +) +expect_snapshot_plot(f, label = "tinytheme_register_float2") + +# error on unregistered name +expect_error( + tinytheme("float3"), + pattern = "must be one of", + info = "unregistered name errors" +) + +# clean up +tinytheme_unregister("float2") From 1c61494e2703ec9e19f5560c51f068361e251907 Mon Sep 17 00:00:00 2001 From: Grant McDermott Date: Wed, 3 Jun 2026 22:04:03 -0700 Subject: [PATCH 04/10] vignette --- altdoc/pkgdown.yml | 2 +- vignettes/themes.qmd | 86 +++++++++++++++++++++++++++++++++++++++----- 2 files changed, 78 insertions(+), 10 deletions(-) diff --git a/altdoc/pkgdown.yml b/altdoc/pkgdown.yml index aeab3dda..084ca55b 100644 --- a/altdoc/pkgdown.yml +++ b/altdoc/pkgdown.yml @@ -2,7 +2,7 @@ altdoc: 0.7.2 pandoc: 3.9.0.2 pkgdown: 2.1.3 pkgdown_sha: ~ -last_built: 2026-06-02T22:46:25+0000 +last_built: 2026-06-04T04:59:37+0000 urls: reference: https://grantmcdermott.com/tinyplot/man article: https://grantmcdermott.com/tinyplot/vignettes diff --git a/vignettes/themes.qmd b/vignettes/themes.qmd index deebf2d4..f718fd35 100644 --- a/vignettes/themes.qmd +++ b/vignettes/themes.qmd @@ -183,16 +183,25 @@ Please feel free to make suggestions about themes, or contribute new themes by ### Custom themes -Tweaking existing themes is easy. For example, the `tinytheme()` function also -accepts any graphical parameter supported by `tpar()`/`par()` and applies them -in persistent fashion. +Creating custom **tinyplot** themes is easy. Below we demonstrate how to tweak +existing themes in an _ad hoc_ manner, as well as how to register themes for +consistent re-use. + +#### _Ad hoc_ customization + +The `tinytheme()` function accepts all of the graphical parameters supported by +`(t)par()`. This means that you customize a persistent theme simply by passing +down the relevant parameter arguments. For example: ```{r} #| layout-ncol: 2 tinytheme( - "ipsum", - pch = 2, col.axis = "darkcyan", cex = 1.2, cex.main = 2, cex.lab = 1.5, - family = "HersheyScript" + "dynamic", + cex = 1.2, cex.main = 1.5, + col.axis = "darkred", col.main = "firebrick", + family = "HersheySans", + grid = TRUE, grid.col = "thistle", + pch = 2 ) tinyplot(mpg ~ hp, data = mtcars, main = "Fuel efficiency vs. horsepower") tinyplot(hp ~ mpg, data = mtcars, main = "Horsepower vs. fuel efficiency") @@ -227,6 +236,66 @@ tinyplot( ) ``` +#### Registering custom themes + +If you find yourself reusing the same custom theme settings across multiple +plots or sessions, you may prefer to register them as a named theme with +`tinytheme_register()`. Once a theme is registered, it works just like a +built-in one. So you can set it persistently with `tinytheme()`, or pass +it ephemerally with `tinyplot(..., theme = )`. + +Here's a rather fancy example of a "pirate"-inspired theme: + +```{r} +# Register a custom "pirate" theme that builds on top of "clean" +tinytheme_register( + "pirate", + theme = "clean", + family = "HersheyScript", + bg = "#f5e6c8", fg = "#3b2209", + cex.lab = 1.5, cex.main = 1.5, cex.sub = 1.2, + col = "#3b2209", col.axis = "#5c3a1e", col.cap = "#7a5230", + col.lab = "#3b2209", col.main = "#1a0f04", col.sub = "#7a5230", + grid = TRUE, grid.col = "#c9a96e", grid.lty = "dotted", + facet.bg = "#e8d4a8", facet.border = "#5c3a1e", + pch = 4, + palette.qualitative = c( + "#8b0000", "#1a5276", "#196f3d", "#7d6608", + "#6c3483", "#a04000", "#1b4f72", "#145a32" + ) +) + +# Use it ephemerally +tinyplot( + Sepal.Length ~ Petal.Length | Species, iris, + main = "Avast, me hearties!", + sub = "x marks the petal", + cap = "Ye Olde Iris Data", + theme = "pirate" +) +``` + +Use `tinytheme_list()` to see all available themes (both built-in and +registered), and `tinytheme_unregister()` to remove one. + +```{r} +tinytheme_unregister("pirate") +``` + +Registered themes are session-scoped: they persist across plots but disappear +when R restarts. To make a custom theme permanently available, register it in +your `.Rprofile`: + +```r +# In ~/.Rprofile +if (requireNamespace("tinyplot", quietly = TRUE)) { + tinyplot::tinytheme_register("my_theme", theme = "clean", grid.col = "pink") +} +``` + +Similarly, package authors can ship their own custom (tiny)themes as part of +their codebase, which any subsequent `tinyplot()` calls can plug into. + ::: {.callout-tip} ## Font families @@ -248,6 +317,8 @@ It plays very nicely with **tinyplot**. ::: +#### Customization tips + One feature of the `tinytheme()` infrastructure that is especially relevant to customized themes is how dynamic spacing works. Dynamic themes in **tinyplot** automatically compute margin positions (`mar`, `mgp`) so that axis @@ -281,7 +352,6 @@ Again, this is much more convenient that fiddling with `mgp` values. But you can always provide `mgp` values if you wish; in which it will take precedence and the primitives are ignored. -::: {.callout-tip} To see the full list of parameters that defines a particular theme, simply assign them to an object. This can be helpful if you want to explore creating your own custom theme, or tweak an existing theme. @@ -296,8 +366,6 @@ parms = tinyplot:::theme_clean # doesn't assign the theme parms ``` -::: - As a final note about customizing themes, please note that `tinytheme` works by setting a persistent hook that resets parameters before each new plot. This is an efficient design choice, but it also means that calling `(t)par()` From 2c7fefb4249306518d4cbff865006c458b63d9c2 Mon Sep 17 00:00:00 2001 From: Grant McDermott Date: Wed, 3 Jun 2026 22:10:03 -0700 Subject: [PATCH 05/10] news --- NEWS.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/NEWS.md b/NEWS.md index 101d01b3..2c3ae34d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -74,6 +74,12 @@ New theme features: - `"nber"` (NBER working paper style) - `"socviz"` (based on Kieran Healy's [book](https://socviz.co/)) - `"web"` (web publication, e.g. FiveThirtyEight) +- New `tinytheme_register()` function for registering custom user themes. + Registered themes inherit from any built-in (or previously registered) theme, + apply user-specified overrides, and can then be used by name with + `tinytheme()` or `tinyplot(..., theme = )`. Companion functions + `tinytheme_list()` and `tinytheme_unregister()` further support this + functionality. (#608 @grantmcdermott) Theme fixes: From fd72b46348da041be3ac54c8c866185b8e45880a Mon Sep 17 00:00:00 2001 From: Grant McDermott Date: Wed, 3 Jun 2026 22:12:27 -0700 Subject: [PATCH 06/10] example tweak --- R/tinytheme.R | 2 +- man/tinytheme_register.Rd | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/R/tinytheme.R b/R/tinytheme.R index 0a0cb8ea..323e240e 100644 --- a/R/tinytheme.R +++ b/R/tinytheme.R @@ -714,7 +714,7 @@ get_theme_def = function(name) { #' theme = "clean", #' family = "HersheyScript", #' bg = "#f5e6c8", fg = "#3b2209", -#' cex.lab = 1.5, cex.main = 1.5, cex.sub = 1.2, cex.cap = 1.2, +#' cex.lab = 1.5, cex.main = 1.5, cex.sub = 1.2, #' col = "#3b2209", col.axis = "#5c3a1e", col.cap = "#7a5230", #' col.lab = "#3b2209", col.main = "#1a0f04", col.sub = "#7a5230", #' grid = TRUE, grid.col = "#c9a96e", grid.lty = "dotted", diff --git a/man/tinytheme_register.Rd b/man/tinytheme_register.Rd index 5734856d..a8e2c5c4 100644 --- a/man/tinytheme_register.Rd +++ b/man/tinytheme_register.Rd @@ -51,7 +51,7 @@ tinytheme_register( theme = "clean", family = "HersheyScript", bg = "#f5e6c8", fg = "#3b2209", - cex.lab = 1.5, cex.main = 1.5, cex.sub = 1.2, cex.cap = 1.2, + cex.lab = 1.5, cex.main = 1.5, cex.sub = 1.2, col = "#3b2209", col.axis = "#5c3a1e", col.cap = "#7a5230", col.lab = "#3b2209", col.main = "#1a0f04", col.sub = "#7a5230", grid = TRUE, grid.col = "#c9a96e", grid.lty = "dotted", From 78ab926687d8e702c70cf2401d2dc6c78795bf48 Mon Sep 17 00:00:00 2001 From: Grant McDermott Date: Thu, 4 Jun 2026 09:17:09 -0700 Subject: [PATCH 07/10] Achim's suggestion --- R/tinytheme.R | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/R/tinytheme.R b/R/tinytheme.R index 323e240e..502286dd 100644 --- a/R/tinytheme.R +++ b/R/tinytheme.R @@ -38,6 +38,10 @@ #' @param ... Named arguments to override specific theme settings. These #' arguments are passed to `tpar()` and take precedence over the predefined #' settings in the selected theme. +#' @param register Optional character string. If provided, the theme (with any +#' `...` overrides) is registered under this name via [`tinytheme_register()`] +#' and simultaneously activated. This is a shortcut for calling +#' [`tinytheme_register()`] and `tinytheme()` separately. #' #' @details #' Sets a list of graphical parameters using `tpar()` @@ -112,7 +116,7 @@ #' @return The function returns nothing. It is called for its side effects. #' #' @seealso [`tpar`] which does the heavy lifting under the hood; -#' [tinytheme_register()] for adding custom named themes. +#' [`tinytheme_register()`] for adding custom named themes. #' #' @examples #' # Reusable plot function @@ -193,7 +197,8 @@ tinytheme = function( "ridge", "ridge2", "tufte", "float", "void" ), - ... + ..., + register = NULL ) { if (length(theme) > 1) theme = theme[1] @@ -257,10 +262,15 @@ tinytheme = function( settings[["mgp"]] = c(.mgp1, .mgp2, 0) } + if (!is.null(register)) { + tinytheme_register(register, theme = theme, ...) + settings[["tinytheme"]] = register + } + if (length(settings) > 0) { if (theme == "default") { # for default theme, we want to revert the original pars and turn off the - # before.new.plot hook (otherwise manual par(x = y) changes won't work) + # before.new.plot hook (otherwise manual par(x = y) changes won't work) tpar(settings, hook = FALSE) old_hooks = get_environment_variable(".tpar_hooks") remove_hooks(old_hooks) From ac815816f007362d54b7917e1efafefc0ae64d21 Mon Sep 17 00:00:00 2001 From: Grant McDermott Date: Thu, 4 Jun 2026 09:45:02 -0700 Subject: [PATCH 08/10] docs and website --- R/tinytheme.R | 106 ++++++------------------ altdoc/pkgdown.yml | 2 +- altdoc/quarto_website.yml | 2 + inst/tinytest/test-tinytheme_register.R | 13 +++ man/tinytheme.Rd | 10 ++- man/tinytheme_list.Rd | 21 ----- man/tinytheme_register.Rd | 77 +++++++---------- man/tinytheme_unregister.Rd | 27 ------ vignettes/themes.qmd | 41 +++++---- 9 files changed, 106 insertions(+), 193 deletions(-) delete mode 100644 man/tinytheme_list.Rd delete mode 100644 man/tinytheme_unregister.Rd diff --git a/R/tinytheme.R b/R/tinytheme.R index 502286dd..98239403 100644 --- a/R/tinytheme.R +++ b/R/tinytheme.R @@ -116,7 +116,7 @@ #' @return The function returns nothing. It is called for its side effects. #' #' @seealso [`tpar`] which does the heavy lifting under the hood; -#' [`tinytheme_register()`] for adding custom named themes. +#' [tinytheme_register()] for registering custom named themes. #' #' @examples #' # Reusable plot function @@ -678,71 +678,48 @@ get_theme_def = function(name) { } -#' Register a Custom Named Theme +#' Register, List, and Unregister Custom Themes #' #' @md #' @description -#' Register a custom theme so it can be used by name with `tinytheme()` -#' or `tinyplot(..., theme = )`. Custom themes inherit from a base theme -#' and apply user-specified overrides. +#' `tinytheme_register()` registers a custom theme so it can be used by name +#' with `tinytheme()` or `tinyplot(..., theme = )`. Custom themes inherit from +#' a base theme and apply user-specified overrides. Registered themes are +#' session-scoped: they persist across plots but not across R sessions. To make +#' a custom theme permanently available, register it in your `.Rprofile`. #' -#' Registered themes are session-scoped: they persist across plots but not -#' across R sessions. To make a custom theme available automatically, register -#' it in your `.Rprofile`. +#' `tinytheme_list()` returns the names of all available themes (built-in and +#' registered). +#' +#' `tinytheme_unregister()` removes a previously registered theme from the +#' registry. Does not reset an active theme. #' #' @param name Character string. The name for your custom theme. Cannot clash #' with or overwrite a built-in theme name (`"default"`, `"clean"`, etc.) -#' @param theme Character string or list. The base theme to inherit from. -#' If a string, it must reference a built-in or previously-registered theme. -#' If a list, it is used directly as the base definition. Default is -#' `"default"`. +#' @param theme Character string or list. The base theme to inherit from. If a +#' string, it must reference a built-in or previously-registered theme. If a +#' list, it is used directly as the base definition. Default is `"default"`. #' @param ... Named arguments to override specific theme settings. These are #' the same parameters accepted by `tpar()`. #' -#' @return Returns the theme definition list (invisibly). +#' @return `tinytheme_register()` returns the theme definition list (invisibly). +#' `tinytheme_list()` returns a named list with character vectors `builtin` +#' and `registered`. `tinytheme_unregister()` returns `NULL` (invisibly). #' -#' @seealso [tinytheme()], [tinytheme_list()], [tinytheme_unregister()] +#' @seealso [tinytheme()] #' #' @examples -#' # Register a simple custom theme, based on "float" but now with a grid +#' # Register a custom theme based on "float" but with a grid #' tinytheme_register("float2", theme = "float", grid = TRUE) #' -#' # Use it persistently -#' tinytheme("float2") -#' tinyplot(1:5) -#' tinytheme() -#' -#' # Or ephemerally +#' # Use it #' tinyplot(1:5, theme = "float2") #' -#' # Optional: unregister the theme -#' tinytheme_unregister("float2") +#' # List all themes +#' tinytheme_list() #' -#' # A more elaborate, pirate-themed example -#' tinytheme_register( -#' "pirate", -#' theme = "clean", -#' family = "HersheyScript", -#' bg = "#f5e6c8", fg = "#3b2209", -#' cex.lab = 1.5, cex.main = 1.5, cex.sub = 1.2, -#' col = "#3b2209", col.axis = "#5c3a1e", col.cap = "#7a5230", -#' col.lab = "#3b2209", col.main = "#1a0f04", col.sub = "#7a5230", -#' grid = TRUE, grid.col = "#c9a96e", grid.lty = "dotted", -#' facet.bg = "#e8d4a8", facet.border = "#5c3a1e", -#' pch = 4, -#' palette.qualitative = c( -#' "#8b0000", "#1a5276", "#196f3d", "#7d6608", -#' "#6c3483", "#a04000", "#1b4f72", "#145a32" -#' ) -#' ) -#' tinyplot( -#' Sepal.Length ~ Petal.Length | Species, iris, -#' main = "Avast, me hearties!", -#' sub = "x marks the petal", -#' cap = "Ye Olde Iris Data", -#' theme = "pirate" -#' ) -#' tinytheme_unregister("pirate") +#' # Unregister +#' tinytheme_unregister("float2") #' #' @export tinytheme_register = function(name, theme = "default", ...) { @@ -779,19 +756,7 @@ tinytheme_register = function(name, theme = "default", ...) { } -#' List Available Themes -#' -#' @md -#' @description -#' Returns the names of all available themes, both built-in and user-registered. -#' -#' @return A named list with two character vectors: `builtin` and `registered`. -#' -#' @seealso [tinytheme()], [tinytheme_register()] -#' -#' @examples -#' tinytheme_list() -#' +#' @rdname tinytheme_register #' @export tinytheme_list = function() { builtins = builtin_themes @@ -800,24 +765,7 @@ tinytheme_list = function() { } -#' Unregister a Custom Theme -#' -#' @md -#' @description -#' Remove a previously registered custom theme. Does not reset an active theme; -#' it only removes the theme from the registry so it can no longer be selected -#' by name. -#' -#' @param name Character string. The name of the registered theme to remove. -#' -#' @return Returns `NULL` (invisibly). -#' -#' @seealso [tinytheme_register()], [tinytheme_list()] -#' -#' @examples -#' tinytheme_register("my_theme", .base = "clean", grid = FALSE) -#' tinytheme_unregister("my_theme") -#' +#' @rdname tinytheme_register #' @export tinytheme_unregister = function(name) { registry = get_environment_variable(".registered_themes") diff --git a/altdoc/pkgdown.yml b/altdoc/pkgdown.yml index 084ca55b..78c34371 100644 --- a/altdoc/pkgdown.yml +++ b/altdoc/pkgdown.yml @@ -2,7 +2,7 @@ altdoc: 0.7.2 pandoc: 3.9.0.2 pkgdown: 2.1.3 pkgdown_sha: ~ -last_built: 2026-06-04T04:59:37+0000 +last_built: 2026-06-04T16:40:01+0000 urls: reference: https://grantmcdermott.com/tinyplot/man article: https://grantmcdermott.com/tinyplot/vignettes diff --git a/altdoc/quarto_website.yml b/altdoc/quarto_website.yml index 972056f1..6b7185b8 100644 --- a/altdoc/quarto_website.yml +++ b/altdoc/quarto_website.yml @@ -64,6 +64,8 @@ website: file: man/tinyplot_add.qmd - text: tinytheme file: man/tinytheme.qmd + - text: tinytheme_register + file: man/tinytheme_register.qmd - section: "Plot types" contents: - section: Shapes diff --git a/inst/tinytest/test-tinytheme_register.R b/inst/tinytest/test-tinytheme_register.R index 17dae0ea..aa78bb9a 100644 --- a/inst/tinytest/test-tinytheme_register.R +++ b/inst/tinytest/test-tinytheme_register.R @@ -20,5 +20,18 @@ expect_error( info = "unregistered name errors" ) +# register shortcut via tinytheme(..., register = ) +tinytheme("float", bg = "#f5e6c8", register = "float3") +expect_equal( + tpar("tinytheme"), "float3", + info = "register shortcut activates under registered name" +) +expect_true( + "float3" %in% tinytheme_list()[["registered"]], + info = "register shortcut adds to registry" +) +tinytheme() + # clean up tinytheme_unregister("float2") +tinytheme_unregister("float3") diff --git a/man/tinytheme.Rd b/man/tinytheme.Rd index 8e272714..78542b61 100644 --- a/man/tinytheme.Rd +++ b/man/tinytheme.Rd @@ -8,7 +8,8 @@ tinytheme( theme = c("default", "basic", "dynamic", "clean", "clean2", "bw", "linedraw", "classic", "minimal", "ipsum", "ipsum2", "dark", "socviz", "broadsheet", "nber", "web", "ridge", "ridge2", "tufte", "float", "void"), - ... + ..., + register = NULL ) } \arguments{ @@ -59,6 +60,11 @@ dynamic plots are marked with an asterisk (*) below. \item{...}{Named arguments to override specific theme settings. These arguments are passed to \code{tpar()} and take precedence over the predefined settings in the selected theme.} + +\item{register}{Optional character string. If provided, the theme (with any +\code{...} overrides) is registered under this name via \code{\link[=tinytheme_register]{tinytheme_register()}} +and simultaneously activated. This is a shortcut for calling +\code{\link[=tinytheme_register]{tinytheme_register()}} and \code{tinytheme()} separately.} } \value{ The function returns nothing. It is called for its side effects. @@ -209,5 +215,5 @@ tinytheme() } \seealso{ \code{\link{tpar}} which does the heavy lifting under the hood; -\code{\link[=tinytheme_register]{tinytheme_register()}} for adding custom named themes. +\code{\link[=tinytheme_register]{tinytheme_register()}} for registering custom named themes. } diff --git a/man/tinytheme_list.Rd b/man/tinytheme_list.Rd deleted file mode 100644 index 6a4ead39..00000000 --- a/man/tinytheme_list.Rd +++ /dev/null @@ -1,21 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/tinytheme.R -\name{tinytheme_list} -\alias{tinytheme_list} -\title{List Available Themes} -\usage{ -tinytheme_list() -} -\value{ -A named list with two character vectors: \code{builtin} and \code{registered}. -} -\description{ -Returns the names of all available themes, both built-in and user-registered. -} -\examples{ -tinytheme_list() - -} -\seealso{ -\code{\link[=tinytheme]{tinytheme()}}, \code{\link[=tinytheme_register]{tinytheme_register()}} -} diff --git a/man/tinytheme_register.Rd b/man/tinytheme_register.Rd index a8e2c5c4..b89b44a3 100644 --- a/man/tinytheme_register.Rd +++ b/man/tinytheme_register.Rd @@ -2,76 +2,59 @@ % Please edit documentation in R/tinytheme.R \name{tinytheme_register} \alias{tinytheme_register} -\title{Register a Custom Named Theme} +\alias{tinytheme_list} +\alias{tinytheme_unregister} +\title{Register, List, and Unregister Custom Themes} \usage{ tinytheme_register(name, theme = "default", ...) + +tinytheme_list() + +tinytheme_unregister(name) } \arguments{ \item{name}{Character string. The name for your custom theme. Cannot clash with or overwrite a built-in theme name (\code{"default"}, \code{"clean"}, etc.)} -\item{theme}{Character string or list. The base theme to inherit from. -If a string, it must reference a built-in or previously-registered theme. -If a list, it is used directly as the base definition. Default is -\code{"default"}.} +\item{theme}{Character string or list. The base theme to inherit from. If a +string, it must reference a built-in or previously-registered theme. If a +list, it is used directly as the base definition. Default is \code{"default"}.} \item{...}{Named arguments to override specific theme settings. These are the same parameters accepted by \code{tpar()}.} } \value{ -Returns the theme definition list (invisibly). +\code{tinytheme_register()} returns the theme definition list (invisibly). +\code{tinytheme_list()} returns a named list with character vectors \code{builtin} +and \code{registered}. \code{tinytheme_unregister()} returns \code{NULL} (invisibly). } \description{ -Register a custom theme so it can be used by name with \verb{tinytheme()} -or \verb{tinyplot(..., theme = )}. Custom themes inherit from a base theme -and apply user-specified overrides. +\code{tinytheme_register()} registers a custom theme so it can be used by name +with \code{tinytheme()} or \code{tinyplot(..., theme = )}. Custom themes inherit from +a base theme and apply user-specified overrides. Registered themes are +session-scoped: they persist across plots but not across R sessions. To make +a custom theme permanently available, register it in your \code{.Rprofile}. + +\code{tinytheme_list()} returns the names of all available themes (built-in and +registered). -Registered themes are session-scoped: they persist across plots but not -across R sessions. To make a custom theme available automatically, register -it in your \code{.Rprofile}. +\code{tinytheme_unregister()} removes a previously registered theme from the +registry. Does not reset an active theme. } \examples{ -# Register a simple custom theme, based on "float" but now with a grid +# Register a custom theme based on "float" but with a grid tinytheme_register("float2", theme = "float", grid = TRUE) -# Use it persistently -tinytheme("float2") -tinyplot(1:5) -tinytheme() - -# Or ephemerally +# Use it tinyplot(1:5, theme = "float2") -# Optional: unregister the theme -tinytheme_unregister("float2") +# List all themes +tinytheme_list() -# A more elaborate, pirate-themed example -tinytheme_register( - "pirate", - theme = "clean", - family = "HersheyScript", - bg = "#f5e6c8", fg = "#3b2209", - cex.lab = 1.5, cex.main = 1.5, cex.sub = 1.2, - col = "#3b2209", col.axis = "#5c3a1e", col.cap = "#7a5230", - col.lab = "#3b2209", col.main = "#1a0f04", col.sub = "#7a5230", - grid = TRUE, grid.col = "#c9a96e", grid.lty = "dotted", - facet.bg = "#e8d4a8", facet.border = "#5c3a1e", - pch = 4, - palette.qualitative = c( - "#8b0000", "#1a5276", "#196f3d", "#7d6608", - "#6c3483", "#a04000", "#1b4f72", "#145a32" - ) -) -tinyplot( - Sepal.Length ~ Petal.Length | Species, iris, - main = "Avast, me hearties!", - sub = "x marks the petal", - cap = "Ye Olde Iris Data", - theme = "pirate" -) -tinytheme_unregister("pirate") +# Unregister +tinytheme_unregister("float2") } \seealso{ -\code{\link[=tinytheme]{tinytheme()}}, \code{\link[=tinytheme_list]{tinytheme_list()}}, \code{\link[=tinytheme_unregister]{tinytheme_unregister()}} +\code{\link[=tinytheme]{tinytheme()}} } diff --git a/man/tinytheme_unregister.Rd b/man/tinytheme_unregister.Rd deleted file mode 100644 index 6ec2e50d..00000000 --- a/man/tinytheme_unregister.Rd +++ /dev/null @@ -1,27 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/tinytheme.R -\name{tinytheme_unregister} -\alias{tinytheme_unregister} -\title{Unregister a Custom Theme} -\usage{ -tinytheme_unregister(name) -} -\arguments{ -\item{name}{Character string. The name of the registered theme to remove.} -} -\value{ -Returns \code{NULL} (invisibly). -} -\description{ -Remove a previously registered custom theme. Does not reset an active theme; -it only removes the theme from the registry so it can no longer be selected -by name. -} -\examples{ -tinytheme_register("my_theme", .base = "clean", grid = FALSE) -tinytheme_unregister("my_theme") - -} -\seealso{ -\code{\link[=tinytheme_register]{tinytheme_register()}}, \code{\link[=tinytheme_list]{tinytheme_list()}} -} diff --git a/vignettes/themes.qmd b/vignettes/themes.qmd index f718fd35..3e116e05 100644 --- a/vignettes/themes.qmd +++ b/vignettes/themes.qmd @@ -183,9 +183,9 @@ Please feel free to make suggestions about themes, or contribute new themes by ### Custom themes -Creating custom **tinyplot** themes is easy. Below we demonstrate how to tweak -existing themes in an _ad hoc_ manner, as well as how to register themes for -consistent re-use. +Creating custom **tinyplot** themes is easy. In this section, we demonstrate how +to tweak aexisting themes in an _ad hoc_ manner, as well as how to register your +own custom themes for convenient re-use. #### _Ad hoc_ customization @@ -213,7 +213,8 @@ tinytheme() ``` Similarly, you can pass a list object to the `themes` argument to customize -an ephemeral theme for a single plot. Here's a more fancy adaption that builds off the "dynamic" theme. +an ephemeral theme for a single plot. Here's a more fancy adaptation that builds +off the "dynamic" theme. ```{r} tinyplot( @@ -239,12 +240,12 @@ tinyplot( #### Registering custom themes If you find yourself reusing the same custom theme settings across multiple -plots or sessions, you may prefer to register them as a named theme with +plots or sessions, you may prefer to _register_ them as a named theme with `tinytheme_register()`. Once a theme is registered, it works just like a -built-in one. So you can set it persistently with `tinytheme()`, or pass -it ephemerally with `tinyplot(..., theme = )`. +built-in one. So you can set it persistently with `tinytheme()`, or +pass it ephemerally with `tinyplot(..., theme = )`. -Here's a rather fancy example of a "pirate"-inspired theme: +Here's a (possibly ill-advised) example of a "pirate"-inspired theme: ```{r} # Register a custom "pirate" theme that builds on top of "clean" @@ -268,23 +269,31 @@ tinytheme_register( # Use it ephemerally tinyplot( Sepal.Length ~ Petal.Length | Species, iris, - main = "Avast, me hearties!", - sub = "x marks the petal", - cap = "Ye Olde Iris Data", - theme = "pirate" + main = 'Avast, me hearties!', + sub = 'Here be a "pirate" theme', + cap = '"x" marks the spot', + theme = 'pirate' ) ``` -Use `tinytheme_list()` to see all available themes (both built-in and +As a convenience, `tinytheme()` also accepts a `register` argument that +registers _and_ activates the theme in a single call: + +```r +# Equivalent to tinytheme_register("float2", ...) + tinytheme("float2") +tinytheme("float", grid = TRUE, register = "float2") +``` + +You can use `tinytheme_list()` to see all available themes (both built-in and registered), and `tinytheme_unregister()` to remove one. ```{r} tinytheme_unregister("pirate") ``` -Registered themes are session-scoped: they persist across plots but disappear -when R restarts. To make a custom theme permanently available, register it in -your `.Rprofile`: +Note that registered themes are session-scoped: they persist across plots but +disappear when R restarts. To make a custom theme permanently available, +register it in your `.Rprofile`: ```r # In ~/.Rprofile From b11a162ecfc07672b75c417e268e8bdb69681bee Mon Sep 17 00:00:00 2001 From: Grant McDermott Date: Thu, 4 Jun 2026 09:49:19 -0700 Subject: [PATCH 09/10] website: chull_type while we're at it --- altdoc/pkgdown.yml | 2 +- altdoc/quarto_website.yml | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/altdoc/pkgdown.yml b/altdoc/pkgdown.yml index 78c34371..534a46ad 100644 --- a/altdoc/pkgdown.yml +++ b/altdoc/pkgdown.yml @@ -2,7 +2,7 @@ altdoc: 0.7.2 pandoc: 3.9.0.2 pkgdown: 2.1.3 pkgdown_sha: ~ -last_built: 2026-06-04T16:40:01+0000 +last_built: 2026-06-04T16:46:48+0000 urls: reference: https://grantmcdermott.com/tinyplot/man article: https://grantmcdermott.com/tinyplot/vignettes diff --git a/altdoc/quarto_website.yml b/altdoc/quarto_website.yml index 6b7185b8..b135e3fe 100644 --- a/altdoc/quarto_website.yml +++ b/altdoc/quarto_website.yml @@ -98,6 +98,8 @@ website: file: man/type_barplot.qmd - text: type_boxplot file: man/type_boxplot.qmd + - text: type_chull + file: man/type_chull.qmd - text: type_density file: man/type_density.qmd - text: type_histogram From 618a5cd395bbd573d9c050e7afcfd979f273e216 Mon Sep 17 00:00:00 2001 From: Grant McDermott Date: Thu, 4 Jun 2026 09:49:38 -0700 Subject: [PATCH 10/10] note to claude --- CLAUDE.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index ea650582..7ab63b91 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -214,6 +214,9 @@ Steps: 4. Run `make document` to update NAMESPACE 5. Add tests in `inst/tinytest/test-type_.R` 6. Add snapshot SVGs by running tests on Linux (devcontainer) +7. Add the new page to the website navigation in `altdoc/quarto_website.yml` + +**Important:** Step 7 applies to any new exported function or page, not just plot types. Whenever you add or rename an exported function that gets its own `.Rd` page, add it to `altdoc/quarto_website.yml` under the appropriate section. ### Modifying Legend Behaviour Type-specific legend customizations should go in the type's `data` function by modifying `settings$legend_args`: