diff --git a/NEWS.md b/NEWS.md index c91420661f..cfc08264c2 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,9 @@ # ggplot2 (development version) +* (breaking) In the `scale_{colour/fill}_gradient2()` and + `scale_{colour/fill}_steps2()` functions, the `midpoint` argument is + transformed by the scale transformation (#3198). + * `guide_colourbar()` and `guide_coloursteps()` gain an `alpha` argument to set the transparency of the bar (#5085). diff --git a/R/scale-.R b/R/scale-.R index 2d22133fae..d4776ca5ea 100644 --- a/R/scale-.R +++ b/R/scale-.R @@ -609,7 +609,7 @@ check_breaks_labels <- function(breaks, labels, call = NULL) { default_transform <- function(self, x) { transformation <- self$get_transformation() new_x <- transformation$transform(x) - check_transformation(x, new_x, self$transformation$name, self$call) + check_transformation(x, new_x, self$transformation$name, call = self$call) new_x } @@ -1329,13 +1329,17 @@ scale_flip_position <- function(scale) { invisible() } -check_transformation <- function(x, transformed, name, call = NULL) { - if (any(is.finite(x) != is.finite(transformed))) { - cli::cli_warn( - "{.field {name}} transformation introduced infinite values.", - call = call - ) +check_transformation <- function(x, transformed, name, arg = NULL, call = NULL) { + if (!any(is.finite(x) != is.finite(transformed))) { + return(invisible()) + } + if (is.null(arg)) { + end <- "." + } else { + end <- paste0(" in {.arg {arg}}.") } + msg <- paste0("{.field {name}} transformation introduced infinite values", end) + cli::cli_warn(msg, call = call) } trans_support_nbreaks <- function(trans) { diff --git a/R/scale-gradient.R b/R/scale-gradient.R index 16461e2ca4..4739fd342f 100644 --- a/R/scale-gradient.R +++ b/R/scale-gradient.R @@ -90,37 +90,47 @@ scale_fill_gradient <- function(..., low = "#132B43", high = "#56B1F7", space = } #' @inheritParams scales::pal_div_gradient +#' @inheritParams continuous_scale #' @param midpoint The midpoint (in data value) of the diverging scale. #' Defaults to 0. #' @rdname scale_gradient #' @export scale_colour_gradient2 <- function(..., low = muted("red"), mid = "white", high = muted("blue"), - midpoint = 0, space = "Lab", na.value = "grey50", guide = "colourbar", + midpoint = 0, space = "Lab", na.value = "grey50", + transform = "identity", guide = "colourbar", aesthetics = "colour") { continuous_scale( aesthetics, - palette = pal_div_gradient(low, mid, high, space), - na.value = na.value, guide = guide, ..., - rescaler = mid_rescaler(mid = midpoint) + palette = div_gradient_pal(low, mid, high, space), + na.value = na.value, transform = transform, guide = guide, ..., + rescaler = mid_rescaler(mid = midpoint, transform = transform) ) } #' @rdname scale_gradient #' @export scale_fill_gradient2 <- function(..., low = muted("red"), mid = "white", high = muted("blue"), - midpoint = 0, space = "Lab", na.value = "grey50", guide = "colourbar", + midpoint = 0, space = "Lab", na.value = "grey50", + transform = "identity", guide = "colourbar", aesthetics = "fill") { continuous_scale( aesthetics, - palette = pal_div_gradient(low, mid, high, space), - na.value = na.value, guide = guide, ..., - rescaler = mid_rescaler(mid = midpoint) + palette = div_gradient_pal(low, mid, high, space), + na.value = na.value, transform = transform, guide = guide, ..., + rescaler = mid_rescaler(mid = midpoint, transform = transform) ) } -mid_rescaler <- function(mid) { +mid_rescaler <- function(mid, transform = "identity", + arg = caller_arg(mid), call = caller_env()) { + transform <- as.trans(transform) + trans_mid <- transform$transform(mid) + check_transformation( + mid, trans_mid, transform$name, + arg = arg, call = call + ) function(x, to = c(0, 1), from = range(x, na.rm = TRUE)) { - rescale_mid(x, to, from, mid) + rescale_mid(x, to, from, trans_mid) } } diff --git a/R/scale-steps.R b/R/scale-steps.R index b5a1b2fb37..1402f0ff75 100644 --- a/R/scale-steps.R +++ b/R/scale-steps.R @@ -52,10 +52,13 @@ scale_colour_steps <- function(..., low = "#132B43", high = "#56B1F7", space = " #' @rdname scale_steps #' @export scale_colour_steps2 <- function(..., low = muted("red"), mid = "white", high = muted("blue"), - midpoint = 0, space = "Lab", na.value = "grey50", guide = "coloursteps", + midpoint = 0, space = "Lab", na.value = "grey50", + transform = "identity", guide = "coloursteps", aesthetics = "colour") { - binned_scale(aesthetics, palette = pal_div_gradient(low, mid, high, space), - na.value = na.value, guide = guide, rescaler = mid_rescaler(mid = midpoint), ...) + binned_scale(aesthetics, palette = div_gradient_pal(low, mid, high, space), + na.value = na.value, transform = transform, guide = guide, + rescaler = mid_rescaler(mid = midpoint, transform = transform), + ...) } #' @rdname scale_steps #' @export @@ -75,10 +78,12 @@ scale_fill_steps <- function(..., low = "#132B43", high = "#56B1F7", space = "La #' @rdname scale_steps #' @export scale_fill_steps2 <- function(..., low = muted("red"), mid = "white", high = muted("blue"), - midpoint = 0, space = "Lab", na.value = "grey50", guide = "coloursteps", + midpoint = 0, space = "Lab", na.value = "grey50", + transform = "identity", guide = "coloursteps", aesthetics = "fill") { - binned_scale(aesthetics, palette = pal_div_gradient(low, mid, high, space), - na.value = na.value, guide = guide, rescaler = mid_rescaler(mid = midpoint), ...) + binned_scale(aesthetics, palette = div_gradient_pal(low, mid, high, space), + na.value = na.value, transform = transform, guide = guide, + rescaler = mid_rescaler(mid = midpoint, transform = transform), ...) } #' @rdname scale_steps #' @export diff --git a/man/scale_gradient.Rd b/man/scale_gradient.Rd index 657ef6e60a..3471bd16e4 100644 --- a/man/scale_gradient.Rd +++ b/man/scale_gradient.Rd @@ -46,6 +46,7 @@ scale_colour_gradient2( midpoint = 0, space = "Lab", na.value = "grey50", + transform = "identity", guide = "colourbar", aesthetics = "colour" ) @@ -58,6 +59,7 @@ scale_fill_gradient2( midpoint = 0, space = "Lab", na.value = "grey50", + transform = "identity", guide = "colourbar", aesthetics = "fill" ) @@ -161,18 +163,6 @@ bounds values with \code{NA}. \item \code{\link[scales:oob]{scales::squish()}} for squishing out of bounds values into range. \item \code{\link[scales:oob]{scales::squish_infinite()}} for squishing infinite values into range. }} - \item{\code{transform}}{For continuous scales, the name of a transformation object -or the object itself. Built-in transformations include "asn", "atanh", -"boxcox", "date", "exp", "hms", "identity", "log", "log10", "log1p", "log2", -"logit", "modulus", "probability", "probit", "pseudo_log", "reciprocal", -"reverse", "sqrt" and "time". - -A transformation object bundles together a transform, its inverse, -and methods for generating breaks and labels. Transformation objects -are defined in the scales package, and are called \verb{transform_}. If -transformations require arguments, you can call them from the scales -package, e.g. \code{\link[scales:transform_boxcox]{scales::transform_boxcox(p = 2)}}. -You can create your own transformation with \code{\link[scales:new_transform]{scales::new_transform()}}.} \item{\code{trans}}{\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} Deprecated in favour of \code{transform}.} \item{\code{expand}}{For position scales, a vector of range expansion constants used to add some @@ -207,6 +197,19 @@ same time, via \code{aesthetics = c("colour", "fill")}.} \item{midpoint}{The midpoint (in data value) of the diverging scale. Defaults to 0.} +\item{transform}{For continuous scales, the name of a transformation object +or the object itself. Built-in transformations include "asn", "atanh", +"boxcox", "date", "exp", "hms", "identity", "log", "log10", "log1p", "log2", +"logit", "modulus", "probability", "probit", "pseudo_log", "reciprocal", +"reverse", "sqrt" and "time". + +A transformation object bundles together a transform, its inverse, +and methods for generating breaks and labels. Transformation objects +are defined in the scales package, and are called \verb{transform_}. If +transformations require arguments, you can call them from the scales +package, e.g. \code{\link[scales:transform_boxcox]{scales::transform_boxcox(p = 2)}}. +You can create your own transformation with \code{\link[scales:new_transform]{scales::new_transform()}}.} + \item{colours, colors}{Vector of colours to use for n-colour gradient.} \item{values}{if colours should not be evenly positioned along the gradient diff --git a/man/scale_steps.Rd b/man/scale_steps.Rd index e8aab0a473..b6f5d788d9 100644 --- a/man/scale_steps.Rd +++ b/man/scale_steps.Rd @@ -30,6 +30,7 @@ scale_colour_steps2( midpoint = 0, space = "Lab", na.value = "grey50", + transform = "identity", guide = "coloursteps", aesthetics = "colour" ) @@ -63,6 +64,7 @@ scale_fill_steps2( midpoint = 0, space = "Lab", na.value = "grey50", + transform = "identity", guide = "coloursteps", aesthetics = "fill" ) @@ -142,18 +144,6 @@ bounds values with \code{NA}. \item \code{\link[scales:oob]{scales::squish()}} for squishing out of bounds values into range. \item \code{\link[scales:oob]{scales::squish_infinite()}} for squishing infinite values into range. }} - \item{\code{transform}}{For continuous scales, the name of a transformation object -or the object itself. Built-in transformations include "asn", "atanh", -"boxcox", "date", "exp", "hms", "identity", "log", "log10", "log1p", "log2", -"logit", "modulus", "probability", "probit", "pseudo_log", "reciprocal", -"reverse", "sqrt" and "time". - -A transformation object bundles together a transform, its inverse, -and methods for generating breaks and labels. Transformation objects -are defined in the scales package, and are called \verb{transform_}. If -transformations require arguments, you can call them from the scales -package, e.g. \code{\link[scales:transform_boxcox]{scales::transform_boxcox(p = 2)}}. -You can create your own transformation with \code{\link[scales:new_transform]{scales::new_transform()}}.} \item{\code{trans}}{\ifelse{html}{\href{https://lifecycle.r-lib.org/articles/stages.html#deprecated}{\figure{lifecycle-deprecated.svg}{options: alt='[Deprecated]'}}}{\strong{[Deprecated]}} Deprecated in favour of \code{transform}.} \item{\code{expand}}{For position scales, a vector of range expansion constants used to add some @@ -188,6 +178,19 @@ same time, via \code{aesthetics = c("colour", "fill")}.} \item{midpoint}{The midpoint (in data value) of the diverging scale. Defaults to 0.} +\item{transform}{For continuous scales, the name of a transformation object +or the object itself. Built-in transformations include "asn", "atanh", +"boxcox", "date", "exp", "hms", "identity", "log", "log10", "log1p", "log2", +"logit", "modulus", "probability", "probit", "pseudo_log", "reciprocal", +"reverse", "sqrt" and "time". + +A transformation object bundles together a transform, its inverse, +and methods for generating breaks and labels. Transformation objects +are defined in the scales package, and are called \verb{transform_}. If +transformations require arguments, you can call them from the scales +package, e.g. \code{\link[scales:transform_boxcox]{scales::transform_boxcox(p = 2)}}. +You can create your own transformation with \code{\link[scales:new_transform]{scales::new_transform()}}.} + \item{colours, colors}{Vector of colours to use for n-colour gradient.} \item{values}{if colours should not be evenly positioned along the gradient diff --git a/tests/testthat/test-scale-gradient.R b/tests/testthat/test-scale-gradient.R index fafa2226fe..d37cb6d80d 100644 --- a/tests/testthat/test-scale-gradient.R +++ b/tests/testthat/test-scale-gradient.R @@ -9,3 +9,19 @@ test_that("points outside the limits are plotted as NA", { correct_fill <- c("#B26D65", "#DCB4AF", "orange") expect_equal(layer_data(p)$fill, correct_fill) }) + +test_that("midpoints are transformed", { + + scale <- scale_colour_gradient2(midpoint = 1, trans = "identity") + scale$train(c(0, 3)) + expect_equal(scale$rescale(c(0, 3)), c(0.25, 1)) + + scale <- scale_colour_gradient2(midpoint = 10, trans = "log10") + scale$train(scale$transform(c(1, 1000))) + ans <- scale$rescale(c(0, 3), c(0.25, 1)) + + expect_warning( + scale_colour_gradient2(midpoint = 0, transform = "log10"), + "introduced infinite values" + ) +})