From 53a2649278e67d2990109c7437d0f6e96e59e5a1 Mon Sep 17 00:00:00 2001 From: Teun van den Brand <49372158+teunbrand@users.noreply.github.com> Date: Thu, 29 Jun 2023 18:05:16 +0200 Subject: [PATCH 01/16] Handle `labels = NULL` better --- R/guide-bins.R | 6 +++++- R/guide-colorbar.R | 5 +++++ R/guide-legend.R | 5 +++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/R/guide-bins.R b/R/guide-bins.R index bfdd9d0701..44f41d6bd1 100644 --- a/R/guide-bins.R +++ b/R/guide-bins.R @@ -340,7 +340,11 @@ GuideBins <- ggproto( }, build_labels = function(key, elements, params) { - key$.label[c(1, nrow(key))[!params$show.limits]] <- "" + n_labels <- length(key$.label) + if (n_labels < 1) { + return(list(labels = zeroGrob())) + } + key$.label[c(1, n_labels)[!params$show.limits]] <- "" just <- if (params$direction == "horizontal") { elements$text$vjust diff --git a/R/guide-colorbar.R b/R/guide-colorbar.R index 65291f37cc..b678d8d4e0 100644 --- a/R/guide-colorbar.R +++ b/R/guide-colorbar.R @@ -416,6 +416,11 @@ GuideColourbar <- ggproto( }, build_labels = function(key, elements, params) { + n_labels <- length(key$.label) + if (n_labels < 1) { + return(list(labels = zeroGrob())) + } + just <- if (params$direction == "horizontal") { elements$text$vjust } else { diff --git a/R/guide-legend.R b/R/guide-legend.R index 0e6193aa24..b2c78cfbb1 100644 --- a/R/guide-legend.R +++ b/R/guide-legend.R @@ -480,6 +480,11 @@ GuideLegend <- ggproto( }, build_labels = function(key, elements, params) { + n_labels <- length(key$.label) + if (n_labels < 1) { + out <- rep(list(zeroGrob()), nrow(key)) + return(out) + } lapply(key$.label, function(lab) { ggname( "guide.label", From 0dd8efd5dbbfaa3f0855e3b3e880c94cd2c46428 Mon Sep 17 00:00:00 2001 From: Teun van den Brand <49372158+teunbrand@users.noreply.github.com> Date: Thu, 29 Jun 2023 18:59:26 +0200 Subject: [PATCH 02/16] Convert `guides()` error to warning --- R/guides-.R | 5 +++-- tests/testthat/test-guides.R | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/R/guides-.R b/R/guides-.R index f8273d07e6..ed844b18dd 100644 --- a/R/guides-.R +++ b/R/guides-.R @@ -84,7 +84,7 @@ guides <- function(...) { return(guides_list(guides = args)) } - # Raise error about unnamed guides + # Raise warning about unnamed guides nms <- names(args) if (is.null(nms)) { msg <- "All guides are unnamed." @@ -97,10 +97,11 @@ guides <- function(...) { msg <- "The {.and {unnamed}} guide{?s} {?is/are} unnamed." } } - cli::cli_abort(c( + cli::cli_warn(c( "Guides provided to {.fun guides} must be named.", i = msg )) + NULL } update_guides <- function(p, guides) { diff --git a/tests/testthat/test-guides.R b/tests/testthat/test-guides.R index 4ef7174f99..d39ddcb1f4 100644 --- a/tests/testthat/test-guides.R +++ b/tests/testthat/test-guides.R @@ -760,12 +760,12 @@ test_that("a warning is generated when guides( = FALSE) is specified", { expect_snapshot_warning(ggplot_gtable(built)) }) -test_that("guides() errors if unnamed guides are provided", { - expect_error( +test_that("guides() warns if unnamed guides are provided", { + expect_warning( guides("axis"), "All guides are unnamed." ) - expect_error( + expect_warning( guides(x = "axis", "axis"), "The 2nd guide is unnamed" ) From 29f5185a907dc4538d7dac8dce3086afbeeb00d9 Mon Sep 17 00:00:00 2001 From: Teun van den Brand <49372158+teunbrand@users.noreply.github.com> Date: Thu, 29 Jun 2023 18:59:57 +0200 Subject: [PATCH 03/16] Ignore no guides --- R/guides-.R | 5 +++++ tests/testthat/test-guides.R | 1 + 2 files changed, 6 insertions(+) diff --git a/R/guides-.R b/R/guides-.R index ed844b18dd..d7d4d58ff1 100644 --- a/R/guides-.R +++ b/R/guides-.R @@ -84,6 +84,11 @@ guides <- function(...) { return(guides_list(guides = args)) } + # If there are no guides, do nothing + if (length(args) == 0) { + return(NULL) + } + # Raise warning about unnamed guides nms <- names(args) if (is.null(nms)) { diff --git a/tests/testthat/test-guides.R b/tests/testthat/test-guides.R index d39ddcb1f4..86edd4cbd1 100644 --- a/tests/testthat/test-guides.R +++ b/tests/testthat/test-guides.R @@ -769,6 +769,7 @@ test_that("guides() warns if unnamed guides are provided", { guides(x = "axis", "axis"), "The 2nd guide is unnamed" ) + expect_null(guides()) }) test_that("old S3 guides can be implemented", { From 33ff7fb0c2b97b9bd6e2b7bc4925deea5ed7aa45 Mon Sep 17 00:00:00 2001 From: Teun van den Brand <49372158+teunbrand@users.noreply.github.com> Date: Thu, 29 Jun 2023 20:43:38 +0200 Subject: [PATCH 04/16] Swap old train order --- R/guide-old.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/guide-old.R b/R/guide-old.R index 735dc96f21..e42cd1d641 100644 --- a/R/guide-old.R +++ b/R/guide-old.R @@ -89,9 +89,9 @@ GuideOld <- ggproto( train = function(self, params, scale, aesthetic = NULL, title = NULL, direction = NULL) { - params <- guide_train(params, scale, aesthetic) params$title <- params$title %|W|% title params$direction <- params$direction %||% direction + params <- guide_train(params, scale, aesthetic) params }, From 84428880ac65ced9467853abc20f1ad130695520 Mon Sep 17 00:00:00 2001 From: Teun van den Brand <49372158+teunbrand@users.noreply.github.com> Date: Sun, 2 Jul 2023 12:08:43 +0200 Subject: [PATCH 05/16] Fix `even.steps`/`show.limits` interaction --- R/guide-colorsteps.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/guide-colorsteps.R b/R/guide-colorsteps.R index d964e1d058..6e7b857b56 100644 --- a/R/guide-colorsteps.R +++ b/R/guide-colorsteps.R @@ -164,7 +164,7 @@ GuideColoursteps <- ggproto( from = c(0.5, nbin - 0.5) / nbin ) key <- params$key - limits <- attr(key, "limits", TRUE) + limits <- attr(key, "limits", TRUE) %||% scale$get_limits() key <- key[c(NA, seq_len(nrow(key)), NA), , drop = FALSE] key$.value[c(1, nrow(key))] <- edges key$.label[c(1, nrow(key))] <- scale$get_labels(limits) From 13e62fe1b54c9af805db6b6d9903903316a8b55c Mon Sep 17 00:00:00 2001 From: Teun van den Brand <49372158+teunbrand@users.noreply.github.com> Date: Sun, 2 Jul 2023 12:09:00 +0200 Subject: [PATCH 06/16] Change to soft deprecation --- R/guide-old.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/guide-old.R b/R/guide-old.R index e42cd1d641..1ca7168ffc 100644 --- a/R/guide-old.R +++ b/R/guide-old.R @@ -65,7 +65,7 @@ guide_gengrob.default <- guide_train.default #' @export #' @rdname old_guide old_guide <- function(guide) { - deprecate_warn0( + deprecate_soft0( when = "3.5.0", what = I("The S3 guide system"), details = c( From 962803ad6cac43bb44f4e2e58629b8c3b5756ec3 Mon Sep 17 00:00:00 2001 From: Teun van den Brand <49372158+teunbrand@users.noreply.github.com> Date: Sun, 2 Jul 2023 13:48:57 +0200 Subject: [PATCH 07/16] Fix old guide title --- R/guide-old.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/guide-old.R b/R/guide-old.R index 1ca7168ffc..edee44dd88 100644 --- a/R/guide-old.R +++ b/R/guide-old.R @@ -89,7 +89,7 @@ GuideOld <- ggproto( train = function(self, params, scale, aesthetic = NULL, title = NULL, direction = NULL) { - params$title <- params$title %|W|% title + params$title <- scale$make_title(params$title %|W|% scale$name %|W|% title) params$direction <- params$direction %||% direction params <- guide_train(params, scale, aesthetic) params From eb186219aade923e59392afe33543bd332174fec Mon Sep 17 00:00:00 2001 From: Teun van den Brand <49372158+teunbrand@users.noreply.github.com> Date: Sun, 2 Jul 2023 13:49:38 +0200 Subject: [PATCH 08/16] Fix `draw_axis()` with `NULL` labels --- R/guide-axis.R | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/R/guide-axis.R b/R/guide-axis.R index a6ce730476..20f8260147 100644 --- a/R/guide-axis.R +++ b/R/guide-axis.R @@ -432,9 +432,10 @@ draw_axis <- function(break_positions, break_labels, axis_position, theme, aes <- if (axis_position %in% c("top", "bottom")) "x" else "y" opp <- setdiff(c("x", "y"), aes) opp_value <- if (axis_position %in% c("top", "right")) 0 else 1 - key <- data_frame( - break_positions, break_positions, break_labels, - .name_repair = ~ c(aes, ".value", ".label") + key <- data_frame0( + !!aes := break_positions, + .value = break_positions, + .label = break_labels ) params$key <- key params$decor <- data_frame0( From 3055d7d2d2045868cd276b55ae859b5d31136995 Mon Sep 17 00:00:00 2001 From: Teun van den Brand <49372158+teunbrand@users.noreply.github.com> Date: Wed, 5 Jul 2023 22:13:06 +0200 Subject: [PATCH 09/16] Default old guide title is `waiver()` --- R/guide-old.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/guide-old.R b/R/guide-old.R index edee44dd88..2320b0bbf2 100644 --- a/R/guide-old.R +++ b/R/guide-old.R @@ -88,7 +88,7 @@ GuideOld <- ggproto( "GuideOld", Guide, train = function(self, params, scale, aesthetic = NULL, - title = NULL, direction = NULL) { + title = waiver(), direction = NULL) { params$title <- scale$make_title(params$title %|W|% scale$name %|W|% title) params$direction <- params$direction %||% direction params <- guide_train(params, scale, aesthetic) From d11322208ceb29848ecf2baaca355d82485162b5 Mon Sep 17 00:00:00 2001 From: Teun van den Brand <49372158+teunbrand@users.noreply.github.com> Date: Thu, 6 Jul 2023 22:19:01 +0200 Subject: [PATCH 10/16] `guide_for_position` becomes method --- R/coord-cartesian-.R | 36 ++++-------------------------------- R/guides-.R | 29 +++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 32 deletions(-) diff --git a/R/coord-cartesian-.R b/R/coord-cartesian-.R index cd68e36eef..97dbd72235 100644 --- a/R/coord-cartesian-.R +++ b/R/coord-cartesian-.R @@ -145,37 +145,9 @@ view_scales_from_scale <- function(scale, coord_limits = NULL, expand = TRUE) { } panel_guides_grob <- function(guides, position, theme) { - pair <- guide_for_position(guides, position) %||% - list(guide = guide_none(), params = NULL) - pair$guide$draw(theme, pair$params) -} - -guide_for_position <- function(guides, position) { - params <- guides$params - has_position <- vapply( - params, function(p) identical(p$position, position), logical(1) - ) - if (!any(has_position)) { - return(NULL) - } - - # Subset guides and parameters - guides <- guides$get_guide(has_position) - params <- params[has_position] - # Pair up guides with parameters - pairs <- Map(list, guide = guides, params = params) - - # Early exit, nothing to merge - if (length(pairs) == 1) { - return(pairs[[1]]) + if (!inherits(guides, "Guides")) { + return(zeroGrob()) } - - # TODO: There must be a smarter way to merge these - order <- order(vapply(params, function(p) as.numeric(p$order), numeric(1))) - Reduce( - function(old, new) { - old$guide$merge(old$params, new$guide, new$params) - }, - pairs[order] - ) + pair <- guides$get_position(position) + pair$guide$draw(theme, pair$params) } diff --git a/R/guides-.R b/R/guides-.R index d7d4d58ff1..163559b99c 100644 --- a/R/guides-.R +++ b/R/guides-.R @@ -219,6 +219,35 @@ Guides <- ggproto( } }, + get_position = function(self, position) { + check_string("position") + + guide_positions <- lapply(self$params, `[[`, "position") + idx <- which(vapply(guide_positions, identical, logical(1), y = position)) + + if (length(idx) < 1) { + # No guide found for position, return missing (guide_none) guide + return(list(guide = self$missing, params = self$missing$params)) + } + if (length(idx) == 1) { + # Happy path when nothing needs to merge + return(list(guide = self$guides[[idx]], params = self$params[[idx]])) + } + + # Pair up guides and parameters + params <- self$params[idx] + pairs <- Map(list, guide = self$guides[idx], params = params) + + # Merge pairs sequentially + order <- order(vapply(params, function(p) as.numeric(p$order), numeric(1))) + Reduce( + function(old, new) { + old$guide$merge(old$params, new$guide, new$params) + }, + pairs[order] + ) + }, + ## Building ------------------------------------------------------------------ # The `Guides$build()` method is called in ggplotGrob (plot-build.R) and makes From 7a50742736c11e89bd30c47be0ef8095f0129aea Mon Sep 17 00:00:00 2001 From: Teun van den Brand <49372158+teunbrand@users.noreply.github.com> Date: Sat, 8 Jul 2023 16:14:45 +0200 Subject: [PATCH 11/16] GuideColoursteps is a named class --- R/guide-colorsteps.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/guide-colorsteps.R b/R/guide-colorsteps.R index 6e7b857b56..d8c85bbad5 100644 --- a/R/guide-colorsteps.R +++ b/R/guide-colorsteps.R @@ -74,7 +74,7 @@ guide_colorsteps <- guide_coloursteps #' @usage NULL #' @export GuideColoursteps <- ggproto( - NULL, GuideColourbar, + "GuideColoursteps", GuideColourbar, params = c( list(even.steps = TRUE, show.limits = NULL), From 93c555ee3cd59d51dd82d68fd362bdc510b1203c Mon Sep 17 00:00:00 2001 From: Teun van den Brand <49372158+teunbrand@users.noreply.github.com> Date: Sat, 8 Jul 2023 17:52:55 +0200 Subject: [PATCH 12/16] `guide_colourbar()` rejects discrete scales --- R/guide-colorbar.R | 8 ++++++++ tests/testthat/test-guides.R | 10 ++++++++++ 2 files changed, 18 insertions(+) diff --git a/R/guide-colorbar.R b/R/guide-colorbar.R index b678d8d4e0..a893a81bb0 100644 --- a/R/guide-colorbar.R +++ b/R/guide-colorbar.R @@ -319,6 +319,14 @@ GuideColourbar <- ggproto( title.align = "legend.title.align" ), + extract_key = function(scale, aesthetic, ...) { + if (scale$is_discrete()) { + cli::cli_warn("{.fn guide_colourbar} needs continuous scales.") + return(NULL) + } + Guide$extract_key(scale, aesthetic, ...) + }, + extract_decor = function(scale, aesthetic, nbin = 300, reverse = FALSE, ...) { limits <- scale$get_limits() diff --git a/tests/testthat/test-guides.R b/tests/testthat/test-guides.R index 86edd4cbd1..25b7087233 100644 --- a/tests/testthat/test-guides.R +++ b/tests/testthat/test-guides.R @@ -311,6 +311,16 @@ test_that("guide_coloursteps and guide_bins return ordered breaks", { expect_true(all(diff(key$.value) < 0)) }) +test_that("guide_colourbar warns about discrete scales", { + + g <- guide_colourbar() + s <- scale_colour_discrete() + s$train(LETTERS[1:3]) + + expect_warning(g <- g$train(g$params, s, "colour"), "needs continuous scales") + expect_null(g) +}) + # Visual tests ------------------------------------------------------------ test_that("axis guides are drawn correctly", { From 22ac19e9f3e1d05bc18e9291ead26421ce75cbc6 Mon Sep 17 00:00:00 2001 From: Teun van den Brand <49372158+teunbrand@users.noreply.github.com> Date: Sat, 8 Jul 2023 17:53:28 +0200 Subject: [PATCH 13/16] Fix test TODO --- tests/testthat/test-guides.R | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/testthat/test-guides.R b/tests/testthat/test-guides.R index 25b7087233..ce111a2fcc 100644 --- a/tests/testthat/test-guides.R +++ b/tests/testthat/test-guides.R @@ -128,11 +128,7 @@ test_that("a warning is generated when more than one position guide is drawn at ) built <- expect_silent(ggplot_build(plot)) - # TODO: These multiple warnings should be summarized nicely. Until this gets - # fixed, this test ignores all the following errors than the first one. - suppressWarnings( - expect_warning(ggplot_gtable(built), "Discarding guide") - ) + expect_warning(ggplot_gtable(built), "Discarding guide") }) test_that("a warning is not generated when properly changing the position of a guide_axis()", { From a3b0d7a6f97f7640f470af2dfe3813d17a154063 Mon Sep 17 00:00:00 2001 From: Teun van den Brand <49372158+teunbrand@users.noreply.github.com> Date: Sat, 8 Jul 2023 23:40:49 +0200 Subject: [PATCH 14/16] Use `vec_slice()` to preserve attributes --- R/guide-.R | 18 +++++++++++++++++- R/guide-axis.R | 12 ++---------- R/guide-colorbar.R | 2 +- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/R/guide-.R b/R/guide-.R index b9e2685ff1..1c0b6489ac 100644 --- a/R/guide-.R +++ b/R/guide-.R @@ -137,13 +137,18 @@ Guide <- ggproto( mapped <- scale$map(breaks) labels <- scale$get_labels(breaks) + # {vctrs} doesn't play nice with expressions, convert to list. + # see also https://github.com/r-lib/vctrs/issues/559 + if (is.expression(labels)) { + labels <- as.list(labels) + } key <- data_frame(mapped, .name_repair = ~ aesthetic) key$.value <- breaks key$.label <- labels if (is.numeric(breaks)) { - key[is.finite(breaks), , drop = FALSE] + vec_slice(key, is.finite(breaks)) } else { key } @@ -342,3 +347,14 @@ flip_names = c( # Shortcut for position argument matching .trbl <- c("top", "right", "bottom", "left") +# Ensure that labels aren't a list of expressions, but proper expressions +validate_labels <- function(labels) { + if (!is.list(labels)) { + return(labels) + } + if (any(vapply(labels, is.language, logical(1)))) { + do.call(expression, labels) + } else { + unlist(labels) + } +} diff --git a/R/guide-axis.R b/R/guide-axis.R index 20f8260147..d7710fba4b 100644 --- a/R/guide-axis.R +++ b/R/guide-axis.R @@ -281,22 +281,14 @@ GuideAxis <- ggproto( }, build_labels = function(key, elements, params) { - labels <- key$.label + labels <- validate_labels(key$.label) n_labels <- length(labels) if (n_labels < 1) { return(list(zeroGrob())) } - pos <- key[[params$aes]] - - if (is.list(labels)) { - if (any(vapply(labels, is.language, logical(1)))) { - labels <- do.call(expression, labels) - } else { - labels <- unlist(labels) - } - } + pos <- key[[params$aes]] dodge_pos <- rep(seq_len(params$n.dodge %||% 1), length.out = n_labels) dodge_indices <- unname(split(seq_len(n_labels), dodge_pos)) diff --git a/R/guide-colorbar.R b/R/guide-colorbar.R index a893a81bb0..1d0d16dbfa 100644 --- a/R/guide-colorbar.R +++ b/R/guide-colorbar.R @@ -437,7 +437,7 @@ GuideColourbar <- ggproto( list(labels = flip_element_grob( elements$text, - label = key$.label, + label = validate_labels(key$.label), x = unit(key$.value, "npc"), y = rep(just, nrow(key)), margin_x = FALSE, From 35a03d95ba76aaca812997d294fdea3e723df9be Mon Sep 17 00:00:00 2001 From: Teun van den Brand <49372158+teunbrand@users.noreply.github.com> Date: Sun, 9 Jul 2023 13:17:56 +0200 Subject: [PATCH 15/16] Document extension points --- R/guide-.R | 74 +++++++++++++++++++++++++++++++++++++++++- man/ggplot2-ggproto.Rd | 59 ++++++++++++++++++++++++++++++++- 2 files changed, 131 insertions(+), 2 deletions(-) diff --git a/R/guide-.R b/R/guide-.R index 1c0b6489ac..bf3d7fe243 100644 --- a/R/guide-.R +++ b/R/guide-.R @@ -74,7 +74,79 @@ new_guide <- function(..., available_aes = "any", super) { #' To create a new type of Guide object, you typically will want to override #' one or more of the following: #' -#' TODO: Fill this in properly +#' Properties: +#' +#' - `available_aes` A `character` vector with aesthetics that this guide +#' supports. The value `"any"` indicates all non-position aesthetics. +#' +#' - `params` A named `list` of parameters that the guide needs to function. +#' It has the following roles: +#' +#' - `params` provides the defaults for a guide. +#' - `names(params)` determines what are valid arguments to `new_guide()`. +#' Some parameters are *required* to render the guide. These are: `title`, +#' `name`, `position`, `direction`, `order` and `hash`. +#' - During build stages, `params` holds information about the guide. +#' +#' - `elements` A named list of `character`s, giving the name of theme elements +#' that should be retrieved automatically, for example `"legend.text"`. +#' +#' - `hashables` An `expression` that can be evaluated in the context of +#' `params`. The hash of the evaluated expression determines the merge +#' compatibility of guides, and is stored in `params$hash`. +#' +#' Methods: +#' +#' - `extract_key()` Returns a `data.frame` with (mapped) breaks and labels +#' extracted from the scale, which will be stored in `params$key`. +#' +#' - `extract_decor()` Returns a `data.frame` containing other structured +#' information extracted from the scale, which will be stored in +#' `params$decor`. The `decor` has a guide-specific meaning: it is the bar in +#' `guide_colourbar()`, but specifies the `axis.line` in `guide_axis()`. +#' +#' - `extract_params()` Updates the `params` with other, unstructured +#' information from the scale. An example of this is inheriting the guide's +#' title from the `scale$name` field. +#' +#' - `transform()` Updates the `params$key` based on the coordinates. This +#' applies to position guides, as it rescales the aesthetic to the \[0, 1\] +#' range. +#' +#' - `merge()` Combines information from multiple guides with the same +#' `params$hash`. This ensures that e.g. `guide_legend()` can display both +#' `shape` and `colour` in the same guide. +#' +#' - `get_layer_key()` Extract information from layers. This can be used to +#' check that the guide's aesthetic is actually in use, or to gather +#' information about how legend keys should be displayed. +#' +#' - `setup_params()` Set up parameters at the beginning of drawing stages. +#' It can be used to overrule user-supplied parameters or perform checks on +#' the `params` property. +#' +#' - `override_elements()` Take populated theme elements derived from the +#' `elements` property and allows overriding these theme settings. +#' +#' - `build_title()` Render the guide's title. +#' +#' - `build_labels()` Render the guide's labels. +#' +#' - `build_decor()` Render the `params$decor`, which is different for every +#' guide. +#' +#' - `build_ticks()` Render tick marks. +#' +#' - `measure_grobs()` Measure dimensions of the graphical objects produced +#' by the `build_*()` methods to be used in the layout or assembly. +#' +#' - `arrange_layout()` Set up a layout for how graphical objects produced by +#' the `build_*()` methods should be arranged. +#' +#' - `assemble_drawing()` Take the graphical objects produced by the `build_*()` +#' methods, the measurements from `measure_grobs()` and layout from +#' `arrange_layout()` to finalise the guide. +#' #' @rdname ggplot2-ggproto #' @format NULL #' @usage NULL diff --git a/man/ggplot2-ggproto.Rd b/man/ggplot2-ggproto.Rd index a74d8877e8..80a17b0fe2 100644 --- a/man/ggplot2-ggproto.Rd +++ b/man/ggplot2-ggproto.Rd @@ -384,7 +384,64 @@ top-level \code{Guide}, and each implements their own methods for drawing. To create a new type of Guide object, you typically will want to override one or more of the following: -TODO: Fill this in properly +Properties: +\itemize{ +\item \code{available_aes} A \code{character} vector with aesthetics that this guide +supports. The value \code{"any"} indicates all non-position aesthetics. +\item \code{params} A named \code{list} of parameters that the guide needs to function. +It has the following roles: +\itemize{ +\item \code{params} provides the defaults for a guide. +\item \code{names(params)} determines what are valid arguments to \code{new_guide()}. +Some parameters are \emph{required} to render the guide. These are: \code{title}, +\code{name}, \code{position}, \code{direction}, \code{order} and \code{hash}. +\item During build stages, \code{params} holds information about the guide. +} +\item \code{elements} A named list of \code{character}s, giving the name of theme elements +that should be retrieved automatically, for example \code{"legend.text"}. +\item \code{hashables} An \code{expression} that can be evaluated in the context of +\code{params}. The hash of the evaluated expression determines the merge +compatibility of guides, and is stored in \code{params$hash}. +} + +Methods: +\itemize{ +\item \code{extract_key()} Returns a \code{data.frame} with (mapped) breaks and labels +extracted from the scale, which will be stored in \code{params$key}. +\item \code{extract_decor()} Returns a \code{data.frame} containing other structured +information extracted the scale, which will be stored in \code{params$decor}. +The \code{decor} has a guide-specific meaning: it is the bar in +\code{guide_colourbar()} but specifies the \code{axis.line} in \code{guide_axis()}. +\item \code{extract_params()} Updates the \code{params} with other, unstructured +information from the scale. An example of this is inheriting the guide's +title from the \code{scale$name} field. +\item \code{transform()} Updates the \code{params$key} based on the coordinates. This +applies to position guides, as it rescales the aesthetic to the [0, 1] +range. +\item \code{merge()} Combines information from multiple guides with the same +\code{params$hash}. This ensures that e.g. \code{guide_legend()} can display both +\code{shape} and \code{colour} in the same guide. +\item \code{get_layer_key()} Extract information from layers. This can be used to +check that the guide's aesthetic is actually in use, or to gather +information about how legend keys should be displayed. +\item \code{setup_params()} Set up parameters at the beginning of drawing stages. +It can be used to overrule user-supplied parameters or perform checks on +the \code{params} property. +\item \code{override_elements()} Take populated theme elements derived from the +\code{elements} property and allows overriding these theme settings. +\item \code{build_title()} Render the guide's title. +\item \code{build_labels()} Render the guide's labels. +\item \code{build_decor()} Render the \code{params$decor}, which is different for every +guide. +\item \code{build_ticks()} Render axis tickmarks. +\item \code{measure_grobs()} Measure dimensions of the graphical objects produced +by the \verb{build_*()} methods to be used in the layout or assembly. +\item \code{arrange_layout()} Set up a layout for how graphical objects produced by +the \verb{build_*()} methods should be arranged. +\item \code{assemble_drawing()} Take the graphical objects produced by the \verb{build_*()} +methods, the measurements from \code{measure_grobs()} and layout from +\code{arrange_layout()} to finalise the guide. +} } \section{Positions}{ From 08bfdb8ebe584544916866c1b605505620283380 Mon Sep 17 00:00:00 2001 From: Teun van den Brand <49372158+teunbrand@users.noreply.github.com> Date: Sun, 9 Jul 2023 13:45:47 +0200 Subject: [PATCH 16/16] Handle hashing in `train()` --- R/guide-.R | 11 ++++++----- R/guide-axis.R | 4 ++-- R/guide-bins.R | 5 ++--- R/guide-colorbar.R | 4 ++-- R/guide-colorsteps.R | 4 ++-- R/guide-legend.R | 5 ++--- man/ggplot2-ggproto.Rd | 8 ++++---- 7 files changed, 20 insertions(+), 21 deletions(-) diff --git a/R/guide-.R b/R/guide-.R index bf3d7fe243..ae774d30c9 100644 --- a/R/guide-.R +++ b/R/guide-.R @@ -189,14 +189,15 @@ Guide <- ggproto( return(NULL) } params$decor <- inject(self$extract_decor(scale, !!!params)) - self$extract_params(scale, params, self$hashables, ...) + params <- self$extract_params(scale, params, ...) + # Make hash + # TODO: Maybe we only need the hash on demand during merging? + params$hash <- hash(lapply(unname(self$hashables), eval_tidy, data = params)) + params }, # Setup parameters that are only available after training - # TODO: Maybe we only need the hash on demand during merging? - extract_params = function(scale, params, hashables, ...) { - # Make hash - params$hash <- hash(lapply(unname(hashables), eval_tidy, data = params)) + extract_params = function(scale, params, ...) { params }, diff --git a/R/guide-axis.R b/R/guide-axis.R index d7710fba4b..eac32b2b98 100644 --- a/R/guide-axis.R +++ b/R/guide-axis.R @@ -103,9 +103,9 @@ GuideAxis <- ggproto( ticks_length = "axis.ticks.length" ), - extract_params = function(scale, params, hashables, ...) { + extract_params = function(scale, params, ...) { params$name <- paste0(params$name, "_", params$aesthetic) - Guide$extract_params(scale, params, hashables) + params }, extract_decor = function(scale, aesthetic, position, key, cap = "none", ...) { diff --git a/R/guide-bins.R b/R/guide-bins.R index 44f41d6bd1..63c75bd0bd 100644 --- a/R/guide-bins.R +++ b/R/guide-bins.R @@ -266,7 +266,7 @@ GuideBins <- ggproto( return(key) }, - extract_params = function(scale, params, hashables, + extract_params = function(scale, params, title = waiver(), direction = NULL, ...) { show.limits <- params$show.limits %||% scale$show.limits %||% FALSE @@ -320,8 +320,7 @@ GuideBins <- ggproto( "not {.val {params$label.position}}." )) } - - Guide$extract_params(scale, params, hashables) + params }, setup_params = function(params) { diff --git a/R/guide-colorbar.R b/R/guide-colorbar.R index 1d0d16dbfa..f31598430b 100644 --- a/R/guide-colorbar.R +++ b/R/guide-colorbar.R @@ -345,7 +345,7 @@ GuideColourbar <- ggproto( return(bar) }, - extract_params = function(scale, params, hashables, + extract_params = function(scale, params, title = waiver(), direction = "vertical", ...) { params$title <- scale$make_title( params$title %|W|% scale$name %|W|% title @@ -374,7 +374,7 @@ GuideColourbar <- ggproto( c(0.5, params$nbin - 0.5) / params$nbin, limits ) - Guide$extract_params(scale, params, hashables) + params }, merge = function(self, params, new_guide, new_params) { diff --git a/R/guide-colorsteps.R b/R/guide-colorsteps.R index d8c85bbad5..e82715c543 100644 --- a/R/guide-colorsteps.R +++ b/R/guide-colorsteps.R @@ -135,7 +135,7 @@ GuideColoursteps <- ggproto( return(bar) }, - extract_params = function(scale, params, hashables, ...) { + extract_params = function(scale, params, ...) { if (params$even.steps) { params$nbin <- nbin <- sum(!is.na(params$key[[1]])) + 1 @@ -177,6 +177,6 @@ GuideColoursteps <- ggproto( params$key <- key } - GuideColourbar$extract_params(scale, params, hashables, ...) + GuideColourbar$extract_params(scale, params, ...) } ) diff --git a/R/guide-legend.R b/R/guide-legend.R index b2c78cfbb1..5a0f625305 100644 --- a/R/guide-legend.R +++ b/R/guide-legend.R @@ -261,7 +261,7 @@ GuideLegend <- ggproto( title.align = "legend.title.align" ), - extract_params = function(scale, params, hashables, + extract_params = function(scale, params, title = waiver(), direction = NULL, ...) { params$title <- scale$make_title( params$title %|W|% scale$name %|W|% title @@ -273,8 +273,7 @@ GuideLegend <- ggproto( if (isTRUE(params$reverse %||% FALSE)) { params$key <- params$key[nrow(params$key):1, , drop = FALSE] } - - Guide$extract_params(scale, params, hashables) + params }, merge = function(self, params, new_guide, new_params) { diff --git a/man/ggplot2-ggproto.Rd b/man/ggplot2-ggproto.Rd index 80a17b0fe2..37a042dd68 100644 --- a/man/ggplot2-ggproto.Rd +++ b/man/ggplot2-ggproto.Rd @@ -409,9 +409,9 @@ Methods: \item \code{extract_key()} Returns a \code{data.frame} with (mapped) breaks and labels extracted from the scale, which will be stored in \code{params$key}. \item \code{extract_decor()} Returns a \code{data.frame} containing other structured -information extracted the scale, which will be stored in \code{params$decor}. -The \code{decor} has a guide-specific meaning: it is the bar in -\code{guide_colourbar()} but specifies the \code{axis.line} in \code{guide_axis()}. +information extracted from the scale, which will be stored in +\code{params$decor}. The \code{decor} has a guide-specific meaning: it is the bar in +\code{guide_colourbar()}, but specifies the \code{axis.line} in \code{guide_axis()}. \item \code{extract_params()} Updates the \code{params} with other, unstructured information from the scale. An example of this is inheriting the guide's title from the \code{scale$name} field. @@ -433,7 +433,7 @@ the \code{params} property. \item \code{build_labels()} Render the guide's labels. \item \code{build_decor()} Render the \code{params$decor}, which is different for every guide. -\item \code{build_ticks()} Render axis tickmarks. +\item \code{build_ticks()} Render tick marks. \item \code{measure_grobs()} Measure dimensions of the graphical objects produced by the \verb{build_*()} methods to be used in the layout or assembly. \item \code{arrange_layout()} Set up a layout for how graphical objects produced by