From 7f4a6a8b212f9d5bf1dedfb37cc31b4fb02ff56a Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Wed, 29 Nov 2023 16:10:25 +0100 Subject: [PATCH 01/24] add more legend theme settings --- R/theme-elements.R | 9 +++++++++ R/theme.R | 47 ++++++++++++++++++++++++++++++++++++---------- man/theme.Rd | 33 ++++++++++++++++++++++++++++++++ 3 files changed, 79 insertions(+), 10 deletions(-) diff --git a/R/theme-elements.R b/R/theme-elements.R index 4dda819879..7b9f555b11 100644 --- a/R/theme-elements.R +++ b/R/theme-elements.R @@ -500,8 +500,17 @@ el_def <- function(class = NULL, inherit = NULL, description = NULL) { legend.key = el_def("element_rect", "rect"), legend.key.height = el_def(c("unit", "rel"), "legend.key.size"), legend.key.width = el_def(c("unit", "rel"), "legend.key.size"), + legend.key.spacing = el_def("unit"), + legend.key.spacing.x = el_def(c("unit", "rel"), "legend.key.spacing"), + legend.key.spacing.y = el_def(c("unit", "rel"), "legend.key.spacing"), + legend.frame = el_def("element_rect", "rect"), + legend.axis.line = el_def("element_line", "line"), + legend.ticks = el_def("element_line", "legend.axis.line"), + legend.ticks.length = el_def("unit"), legend.text = el_def("element_text", "text"), + legend.text.position = el_def("character"), legend.title = el_def("element_text", "title"), + legend.title.position = el_def("character"), legend.position = el_def(c("character", "numeric", "integer")), legend.direction = el_def("character"), legend.justification = el_def(c("character", "numeric", "integer")), diff --git a/R/theme.R b/R/theme.R index 1774a23e08..cbe4725e89 100644 --- a/R/theme.R +++ b/R/theme.R @@ -74,14 +74,29 @@ #' @param legend.key.size,legend.key.height,legend.key.width #' size of legend keys (`unit`); key background height & width inherit from #' `legend.key.size` or can be specified separately +#' @param legend.key.spacing,legend.key.spacing.x,legend.key.spacing.y spacing +#' between legend keys given as a `unit`. Spacing in the horizontal (x) and +#' vertical (y) direction inherit from `legend.key.spacing` or can be +#' specified separately. +#' @param legend.frame frame drawn around the bar ([element_rect()]). +#' @param legend.ticks tick marks shown along bars or axes ([element_line()]) +#' @param legend.ticks.length length of tick marks in legend (`unit`) +#' @param legend.axis.line lines along axes in legends ([element_line()]) #' @param legend.text legend item labels ([element_text()]; inherits from #' `text`) +#' @param legend.text.position placement of legend text relative to legend keys +#' or bars ("top", "right", "bottom" or "left"). The legend text placement +#' might be incompatible with the legend's direction for some guides. #' @param legend.title title of legend ([element_text()]; inherits from #' `title`) +#' @param legend.title.position placement of legend title relative to the main +#' legend ("top", "right", "bottom" or "left"). #' @param legend.position the position of legends ("none", "left", "right", #' "bottom", "top", or two-element numeric vector) #' @param legend.direction layout of items in legends ("horizontal" or #' "vertical") +#' @param legend.byrow whether the legend-matrix is filled by columns +#' (`FALSE`, the default) or by rows (`TRUE`). #' @param legend.justification anchor point for positioning legend inside plot #' ("center" or two-element numeric vector) or the justification according to #' the plot area when positioned outside the plot @@ -341,10 +356,20 @@ theme <- function(line, legend.key.size, legend.key.height, legend.key.width, + legend.key.spacing, + legend.key.spacing.x, + legend.key.spacing.y, + legend.frame, + legend.ticks, + legend.ticks.length, + legend.axis.line, legend.text, + legend.text.position, legend.title, + legend.title.position, legend.position, legend.direction, + legend.byrow, legend.justification, legend.box, legend.box.just, @@ -479,10 +504,17 @@ 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) + isTRUE(validate %||% TRUE) +} + +validate_theme <- function(theme, tree = get_element_tree()) { + if (!is_theme_validate(theme)) { + return() + } + mapply( + validate_element, theme, names(theme), + MoreArgs = list(element_tree = tree) + ) } # Combine plot defaults with current theme to get complete theme for a plot @@ -505,12 +537,7 @@ plot_theme <- function(x, default = theme_get()) { theme[missing] <- ggplot_global$theme_default[missing] # 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 = get_element_tree()) - ) - } + validate_theme(theme) theme } diff --git a/man/theme.Rd b/man/theme.Rd index 7672d42c5a..ad329ace3d 100644 --- a/man/theme.Rd +++ b/man/theme.Rd @@ -65,10 +65,20 @@ theme( legend.key.size, legend.key.height, legend.key.width, + legend.key.spacing, + legend.key.spacing.x, + legend.key.spacing.y, + legend.frame, + legend.ticks, + legend.ticks.length, + legend.axis.line, legend.text, + legend.text.position, legend.title, + legend.title.position, legend.position, legend.direction, + legend.byrow, legend.justification, legend.box, legend.box.just, @@ -178,18 +188,41 @@ inherits from \code{rect})} \item{legend.key.size, legend.key.height, legend.key.width}{size of legend keys (\code{unit}); key background height & width inherit from \code{legend.key.size} or can be specified separately} +\item{legend.key.spacing, legend.key.spacing.x, legend.key.spacing.y}{spacing +between legend keys given as a \code{unit}. Spacing in the horizontal (x) and +vertical (y) direction inherit from \code{legend.key.spacing} or can be +specified separately.} + +\item{legend.frame}{frame drawn around the bar (\code{\link[=element_rect]{element_rect()}}).} + +\item{legend.ticks}{tick marks shown along bars or axes (\code{\link[=element_line]{element_line()}})} + +\item{legend.ticks.length}{length of tick marks in legend (\code{unit})} + +\item{legend.axis.line}{lines along axes in legends (\code{\link[=element_line]{element_line()}})} + \item{legend.text}{legend item labels (\code{\link[=element_text]{element_text()}}; inherits from \code{text})} +\item{legend.text.position}{placement of legend text relative to legend keys +or bars ("top", "right", "bottom" or "left"). The legend text placement +might be incompatible with the legend's direction for some guides.} + \item{legend.title}{title of legend (\code{\link[=element_text]{element_text()}}; inherits from \code{title})} +\item{legend.title.position}{placement of legend title relative to the main +legend ("top", "right", "bottom" or "left").} + \item{legend.position}{the position of legends ("none", "left", "right", "bottom", "top", or two-element numeric vector)} \item{legend.direction}{layout of items in legends ("horizontal" or "vertical")} +\item{legend.byrow}{whether the legend-matrix is filled by columns +(\code{FALSE}, the default) or by rows (\code{TRUE}).} + \item{legend.justification}{anchor point for positioning legend inside plot ("center" or two-element numeric vector) or the justification according to the plot area when positioned outside the plot} From 6751a66e420183c1477b410992fe46ff3aadaadb Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Thu, 30 Nov 2023 17:27:38 +0100 Subject: [PATCH 02/24] remove elements as part of guide construction --- R/guide-.R | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/R/guide-.R b/R/guide-.R index cdb750ce56..e910eaab72 100644 --- a/R/guide-.R +++ b/R/guide-.R @@ -25,13 +25,8 @@ new_guide <- function(..., available_aes = "any", super) { params <- intersect(names(args), param_names) params <- defaults(args[params], super$params) - # Set elements - elems_names <- names(super$elements) - elems <- intersect(names(args), elems_names) - elems <- defaults(args[elems], super$elements) - # Warn about extra arguments - extra_args <- setdiff(names(args), union(param_names, elems_names)) + extra_args <- setdiff(names(args), param_names) if (length(extra_args) > 0) { cli::cli_warn(paste0( "Ignoring unknown {cli::qty(extra_args)} argument{?s} to ", @@ -56,8 +51,7 @@ new_guide <- function(..., available_aes = "any", super) { ggproto( NULL, super, - params = params, - elements = elems, + params = params, available_aes = available_aes ) } From 36136a3a2f911df7ea34e3ff03f79167b36df3e0 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Thu, 30 Nov 2023 17:27:58 +0100 Subject: [PATCH 03/24] add `theme` as required guide parameter --- DESCRIPTION | 2 +- R/guide-.R | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 1b869d4bdf..8386ae979c 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -173,6 +173,7 @@ Collate: 'grob-dotstack.R' 'grob-null.R' 'grouping.R' + 'theme-elements.R' 'guide-.R' 'guide-axis.R' 'guide-axis-logticks.R' @@ -265,7 +266,6 @@ Collate: 'stat-ydensity.R' 'summarise-plot.R' 'summary.R' - 'theme-elements.R' 'theme.R' 'theme-defaults.R' 'theme-current.R' diff --git a/R/guide-.R b/R/guide-.R index e910eaab72..03ffbd3c0c 100644 --- a/R/guide-.R +++ b/R/guide-.R @@ -1,3 +1,6 @@ +#' @include theme-elements.R +NULL + #' Guide constructor #' #' A constructor function for guides, which performs some standard compatibility @@ -45,6 +48,11 @@ new_guide <- function(..., available_aes = "any", super) { )) } + # Validate theme settings + if (!is.null(params$theme)) { + validate_theme(theme) + } + # Ensure 'order' is length 1 integer params$order <- vec_cast(params$order, 0L, x_arg = "order", call = pf) vec_assert(params$order, 0L, size = 1L, arg = "order", call = pf) @@ -156,6 +164,7 @@ Guide <- ggproto( # `GuidesList` class. params = list( title = waiver(), + theme = NULL, name = character(), position = waiver(), direction = NULL, @@ -268,6 +277,7 @@ Guide <- ggproto( # Converts the `elements` field to proper elements to be accepted by # `element_grob()`. String-interpolates aesthetic/position dependent elements. setup_elements = function(params, elements, theme) { + theme <- add_theme(theme, params$theme) is_char <- vapply(elements, is.character, logical(1)) elements[is_char] <- lapply(elements[is_char], calc_element, theme = theme) elements From 3425f5b5ca46e7735e023025f86f02c590e88faa Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Thu, 30 Nov 2023 17:30:39 +0100 Subject: [PATCH 04/24] bequeath axes with `theme` argument --- R/guide-axis-logticks.R | 2 ++ R/guide-axis-theta.R | 2 +- R/guide-axis.R | 25 ++++++++++++------------- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/R/guide-axis-logticks.R b/R/guide-axis-logticks.R index 5e97d3f193..413c7e874a 100644 --- a/R/guide-axis-logticks.R +++ b/R/guide-axis-logticks.R @@ -72,6 +72,7 @@ guide_axis_logticks <- function( short_theme = element_line(), expanded = TRUE, cap = "none", + theme = NULL, ... ) { if (is.logical(cap)) { @@ -108,6 +109,7 @@ guide_axis_logticks <- function( cap = cap, minor.ticks = TRUE, short_theme = short_theme, + theme = theme, ..., super = GuideAxisLogticks ) diff --git a/R/guide-axis-theta.R b/R/guide-axis-theta.R index abdc9277c1..23e8fa756f 100644 --- a/R/guide-axis-theta.R +++ b/R/guide-axis-theta.R @@ -23,7 +23,7 @@ NULL #' #' # The `angle` argument can be used to set relative angles #' p + guides(theta = guide_axis_theta(angle = 0)) -guide_axis_theta <- function(title = waiver(), angle = waiver(), +guide_axis_theta <- function(title = waiver(), theme = NULL, angle = waiver(), minor.ticks = FALSE, cap = "none", order = 0, position = waiver()) { diff --git a/R/guide-axis.R b/R/guide-axis.R index 0e8e49215c..3821176abd 100644 --- a/R/guide-axis.R +++ b/R/guide-axis.R @@ -46,9 +46,9 @@ #' #' # can also be used to add a duplicate guide #' p + guides(x = guide_axis(n.dodge = 2), y.sec = guide_axis()) -guide_axis <- function(title = waiver(), check.overlap = FALSE, angle = waiver(), - n.dodge = 1, minor.ticks = FALSE, cap = "none", - order = 0, position = waiver()) { +guide_axis <- function(title = waiver(), theme = NULL, check.overlap = FALSE, + angle = waiver(), n.dodge = 1, minor.ticks = FALSE, + cap = "none", order = 0, position = waiver()) { check_bool(minor.ticks) if (is.logical(cap)) { check_bool(cap) @@ -58,6 +58,7 @@ guide_axis <- function(title = waiver(), check.overlap = FALSE, angle = waiver() new_guide( title = title, + theme = theme, # customisations check.overlap = check.overlap, @@ -86,6 +87,7 @@ GuideAxis <- ggproto( params = list( title = waiver(), + theme = NULL, name = "axis", hash = character(), position = waiver(), @@ -225,17 +227,14 @@ GuideAxis <- ggproto( }, setup_elements = function(params, elements, theme) { - axis_elem <- c("line", "text", "ticks", "minor", "major_length", "minor_length") - is_char <- vapply(elements[axis_elem], is.character, logical(1)) - axis_elem <- axis_elem[is_char] - elements[axis_elem] <- lapply( - paste( - unlist(elements[axis_elem]), - params$aes, params$position, sep = "." - ), - calc_element, theme = theme + is_char <- vapply(elements, is.character, logical(1)) + suffix <- paste(params$aes, params$position, sep = ".") + elements[is_char] <- vapply( + elements[is_char], + function(x) paste(x, suffix, sep = "."), + character(1) ) - elements + Guide$setup_elements(params, elements, theme) }, override_elements = function(params, elements, theme) { From 4f371809d942677d0982025d4b5e36362e7458b0 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Thu, 30 Nov 2023 17:32:14 +0100 Subject: [PATCH 05/24] wire `theme` into legends --- R/guide-legend.R | 344 ++++++++++++++++++----------------------------- 1 file changed, 134 insertions(+), 210 deletions(-) diff --git a/R/guide-legend.R b/R/guide-legend.R index cb5d671393..d4d60301e3 100644 --- a/R/guide-legend.R +++ b/R/guide-legend.R @@ -131,90 +131,31 @@ #' } guide_legend <- function( # Title - title = waiver(), - title.position = NULL, - title.theme = NULL, - title.hjust = NULL, - title.vjust = NULL, - - # Label - label = TRUE, - label.position = NULL, - label.theme = NULL, - label.hjust = NULL, - label.vjust = NULL, - - # Key size - keywidth = NULL, - keyheight = NULL, - key.spacing = NULL, - key.spacing.x = NULL, - key.spacing.y = NULL, + title = waiver(), + + # Theme + theme = NULL, # General direction = NULL, - default.unit = "line", override.aes = list(), nrow = NULL, ncol = NULL, - byrow = FALSE, reverse = FALSE, order = 0, ... ) { - # Resolve key sizes - if (!(is.null(keywidth) || is.unit(keywidth))) { - keywidth <- unit(keywidth, default.unit) - } - if (!(is.null(keyheight) || is.unit(keyheight))) { - keyheight <- unit(keyheight, default.unit) - } - - # Resolve spacing - key.spacing.x <- key.spacing.x %||% key.spacing - if (!is.null(key.spacing.x) || is.unit(key.spacing.x)) { - key.spacing.x <- unit(key.spacing.x, default.unit) - } - key.spacing.y <- key.spacing.y %||% key.spacing - if (!is.null(key.spacing.y) || is.unit(key.spacing.y)) { - key.spacing.y <- unit(key.spacing.y, default.unit) - } - - - if (!is.null(title.position)) { - title.position <- arg_match0(title.position, .trbl) - } - if (!is.null(label.position)) { - label.position <- arg_match0(label.position, .trbl) - } new_guide( # Title title = title, - title.position = title.position, - title.theme = title.theme, - title.hjust = title.hjust, - title.vjust = title.vjust, - - # Label - label = label, - label.position = label.position, - label.theme = label.theme, - label.hjust = label.hjust, - label.vjust = label.vjust, - - # Key size - keywidth = keywidth, - keyheight = keyheight, - key.spacing.x = key.spacing.x, - key.spacing.y = key.spacing.y, + theme = theme, # General direction = direction, override.aes = rename_aes(override.aes), nrow = nrow, ncol = ncol, - byrow = byrow, reverse = reverse, order = order, @@ -234,27 +175,12 @@ GuideLegend <- ggproto( params = list( title = waiver(), - title.position = NULL, - title.theme = NULL, - title.hjust = NULL, - title.vjust = NULL, - - label = TRUE, - label.position = NULL, - label.theme = NULL, - label.hjust = NULL, - label.vjust = NULL, - - keywidth = NULL, - keyheight = NULL, - key.spacing.x = NULL, - key.spacing.y = NULL, + theme = NULL, # General override.aes = list(), nrow = NULL, ncol = NULL, - byrow = FALSE, reverse = FALSE, order = 0, @@ -269,20 +195,23 @@ GuideLegend <- ggproto( hashables = exprs(title, key$.label, name), elements = list( - background = "legend.background", - margin = "legend.margin", - key = "legend.key", - key.height = "legend.key.height", - key.width = "legend.key.width", - text = "legend.text", - theme.title = "legend.title" + background = "legend.background", + margin = "legend.margin", + key = "legend.key", + key_height = "legend.key.height", + key_width = "legend.key.width", + text = "legend.text", + theme.title = "legend.title", + spacing_x = "legend.key.spacing.x", + spacing_y = "legend.key.spacing.y", + text_position = "legend.text.position", + title_position = "legend.title.position", + byrow = "legend.byrow" ), extract_params = function(scale, params, title = waiver(), ...) { - params$title <- scale$make_title( - params$title %|W|% scale$name %|W|% title - ) + params$title <- scale$make_title(params$title %|W|% scale$name %|W|% title) if (isTRUE(params$reverse %||% FALSE)) { params$key <- params$key[nrow(params$key):1, , drop = FALSE] } @@ -368,25 +297,9 @@ GuideLegend <- ggproto( setup_params = function(params) { params$direction <- arg_match0( - params$direction %||% direction, + params$direction, c("horizontal", "vertical"), arg_nm = "direction" ) - - if ("title.position" %in% names(params)) { - params$title.position <- arg_match0( - params$title.position %||% - switch(params$direction, vertical = "top", horizontal = "left"), - .trbl, arg_nm = "title.position" - ) - } - if ("label.position" %in% names(params)) { - params$label.position <- arg_match0( - params$label.position %||% "right", - .trbl, arg_nm = "label.position" - ) - params$rejust_labels <- TRUE - } - params$n_breaks <- n_breaks <- nrow(params$key) params$n_key_layers <- length(params$decor) + 1 # +1 is key background @@ -410,91 +323,89 @@ GuideLegend <- ggproto( params }, - override_elements = function(params, elements, theme) { + setup_elements = function(params, elements, theme) { + theme <- add_theme(theme, params$theme) + params$theme <- NULL - # Title - title <- combine_elements(params$title.theme, elements$theme.title) - title$hjust <- params$title.hjust %||% title$hjust %||% 0 - title$vjust <- params$title.vjust %||% title$vjust %||% 0.5 - elements$title <- title - - # Labels - if (!is.null(elements$text)) { - label <- combine_elements(params$label.theme, elements$text) - if (!params$label || is.null(params$key$.label)) { - label <- element_blank() - } else { - hjust <- unname(label_hjust_defaults[params$label.position]) - vjust <- unname(label_vjust_defaults[params$label.position]) - # Expressions default to right-justified - if (hjust == 0 && any(is.expression(params$key$.label))) { - hjust <- 1 - } - # Breaking justification inheritance for intuition purposes. - if (is.null(params$label.theme$hjust) && - is.null(theme$legend.text$hjust)) { - label$hjust <- NULL - } - if (is.null(params$label.theme$vjust) && - is.null(theme$legend.text$vjust)) { - label$vjust <- NULL - } - label$hjust <- params$label.hjust %||% label$hjust %||% hjust - label$vjust <- params$label.vjust %||% label$vjust %||% vjust - } - elements$text <- label - } - - # Keys - if (any(c("key.width", "key.height") %in% names(elements))) { - elements$key.width <- width_cm( params$keywidth %||% elements$key.width) - elements$key.height <- height_cm(params$keyheight %||% elements$key.height) - } - - # Spacing - gap <- title$size %||% elements$theme.title$size %||% - elements$text$size %||% 11 + # Resolve text positions + text_position <- theme$legend.text.position %||% "right" + title_position <- theme$legend.title.position %||% switch( + params$direction, + vertical = "top", horizontal = "left" + ) + theme$legend.text.position <- + arg_match0(text_position, .trbl, arg_nm = "legend.text.position") + theme$legend.title.position <- + arg_match0(title_position, .trbl, arg_nm = "legend.title.position") + + # Resolve spacing. For the default gap, we break classic inheritance. + gap <- calc_element("legend.title", theme)$size %||% + calc_element("legend.text", theme)$size %||% 11 gap <- unit(gap * 0.5, "pt") - # Should maybe be elements$spacing.{x/y} instead of the theme's spacing? + # Set default spacing + theme$legend.key.spacing <- theme$legend.key.spacing %||% gap + + # For backward compatibility, default vertical spacing is no spacing if (params$direction == "vertical") { - # For backward compatibility, vertical default is no spacing - vgap <- params$key.spacing.y %||% unit(0, "pt") - } else { - vgap <- params$key.spacing.y %||% gap + theme$legend.key.spacing.y <- theme$legend.key.spacing.y %||% + unit(0, "pt") } - elements$hgap <- width_cm( params$key.spacing.x %||% gap) - elements$vgap <- height_cm(vgap) - elements$padding <- convertUnit( - elements$margin %||% margin(), - "cm", valueOnly = TRUE + # Resolve title. The trick here is to override the main text element, so + # that any settings declared in `legend.title` will be honoured but we have + # custom defaults for the guide. + margin <- calc_element("text", theme)$margin + title <- theme(text = element_text( + hjust = 0, vjust = 0.5, + margin = position_margin(title_position, margin, gap) + )) + elements$title <- calc_element("legend.title", add_theme(theme, title)) + + # Resolve text, setting default justification and margins. Again, the + # trick here is to set the main text element to propagate defaults while + # honouring the `legend.text` settings. + margin <- position_margin(text_position, margin, gap) + text <- theme( + text = switch( + text_position, + top = element_text(hjust = 0.5, vjust = 0.0, margin = margin), + bottom = element_text(hjust = 0.5, vjust = 1.0, margin = margin), + left = element_text(hjust = 1.0, vjust = 0.5, margin = margin), + right = element_text(hjust = 0.0, vjust = 0.5, margin = margin) + ) ) + elements$text <- calc_element("legend.text", add_theme(theme, text)) + Guide$setup_elements(params, elements, theme) + }, + + override_elements = function(params, elements, theme) { - # When no explicit margin has been set, either in this guide or in the - # theme, we set a default text margin to leave a small gap in between - # the label and the key. - if (is.null(params$label.theme$margin %||% theme$legend.text$margin) && - !inherits(elements$text, "element_blank")) { - i <- match(params$label.position, .trbl[c(3, 4, 1, 2)]) - elements$text$margin[i] <- elements$text$margin[i] + gap + # Convert key sizes to cm + if (any(c("key_width", "key_height") %in% names(elements))) { + elements$key_width <- width_cm(elements$key_width) + elements$key_height <- height_cm(elements$key_height) } - if (is.null(params$title.theme$margin %||% theme$legend.title$margin) && - !inherits(elements$title, "element_blank")) { - i <- match(params$title.position, .trbl[c(3, 4, 1, 2)]) - elements$title$margin[i] <- elements$title$margin[i] + gap + + # Convert padding and spacing to cm + if (any(c("spacing_x", "spacing_y") %in% names(elements))) { + elements$spacing_x <- width_cm(elements$spacing_x) + elements$spacing_y <- height_cm(elements$spacing_y) } + elements$padding <- convertUnit( + elements$margin %||% margin(), + "cm", valueOnly = TRUE + ) + # Evaluate backgrounds early if (!is.null(elements$background)) { - elements$background <- ggname( - "legend.background", element_grob(elements$background) - ) + elements$background <- + ggname("legend.background", element_grob(elements$background)) } if (!is.null(elements$key)) { - elements$key <- ggname( - "legend.key", element_grob(elements$key) - ) + elements$key <- + ggname("legend.key", element_grob(elements$key)) } elements @@ -506,7 +417,7 @@ GuideLegend <- ggproto( build_decor = function(decor, grobs, elements, params) { - key_size <- c(elements$key.width, elements$key.height) * 10 + key_size <- c(elements$key_width, elements$key_height) * 10 draw <- function(i) { bg <- elements$key @@ -544,16 +455,17 @@ GuideLegend <- ggproto( }, measure_grobs = function(grobs, params, elements) { - byrow <- params$byrow %||% FALSE + + byrow <- elements$byrow %||% FALSE n_breaks <- params$n_breaks %||% 1L - dim <- c(params$nrow %||% 1L, params$ncol %||% 1L) + dim <- c(params$nrow %||% 1L, params$ncol %||% 1L) # A guide may have already specified the size of the decoration, only # measure when it hasn't already. sizes <- params$sizes %||% measure_legend_keys( grobs$decor, n = n_breaks, dim = dim, byrow = byrow, - default_width = elements$key.width, - default_height = elements$key.height + default_width = elements$key_width, + default_height = elements$key_height ) widths <- sizes$widths heights <- sizes$heights @@ -572,18 +484,18 @@ GuideLegend <- ggproto( # Interleave gaps between keys and labels, which depends on the label # position. For unclear reasons, we need to adjust some gaps based on the # `byrow` parameter (see also #4352). - hgap <- elements$hgap %||% 0 + hgap <- elements$spacing_x %||% 0 widths <- switch( - params$label.position, + elements$text_position, "left" = list(label_widths, widths, hgap), "right" = list(widths, label_widths, hgap), list(pmax(label_widths, widths), hgap) ) widths <- head(vec_interleave(!!!widths), -1) - vgap <- elements$vgap %||% 0 + vgap <- elements$spacing_y %||% 0 heights <- switch( - params$label.position, + elements$text_position, "top" = list(label_heights, heights, vgap), "bottom" = list(heights, label_heights, vgap), list(pmax(label_heights, heights), vgap) @@ -599,13 +511,13 @@ GuideLegend <- ggproto( # Combine title with rest of the sizes based on its position widths <- switch( - params$title.position, + elements$title_position, "left" = c(title_width, widths), "right" = c(widths, title_width), c(widths, max(0, title_width - sum(widths))) ) heights <- switch( - params$title.position, + elements$title_position, "top" = c(title_height, heights), "bottom" = c(heights, title_height), c(heights, max(0, title_height - sum(heights))) @@ -616,7 +528,9 @@ GuideLegend <- ggproto( widths = widths, heights = heights, padding = elements$padding, - has_title = has_title + has_title = has_title, + label_position = elements$text_position, + title_position = elements$title_position ) }, @@ -627,63 +541,65 @@ GuideLegend <- ggproto( # Find rows / columns of legend items if (params$byrow %||% FALSE) { - df <- data_frame0( - R = ceiling(break_seq / dim[2]), - C = (break_seq - 1) %% dim[2] + 1 - ) + row <- ceiling(break_seq / dim[2L]) + col <- (break_seq - 1L) %% dim[2L] + 1L } else { df <- mat_2_df(arrayInd(break_seq, dim), c("R", "C")) + row <- df$R + col <- df$C } # Make spacing for padding / gaps. For example: because first gtable cell # will be padding, first item will be at [2, 2] position. Then the # second item-row will be [4, 2] because [3, 2] will be a gap cell. - key_row <- label_row <- df$R * 2 - key_col <- label_col <- df$C * 2 + key_row <- label_row <- row * 2 + key_col <- label_col <- col * 2 # Make gaps for key-label spacing depending on label position switch( - params$label.position, + sizes$label_position, "top" = { - key_row <- key_row + df$R + key_row <- key_row + row label_row <- key_row - 1 }, "bottom" = { - key_row <- key_row + df$R - 1 + key_row <- key_row + row - 1 label_row <- key_row + 1 }, "left" = { - key_col <- key_col + df$C + key_col <- key_col + col label_col <- key_col - 1 }, "right" = { - key_col <- key_col + df$C - 1 + key_col <- key_col + col - 1 label_col <- key_col + 1 } ) # Offset layout based on title position if (sizes$has_title) { + ncol <- length(sizes$widths) + nrow <- length(sizes$heights) switch( - params$title.position, + sizes$title_position, "top" = { key_row <- key_row + 1 label_row <- label_row + 1 title_row <- 2 - title_col <- seq_along(sizes$widths) + 1 + title_col <- seq_len(ncol) + 1 }, "bottom" = { - title_row <- length(sizes$heights) + 1 - title_col <- seq_along(sizes$widths) + 1 + title_row <- nrow + 1 + title_col <- seq_len(ncol) + 1 }, "left" = { key_col <- key_col + 1 label_col <- label_col + 1 - title_row <- seq_along(sizes$heights) + 1 + title_row <- seq_len(nrow) + 1 title_col <- 2 }, "right" = { - title_row <- seq_along(sizes$heights) + 1 - title_col <- length(sizes$widths) + 1 + title_row <- seq_len(nrow) + 1 + title_col <- ncol + 1 } ) } else { @@ -699,7 +615,7 @@ GuideLegend <- ggproto( assemble_drawing = function(grobs, layout, sizes, params, elements) { gt <- gtable( - widths = unit(c(sizes$padding[4], sizes$widths, sizes$padding[2]), "cm"), + widths = unit(c(sizes$padding[4], sizes$widths, sizes$padding[2]), "cm"), heights = unit(c(sizes$padding[1], sizes$heights, sizes$padding[3]), "cm") ) @@ -774,8 +690,6 @@ GuideLegend <- ggproto( } ) -label_hjust_defaults <- c(top = 0.5, bottom = 0.5, left = 1, right = 0) -label_vjust_defaults <- c(top = 0, bottom = 1, left = 0.5, right = 0.5) measure_legend_keys <- function(keys, n, dim, byrow = FALSE, default_width = 1, default_height = 1) { @@ -860,3 +774,13 @@ keep_key_data <- function(key, data, aes, show) { } keep } + +position_margin <- function(position, margin = margin(), gap = unit(0, "pt")) { + switch( + position, + top = replace(margin, 3, margin[3] + gap), + bottom = replace(margin, 1, margin[1] + gap), + left = replace(margin, 2, margin[2] + gap), + right = replace(margin, 4, margin[4] + gap) + ) +} From bf70ff9e82bf5748b99d1653d1de4058a1891366 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Thu, 30 Nov 2023 17:34:16 +0100 Subject: [PATCH 06/24] impart `theme` upon colourbars --- R/guide-colorbar.R | 211 +++++++++++++------------------------------ R/guide-colorsteps.R | 6 +- 2 files changed, 64 insertions(+), 153 deletions(-) diff --git a/R/guide-colorbar.R b/R/guide-colorbar.R index 6e2206a26e..92b90d43a1 100644 --- a/R/guide-colorbar.R +++ b/R/guide-colorbar.R @@ -118,35 +118,13 @@ guide_colourbar <- function( # title title = waiver(), - title.position = NULL, - title.theme = NULL, - title.hjust = NULL, - title.vjust = NULL, - - # label - label = TRUE, - label.position = NULL, - label.theme = NULL, - label.hjust = NULL, - label.vjust = NULL, + theme = NULL, # bar - barwidth = NULL, - barheight = NULL, nbin = 300, raster = TRUE, - # frame - frame = element_blank(), - frame.colour = NULL, - frame.linewidth = NULL, - frame.linetype = NULL, - # ticks - ticks = element_line(), - ticks.colour = NULL, - ticks.linewidth = NULL, - ticks.length = unit(0.2, "npc"), draw.ulim = TRUE, draw.llim = TRUE, @@ -158,52 +136,6 @@ guide_colourbar <- function( available_aes = c("colour", "color", "fill"), ... ) { - if (!(is.null(barwidth) || is.unit(barwidth))) { - barwidth <- unit(barwidth, default.unit) - } - if (!(is.null(barheight) || is.unit(barheight))) { - barheight <- unit(barheight, default.unit) - } - if (!is.unit(ticks.length)) { - ticks.length <- unit(ticks.length, default.unit) - } - - if (!is.null(title.position)) { - title.position <- arg_match0(title.position, .trbl) - } - if (!is.null(direction)) { - direction <- arg_match0(direction, c("horizontal", "vertical")) - } - if (!is.null(label.position)) { - label.position <- arg_match0(label.position, .trbl) - } - - if (!is.null(frame.colour) && !inherits(frame, "element_rect")) { - # For backward compatibility, frame should not be element_blank when - # colour is not NULL - cli::cli_inform(c(paste0( - "If {.arg frame.colour} is set, {.arg frame} should not be ", - "{.cls {class(frame)[[1]]}}." - ), "i" = "{.arg frame} has been converted to {.cls element_rect}.")) - frame <- element_rect() - } - if (inherits(frame, "element_rect")) { - frame$colour <- frame.colour %||% frame$colour - frame$linewidth <- frame.linewidth %||% frame$linewidth %||% (0.5 / .pt) - frame$linetype <- frame.linetype %||% frame$linetype %||% 1 - } else { - frame <- element_blank() - } - - if (is.logical(ticks)) { - # Also for backward compatibility. `ticks = FALSE` used to mean: don't draw - # the ticks - ticks <- if (ticks) element_line() else element_blank() - } - if (inherits(ticks, "element_line")) { - ticks$colour <- ticks.colour %||% ticks$colour %||% "white" - ticks$linewidth <- ticks.linewidth %||% ticks$linewidth %||% (0.5 / .pt) - } # Trick to re-use this constructor in `guide_coloursteps()`. args <- list2(...) @@ -213,30 +145,12 @@ guide_colourbar <- function( new_guide( # title title = title, - title.position = title.position, - title.theme = title.theme, - title.hjust = title.hjust, - title.vjust = title.vjust, - - # label - label = label, - label.position = label.position, - label.theme = label.theme, - label.hjust = label.hjust, - label.vjust = label.vjust, + theme = theme, - # bar - keywidth = barwidth, - keyheight = barheight, nbin = nbin, raster = raster, - # frame - frame = frame, - # ticks - ticks = ticks, - ticks_length = ticks.length, draw_lim = c(isTRUE(draw.llim), isTRUE(draw.ulim)), # general @@ -266,21 +180,14 @@ GuideColourbar <- ggproto( params = list( # title title = waiver(), - title.position = NULL, - title.theme = NULL, - title.hjust = NULL, - title.vjust = NULL, - - # label - label = TRUE, - label.position = NULL, - label.theme = NULL, - label.hjust = NULL, - label.vjust = NULL, + + # theming + theme = NULL, + default_ticks = element_line(colour = "white", linewidth = 0.5 / .pt), + default_frame = element_blank(), + default_tick_length = unit(0.2, "npc"), # bar - keywidth = NULL, - keyheight = NULL, nbin = 300, raster = TRUE, @@ -291,6 +198,8 @@ GuideColourbar <- ggproto( reverse = FALSE, order = 0, + rejust_labels = FALSE, + # parameter name = "colourbar", hash = character(), @@ -302,16 +211,19 @@ GuideColourbar <- ggproto( hashables = exprs(title, key$.label, decor, name), elements = list( - frame = "rect", - ticks = "line", - ticks_length = unit(0.2, "npc"), - background = "legend.background", - margin = "legend.margin", - key = "legend.key", - key.height = "legend.key.height", - key.width = "legend.key.width", - text = "legend.text", - theme.title = "legend.title" + background = "legend.background", + margin = "legend.margin", + key = "legend.key", + key_height = "legend.key.height", + key_width = "legend.key.width", + text = "legend.text", + theme.title = "legend.title", + text_position = "legend.text.position", + title_position = "legend.title.position", + axis_line = "legend.axis.line", + ticks = "legend.ticks", + ticks_length = "legend.ticks.length", + frame = "legend.frame" ), extract_key = function(scale, aesthetic, ...) { @@ -342,11 +254,9 @@ GuideColourbar <- ggproto( extract_params = function(scale, params, title = waiver(), ...) { - params$title <- scale$make_title( - params$title %|W|% scale$name %|W|% title - ) + params$title <- scale$make_title(params$title %|W|% scale$name %|W|% title) - limits <- c(params$decor$value[1], params$decor$value[nrow(params$decor)]) + limits <- params$decor$value[c(1L, nrow(params$decor))] params$key$.value <- rescale( params$key$.value, c(0.5, params$nbin - 0.5) / params$nbin, @@ -370,38 +280,41 @@ GuideColourbar <- ggproto( params$direction, c("horizontal", "vertical"), arg_nm = "direction" ) - valid_label_pos <- switch( - params$direction, - "horizontal" = c("bottom", "top"), - "vertical" = c("right", "left") - ) - params$label.position <- params$label.position %||% valid_label_pos[1] - if (!params$label.position %in% valid_label_pos) { - cli::cli_abort(paste0( - "When {.arg direction} is {.val {params$direction}}, ", - "{.arg label.position} must be one of {.or {.val {valid_label_pos}}}, ", - "not {.val {params$label.position}}." - )) - } - params$title.position <- arg_match0( - params$title.position %||% - switch(params$direction, vertical = "top", horizontal = "left"), - .trbl, arg_nm = "title.position" - ) - params$rejust_labels <- FALSE params }, - override_elements = function(params, elements, theme) { - # These key sizes are the defaults, the GuideLegend method may overrule this + setup_elements = function(params, elements, theme) { + # We set the defaults in `theme` so that the `params$theme` can still + # overrule defaults given here if (params$direction == "horizontal") { - elements$key.width <- elements$key.width * 5 + theme$legend.key.width <- theme$legend.key.width * 5 + valid_position <- c("bottom", "top") } else { - elements$key.height <- elements$key.height * 5 + theme$legend.key.height <- theme$legend.key.height * 5 + valid_position <- c("right", "left") + } + + # Set defaults + theme <- replace_null( + theme, + legend.text.position = valid_position[1], + legend.ticks.length = params$default_tick_length, + legend.ticks = params$default_ticks, + legend.frame = params$default_frame + ) + + # Let the legend guide handle the rest + elements <- GuideLegend$setup_elements(params, elements, theme) + + # Check text position + if (!elements$text_position %in% valid_position) { + cli::cli_abort(paste0( + "When {.arg direction} is {.val {params$direction}}, ", + "{.arg legend.text.position} must be one of ", + "{.or {.val {valid_position}}}, not {.val {elements$text_position}}." + )) } - elements$ticks <- combine_elements(elements$ticks, theme$line) - elements$frame <- combine_elements(elements$frame, theme$rect) - GuideLegend$override_elements(params, elements, theme) + elements }, build_labels = function(key, elements, params) { @@ -447,21 +360,21 @@ GuideColourbar <- ggproto( ) grob <- rasterGrob( image = image, - width = elements$key.width, - height = elements$key.height, + width = elements$key_width, + height = elements$key_height, default.units = "cm", gp = gpar(col = NA), interpolate = TRUE ) } else{ if (params$direction == "horizontal") { - width <- elements$key.width / nrow(decor) - height <- elements$key.height + width <- elements$key_width / nrow(decor) + height <- elements$key_height x <- (seq(nrow(decor)) - 1) * width y <- 0 } else { - width <- elements$key.width - height <- elements$key.height / nrow(decor) + width <- elements$key_width + height <- elements$key_height / nrow(decor) y <- (seq(nrow(decor)) - 1) * height x <- 0 } @@ -481,8 +394,8 @@ GuideColourbar <- ggproto( measure_grobs = function(grobs, params, elements) { params$sizes <- list( - widths = elements$key.width, - heights = elements$key.height + widths = elements$key_width, + heights = elements$key_height ) GuideLegend$measure_grobs(grobs, params, elements) } diff --git a/R/guide-colorsteps.R b/R/guide-colorsteps.R index c5315e6da6..275f867128 100644 --- a/R/guide-colorsteps.R +++ b/R/guide-colorsteps.R @@ -51,13 +51,11 @@ guide_coloursteps <- function( even.steps = TRUE, show.limits = NULL, - ticks = element_blank(), ... ) { guide_colourbar( even.steps = even.steps, show.limits = show.limits, - ticks = ticks, ..., super = GuideColoursteps ) @@ -76,7 +74,7 @@ GuideColoursteps <- ggproto( params = c( list(even.steps = TRUE, show.limits = NULL), - GuideColourbar$params + vec_assign(GuideColourbar$params, "default_ticks", list(element_blank())) ), extract_key = function(scale, aesthetic, even.steps, ...) { @@ -94,7 +92,7 @@ GuideColoursteps <- ggproto( limits <- parsed$limits breaks <- parsed$breaks - key <- data_frame(scale$map(breaks), .name_repair = ~ aesthetic) + key <- data_frame0(!!aesthetic := scale$map(breaks)) key$.value <- seq_along(breaks) key$.label <- scale$get_labels(breaks) From de32582a2cf9adba69cdb4a9667778848dcc0f30 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Thu, 30 Nov 2023 17:37:12 +0100 Subject: [PATCH 07/24] entrust bins guide with `theme` --- R/guide-bins.R | 163 +++++++++++++------------------------------------ 1 file changed, 43 insertions(+), 120 deletions(-) diff --git a/R/guide-bins.R b/R/guide-bins.R index 77ea847b53..0b7e527a98 100644 --- a/R/guide-bins.R +++ b/R/guide-bins.R @@ -74,30 +74,7 @@ NULL guide_bins <- function( # title title = waiver(), - title.position = NULL, - title.theme = NULL, - title.hjust = NULL, - title.vjust = NULL, - - # label - label = TRUE, - label.position = NULL, - label.theme = NULL, - label.hjust = NULL, - label.vjust = NULL, - - # key - keywidth = NULL, - keyheight = NULL, - - # ticks - axis = TRUE, - axis.colour = "black", - axis.linewidth = NULL, - axis.arrow = NULL, - - ticks = NULL, - ticks.length = unit(0.2, "npc"), + theme = NULL, # general direction = NULL, @@ -109,64 +86,14 @@ guide_bins <- function( ... ) { - if (!(is.null(keywidth) || is.unit(keywidth))) { - keywidth <- unit(keywidth, default.unit) - } - if (!(is.null(keyheight) || is.unit(keyheight))) { - keyheight <- unit(keyheight, default.unit) - } - if (!is.unit(ticks.length)) { - ticks.length <- unit(ticks.length, default.unit) - } - if (!is.null(title.position)) { - title.position <- arg_match0(title.position, .trbl) - } if (!is.null(direction)) { direction <- arg_match0(direction, c("horizontal", "vertical")) } - if (!is.null(label.position)) { - label.position <- arg_match0(label.position, .trbl) - } - - if (is.logical(axis)) { - axis <- if (axis) element_line() else element_rect() - } - if (inherits(axis, "element_line")) { - axis$colour <- axis.colour %||% axis$colour %||% "black" - axis$linewidth <- axis.linewidth %||% axis$linewidth %||% (0.5 / .pt) - axis$arrow <- axis.arrow %||% axis$arrow - } else { - axis <- element_blank() - } - - if (is.null(ticks)) { - ticks <- axis - ticks$arrow <- NULL - } new_guide( # title title = title, - title.position = title.position, - title.theme = title.theme, - title.hjust = title.hjust, - title.vjust = title.vjust, - - # label - label = label, - label.position = label.position, - label.theme = label.theme, - label.hjust = label.hjust, - label.vjust = label.vjust, - - # key - keywidth = keywidth, - keyheight = keyheight, - - # ticks - line = axis, - ticks = ticks, - ticks_length = ticks.length, + theme = theme, # general direction = direction, @@ -192,19 +119,13 @@ GuideBins <- ggproto( params = list( title = waiver(), - title.position = NULL, - title.theme = NULL, - title.hjust = NULL, - title.vjust = NULL, - - label = TRUE, - label.position = NULL, - label.theme = NULL, - label.hjust = NULL, - label.vjust = NULL, - keywidth = NULL, - keyheight = NULL, + # theming + theme = NULL, + default_axis = element_line("black", linewidth = (0.5 / .pt)), + default_ticks = element_line(inherit.blank = TRUE), + default_tick_length = unit(0.2, "npc"), + rejust_labels = FALSE, direction = NULL, override.aes = list(), @@ -221,9 +142,9 @@ GuideBins <- ggproto( elements = c( GuideLegend$elements, list( - line = "line", - ticks = "line", - ticks_length = unit(0.2, "npc") + axis_line = "legend.axis.line", + ticks_length = "legend.ticks.length", + ticks = "legend.ticks" ) ), @@ -295,43 +216,45 @@ GuideBins <- ggproto( key$.value <- 1 - key$.value } - params$title <- scale$make_title( - params$title %|W|% scale$name %|W|% title - ) + params$title <- scale$make_title(params$title %|W|% scale$name %|W|% title) params$key <- key params }, setup_params = function(params) { - params$direction <- arg_match0( - params$direction, - c("horizontal", "vertical"), arg_nm = "direction" - ) - valid_label_pos <- switch( + params <- GuideLegend$setup_params(params) + params$nrow <- params$ncol <- params$n_breaks <- params$n_key_layers <- 1 + params + }, + + setup_elements = function(params, elements, theme) { + valid_position <- switch( params$direction, "horizontal" = c("bottom", "top"), "vertical" = c("right", "left") ) - params$label.position <- params$label.position %||% valid_label_pos[1] - if (!params$label.position %in% valid_label_pos) { + + # Set defaults + theme <- replace_null( + theme, + legend.text.position = valid_position[1], + legend.ticks.length = params$default_tick_length, + legend.axis.line = params$default_axis, + legend.ticks = params$default_ticks + ) + + # Let the legend guide handle the rest + elements <- GuideLegend$setup_elements(params, elements, theme) + + # Check text position + if (!elements$text_position %in% valid_position) { cli::cli_abort(paste0( - "When {.arg direction} is {.val {params$direction}}, ", - "{.arg label.position} must be one of {.or {.val {valid_label_pos}}}, ", - "not {.val {params$label.position}}." + "When {.arg direction} is {.val {params$direction}, ", + "{.arg legend.text.position} must be one of ", + "{.or {.val {valid_position}}}, not {.val {elements$text.position}}." )) } - params <- GuideLegend$setup_params(params) - params$byrow <- FALSE - params$rejust_labels <- FALSE - params$nrow <- params$ncol <- params$n_breaks <- params$n_key_layers <- 1 - params$multikey_decor <- FALSE - params - }, - - override_elements = function(params, elements, theme) { - elements$ticks <- combine_elements(elements$ticks, theme$line) - elements$line <- combine_elements(elements$line, theme$line) - GuideLegend$override_elements(params, elements, theme) + elements }, build_labels = function(key, elements, params) { @@ -360,7 +283,7 @@ GuideBins <- ggproto( key$.value <- 1 - key$.value } key$.value[c(1, nrow(key))[!params$show.limits]] <- NA - Guide$build_ticks(key$.value, elements, params, params$label.position) + Guide$build_ticks(key$.value, elements, params, elements$text_position) }, build_decor = function(decor, grobs, elements, params) { @@ -372,8 +295,8 @@ GuideBins <- ggproto( sizes <- measure_legend_keys( decor, nkeys, dim, byrow = FALSE, - default_width = elements$key.width, - default_height = elements$key.height + default_width = elements$key_width, + default_height = elements$key_height ) sizes <- lapply(sizes, function(x) rep_len(max(x), length(x))) @@ -395,13 +318,13 @@ GuideBins <- ggproto( name = key_nm, clip = "off") axis <- switch( - params$label.position, + elements$text_position, "top" = list(x = c(0, 1), y = c(1, 1)), "bottom" = list(x = c(0, 1), y = c(0, 0)), "left" = list(x = c(0, 0), y = c(0, 1)), "right" = list(x = c(1, 1), y = c(0, 1)) ) - axis <- element_grob(elements$line, x = axis$x, y = axis$y) + axis <- element_grob(elements$axis_line, x = axis$x, y = axis$y) list(keys = gt, axis_line = axis, ticks = grobs$ticks) }, From 854bca720606136ea99444b572b562fda0bc0d36 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Thu, 30 Nov 2023 17:37:29 +0100 Subject: [PATCH 08/24] replacement utility --- R/utilities.R | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/R/utilities.R b/R/utilities.R index 1efbc121ff..c99cf103b1 100644 --- a/R/utilities.R +++ b/R/utilities.R @@ -710,6 +710,17 @@ vec_rbind0 <- function(..., .error_call = current_env(), .call = caller_env()) { ) } +replace_null <- function(list, ..., env = caller_env()) { + # Collect dots without evaluating + dots <- match.call(replace_null, expand.dots = FALSE)$`...` + # Select arguments that are null in `list` + nms <- names(dots) + nms <- nms[vapply(list[nms], is.null, logical(1))] + # Replace those with the evaluated dots + list[nms] <- inject(list(!!!dots[nms]), env = env) + list +} + attach_plot_env <- function(env) { old_env <- getOption("ggplot2_plot_env") options(ggplot2_plot_env = env) From 21a000bca1acefbbe052627128648cd29deeb64c Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Fri, 1 Dec 2023 10:17:41 +0100 Subject: [PATCH 09/24] redocument --- R/guide-bins.R | 24 ++---- R/guide-colorbar.R | 60 ++++++--------- R/guide-colorsteps.R | 7 +- R/guide-legend.R | 82 ++++++--------------- man/element.Rd | 20 ++--- man/guide_axis.Rd | 5 ++ man/guide_axis_logticks.Rd | 5 ++ man/guide_axis_theta.Rd | 5 ++ man/guide_bins.Rd | 93 +++--------------------- man/guide_colourbar.Rd | 145 ++++++++----------------------------- man/guide_coloursteps.Rd | 69 ++---------------- man/guide_legend.Rd | 113 ++++++----------------------- 12 files changed, 143 insertions(+), 485 deletions(-) diff --git a/R/guide-bins.R b/R/guide-bins.R index 0b7e527a98..f5feabdd53 100644 --- a/R/guide-bins.R +++ b/R/guide-bins.R @@ -11,26 +11,11 @@ NULL #' guide if they are mapped in the same way. #' #' @inheritParams guide_legend -#' @param axis A theme object for rendering a small axis along the guide. -#' Usually, the object of `element_line()` is expected (default). If -#' `element_blank()`, no axis is drawn. For backward compatibility, can also -#' be a logical which translates `TRUE` to `element_line()` and `FALSE` to -#' `element_blank()`. -#' @param axis.colour,axis.linewidth Graphic specifications for the look of the -#' axis. -#' @param axis.arrow A call to `arrow()` to specify arrows at the end of the -#' axis line, thus showing an open interval. #' @param show.limits Logical. Should the limits of the scale be shown with #' labels and ticks. Default is `NULL` meaning it will take the value from the #' scale. This argument is ignored if `labels` is given as a vector of #' values. If one or both of the limits is also given in `breaks` it will be #' shown irrespective of the value of `show.limits`. -#' @param ticks A theme object for rendering tick marks at the colourbar. -#' Usually, the object of `element_line()` is expected. If `element_blank()`, -#' no tick marks are drawn. If `NULL` (default), the `axis` argument is -#' re-used as `ticks` argument (without arrow). -#' @param ticks.length A numeric or a [grid::unit()] object specifying the -#' length of tick marks between the keys. #' #' @section Use with discrete scale: #' This guide is intended to show binned data and work together with ggplot2's @@ -57,13 +42,14 @@ NULL #' p #' #' # Remove the axis or style it -#' p + guides(size = guide_bins(axis = FALSE)) +#' p + guides(size = guide_bins(theme = theme(legend.axis = element_blank()))) #' #' p + guides(size = guide_bins(show.limits = TRUE)) #' -#' p + guides(size = guide_bins( -#' axis.arrow = arrow(length = unit(1.5, 'mm'), ends = 'both') -#' )) +#' my_arrow <- arrow(length = unit(1.5, "mm"), ends = "both") +#' p + guides(size = guide_bins(theme = theme( +#' legend.axis = element_line(arrow = my_arrow) +#' ))) #' #' # Guides are merged together if possible #' ggplot(mtcars) + diff --git a/R/guide-colorbar.R b/R/guide-colorbar.R index 92b90d43a1..97426f0e85 100644 --- a/R/guide-colorbar.R +++ b/R/guide-colorbar.R @@ -15,46 +15,18 @@ NULL #' see [guides()]. #' #' @inheritParams guide_legend -#' @param barwidth A numeric or a [grid::unit()] object specifying -#' the width of the colourbar. Default value is `legend.key.width` or -#' `legend.key.size` in [theme()] or theme. -#' @param barheight A numeric or a [grid::unit()] object specifying -#' the height of the colourbar. Default value is `legend.key.height` or -#' `legend.key.size` in [theme()] or theme. -#' @param frame A theme object for rendering a frame drawn around the bar. -#' Usually, the object of `element_rect()` is expected. If `element_blank()` -#' (default), no frame is drawn. -#' @param frame.colour A string specifying the colour of the frame -#' drawn around the bar. For backward compatibility, if this argument is -#' not `NULL`, the `frame` argument will be set to `element_rect()`. -#' @param frame.linewidth A numeric specifying the width of the frame -#' drawn around the bar in millimetres. -#' @param frame.linetype A numeric specifying the linetype of the frame -#' drawn around the bar. #' @param nbin A numeric specifying the number of bins for drawing the #' colourbar. A smoother colourbar results from a larger value. #' @param raster A logical. If `TRUE` then the colourbar is rendered as a #' raster object. If `FALSE` then the colourbar is rendered as a set of #' rectangles. Note that not all graphics devices are capable of rendering #' raster image. -#' @param ticks A theme object for rendering tick marks at the colourbar. -#' Usually, the object of `element_line()` is expected (default). If -#' `element_blank()`, no tick marks are drawn. For backward compatibility, -#' can also be a logical which translates `TRUE` to `element_line()` and -#' `FALSE` to `element_blank()`. -#' @param ticks.colour A string specifying the colour of the tick marks. -#' @param ticks.linewidth A numeric specifying the width of the tick marks in -#' millimetres. -#' @param ticks.length A numeric or a [grid::unit()] object specifying the -#' length of tick marks at the colourbar. #' @param draw.ulim A logical specifying if the upper limit tick marks should #' be visible. #' @param draw.llim A logical specifying if the lower limit tick marks should #' be visible. #' @param direction A character string indicating the direction of the guide. #' One of "horizontal" or "vertical." -#' @param default.unit A character string indicating [grid::unit()] -#' for `barwidth` and `barheight`. #' @param reverse logical. If `TRUE` the colourbar is reversed. By default, #' the highest value is on the top and the lowest value is on the bottom #' @param available_aes A vector of character strings listing the aesthetics @@ -78,19 +50,31 @@ NULL #' # Control styles #' #' # bar size -#' p1 + guides(fill = guide_colourbar(barwidth = 0.5, barheight = 10)) +#' p1 + guides(fill = guide_colourbar(theme = theme( +#' legend.key.width = unit(0.5, "lines"), +#' legend.key.height = unit(10, "lines") +#' ))) +#' #' #' # no label -#' p1 + guides(fill = guide_colourbar(label = FALSE)) +#' p1 + guides(fill = guide_colourbar(theme = theme( +#' legend.text = element_blank() +#' ))) #' #' # no tick marks -#' p1 + guides(fill = guide_colourbar(ticks = FALSE)) +#' p1 + guides(fill = guide_colourbar(theme = theme( +#' legend.ticks = element_blank() +#' ))) #' #' # label position -#' p1 + guides(fill = guide_colourbar(label.position = "left")) +#' p1 + guides(fill = guide_colourbar(theme = theme( +#' legend.text.position = "left" +#' ))) #' #' # label theme -#' p1 + guides(fill = guide_colourbar(label.theme = element_text(colour = "blue", angle = 0))) +#' p1 + guides(fill = guide_colourbar(theme = theme( +#' legend.text = element_text(colour = "blue", angle = 0) +#' ))) #' #' # small number of bins #' p1 + guides(fill = guide_colourbar(nbin = 3)) @@ -103,7 +87,7 @@ NULL #' scale_fill_continuous( #' limits = c(0,20), breaks = c(0, 5, 10, 15, 20), #' guide = guide_colourbar(nbin = 100, draw.ulim = FALSE, draw.llim = FALSE) -#' ) +#' ) #' #' # guides can be controlled independently #' p2 + @@ -112,8 +96,12 @@ NULL #' p2 + guides(fill = "colourbar", size = "legend") #' #' p2 + -#' scale_fill_continuous(guide = guide_colourbar(direction = "horizontal")) + -#' scale_size(guide = guide_legend(direction = "vertical")) +#' scale_fill_continuous(guide = guide_colourbar(theme = theme( +#' legend.direction = "horizontal" +#' ))) + +#' scale_size(guide = guide_legend(theme = theme( +#' legend.direction = "vertical" +#' ))) guide_colourbar <- function( # title diff --git a/R/guide-colorsteps.R b/R/guide-colorsteps.R index 275f867128..588fb83196 100644 --- a/R/guide-colorsteps.R +++ b/R/guide-colorsteps.R @@ -11,12 +11,7 @@ #' scale. This argument is ignored if `labels` is given as a vector of #' values. If one or both of the limits is also given in `breaks` it will be #' shown irrespective of the value of `show.limits`. -#' @param ticks A theme object for rendering tick marks at the colourbar. -#' Usually, the object of `element_line()` is expected. If `element_blank()` -#' (default), no tick marks are drawn. For backward compatibility, can also -#' be a logical which translates `TRUE` to `element_line()` and `FALSE` to -#' `element_blank()`. -#' @inheritDotParams guide_colourbar -nbin -raster -ticks -available_aes +#' @inheritDotParams guide_colourbar -nbin -raster -available_aes #' #' @inheritSection guide_bins Use with discrete scale #' diff --git a/R/guide-legend.R b/R/guide-legend.R index d4d60301e3..10e10ccd06 100644 --- a/R/guide-legend.R +++ b/R/guide-legend.R @@ -13,49 +13,15 @@ #' If `NULL`, the title is not shown. By default #' ([waiver()]), the name of the scale object or the name #' specified in [labs()] is used for the title. -#' @param title.position A character string indicating the position of a -#' title. One of "top" (default for a vertical guide), "bottom", "left" -#' (default for a horizontal guide), or "right." -#' @param title.theme A theme object for rendering the title text. Usually the -#' object of [element_text()] is expected. By default, the theme is -#' specified by `legend.title` in [theme()] or theme. -#' @param title.hjust A number specifying horizontal justification of the -#' title text. -#' @param title.vjust A number specifying vertical justification of the title -#' text. -#' @param label logical. If `TRUE` then the labels are drawn. If -#' `FALSE` then the labels are invisible. -#' @param label.position A character string indicating the position of a -#' label. One of "top", "bottom" (default for horizontal guide), "left", or -#' "right" (default for vertical guide). -#' @param label.theme A theme object for rendering the label text. Usually the -#' object of [element_text()] is expected. By default, the theme is -#' specified by `legend.text` in [theme()]. -#' @param label.hjust A numeric specifying horizontal justification of the -#' label text. The default for standard text is 0 (left-aligned) and 1 -#' (right-aligned) for expressions. -#' @param label.vjust A numeric specifying vertical justification of the label -#' text. -#' @param keywidth A numeric or a [grid::unit()] object specifying -#' the width of the legend key. Default value is `legend.key.width` or -#' `legend.key.size` in [theme()]. -#' @param keyheight A numeric or a [grid::unit()] object specifying -#' the height of the legend key. Default value is `legend.key.height` or -#' `legend.key.size` in [theme()]. -#' @param key.spacing,key.spacing.x,key.spacing.y A numeric or [grid::unit()] -#' object specifying the distance between key-label pairs in the horizontal -#' direction (`key.spacing.x`), vertical direction (`key.spacing.y`) or both -#' (`key.spacing`). +#' @param theme A [`theme`][theme()] object to style the guide individually or +#' differently from the plot's theme settings. The `theme` argument in the +#' guide override,s and is combined with, the plot's theme. #' @param direction A character string indicating the direction of the guide. #' One of "horizontal" or "vertical." -#' @param default.unit A character string indicating [grid::unit()] -#' for `keywidth` and `keyheight`. #' @param override.aes A list specifying aesthetic parameters of legend key. #' See details and examples. -#' @param nrow The desired number of rows of legends. -#' @param ncol The desired number of column of legends. -#' @param byrow logical. If `FALSE` (the default) the legend-matrix is -#' filled by columns, otherwise the legend-matrix is filled by rows. +#' @param nrow,ncol The desired number of rows and column of legends +#' respectively. #' @param reverse logical. If `TRUE` the order of legends is reversed. #' @param order positive integer less than 99 that specifies the order of #' this guide among multiple guides. This controls the order in which @@ -78,36 +44,32 @@ #' # Control styles #' #' # title position -#' p1 + guides(fill = guide_legend(title = "LEFT", title.position = "left")) +#' p1 + guides(fill = guide_legend( +#' title = "LEFT", theme(legend.title.position = "left") +#' )) #' #' # title text styles via element_text -#' p1 + guides(fill = -#' guide_legend( -#' title.theme = element_text( -#' size = 15, -#' face = "italic", -#' colour = "red", -#' angle = 0 -#' ) -#' ) -#' ) +#' p1 + guides(fill = guide_legend(theme = theme( +#' legend.title = element_text(size = 15, face = "italic", colour = "red") +#' ))) #' #' # label position -#' p1 + guides(fill = guide_legend(label.position = "left", label.hjust = 1)) +#' p1 + guides(fill = guide_legend(theme = theme( +#' legend.text.position = "left", +#' legend.text = element_text(hjust = 1) +#' ))) #' #' # label styles #' p1 + #' scale_fill_continuous( #' breaks = c(5, 10, 15), #' labels = paste("long", c(5, 10, 15)), -#' guide = guide_legend( -#' direction = "horizontal", -#' title.position = "top", -#' label.position = "bottom", -#' label.hjust = 0.5, -#' label.vjust = 1, -#' label.theme = element_text(angle = 90) -#' ) +#' guide = guide_legend(theme = theme( +#' legend.direction = "horizontal", +#' legend.title.position = "top", +#' legend.text.position = "bottom", +#' legend.text = element_text(hjust = 0.5, vjust = 1, angle = 90) +#' )) #' ) #' #' # Set aesthetic of legend key @@ -124,7 +86,7 @@ #' geom_point(aes(colour = color)) #' p + guides(col = guide_legend(nrow = 8)) #' p + guides(col = guide_legend(ncol = 8)) -#' p + guides(col = guide_legend(nrow = 8, byrow = TRUE)) +#' p + guides(col = guide_legend(nrow = 8, theme = theme(legend.byrow = TRUE))) #' #' # reversed order legend #' p + guides(col = guide_legend(reverse = TRUE)) diff --git a/man/element.Rd b/man/element.Rd index 3102d9a7d4..a3c27a259c 100644 --- a/man/element.Rd +++ b/man/element.Rd @@ -1,16 +1,14 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/margins.R, R/theme-elements.R -\name{margin} -\alias{margin} +% Please edit documentation in R/theme-elements.R, R/margins.R +\name{element} \alias{element_blank} \alias{element_rect} \alias{element_line} \alias{element_text} \alias{rel} +\alias{margin} \title{Theme elements} \usage{ -margin(t = 0, r = 0, b = 0, l = 0, unit = "pt") - element_blank() element_rect( @@ -50,13 +48,10 @@ element_text( ) rel(x) + +margin(t = 0, r = 0, b = 0, l = 0, unit = "pt") } \arguments{ -\item{t, r, b, l}{Dimensions of each margin. (To remember order, think trouble).} - -\item{unit}{Default units of dimensions. Defaults to "pt" so it -can be most easily scaled with the text.} - \item{fill}{Fill colour.} \item{colour, color}{Line/border colour. Color is an alias for colour.} @@ -101,6 +96,11 @@ rectangle behind the complete text area, and a point where each label is anchored.} \item{x}{A single number specifying size relative to parent element.} + +\item{t, r, b, l}{Dimensions of each margin. (To remember order, think trouble).} + +\item{unit}{Default units of dimensions. Defaults to "pt" so it +can be most easily scaled with the text.} } \value{ An S3 object of class \code{element}, \code{rel}, or \code{margin}. diff --git a/man/guide_axis.Rd b/man/guide_axis.Rd index fa09421300..8b3b511a03 100644 --- a/man/guide_axis.Rd +++ b/man/guide_axis.Rd @@ -6,6 +6,7 @@ \usage{ guide_axis( title = waiver(), + theme = NULL, check.overlap = FALSE, angle = waiver(), n.dodge = 1, @@ -21,6 +22,10 @@ If \code{NULL}, the title is not shown. By default (\code{\link[=waiver]{waiver()}}), the name of the scale object or the name specified in \code{\link[=labs]{labs()}} is used for the title.} +\item{theme}{A \code{\link[=theme]{theme}} object to style the guide individually or +differently from the plot's theme settings. The \code{theme} argument in the +guide override,s and is combined with, the plot's theme.} + \item{check.overlap}{silently remove overlapping labels, (recursively) prioritizing the first, last, and middle labels.} diff --git a/man/guide_axis_logticks.Rd b/man/guide_axis_logticks.Rd index 60ebaa8b12..16dbbafd30 100644 --- a/man/guide_axis_logticks.Rd +++ b/man/guide_axis_logticks.Rd @@ -13,6 +13,7 @@ guide_axis_logticks( short_theme = element_line(), expanded = TRUE, cap = "none", + theme = NULL, ... ) } @@ -45,6 +46,10 @@ be \code{"none"} (default) to draw the axis line along the whole panel, or \code{"both"} to only draw the line in between the most extreme breaks. \code{TRUE} and \code{FALSE} are shorthand for \code{"both"} and \code{"none"} respectively.} +\item{theme}{A \code{\link[=theme]{theme}} object to style the guide individually or +differently from the plot's theme settings. The \code{theme} argument in the +guide override,s and is combined with, the plot's theme.} + \item{...}{ Arguments passed on to \code{\link[=guide_axis]{guide_axis}} \describe{ diff --git a/man/guide_axis_theta.Rd b/man/guide_axis_theta.Rd index 16a8e89cf1..b7ee8b9d92 100644 --- a/man/guide_axis_theta.Rd +++ b/man/guide_axis_theta.Rd @@ -6,6 +6,7 @@ \usage{ guide_axis_theta( title = waiver(), + theme = NULL, angle = waiver(), minor.ticks = FALSE, cap = "none", @@ -19,6 +20,10 @@ If \code{NULL}, the title is not shown. By default (\code{\link[=waiver]{waiver()}}), the name of the scale object or the name specified in \code{\link[=labs]{labs()}} is used for the title.} +\item{theme}{A \code{\link[=theme]{theme}} object to style the guide individually or +differently from the plot's theme settings. The \code{theme} argument in the +guide override,s and is combined with, the plot's theme.} + \item{angle}{Compared to setting the angle in \code{\link[=theme]{theme()}} / \code{\link[=element_text]{element_text()}}, this also uses some heuristics to automatically pick the \code{hjust} and \code{vjust} that you probably want. Can be one of the following: diff --git a/man/guide_bins.Rd b/man/guide_bins.Rd index 6eeada9598..ad79b63d40 100644 --- a/man/guide_bins.Rd +++ b/man/guide_bins.Rd @@ -6,25 +6,8 @@ \usage{ guide_bins( title = waiver(), - title.position = NULL, - title.theme = NULL, - title.hjust = NULL, - title.vjust = NULL, - label = TRUE, - label.position = NULL, - label.theme = NULL, - label.hjust = NULL, - label.vjust = NULL, - keywidth = NULL, - keyheight = NULL, - axis = TRUE, - axis.colour = "black", - axis.linewidth = NULL, - axis.arrow = NULL, - ticks = NULL, - ticks.length = unit(0.2, "npc"), + theme = NULL, direction = NULL, - default.unit = "line", override.aes = list(), reverse = FALSE, order = 0, @@ -38,72 +21,13 @@ If \code{NULL}, the title is not shown. By default (\code{\link[=waiver]{waiver()}}), the name of the scale object or the name specified in \code{\link[=labs]{labs()}} is used for the title.} -\item{title.position}{A character string indicating the position of a -title. One of "top" (default for a vertical guide), "bottom", "left" -(default for a horizontal guide), or "right."} - -\item{title.theme}{A theme object for rendering the title text. Usually the -object of \code{\link[=element_text]{element_text()}} is expected. By default, the theme is -specified by \code{legend.title} in \code{\link[=theme]{theme()}} or theme.} - -\item{title.hjust}{A number specifying horizontal justification of the -title text.} - -\item{title.vjust}{A number specifying vertical justification of the title -text.} - -\item{label}{logical. If \code{TRUE} then the labels are drawn. If -\code{FALSE} then the labels are invisible.} - -\item{label.position}{A character string indicating the position of a -label. One of "top", "bottom" (default for horizontal guide), "left", or -"right" (default for vertical guide).} - -\item{label.theme}{A theme object for rendering the label text. Usually the -object of \code{\link[=element_text]{element_text()}} is expected. By default, the theme is -specified by \code{legend.text} in \code{\link[=theme]{theme()}}.} - -\item{label.hjust}{A numeric specifying horizontal justification of the -label text. The default for standard text is 0 (left-aligned) and 1 -(right-aligned) for expressions.} - -\item{label.vjust}{A numeric specifying vertical justification of the label -text.} - -\item{keywidth}{A numeric or a \code{\link[grid:unit]{grid::unit()}} object specifying -the width of the legend key. Default value is \code{legend.key.width} or -\code{legend.key.size} in \code{\link[=theme]{theme()}}.} - -\item{keyheight}{A numeric or a \code{\link[grid:unit]{grid::unit()}} object specifying -the height of the legend key. Default value is \code{legend.key.height} or -\code{legend.key.size} in \code{\link[=theme]{theme()}}.} - -\item{axis}{A theme object for rendering a small axis along the guide. -Usually, the object of \code{element_line()} is expected (default). If -\code{element_blank()}, no axis is drawn. For backward compatibility, can also -be a logical which translates \code{TRUE} to \code{element_line()} and \code{FALSE} to -\code{element_blank()}.} - -\item{axis.colour, axis.linewidth}{Graphic specifications for the look of the -axis.} - -\item{axis.arrow}{A call to \code{arrow()} to specify arrows at the end of the -axis line, thus showing an open interval.} - -\item{ticks}{A theme object for rendering tick marks at the colourbar. -Usually, the object of \code{element_line()} is expected. If \code{element_blank()}, -no tick marks are drawn. If \code{NULL} (default), the \code{axis} argument is -re-used as \code{ticks} argument (without arrow).} - -\item{ticks.length}{A numeric or a \code{\link[grid:unit]{grid::unit()}} object specifying the -length of tick marks between the keys.} +\item{theme}{A \code{\link[=theme]{theme}} object to style the guide individually or +differently from the plot's theme settings. The \code{theme} argument in the +guide override,s and is combined with, the plot's theme.} \item{direction}{A character string indicating the direction of the guide. One of "horizontal" or "vertical."} -\item{default.unit}{A character string indicating \code{\link[grid:unit]{grid::unit()}} -for \code{keywidth} and \code{keyheight}.} - \item{override.aes}{A list specifying aesthetic parameters of legend key. See details and examples.} @@ -156,13 +80,14 @@ p <- ggplot(mtcars) + p # Remove the axis or style it -p + guides(size = guide_bins(axis = FALSE)) +p + guides(size = guide_bins(theme = theme(legend.axis = element_blank()))) p + guides(size = guide_bins(show.limits = TRUE)) -p + guides(size = guide_bins( - axis.arrow = arrow(length = unit(1.5, 'mm'), ends = 'both') -)) +my_arrow <- arrow(length = unit(1.5, "mm"), ends = "both") +p + guides(size = guide_bins(theme = theme( + legend.axis = element_line(arrow = my_arrow) +))) # Guides are merged together if possible ggplot(mtcars) + diff --git a/man/guide_colourbar.Rd b/man/guide_colourbar.Rd index 7813c12b1c..e4582bda3d 100644 --- a/man/guide_colourbar.Rd +++ b/man/guide_colourbar.Rd @@ -7,27 +7,9 @@ \usage{ guide_colourbar( title = waiver(), - title.position = NULL, - title.theme = NULL, - title.hjust = NULL, - title.vjust = NULL, - label = TRUE, - label.position = NULL, - label.theme = NULL, - label.hjust = NULL, - label.vjust = NULL, - barwidth = NULL, - barheight = NULL, + theme = NULL, nbin = 300, raster = TRUE, - frame = element_blank(), - frame.colour = NULL, - frame.linewidth = NULL, - frame.linetype = NULL, - ticks = element_line(), - ticks.colour = NULL, - ticks.linewidth = NULL, - ticks.length = unit(0.2, "npc"), draw.ulim = TRUE, draw.llim = TRUE, direction = NULL, @@ -40,27 +22,9 @@ guide_colourbar( guide_colorbar( title = waiver(), - title.position = NULL, - title.theme = NULL, - title.hjust = NULL, - title.vjust = NULL, - label = TRUE, - label.position = NULL, - label.theme = NULL, - label.hjust = NULL, - label.vjust = NULL, - barwidth = NULL, - barheight = NULL, + theme = NULL, nbin = 300, raster = TRUE, - frame = element_blank(), - frame.colour = NULL, - frame.linewidth = NULL, - frame.linetype = NULL, - ticks = element_line(), - ticks.colour = NULL, - ticks.linewidth = NULL, - ticks.length = unit(0.2, "npc"), draw.ulim = TRUE, draw.llim = TRUE, direction = NULL, @@ -77,45 +41,9 @@ If \code{NULL}, the title is not shown. By default (\code{\link[=waiver]{waiver()}}), the name of the scale object or the name specified in \code{\link[=labs]{labs()}} is used for the title.} -\item{title.position}{A character string indicating the position of a -title. One of "top" (default for a vertical guide), "bottom", "left" -(default for a horizontal guide), or "right."} - -\item{title.theme}{A theme object for rendering the title text. Usually the -object of \code{\link[=element_text]{element_text()}} is expected. By default, the theme is -specified by \code{legend.title} in \code{\link[=theme]{theme()}} or theme.} - -\item{title.hjust}{A number specifying horizontal justification of the -title text.} - -\item{title.vjust}{A number specifying vertical justification of the title -text.} - -\item{label}{logical. If \code{TRUE} then the labels are drawn. If -\code{FALSE} then the labels are invisible.} - -\item{label.position}{A character string indicating the position of a -label. One of "top", "bottom" (default for horizontal guide), "left", or -"right" (default for vertical guide).} - -\item{label.theme}{A theme object for rendering the label text. Usually the -object of \code{\link[=element_text]{element_text()}} is expected. By default, the theme is -specified by \code{legend.text} in \code{\link[=theme]{theme()}}.} - -\item{label.hjust}{A numeric specifying horizontal justification of the -label text. The default for standard text is 0 (left-aligned) and 1 -(right-aligned) for expressions.} - -\item{label.vjust}{A numeric specifying vertical justification of the label -text.} - -\item{barwidth}{A numeric or a \code{\link[grid:unit]{grid::unit()}} object specifying -the width of the colourbar. Default value is \code{legend.key.width} or -\code{legend.key.size} in \code{\link[=theme]{theme()}} or theme.} - -\item{barheight}{A numeric or a \code{\link[grid:unit]{grid::unit()}} object specifying -the height of the colourbar. Default value is \code{legend.key.height} or -\code{legend.key.size} in \code{\link[=theme]{theme()}} or theme.} +\item{theme}{A \code{\link[=theme]{theme}} object to style the guide individually or +differently from the plot's theme settings. The \code{theme} argument in the +guide override,s and is combined with, the plot's theme.} \item{nbin}{A numeric specifying the number of bins for drawing the colourbar. A smoother colourbar results from a larger value.} @@ -125,34 +53,6 @@ raster object. If \code{FALSE} then the colourbar is rendered as a set of rectangles. Note that not all graphics devices are capable of rendering raster image.} -\item{frame}{A theme object for rendering a frame drawn around the bar. -Usually, the object of \code{element_rect()} is expected. If \code{element_blank()} -(default), no frame is drawn.} - -\item{frame.colour}{A string specifying the colour of the frame -drawn around the bar. For backward compatibility, if this argument is -not \code{NULL}, the \code{frame} argument will be set to \code{element_rect()}.} - -\item{frame.linewidth}{A numeric specifying the width of the frame -drawn around the bar in millimetres.} - -\item{frame.linetype}{A numeric specifying the linetype of the frame -drawn around the bar.} - -\item{ticks}{A theme object for rendering tick marks at the colourbar. -Usually, the object of \code{element_line()} is expected (default). If -\code{element_blank()}, no tick marks are drawn. For backward compatibility, -can also be a logical which translates \code{TRUE} to \code{element_line()} and -\code{FALSE} to \code{element_blank()}.} - -\item{ticks.colour}{A string specifying the colour of the tick marks.} - -\item{ticks.linewidth}{A numeric specifying the width of the tick marks in -millimetres.} - -\item{ticks.length}{A numeric or a \code{\link[grid:unit]{grid::unit()}} object specifying the -length of tick marks at the colourbar.} - \item{draw.ulim}{A logical specifying if the upper limit tick marks should be visible.} @@ -162,9 +62,6 @@ be visible.} \item{direction}{A character string indicating the direction of the guide. One of "horizontal" or "vertical."} -\item{default.unit}{A character string indicating \code{\link[grid:unit]{grid::unit()}} -for \code{barwidth} and \code{barheight}.} - \item{reverse}{logical. If \code{TRUE} the colourbar is reversed. By default, the highest value is on the top and the lowest value is on the bottom} @@ -209,19 +106,31 @@ p1 + guides(fill = guide_colourbar()) # Control styles # bar size -p1 + guides(fill = guide_colourbar(barwidth = 0.5, barheight = 10)) +p1 + guides(fill = guide_colourbar(theme = theme( + legend.key.width = unit(0.5, "lines"), + legend.key.height = unit(10, "lines") +))) + # no label -p1 + guides(fill = guide_colourbar(label = FALSE)) +p1 + guides(fill = guide_colourbar(theme = theme( + legend.text = element_blank() +))) # no tick marks -p1 + guides(fill = guide_colourbar(ticks = FALSE)) +p1 + guides(fill = guide_colourbar(theme = theme( + legend.ticks = element_blank() +))) # label position -p1 + guides(fill = guide_colourbar(label.position = "left")) +p1 + guides(fill = guide_colourbar(theme = theme( + legend.text.position = "left" +))) # label theme -p1 + guides(fill = guide_colourbar(label.theme = element_text(colour = "blue", angle = 0))) +p1 + guides(fill = guide_colourbar(theme = theme( + legend.text = element_text(colour = "blue", angle = 0) +))) # small number of bins p1 + guides(fill = guide_colourbar(nbin = 3)) @@ -234,7 +143,7 @@ p1 + scale_fill_continuous( limits = c(0,20), breaks = c(0, 5, 10, 15, 20), guide = guide_colourbar(nbin = 100, draw.ulim = FALSE, draw.llim = FALSE) - ) + ) # guides can be controlled independently p2 + @@ -243,8 +152,12 @@ p2 + p2 + guides(fill = "colourbar", size = "legend") p2 + - scale_fill_continuous(guide = guide_colourbar(direction = "horizontal")) + - scale_size(guide = guide_legend(direction = "vertical")) + scale_fill_continuous(guide = guide_colourbar(theme = theme( + legend.direction = "horizontal" + ))) + + scale_size(guide = guide_legend(theme = theme( + legend.direction = "vertical" + ))) } \seealso{ Other guides: diff --git a/man/guide_coloursteps.Rd b/man/guide_coloursteps.Rd index e97230b6f4..7c832c7fe5 100644 --- a/man/guide_coloursteps.Rd +++ b/man/guide_coloursteps.Rd @@ -5,19 +5,9 @@ \alias{guide_colorsteps} \title{Discretized colourbar guide} \usage{ -guide_coloursteps( - even.steps = TRUE, - show.limits = NULL, - ticks = element_blank(), - ... -) +guide_coloursteps(even.steps = TRUE, show.limits = NULL, ...) -guide_colorsteps( - even.steps = TRUE, - show.limits = NULL, - ticks = element_blank(), - ... -) +guide_colorsteps(even.steps = TRUE, show.limits = NULL, ...) } \arguments{ \item{even.steps}{Should the rendered size of the bins be equal, or should @@ -29,73 +19,24 @@ scale. This argument is ignored if \code{labels} is given as a vector of values. If one or both of the limits is also given in \code{breaks} it will be shown irrespective of the value of \code{show.limits}.} -\item{ticks}{A theme object for rendering tick marks at the colourbar. -Usually, the object of \code{element_line()} is expected. If \code{element_blank()} -(default), no tick marks are drawn. For backward compatibility, can also -be a logical which translates \code{TRUE} to \code{element_line()} and \code{FALSE} to -\code{element_blank()}.} - \item{...}{ Arguments passed on to \code{\link[=guide_colourbar]{guide_colourbar}} \describe{ - \item{\code{barwidth}}{A numeric or a \code{\link[grid:unit]{grid::unit()}} object specifying -the width of the colourbar. Default value is \code{legend.key.width} or -\code{legend.key.size} in \code{\link[=theme]{theme()}} or theme.} - \item{\code{barheight}}{A numeric or a \code{\link[grid:unit]{grid::unit()}} object specifying -the height of the colourbar. Default value is \code{legend.key.height} or -\code{legend.key.size} in \code{\link[=theme]{theme()}} or theme.} - \item{\code{frame}}{A theme object for rendering a frame drawn around the bar. -Usually, the object of \code{element_rect()} is expected. If \code{element_blank()} -(default), no frame is drawn.} - \item{\code{frame.colour}}{A string specifying the colour of the frame -drawn around the bar. For backward compatibility, if this argument is -not \code{NULL}, the \code{frame} argument will be set to \code{element_rect()}.} - \item{\code{frame.linewidth}}{A numeric specifying the width of the frame -drawn around the bar in millimetres.} - \item{\code{frame.linetype}}{A numeric specifying the linetype of the frame -drawn around the bar.} - \item{\code{ticks.colour}}{A string specifying the colour of the tick marks.} - \item{\code{ticks.linewidth}}{A numeric specifying the width of the tick marks in -millimetres.} - \item{\code{ticks.length}}{A numeric or a \code{\link[grid:unit]{grid::unit()}} object specifying the -length of tick marks at the colourbar.} \item{\code{draw.ulim}}{A logical specifying if the upper limit tick marks should be visible.} \item{\code{draw.llim}}{A logical specifying if the lower limit tick marks should be visible.} \item{\code{direction}}{A character string indicating the direction of the guide. One of "horizontal" or "vertical."} - \item{\code{default.unit}}{A character string indicating \code{\link[grid:unit]{grid::unit()}} -for \code{barwidth} and \code{barheight}.} \item{\code{reverse}}{logical. If \code{TRUE} the colourbar is reversed. By default, the highest value is on the top and the lowest value is on the bottom} \item{\code{title}}{A character string or expression indicating a title of guide. If \code{NULL}, the title is not shown. By default (\code{\link[=waiver]{waiver()}}), the name of the scale object or the name specified in \code{\link[=labs]{labs()}} is used for the title.} - \item{\code{title.position}}{A character string indicating the position of a -title. One of "top" (default for a vertical guide), "bottom", "left" -(default for a horizontal guide), or "right."} - \item{\code{title.theme}}{A theme object for rendering the title text. Usually the -object of \code{\link[=element_text]{element_text()}} is expected. By default, the theme is -specified by \code{legend.title} in \code{\link[=theme]{theme()}} or theme.} - \item{\code{title.hjust}}{A number specifying horizontal justification of the -title text.} - \item{\code{title.vjust}}{A number specifying vertical justification of the title -text.} - \item{\code{label}}{logical. If \code{TRUE} then the labels are drawn. If -\code{FALSE} then the labels are invisible.} - \item{\code{label.position}}{A character string indicating the position of a -label. One of "top", "bottom" (default for horizontal guide), "left", or -"right" (default for vertical guide).} - \item{\code{label.theme}}{A theme object for rendering the label text. Usually the -object of \code{\link[=element_text]{element_text()}} is expected. By default, the theme is -specified by \code{legend.text} in \code{\link[=theme]{theme()}}.} - \item{\code{label.hjust}}{A numeric specifying horizontal justification of the -label text. The default for standard text is 0 (left-aligned) and 1 -(right-aligned) for expressions.} - \item{\code{label.vjust}}{A numeric specifying vertical justification of the label -text.} + \item{\code{theme}}{A \code{\link[=theme]{theme}} object to style the guide individually or +differently from the plot's theme settings. The \code{theme} argument in the +guide override,s and is combined with, the plot's theme.} \item{\code{order}}{positive integer less than 99 that specifies the order of this guide among multiple guides. This controls the order in which multiple guides are displayed, not the contents of the guide itself. diff --git a/man/guide_legend.Rd b/man/guide_legend.Rd index 224de5587a..ba1704a908 100644 --- a/man/guide_legend.Rd +++ b/man/guide_legend.Rd @@ -6,26 +6,11 @@ \usage{ guide_legend( title = waiver(), - title.position = NULL, - title.theme = NULL, - title.hjust = NULL, - title.vjust = NULL, - label = TRUE, - label.position = NULL, - label.theme = NULL, - label.hjust = NULL, - label.vjust = NULL, - keywidth = NULL, - keyheight = NULL, - key.spacing = NULL, - key.spacing.x = NULL, - key.spacing.y = NULL, + theme = NULL, direction = NULL, - default.unit = "line", override.aes = list(), nrow = NULL, ncol = NULL, - byrow = FALSE, reverse = FALSE, order = 0, ... @@ -37,66 +22,18 @@ If \code{NULL}, the title is not shown. By default (\code{\link[=waiver]{waiver()}}), the name of the scale object or the name specified in \code{\link[=labs]{labs()}} is used for the title.} -\item{title.position}{A character string indicating the position of a -title. One of "top" (default for a vertical guide), "bottom", "left" -(default for a horizontal guide), or "right."} - -\item{title.theme}{A theme object for rendering the title text. Usually the -object of \code{\link[=element_text]{element_text()}} is expected. By default, the theme is -specified by \code{legend.title} in \code{\link[=theme]{theme()}} or theme.} - -\item{title.hjust}{A number specifying horizontal justification of the -title text.} - -\item{title.vjust}{A number specifying vertical justification of the title -text.} - -\item{label}{logical. If \code{TRUE} then the labels are drawn. If -\code{FALSE} then the labels are invisible.} - -\item{label.position}{A character string indicating the position of a -label. One of "top", "bottom" (default for horizontal guide), "left", or -"right" (default for vertical guide).} - -\item{label.theme}{A theme object for rendering the label text. Usually the -object of \code{\link[=element_text]{element_text()}} is expected. By default, the theme is -specified by \code{legend.text} in \code{\link[=theme]{theme()}}.} - -\item{label.hjust}{A numeric specifying horizontal justification of the -label text. The default for standard text is 0 (left-aligned) and 1 -(right-aligned) for expressions.} - -\item{label.vjust}{A numeric specifying vertical justification of the label -text.} - -\item{keywidth}{A numeric or a \code{\link[grid:unit]{grid::unit()}} object specifying -the width of the legend key. Default value is \code{legend.key.width} or -\code{legend.key.size} in \code{\link[=theme]{theme()}}.} - -\item{keyheight}{A numeric or a \code{\link[grid:unit]{grid::unit()}} object specifying -the height of the legend key. Default value is \code{legend.key.height} or -\code{legend.key.size} in \code{\link[=theme]{theme()}}.} - -\item{key.spacing, key.spacing.x, key.spacing.y}{A numeric or \code{\link[grid:unit]{grid::unit()}} -object specifying the distance between key-label pairs in the horizontal -direction (\code{key.spacing.x}), vertical direction (\code{key.spacing.y}) or both -(\code{key.spacing}).} +\item{theme}{A \code{\link[=theme]{theme}} object to style the guide individually or +differently from the plot's theme settings. The \code{theme} argument in the +guide override,s and is combined with, the plot's theme.} \item{direction}{A character string indicating the direction of the guide. One of "horizontal" or "vertical."} -\item{default.unit}{A character string indicating \code{\link[grid:unit]{grid::unit()}} -for \code{keywidth} and \code{keyheight}.} - \item{override.aes}{A list specifying aesthetic parameters of legend key. See details and examples.} -\item{nrow}{The desired number of rows of legends.} - -\item{ncol}{The desired number of column of legends.} - -\item{byrow}{logical. If \code{FALSE} (the default) the legend-matrix is -filled by columns, otherwise the legend-matrix is filled by rows.} +\item{nrow, ncol}{The desired number of rows and column of legends +respectively.} \item{reverse}{logical. If \code{TRUE} the order of legends is reversed.} @@ -132,36 +69,32 @@ p1 + scale_fill_continuous(guide = guide_legend()) # Control styles # title position -p1 + guides(fill = guide_legend(title = "LEFT", title.position = "left")) +p1 + guides(fill = guide_legend( + title = "LEFT", theme(legend.title.position = "left") +)) # title text styles via element_text -p1 + guides(fill = - guide_legend( - title.theme = element_text( - size = 15, - face = "italic", - colour = "red", - angle = 0 - ) - ) -) +p1 + guides(fill = guide_legend(theme = theme( + legend.title = element_text(size = 15, face = "italic", colour = "red") +))) # label position -p1 + guides(fill = guide_legend(label.position = "left", label.hjust = 1)) +p1 + guides(fill = guide_legend(theme = theme( + legend.text.position = "left", + legend.text = element_text(hjust = 1) +))) # label styles p1 + scale_fill_continuous( breaks = c(5, 10, 15), labels = paste("long", c(5, 10, 15)), - guide = guide_legend( - direction = "horizontal", - title.position = "top", - label.position = "bottom", - label.hjust = 0.5, - label.vjust = 1, - label.theme = element_text(angle = 90) - ) + guide = guide_legend(theme = theme( + legend.direction = "horizontal", + legend.title.position = "top", + legend.text.position = "bottom", + legend.text = element_text(hjust = 0.5, vjust = 1, angle = 90) + )) ) # Set aesthetic of legend key @@ -178,7 +111,7 @@ p <- ggplot(df, aes(x, y)) + geom_point(aes(colour = color)) p + guides(col = guide_legend(nrow = 8)) p + guides(col = guide_legend(ncol = 8)) -p + guides(col = guide_legend(nrow = 8, byrow = TRUE)) +p + guides(col = guide_legend(nrow = 8, theme = theme(legend.byrow = TRUE))) # reversed order legend p + guides(col = guide_legend(reverse = TRUE)) From 72bcd1caec6855036ca8d875374594a1bc5ae2ea Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Fri, 1 Dec 2023 10:28:34 +0100 Subject: [PATCH 10/24] small tweaks --- R/guide-.R | 4 +++- R/guide-bins.R | 5 ----- R/theme-elements.R | 1 + 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/R/guide-.R b/R/guide-.R index 03ffbd3c0c..6028bf91e1 100644 --- a/R/guide-.R +++ b/R/guide-.R @@ -50,7 +50,9 @@ new_guide <- function(..., available_aes = "any", super) { # Validate theme settings if (!is.null(params$theme)) { - validate_theme(theme) + check_object(params$theme, is.theme, what = "a {.cls theme} object") + validate_theme(params$theme) + params$direction <- params$direction %||% params$theme$legend.direction } # Ensure 'order' is length 1 integer diff --git a/R/guide-bins.R b/R/guide-bins.R index f5feabdd53..4e482de2b1 100644 --- a/R/guide-bins.R +++ b/R/guide-bins.R @@ -64,7 +64,6 @@ guide_bins <- function( # general direction = NULL, - default.unit = "line", override.aes = list(), reverse = FALSE, order = 0, @@ -72,10 +71,6 @@ guide_bins <- function( ... ) { - if (!is.null(direction)) { - direction <- arg_match0(direction, c("horizontal", "vertical")) - } - new_guide( # title title = title, diff --git a/R/theme-elements.R b/R/theme-elements.R index 7b9f555b11..6ed999b884 100644 --- a/R/theme-elements.R +++ b/R/theme-elements.R @@ -513,6 +513,7 @@ el_def <- function(class = NULL, inherit = NULL, description = NULL) { legend.title.position = el_def("character"), legend.position = el_def(c("character", "numeric", "integer")), legend.direction = el_def("character"), + legend.byrow = el_def("logical"), legend.justification = el_def(c("character", "numeric", "integer")), legend.box = el_def("character"), legend.box.just = el_def("character"), From 3783e2ed93e6380097a88b933e41343b847873ee Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Fri, 1 Dec 2023 13:18:07 +0100 Subject: [PATCH 11/24] backward compatibility mechanism --- R/guide-bins.R | 2 + R/guide-colorbar.R | 4 +- R/guide-legend.R | 118 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 122 insertions(+), 2 deletions(-) diff --git a/R/guide-bins.R b/R/guide-bins.R index 4e482de2b1..ac5505b895 100644 --- a/R/guide-bins.R +++ b/R/guide-bins.R @@ -71,6 +71,8 @@ guide_bins <- function( ... ) { + theme <- deprecated_guide_args(theme, ...) + new_guide( # title title = title, diff --git a/R/guide-colorbar.R b/R/guide-colorbar.R index 97426f0e85..f77bf677fa 100644 --- a/R/guide-colorbar.R +++ b/R/guide-colorbar.R @@ -125,10 +125,11 @@ guide_colourbar <- function( ... ) { + theme <- deprecated_guide_args(theme, ...) + # Trick to re-use this constructor in `guide_coloursteps()`. args <- list2(...) super <- args$super %||% GuideColourbar - args$super <- NULL new_guide( # title @@ -149,7 +150,6 @@ guide_colourbar <- function( # parameter available_aes = available_aes, name = "colourbar", - !!!args, super = super ) } diff --git a/R/guide-legend.R b/R/guide-legend.R index 10e10ccd06..c66f515891 100644 --- a/R/guide-legend.R +++ b/R/guide-legend.R @@ -108,6 +108,8 @@ guide_legend <- function( ... ) { + theme <- deprecated_guide_args(theme, ...) + new_guide( # Title title = title, @@ -746,3 +748,119 @@ position_margin <- function(position, margin = margin(), gap = unit(0, "pt")) { right = replace(margin, 4, margin[4] + gap) ) } + +# Function implementing backward compatibility with the old way of specifying +# guide styling +deprecated_guide_args <- function( + theme = NULL, + title.position = NULL, + title.theme = NULL, title.hjust = NULL, title.vjust = NULL, + label = NULL, + label.position = NULL, + label.theme = NULL, label.hjust = NULL, label.vjust = NULL, + keywidth = NULL, keyheight = NULL, barwidth = NULL, barheight = NULL, + byrow = NULL, + frame.colour = NULL, frame.linewidth = NULL, frame.linetype = NULL, + ticks = NULL, ticks.colour = NULL, ticks.linewidth = NULL, + axis = NULL, axis.colour = NULL, axis.linewidth = NULL, axis.arrow = NULL, + default.unit = "line", + ..., + .call = caller_call()) { + + args <- names(formals(deprecated_guide_args)) + args <- setdiff(args, c("theme", "default.unit", "...", ".call")) + vals <- compact(mget(args, current_env())) + + # Early exit when no old arguments have been supplied + if (length(vals) == 0) { + return(theme) + } + fun_name <- call_name(.call) + replacement <- paste0(fun_name, "(theme)") + for (arg_name in names(vals)) { + deprecate_soft0( + when = "3.5.0", + what = paste0(fun_name, "(", arg_name, ")"), + with = replacement + ) + } + def_unit <- function(x) { + if (is.null(x) || is.unit(x)) { + return(x) + } + unit(x, default.unit) + } + + theme <- theme %||% list() + + # Resolve straightforward arguments + theme <- replace_null( + theme, + legend.title.position = title.position, + legend.text.position = label.position, + legend.byrow = byrow, + legend.key.width = def_unit(keywidth %||% barwidth), + legend.key.height = def_unit(keyheight %||% barheight) + ) + + # Set legend.text + if (isFALSE(label)) { + label.theme <- element_blank() + } else if (!is.null(label.theme %||% label.hjust %||% label.vjust)) { + label.theme <- label.theme %||% element_text() + label.theme <- replace_null( + label.theme, + hjust = label.hjust %||% label.theme$hjust, + vjust = label.vjust %||% label.theme$vjust + ) + } + theme$legend.text <- theme$legend.text %||% label.theme + + # Set legend.title + if (!is.null(title.hjust %||% title.vjust)) { + title.theme <- title.theme %||% element_text() + title.theme <- replace_null( + title.theme, + hjust = title.hjust %||% title.theme$hjust, + vjust = title.vjust %||% title.theme$vjust + ) + } + theme$legend.title <- theme$legend.title %||% title.theme + + # Set legend.frame + if (!is.null(frame.colour %||% frame.linewidth %||% frame.linetype)) { + frame <- theme$legend.frame %||% element_rect( + colour = frame.colour, + linewidth = frame.linewidth, + linetype = frame.linetype + ) + theme$legend.frame <- theme$legend.frame %||% frame + } + + # Set legend.ticks + if (isFALSE(ticks)) { + ticks <- element_blank() + } else if (!is.null(ticks.colour %||% ticks.linewidth)) { + ticks <- element_line(colour = ticks.colour, linewidth = ticks.linewidth) + theme$legend.ticks <- theme$legend.ticks %||% ticks + } + + # Set legend.axis + if (isFALSE(axis)) { + axis <- element_blank() + } else if (!is.null(axis.colour %||% axis.linewidth %||% axis.arrow)) { + axis <- element_line( + colour = axis.colour, + linewidth = axis.linewidth, + arrow = axis.arrow + ) + theme$legend.axis.line <- theme$legend.axis.line %||% axis + } + + # Set as theme + theme <- compact(theme) + if (!is.theme(theme)) { + theme <- inject(theme(!!!theme)) + } + theme +} From 9f8c0a76fc058d26bc0f1b5987c9172a28960682 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Fri, 1 Dec 2023 13:23:30 +0100 Subject: [PATCH 12/24] add news bullet --- NEWS.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/NEWS.md b/NEWS.md index 4596436f9a..682e44c6ab 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,12 @@ # ggplot2 (development version) +* `guide_*()` functions get a new `theme` argument to style individual guides. + The `theme()` function has gained additional arguments for styling guides: + `legend.key.spacing{.x/.y}`, `legend.frame`, `legend.axis.line`, + `legend.ticks`, `legend.ticks.length`, `legend.text.position` and + `legend.title.position`. Previous style arguments in the `guide_*()` functions + have been soft-deprecated. + * Lines where `linewidth = NA` are now dropped in `geom_sf()` (#5204). * New `guide_axis_logticks()` can be used to draw logarithmic tick marks as From f2b872afde341f3745928e14d42ac078c85d7895 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Fri, 1 Dec 2023 13:24:34 +0100 Subject: [PATCH 13/24] update tests --- tests/testthat/_snaps/guides.md | 8 ++-- tests/testthat/test-guides.R | 72 ++++++++++++++++++++++----------- 2 files changed, 52 insertions(+), 28 deletions(-) diff --git a/tests/testthat/_snaps/guides.md b/tests/testthat/_snaps/guides.md index 6e49237a76..cd96f32994 100644 --- a/tests/testthat/_snaps/guides.md +++ b/tests/testthat/_snaps/guides.md @@ -18,7 +18,7 @@ --- - `title.position` must be one of "top", "right", "bottom", or "left", not "leftish". + `legend.title.position` must be one of "top", "right", "bottom", or "left", not "leftish". --- @@ -27,15 +27,15 @@ --- - When `direction` is "vertical", `label.position` must be one of "right" or "left", not "top". + When `direction` is "vertical", `legend.text.position` must be one of "right" or "left", not "top". --- - When `direction` is "horizontal", `label.position` must be one of "bottom" or "top", not "left". + When `direction` is "horizontal", `legend.text.position` must be one of "bottom" or "top", not "left". --- - `label.position` must be one of "top", "right", "bottom", or "left", not "test". + `legend.text.position` must be one of "top", "right", "bottom", or "left", not "test". i Did you mean "left"? --- diff --git a/tests/testthat/test-guides.R b/tests/testthat/test-guides.R index c8ee9b2bb8..ca98a14822 100644 --- a/tests/testthat/test-guides.R +++ b/tests/testthat/test-guides.R @@ -234,20 +234,24 @@ test_that("guide specifications are properly checked", { expect_snapshot_warning(ggplotGrob(p)) - expect_snapshot_error(guide_legend(title.position = "leftish")) + p <- p + guides(shape = guide_legend(theme = theme(legend.title.position = "leftish"))) + expect_snapshot_error(ggplotGrob(p)) expect_snapshot_error(guide_colourbar()$transform()) p <- ggplot(mtcars) + geom_point(aes(mpg, disp, colour = gear)) + - guides(colour = guide_colourbar(label.position = "top")) + guides(colour = guide_colourbar(theme = theme(legend.text.position = "top"))) expect_snapshot_error(ggplotGrob(p)) p <- ggplot(mtcars) + geom_point(aes(mpg, disp, colour = gear)) + - guides(colour = guide_colourbar(direction = "horizontal", label.position = "left")) + guides(colour = guide_colourbar(direction = "horizontal", theme = theme(legend.text.position = "left"))) expect_snapshot_error(ggplotGrob(p)) - expect_snapshot_error(guide_legend(label.position = "test")) + p <- ggplot(mtcars) + + geom_point(aes(mpg, disp, colour = gear)) + + guides(colour = guide_legend(theme = theme(legend.text.position = "test"))) + expect_snapshot_error(ggplotGrob(p)) p <- ggplot(mtcars) + geom_point(aes(mpg, disp, colour = gear)) + guides(colour = guide_legend(nrow = 2, ncol = 2)) @@ -415,9 +419,12 @@ test_that("guide_axis_logticks calculates appropriate ticks", { test_that("guide_legend uses key.spacing correctly", { p <- ggplot(mtcars, aes(disp, mpg, colour = factor(carb))) + geom_point() + - guides(colour = guide_legend( - ncol = 2, key.spacing.y = 1, key.spacing.x = 2 - )) + guides(colour = guide_legend(ncol = 2)) + + theme_test() + + theme( + legend.key.spacing.x = unit(2, "lines"), + legend.key.spacing.y = unit(1, "lines") + ) expect_doppelganger("legend with widely spaced keys", p) }) @@ -786,8 +793,10 @@ test_that("guides title and text are positioned correctly", { scale_colour_continuous( name = "value", guide = guide_colorbar( - title.theme = element_text(size = 11, angle = 0, hjust = 0.5, vjust = 1), - label.theme = element_text(size = 0.8*11, angle = 270, hjust = 0.5, vjust = 1), + theme = theme( + legend.title = element_text(size = 11, angle = 0, hjust = 0.5, vjust = 1), + legend.text = element_text(size = 0.8 * 11, angle = 270, hjust = 0.5, vjust = 1) + ), order = 2 # set guide order to keep visual test stable ) ) + @@ -798,10 +807,12 @@ test_that("guides title and text are positioned correctly", { name = "fill value", guide = guide_legend( direction = "horizontal", - title.position = "top", - label.position = "bottom", - title.theme = element_text(size = 11, angle = 180, hjust = 0, vjust = 1), - label.theme = element_text(size = 0.8*11, angle = 90, hjust = 1, vjust = 0.5), + theme = theme( + legend.title.position = "top", + legend.text.position = "bottom", + legend.title = element_text(size = 11, angle = 180, hjust = 0, vjust = 1), + legend.text = element_text(size = 0.8 * 11, angle = 90, hjust = 1, vjust = 0.5) + ), order = 1 ) ) @@ -830,16 +841,16 @@ test_that("colorbar can be styled", { expect_doppelganger("white-to-red colorbar, long thick black ticks, green frame", p + scale_color_gradient( - low = 'white', high = 'red', - guide = guide_colorbar( - frame = element_rect(colour = "green"), - frame.linewidth = 1.5 / .pt, - ticks.colour = "black", - ticks.linewidth = 2.5 / .pt, - ticks.length = unit(0.4, "npc") - ) + low = 'white', high = 'red', + guide = guide_colorbar( + theme = theme( + legend.frame = element_rect(colour = "green", linewidth = 1.5 / .pt), + legend.ticks = element_line("black", linewidth = 2.5 / .pt), + legend.ticks.length = unit(0.4, "npc") ) + ) ) + ) }) test_that("guides can handle multiple aesthetics for one scale", { @@ -869,10 +880,21 @@ test_that("bin guide can be styled correctly", { p + guides(size = guide_bins(show.limits = TRUE)) ) expect_doppelganger("guide_bins can show arrows", - p + guides(size = guide_bins(axis.arrow = arrow(length = unit(1.5, "mm"), ends = "both"))) + p + guides(size = guide_bins()) + + theme_test() + + theme( + legend.axis.line = element_line( + linewidth = 0.5 / .pt, + arrow = arrow(length = unit(1.5, "mm"), ends = "both") + ) + ) ) expect_doppelganger("guide_bins can remove axis", - p + guides(size = guide_bins(axis = FALSE)) + p + guides(size = guide_bins()) + + theme_test() + + theme( + legend.axis.line = element_blank() + ) ) expect_doppelganger("guide_bins work horizontally", p + guides(size = guide_bins(direction = "horizontal")) @@ -895,7 +917,9 @@ test_that("coloursteps guide can be styled correctly", { p + guides(colour = guide_coloursteps(even.steps = FALSE)) ) expect_doppelganger("guide_bins can show ticks", - p + guides(colour = guide_coloursteps(ticks = TRUE)) + p + guides(colour = guide_coloursteps( + theme = theme(legend.ticks = element_line(linewidth = 0.5 / .pt, colour = "white")) + )) ) }) From d35f5be94b3d2e4060df564eb7f98748c60f8fb4 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Fri, 1 Dec 2023 13:42:23 +0100 Subject: [PATCH 14/24] Separate colourbar/coloursteps constructors --- R/guide-colorbar.R | 23 +---------------------- R/guide-colorsteps.R | 19 ++++++++++++++++--- 2 files changed, 17 insertions(+), 25 deletions(-) diff --git a/R/guide-colorbar.R b/R/guide-colorbar.R index f77bf677fa..75904b4398 100644 --- a/R/guide-colorbar.R +++ b/R/guide-colorbar.R @@ -103,22 +103,13 @@ NULL #' legend.direction = "vertical" #' ))) guide_colourbar <- function( - - # title title = waiver(), theme = NULL, - - # bar nbin = 300, raster = TRUE, - - # ticks draw.ulim = TRUE, draw.llim = TRUE, - - # general direction = NULL, - default.unit = "line", reverse = FALSE, order = 0, available_aes = c("colour", "color", "fill"), @@ -127,30 +118,18 @@ guide_colourbar <- function( theme <- deprecated_guide_args(theme, ...) - # Trick to re-use this constructor in `guide_coloursteps()`. - args <- list2(...) - super <- args$super %||% GuideColourbar - new_guide( - # title title = title, theme = theme, - nbin = nbin, raster = raster, - - # ticks draw_lim = c(isTRUE(draw.llim), isTRUE(draw.ulim)), - - # general direction = direction, reverse = reverse, order = order, - - # parameter available_aes = available_aes, name = "colourbar", - super = super + super = GuideColourbar ) } diff --git a/R/guide-colorsteps.R b/R/guide-colorsteps.R index 588fb83196..85af4a8b56 100644 --- a/R/guide-colorsteps.R +++ b/R/guide-colorsteps.R @@ -44,15 +44,28 @@ #' # (can also be set in the scale) #' p + scale_fill_binned(show.limits = TRUE) guide_coloursteps <- function( + title = waiver(), + theme = NULL, even.steps = TRUE, show.limits = NULL, + direction = NULL, + reverse = FALSE, + order = 0, + available_aes = c("colour", "color", "fill"), ... ) { - guide_colourbar( + + theme <- deprecated_guide_args(theme, ...) + + new_guide( + title = title, + theme = theme, even.steps = even.steps, show.limits = show.limits, - ..., - super = GuideColoursteps + direction = direction, + reverse = reverse, + order = order, + super = GuideColoursteps ) } From ec87aa364facd4bd2a7ca0b10525987db0dd5274 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Fri, 1 Dec 2023 13:48:48 +0100 Subject: [PATCH 15/24] themes have default `legend.key.spacing` --- R/guide-legend.R | 8 ++------ R/theme-defaults.R | 5 +++++ .../_snaps/guides/left-aligned-legend-key.svg | 16 ++++++++-------- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/R/guide-legend.R b/R/guide-legend.R index c66f515891..4fa6744210 100644 --- a/R/guide-legend.R +++ b/R/guide-legend.R @@ -302,13 +302,9 @@ GuideLegend <- ggproto( theme$legend.title.position <- arg_match0(title_position, .trbl, arg_nm = "legend.title.position") - # Resolve spacing. For the default gap, we break classic inheritance. - gap <- calc_element("legend.title", theme)$size %||% - calc_element("legend.text", theme)$size %||% 11 - gap <- unit(gap * 0.5, "pt") - # Set default spacing - theme$legend.key.spacing <- theme$legend.key.spacing %||% gap + theme$legend.key.spacing <- theme$legend.key.spacing %||% unit(5.5, "pt") + gap <- calc_element("legend.key.spacing", theme) # For backward compatibility, default vertical spacing is no spacing if (params$direction == "vertical") { diff --git a/R/theme-defaults.R b/R/theme-defaults.R index da315e2e25..c09b232403 100644 --- a/R/theme-defaults.R +++ b/R/theme-defaults.R @@ -176,6 +176,7 @@ theme_grey <- function(base_size = 11, base_family = "", legend.key.size = unit(1.2, "lines"), legend.key.height = NULL, legend.key.width = NULL, + legend.key.spacing = unit(half_line, "pt"), legend.text = element_text(size = rel(0.8)), legend.title = element_text(hjust = 0), legend.position = "right", @@ -485,6 +486,7 @@ theme_void <- function(base_size = 11, base_family = "", legend.position = "right", legend.text = element_text(size = rel(0.8)), legend.title = element_text(hjust = 0), + legend.key.spacing = unit(half_line, "pt"), strip.clip = "inherit", strip.text = element_text(size = rel(0.8)), strip.switch.pad.grid = unit(half_line / 2, "pt"), @@ -590,6 +592,9 @@ theme_test <- function(base_size = 11, base_family = "", legend.key.size = unit(1.2, "lines"), legend.key.height = NULL, legend.key.width = NULL, + legend.key.spacing = unit(half_line, "pt"), + legend.key.spacing.x = NULL, + legend.key.spacing.y = NULL, legend.text = element_text(size = rel(0.8)), legend.title = element_text(hjust = 0), legend.position = "right", diff --git a/tests/testthat/_snaps/guides/left-aligned-legend-key.svg b/tests/testthat/_snaps/guides/left-aligned-legend-key.svg index 386f9c2f08..0095e8608f 100644 --- a/tests/testthat/_snaps/guides/left-aligned-legend-key.svg +++ b/tests/testthat/_snaps/guides/left-aligned-legend-key.svg @@ -104,16 +104,16 @@ 400 disp mpg - + - - - - -4 -6 -8 + + + + +4 +6 +8 left aligned legend key From 064bdcd26aaeab8895e1beb6402b6abce5354bb7 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Fri, 1 Dec 2023 14:10:05 +0100 Subject: [PATCH 16/24] doc fixes --- R/guide-colorsteps.R | 2 +- man/guide_colourbar.Rd | 2 -- man/guide_coloursteps.Rd | 61 +++++++++++++++++++++++++++------------- 3 files changed, 42 insertions(+), 23 deletions(-) diff --git a/R/guide-colorsteps.R b/R/guide-colorsteps.R index 85af4a8b56..5eb4ca0a07 100644 --- a/R/guide-colorsteps.R +++ b/R/guide-colorsteps.R @@ -11,7 +11,7 @@ #' scale. This argument is ignored if `labels` is given as a vector of #' values. If one or both of the limits is also given in `breaks` it will be #' shown irrespective of the value of `show.limits`. -#' @inheritDotParams guide_colourbar -nbin -raster -available_aes +#' @inheritParams guide_colourbar #' #' @inheritSection guide_bins Use with discrete scale #' diff --git a/man/guide_colourbar.Rd b/man/guide_colourbar.Rd index e4582bda3d..8c0274f7f8 100644 --- a/man/guide_colourbar.Rd +++ b/man/guide_colourbar.Rd @@ -13,7 +13,6 @@ guide_colourbar( draw.ulim = TRUE, draw.llim = TRUE, direction = NULL, - default.unit = "line", reverse = FALSE, order = 0, available_aes = c("colour", "color", "fill"), @@ -28,7 +27,6 @@ guide_colorbar( draw.ulim = TRUE, draw.llim = TRUE, direction = NULL, - default.unit = "line", reverse = FALSE, order = 0, available_aes = c("colour", "color", "fill"), diff --git a/man/guide_coloursteps.Rd b/man/guide_coloursteps.Rd index 7c832c7fe5..2aec04d0e6 100644 --- a/man/guide_coloursteps.Rd +++ b/man/guide_coloursteps.Rd @@ -5,11 +5,40 @@ \alias{guide_colorsteps} \title{Discretized colourbar guide} \usage{ -guide_coloursteps(even.steps = TRUE, show.limits = NULL, ...) +guide_coloursteps( + title = waiver(), + theme = NULL, + even.steps = TRUE, + show.limits = NULL, + direction = NULL, + reverse = FALSE, + order = 0, + available_aes = c("colour", "color", "fill"), + ... +) -guide_colorsteps(even.steps = TRUE, show.limits = NULL, ...) +guide_colorsteps( + title = waiver(), + theme = NULL, + even.steps = TRUE, + show.limits = NULL, + direction = NULL, + reverse = FALSE, + order = 0, + available_aes = c("colour", "color", "fill"), + ... +) } \arguments{ +\item{title}{A character string or expression indicating a title of guide. +If \code{NULL}, the title is not shown. By default +(\code{\link[=waiver]{waiver()}}), the name of the scale object or the name +specified in \code{\link[=labs]{labs()}} is used for the title.} + +\item{theme}{A \code{\link[=theme]{theme}} object to style the guide individually or +differently from the plot's theme settings. The \code{theme} argument in the +guide override,s and is combined with, the plot's theme.} + \item{even.steps}{Should the rendered size of the bins be equal, or should they be proportional to their length in the data space? Defaults to \code{TRUE}} @@ -19,29 +48,21 @@ scale. This argument is ignored if \code{labels} is given as a vector of values. If one or both of the limits is also given in \code{breaks} it will be shown irrespective of the value of \code{show.limits}.} -\item{...}{ - Arguments passed on to \code{\link[=guide_colourbar]{guide_colourbar}} - \describe{ - \item{\code{draw.ulim}}{A logical specifying if the upper limit tick marks should -be visible.} - \item{\code{draw.llim}}{A logical specifying if the lower limit tick marks should -be visible.} - \item{\code{direction}}{A character string indicating the direction of the guide. +\item{direction}{A character string indicating the direction of the guide. One of "horizontal" or "vertical."} - \item{\code{reverse}}{logical. If \code{TRUE} the colourbar is reversed. By default, + +\item{reverse}{logical. If \code{TRUE} the colourbar is reversed. By default, the highest value is on the top and the lowest value is on the bottom} - \item{\code{title}}{A character string or expression indicating a title of guide. -If \code{NULL}, the title is not shown. By default -(\code{\link[=waiver]{waiver()}}), the name of the scale object or the name -specified in \code{\link[=labs]{labs()}} is used for the title.} - \item{\code{theme}}{A \code{\link[=theme]{theme}} object to style the guide individually or -differently from the plot's theme settings. The \code{theme} argument in the -guide override,s and is combined with, the plot's theme.} - \item{\code{order}}{positive integer less than 99 that specifies the order of + +\item{order}{positive integer less than 99 that specifies the order of this guide among multiple guides. This controls the order in which multiple guides are displayed, not the contents of the guide itself. If 0 (default), the order is determined by a secret algorithm.} - }} + +\item{available_aes}{A vector of character strings listing the aesthetics +for which a colourbar can be drawn.} + +\item{...}{ignored.} } \value{ A guide object From 2d9e7fc3c1270383cebf35e2196df9745dffc36f Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Thu, 7 Dec 2023 14:48:47 +0100 Subject: [PATCH 17/24] add `theme` to stacked axis --- R/guide-axis-stack.R | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/R/guide-axis-stack.R b/R/guide-axis-stack.R index 2fdd73b34e..1e0f765898 100644 --- a/R/guide-axis-stack.R +++ b/R/guide-axis-stack.R @@ -29,7 +29,7 @@ NULL #' #' # A normal axis first, then a capped axis #' p + guides(x = guide_axis_stack("axis", guide_axis(cap = "both"))) -guide_axis_stack <- function(first = "axis", ..., title = waiver(), +guide_axis_stack <- function(first = "axis", ..., title = waiver(), theme = NULL, spacing = NULL, order = 0, position = waiver()) { check_object(spacing, is.unit, "{.cls unit}", allow_null = TRUE) @@ -63,6 +63,7 @@ guide_axis_stack <- function(first = "axis", ..., title = waiver(), new_guide( title = title, + theme = theme, guides = axes, guide_params = params, available_aes = c("x", "y", "theta", "r"), @@ -88,6 +89,7 @@ GuideAxisStack <- ggproto( # Standard guide stuff name = "stacked_axis", title = waiver(), + theme = NULL, angle = waiver(), hash = character(), position = waiver(), @@ -142,6 +144,7 @@ GuideAxisStack <- ggproto( draw = function(self, theme, position = NULL, direction = NULL, params = self$params) { + theme <- add_theme(theme, params$theme) position <- params$position %||% position direction <- params$direction %||% direction From 33897aea5abdd0f07bed65b50f67f9dd91f06983 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Thu, 7 Dec 2023 14:52:34 +0100 Subject: [PATCH 18/24] There is no need for `justify_grobs()` --- R/guide-bins.R | 1 - R/guide-colorbar.R | 2 -- R/guide-legend.R | 12 +----------- 3 files changed, 1 insertion(+), 14 deletions(-) diff --git a/R/guide-bins.R b/R/guide-bins.R index ac5505b895..9ce4d6a411 100644 --- a/R/guide-bins.R +++ b/R/guide-bins.R @@ -108,7 +108,6 @@ GuideBins <- ggproto( default_axis = element_line("black", linewidth = (0.5 / .pt)), default_ticks = element_line(inherit.blank = TRUE), default_tick_length = unit(0.2, "npc"), - rejust_labels = FALSE, direction = NULL, override.aes = list(), diff --git a/R/guide-colorbar.R b/R/guide-colorbar.R index 75904b4398..cb007ca44c 100644 --- a/R/guide-colorbar.R +++ b/R/guide-colorbar.R @@ -165,8 +165,6 @@ GuideColourbar <- ggproto( reverse = FALSE, order = 0, - rejust_labels = FALSE, - # parameter name = "colourbar", hash = character(), diff --git a/R/guide-legend.R b/R/guide-legend.R index 7ba43b02a4..7b57539094 100644 --- a/R/guide-legend.R +++ b/R/guide-legend.R @@ -626,18 +626,8 @@ GuideLegend <- ggproto( } if (!is.zero(grobs$labels)) { - labels <- if (params$rejust_labels %||% TRUE) { - justify_grobs( - grobs$labels, - hjust = elements$text$hjust, vjust = elements$text$vjust, - int_angle = elements$text$angle, debug = elements$text$debug - ) - } else { - grobs$labels - } - gt <- gtable_add_grob( - gt, labels, + gt, grobs$labels, name = names(labels) %||% paste("label", layout$label_row, layout$label_col, sep = "-"), clip = "off", From 4ee4cfb3883780dd225be70cd56fff2cccef36ba Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Fri, 8 Dec 2023 14:29:06 +0100 Subject: [PATCH 19/24] update `replace_null()` --- R/guide-.R | 3 +-- R/utilities.R | 10 +++++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/R/guide-.R b/R/guide-.R index 7d42f75446..b90c7b93f4 100644 --- a/R/guide-.R +++ b/R/guide-.R @@ -300,8 +300,7 @@ Guide <- ggproto( key <- params$key # Setup parameters and theme - params$position <- params$position %||% position - params$direction <- params$direction %||% direction + params <- replace_null(params, position = position, direction = direction) params <- self$setup_params(params) elems <- self$setup_elements(params, self$elements, theme) elems <- self$override_elements(params, elems, theme) diff --git a/R/utilities.R b/R/utilities.R index 53fb01efb8..d23cf49e8c 100644 --- a/R/utilities.R +++ b/R/utilities.R @@ -773,9 +773,17 @@ vec_rbind0 <- function(..., .error_call = current_env(), .call = caller_env()) { ) } +# This function is used to vectorise the following pattern: +# +# list$name1 <- list$name1 %||% value +# list$name2 <- list$name2 %||% value +# +# and express this pattern as: +# +# replace_null(list, name1 = value, name2 = value) replace_null <- function(list, ..., env = caller_env()) { # Collect dots without evaluating - dots <- match.call(replace_null, expand.dots = FALSE)$`...` + dots <- enexprs(...) # Select arguments that are null in `list` nms <- names(dots) nms <- nms[vapply(list[nms], is.null, logical(1))] From 6530b3c808d8ee8a69e30759aaa7e53bc6d1cc94 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Fri, 8 Dec 2023 16:43:46 +0100 Subject: [PATCH 20/24] fix examples --- R/guide-bins.R | 10 ++++++---- man/guide_bins.Rd | 10 ++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/R/guide-bins.R b/R/guide-bins.R index ced521916b..44544569a2 100644 --- a/R/guide-bins.R +++ b/R/guide-bins.R @@ -42,14 +42,16 @@ NULL #' p #' #' # Remove the axis or style it -#' p + guides(size = guide_bins(theme = theme(legend.axis = element_blank()))) +#' p + guides(size = guide_bins( +#' theme = theme(legend.axis.line = element_blank()) +#' )) #' #' p + guides(size = guide_bins(show.limits = TRUE)) #' #' my_arrow <- arrow(length = unit(1.5, "mm"), ends = "both") -#' p + guides(size = guide_bins(theme = theme( -#' legend.axis = element_line(arrow = my_arrow) -#' ))) +#' p + guides(size = guide_bins( +#' theme = theme(legend.axis.line = element_line(arrow = my_arrow)) +#' )) #' #' # Guides are merged together if possible #' ggplot(mtcars) + diff --git a/man/guide_bins.Rd b/man/guide_bins.Rd index 024f346efc..a775973d87 100644 --- a/man/guide_bins.Rd +++ b/man/guide_bins.Rd @@ -84,14 +84,16 @@ p <- ggplot(mtcars) + p # Remove the axis or style it -p + guides(size = guide_bins(theme = theme(legend.axis = element_blank()))) +p + guides(size = guide_bins( + theme = theme(legend.axis.line = element_blank()) +)) p + guides(size = guide_bins(show.limits = TRUE)) my_arrow <- arrow(length = unit(1.5, "mm"), ends = "both") -p + guides(size = guide_bins(theme = theme( - legend.axis = element_line(arrow = my_arrow) -))) +p + guides(size = guide_bins( + theme = theme(legend.axis.line = element_line(arrow = my_arrow)) +)) # Guides are merged together if possible ggplot(mtcars) + From a14493b0ff70ec8802deba1c01aeb72ad41b0717 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Mon, 11 Dec 2023 16:40:25 +0100 Subject: [PATCH 21/24] adapt to latest changes --- R/guide-bins.R | 4 +-- R/guide-colorbar.R | 4 +-- R/guide-legend.R | 53 +++++++++++++++--------------------- tests/testthat/test-guides.R | 33 +++++++++++++++++----- tests/testthat/test-theme.R | 8 +++--- 5 files changed, 56 insertions(+), 46 deletions(-) diff --git a/R/guide-bins.R b/R/guide-bins.R index 44544569a2..c13447eb32 100644 --- a/R/guide-bins.R +++ b/R/guide-bins.R @@ -285,8 +285,8 @@ GuideBins <- ggproto( sizes <- measure_legend_keys( decor, nkeys, dim, byrow = FALSE, - default_width = elements$key_width, - default_height = elements$key_height + default_width = elements$width_cm, + default_height = elements$height_cm ) sizes <- lapply(sizes, function(x) rep_len(max(x), length(x))) diff --git a/R/guide-colorbar.R b/R/guide-colorbar.R index 5fbe70a4ba..9fdc92c437 100644 --- a/R/guide-colorbar.R +++ b/R/guide-colorbar.R @@ -364,8 +364,8 @@ GuideColourbar <- ggproto( measure_grobs = function(grobs, params, elements) { params$sizes <- list( - widths = elements$key_width, - heights = elements$key_height + widths = elements$width_cm, + heights = elements$height_cm ) GuideLegend$measure_grobs(grobs, params, elements) } diff --git a/R/guide-legend.R b/R/guide-legend.R index 866ab8853e..594d2c4d2d 100644 --- a/R/guide-legend.R +++ b/R/guide-legend.R @@ -349,10 +349,13 @@ GuideLegend <- ggproto( override_elements = function(params, elements, theme) { - # Convert key sizes to cm if (any(c("key_width", "key_height") %in% names(elements))) { - elements$key_width <- width_cm(elements$key_width) - elements$key_height <- height_cm(elements$key_height) + # Determine if the key is stretched + elements$stretch_x <- unitType(elements$key_width) == "null" + elements$stretch_y <- unitType(elements$key_height) == "null" + # Convert key sizes to cm + elements$width_cm <- width_cm(elements$key_width) + elements$height_cm <- height_cm(elements$key_height) } # Convert padding and spacing to cm @@ -361,10 +364,8 @@ GuideLegend <- ggproto( elements$spacing_y <- height_cm(elements$spacing_y) } - elements$padding <- convertUnit( - elements$margin %||% margin(), - "cm", valueOnly = TRUE - ) + elements$padding <- + convertUnit(elements$margin %||% margin(), "cm", valueOnly = TRUE) # Evaluate backgrounds early if (!is.null(elements$background)) { @@ -385,7 +386,7 @@ GuideLegend <- ggproto( build_decor = function(decor, grobs, elements, params) { - key_size <- c(elements$key_width, elements$key_height) * 10 + key_size <- c(elements$width_cm, elements$height_cm) * 10 draw <- function(i) { bg <- elements$key @@ -432,8 +433,8 @@ GuideLegend <- ggproto( # measure when it hasn't already. sizes <- params$sizes %||% measure_legend_keys( grobs$decor, n = n_breaks, dim = dim, byrow = byrow, - default_width = elements$key_width, - default_height = elements$key_height + default_width = elements$width_cm, + default_height = elements$height_cm ) widths <- sizes$widths heights <- sizes$heights @@ -478,33 +479,25 @@ GuideLegend <- ggproto( title_height <- height_cm(grobs$title) # Titles are assumed to have sufficient size when keys are null units - if (is.unit(params$keywidth) && unitType(params$keywidth) == "null") { - extra_width <- 0 - } else { - extra_width <- max(0, title_width - sum(widths)) - } - if (is.unit(params$keyheight) && unitType(params$keyheight) == "null") { - extra_height <- 0 - } else { - extra_height <- max(0, title_height - sum(heights)) - } + extra_width <- + if (isTRUE(elements$stretch_x)) 0 else max(0, title_width - sum(widths)) + extra_height <- + if (isTRUE(elements$stretch_y)) 0 else max(0, title_height - sum(heights)) - just <- with(elements$title, rotate_just(angle, hjust, vjust)) - hjust <- just$hjust - vjust <- just$vjust + just <- with(elements$title, rotate_just(angle, hjust, vjust)) # Combine title with rest of the sizes based on its position widths <- switch( elements$title_position, "left" = c(title_width, widths), "right" = c(widths, title_width), - c(extra_width * hjust, widths, extra_width * (1 - hjust)) + c(extra_width * just$hjust, widths, extra_width * (1 - just$hjust)) ) heights <- switch( elements$title_position, "top" = c(title_height, heights), "bottom" = c(heights, title_height), - c(extra_height * (1 - vjust), heights, extra_height * vjust) + c(extra_height * (1 - just$vjust), heights, extra_height * just$vjust) ) } @@ -586,15 +579,13 @@ GuideLegend <- ggproto( assemble_drawing = function(grobs, layout, sizes, params, elements) { widths <- unit(c(sizes$padding[4], sizes$widths, sizes$padding[2]), "cm") - if (is.unit(params$keywidth) && unitType(params$keywidth) == "null") { - i <- unique(layout$layout$key_col) - widths[i] <- params$keywidth + if (isTRUE(elements$stretch_x)) { + widths[unique(layout$layout$key_col)] <- elements$key_width } heights <- unit(c(sizes$padding[1], sizes$heights, sizes$padding[3]), "cm") - if (is.unit(params$keyheight) && unitType(params$keyheight) == "null") { - i <- unique(layout$layout$key_row) - heights[i] <- params$keyheight + if (isTRUE(elements$stretch_y)) { + heights[unique(layout$layout$key_row)] <- elements$key_height } gt <- gtable(widths = widths, heights = heights) diff --git a/tests/testthat/test-guides.R b/tests/testthat/test-guides.R index 0578b7b4a3..1614da03ea 100644 --- a/tests/testthat/test-guides.R +++ b/tests/testthat/test-guides.R @@ -849,13 +849,32 @@ test_that("guides title and text are positioned correctly", { geom_point() + scale_alpha(breaks = 1:2) + guides( - colour = guide_legend("colour title with hjust = 0", title.hjust = 0, order = 1), - fill = guide_legend("fill title with hjust = 1", title.hjust = 1, order = 2, - title.position = "bottom", override.aes = list(shape = 21)), - alpha = guide_legend("Title\nfor\nalpha\nwith\nvjust=0", title.vjust = 0, - title.position = "left", order = 3), - shape = guide_legend("Title\nfor\nshape\nwith\nvjust=1", title.vjust = 1, - title.position = "right", order = 4) + colour = guide_legend( + "colour title with hjust = 0", order = 1, + theme = theme(legend.title = element_text(hjust = 0)) + ), + fill = guide_legend( + "fill title with hjust = 1", order = 2, + theme = theme( + legend.title = element_text(hjust = 1), + legend.title.position = "bottom" + ), + override.aes = list(shape = 21) + ), + alpha = guide_legend( + "Title\nfor\nalpha\nwith\nvjust=0", order = 3, + theme = theme( + legend.title = element_text(vjust = 0), + legend.title.position = "left" + ) + ), + shape = guide_legend( + "Title\nfor\nshape\nwith\nvjust=1", order = 4, + theme = theme( + legend.title = element_text(vjust = 1), + legend.title.position = "right" + ) + ) ) expect_doppelganger("legends with all title justifications", p) }) diff --git a/tests/testthat/test-theme.R b/tests/testthat/test-theme.R index dcaba82966..146ad29fc8 100644 --- a/tests/testthat/test-theme.R +++ b/tests/testthat/test-theme.R @@ -806,8 +806,8 @@ test_that("legend margins are correct when using relative key sizes", { ) vertical <- p + guides( - colour = guide_colourbar(barheight = unit(1, "null")), - shape = guide_legend(keyheight = unit(1/3, "null")) + colour = guide_colourbar(theme = theme(legend.key.height = unit(1, "null"))), + shape = guide_legend(theme = theme(legend.key.height = unit(1/3, "null"))) ) + theme( legend.box.margin = margin(t = 5, b = 10, unit = "mm"), legend.margin = margin(t = 10, b = 5, unit = "mm") @@ -816,8 +816,8 @@ test_that("legend margins are correct when using relative key sizes", { expect_doppelganger("stretched vertical legends", vertical) horizontal <- p + guides( - colour = guide_colourbar(barwidth = unit(1, "null")), - shape = guide_legend(keywidth = unit(1/3, "null")) + colour = guide_colourbar(theme = theme(legend.key.width = unit(1, "null"))), + shape = guide_legend(theme = theme(legend.key.width = unit(1/3, "null"))) ) + theme( legend.position = "top", legend.box.margin = margin(l = 5, r = 10, unit = "mm"), From b16a1bb69ea44e410ff35f54e95e6ec598334342 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Mon, 11 Dec 2023 16:44:18 +0100 Subject: [PATCH 22/24] rename argument --- R/utilities.R | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/R/utilities.R b/R/utilities.R index d23cf49e8c..127765dafb 100644 --- a/R/utilities.R +++ b/R/utilities.R @@ -775,21 +775,21 @@ vec_rbind0 <- function(..., .error_call = current_env(), .call = caller_env()) { # This function is used to vectorise the following pattern: # -# list$name1 <- list$name1 %||% value -# list$name2 <- list$name2 %||% value +# obj$name1 <- obj$name1 %||% value +# obj$name2 <- obj$name2 %||% value # # and express this pattern as: # -# replace_null(list, name1 = value, name2 = value) -replace_null <- function(list, ..., env = caller_env()) { +# replace_null(obj, name1 = value, name2 = value) +replace_null <- function(obj, ..., env = caller_env()) { # Collect dots without evaluating dots <- enexprs(...) - # Select arguments that are null in `list` + # Select arguments that are null in `obj` nms <- names(dots) - nms <- nms[vapply(list[nms], is.null, logical(1))] + nms <- nms[vapply(obj[nms], is.null, logical(1))] # Replace those with the evaluated dots - list[nms] <- inject(list(!!!dots[nms]), env = env) - list + obj[nms] <- inject(list(!!!dots[nms]), env = env) + obj } attach_plot_env <- function(env) { From dad2815e0e809b26170789360f0b74b2ff1abaa1 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Mon, 11 Dec 2023 16:46:22 +0100 Subject: [PATCH 23/24] Fix typo --- R/guide-legend.R | 2 +- man/guide_axis.Rd | 2 +- man/guide_axis_logticks.Rd | 2 +- man/guide_axis_stack.Rd | 2 +- man/guide_axis_theta.Rd | 2 +- man/guide_bins.Rd | 2 +- man/guide_colourbar.Rd | 2 +- man/guide_coloursteps.Rd | 2 +- man/guide_legend.Rd | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/R/guide-legend.R b/R/guide-legend.R index 594d2c4d2d..1364d2aa52 100644 --- a/R/guide-legend.R +++ b/R/guide-legend.R @@ -15,7 +15,7 @@ #' specified in [labs()] is used for the title. #' @param theme A [`theme`][theme()] object to style the guide individually or #' differently from the plot's theme settings. The `theme` argument in the -#' guide override,s and is combined with, the plot's theme. +#' guide overrides, and is combined with, the plot's theme. #' @param position A character string indicating where the legend should be #' placed relative to the plot panels. #' @param direction A character string indicating the direction of the guide. diff --git a/man/guide_axis.Rd b/man/guide_axis.Rd index 8b3b511a03..4d4ba4f166 100644 --- a/man/guide_axis.Rd +++ b/man/guide_axis.Rd @@ -24,7 +24,7 @@ specified in \code{\link[=labs]{labs()}} is used for the title.} \item{theme}{A \code{\link[=theme]{theme}} object to style the guide individually or differently from the plot's theme settings. The \code{theme} argument in the -guide override,s and is combined with, the plot's theme.} +guide overrides, and is combined with, the plot's theme.} \item{check.overlap}{silently remove overlapping labels, (recursively) prioritizing the first, last, and middle labels.} diff --git a/man/guide_axis_logticks.Rd b/man/guide_axis_logticks.Rd index 16dbbafd30..3b8fcb5478 100644 --- a/man/guide_axis_logticks.Rd +++ b/man/guide_axis_logticks.Rd @@ -48,7 +48,7 @@ and \code{FALSE} are shorthand for \code{"both"} and \code{"none"} respectively. \item{theme}{A \code{\link[=theme]{theme}} object to style the guide individually or differently from the plot's theme settings. The \code{theme} argument in the -guide override,s and is combined with, the plot's theme.} +guide overrides, and is combined with, the plot's theme.} \item{...}{ Arguments passed on to \code{\link[=guide_axis]{guide_axis}} diff --git a/man/guide_axis_stack.Rd b/man/guide_axis_stack.Rd index 8ae600d2b9..a001a35ac2 100644 --- a/man/guide_axis_stack.Rd +++ b/man/guide_axis_stack.Rd @@ -30,7 +30,7 @@ specified in \code{\link[=labs]{labs()}} is used for the title.} \item{theme}{A \code{\link[=theme]{theme}} object to style the guide individually or differently from the plot's theme settings. The \code{theme} argument in the -guide override,s and is combined with, the plot's theme.} +guide overrides, and is combined with, the plot's theme.} \item{spacing}{A \code{\link[=unit]{unit()}} objects that determines how far separate guides are spaced apart.} diff --git a/man/guide_axis_theta.Rd b/man/guide_axis_theta.Rd index b7ee8b9d92..6e18e57a60 100644 --- a/man/guide_axis_theta.Rd +++ b/man/guide_axis_theta.Rd @@ -22,7 +22,7 @@ specified in \code{\link[=labs]{labs()}} is used for the title.} \item{theme}{A \code{\link[=theme]{theme}} object to style the guide individually or differently from the plot's theme settings. The \code{theme} argument in the -guide override,s and is combined with, the plot's theme.} +guide overrides, and is combined with, the plot's theme.} \item{angle}{Compared to setting the angle in \code{\link[=theme]{theme()}} / \code{\link[=element_text]{element_text()}}, this also uses some heuristics to automatically pick the \code{hjust} and \code{vjust} that diff --git a/man/guide_bins.Rd b/man/guide_bins.Rd index a775973d87..8633915f2d 100644 --- a/man/guide_bins.Rd +++ b/man/guide_bins.Rd @@ -24,7 +24,7 @@ specified in \code{\link[=labs]{labs()}} is used for the title.} \item{theme}{A \code{\link[=theme]{theme}} object to style the guide individually or differently from the plot's theme settings. The \code{theme} argument in the -guide override,s and is combined with, the plot's theme.} +guide overrides, and is combined with, the plot's theme.} \item{position}{A character string indicating where the legend should be placed relative to the plot panels.} diff --git a/man/guide_colourbar.Rd b/man/guide_colourbar.Rd index dbde24ab48..2078bc13db 100644 --- a/man/guide_colourbar.Rd +++ b/man/guide_colourbar.Rd @@ -43,7 +43,7 @@ specified in \code{\link[=labs]{labs()}} is used for the title.} \item{theme}{A \code{\link[=theme]{theme}} object to style the guide individually or differently from the plot's theme settings. The \code{theme} argument in the -guide override,s and is combined with, the plot's theme.} +guide overrides, and is combined with, the plot's theme.} \item{nbin}{A numeric specifying the number of bins for drawing the colourbar. A smoother colourbar results from a larger value.} diff --git a/man/guide_coloursteps.Rd b/man/guide_coloursteps.Rd index 2aec04d0e6..3df628de34 100644 --- a/man/guide_coloursteps.Rd +++ b/man/guide_coloursteps.Rd @@ -37,7 +37,7 @@ specified in \code{\link[=labs]{labs()}} is used for the title.} \item{theme}{A \code{\link[=theme]{theme}} object to style the guide individually or differently from the plot's theme settings. The \code{theme} argument in the -guide override,s and is combined with, the plot's theme.} +guide overrides, and is combined with, the plot's theme.} \item{even.steps}{Should the rendered size of the bins be equal, or should they be proportional to their length in the data space? Defaults to \code{TRUE}} diff --git a/man/guide_legend.Rd b/man/guide_legend.Rd index 12aba8ed0b..952cc1d1d4 100644 --- a/man/guide_legend.Rd +++ b/man/guide_legend.Rd @@ -25,7 +25,7 @@ specified in \code{\link[=labs]{labs()}} is used for the title.} \item{theme}{A \code{\link[=theme]{theme}} object to style the guide individually or differently from the plot's theme settings. The \code{theme} argument in the -guide override,s and is combined with, the plot's theme.} +guide overrides, and is combined with, the plot's theme.} \item{position}{A character string indicating where the legend should be placed relative to the plot panels.} From 23b74f2b952e5c8c7d1f96bb97e291a75557e150 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Mon, 11 Dec 2023 16:58:00 +0100 Subject: [PATCH 24/24] no need to use `justify_grobs()` --- R/guide-legend.R | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/R/guide-legend.R b/R/guide-legend.R index 1364d2aa52..26a0b401b0 100644 --- a/R/guide-legend.R +++ b/R/guide-legend.R @@ -602,14 +602,7 @@ GuideLegend <- ggproto( # Add title if (!is.zero(grobs$title)) { gt <- gtable_add_grob( - gt, - justify_grobs( - grobs$title, - hjust = elements$title$hjust, - vjust = elements$title$vjust, - int_angle = elements$title$angle, - debug = elements$title$debug - ), + gt, grobs$title, name = "title", clip = "off", t = min(layout$title_row), r = max(layout$title_col), b = max(layout$title_row), l = min(layout$title_col)