From c0e9b80442626021d0827cc7a42048ec3fe27607 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Wed, 4 Dec 2024 15:13:42 +0100 Subject: [PATCH 01/16] collect all default colour scales in one place --- R/scale-colour.R | 88 +++++++++++++++++++++++++++++++++++++++++++++++ R/scale-hue.R | 89 ------------------------------------------------ 2 files changed, 88 insertions(+), 89 deletions(-) diff --git a/R/scale-colour.R b/R/scale-colour.R index a17d872dbe..01d6d3e9d5 100644 --- a/R/scale-colour.R +++ b/R/scale-colour.R @@ -154,6 +154,94 @@ scale_fill_binned <- function(..., aesthetics = "fill", guide = "coloursteps", ) } +#' Discrete colour scales +#' +#' The default discrete colour scale. Defaults to [scale_fill_hue()]/[scale_fill_brewer()] +#' unless `type` (which defaults to the `ggplot2.discrete.fill`/`ggplot2.discrete.colour` options) +#' is specified. +#' +#' @param ... Additional parameters passed on to the scale type, +#' @inheritParams discrete_scale +#' @param type One of the following: +#' * A character vector of color codes. The codes are used for a 'manual' color +#' scale as long as the number of codes exceeds the number of data levels +#' (if there are more levels than codes, [scale_colour_hue()]/[scale_fill_hue()] +#' are used to construct the default scale). If this is a named vector, then the color values +#' will be matched to levels based on the names of the vectors. Data values that +#' don't match will be set as `na.value`. +#' * A list of character vectors of color codes. The minimum length vector that exceeds the +#' number of data levels is chosen for the color scaling. This is useful if you +#' want to change the color palette based on the number of levels. +#' * A function that returns a discrete colour/fill scale (e.g., [scale_fill_hue()], +#' [scale_fill_brewer()], etc). +#' @export +#' @seealso +#' The `r link_book("discrete colour scales section", "scales-colour#sec-colour-discrete")` +#' @examples +#' # Template function for creating densities grouped by a variable +#' cty_by_var <- function(var) { +#' ggplot(mpg, aes(cty, colour = factor({{var}}), fill = factor({{var}}))) + +#' geom_density(alpha = 0.2) +#' } +#' +#' # The default, scale_fill_hue(), is not colour-blind safe +#' cty_by_var(class) +#' +#' # (Temporarily) set the default to Okabe-Ito (which is colour-blind safe) +#' okabe <- c("#E69F00", "#56B4E9", "#009E73", "#F0E442", "#0072B2", "#D55E00", "#CC79A7") +#' withr::with_options( +#' list(ggplot2.discrete.fill = okabe), +#' print(cty_by_var(class)) +#' ) +#' +#' # Define a collection of palettes to alter the default based on number of levels to encode +#' discrete_palettes <- list( +#' c("skyblue", "orange"), +#' RColorBrewer::brewer.pal(3, "Set2"), +#' RColorBrewer::brewer.pal(6, "Accent") +#' ) +#' withr::with_options( +#' list(ggplot2.discrete.fill = discrete_palettes), { +#' # 1st palette is used when there 1-2 levels (e.g., year) +#' print(cty_by_var(year)) +#' # 2nd palette is used when there are 3 levels +#' print(cty_by_var(drv)) +#' # 3rd palette is used when there are 4-6 levels +#' print(cty_by_var(fl)) +#' }) +#' +scale_colour_discrete <- function(..., aesthetics = "colour", na.value = "grey50", + type = getOption("ggplot2.discrete.colour")) { + if (!is.null(type)) { + scale <- scale_backward_compatibility( + ..., na.value = na.value, scale = type, + aesthetic = "colour", type = "discrete" + ) + return(scale) + } + discrete_scale( + aesthetics, palette = NULL, na.value = na.value, + ... + ) +} + +#' @rdname scale_colour_discrete +#' @export +scale_fill_discrete <- function(..., aesthetics = "fill", na.value = "grey50", + type = getOption("ggplot2.discrete.fill")) { + if (!is.null(type)) { + scale <- scale_backward_compatibility( + ..., na.value = na.value, scale = type, + aesthetic = "fill", type = "discrete" + ) + return(scale) + } + discrete_scale( + aesthetics, palette = NULL, na.value = na.value, + ... + ) +} + # helper function to make sure that the provided scale is of the correct # type (i.e., is continuous and works with the provided aesthetic) check_scale_type <- function(scale, name, aesthetic, scale_is_discrete = FALSE, call = caller_env()) { diff --git a/R/scale-hue.R b/R/scale-hue.R index b95938ceb6..311533e283 100644 --- a/R/scale-hue.R +++ b/R/scale-hue.R @@ -78,95 +78,6 @@ scale_fill_hue <- function(name = waiver(), ..., h = c(0, 360) + 15, c = 100, ) } - -#' Discrete colour scales -#' -#' The default discrete colour scale. Defaults to [scale_fill_hue()]/[scale_fill_brewer()] -#' unless `type` (which defaults to the `ggplot2.discrete.fill`/`ggplot2.discrete.colour` options) -#' is specified. -#' -#' @param ... Additional parameters passed on to the scale type, -#' @inheritParams discrete_scale -#' @param type One of the following: -#' * A character vector of color codes. The codes are used for a 'manual' color -#' scale as long as the number of codes exceeds the number of data levels -#' (if there are more levels than codes, [scale_colour_hue()]/[scale_fill_hue()] -#' are used to construct the default scale). If this is a named vector, then the color values -#' will be matched to levels based on the names of the vectors. Data values that -#' don't match will be set as `na.value`. -#' * A list of character vectors of color codes. The minimum length vector that exceeds the -#' number of data levels is chosen for the color scaling. This is useful if you -#' want to change the color palette based on the number of levels. -#' * A function that returns a discrete colour/fill scale (e.g., [scale_fill_hue()], -#' [scale_fill_brewer()], etc). -#' @export -#' @seealso -#' The `r link_book("discrete colour scales section", "scales-colour#sec-colour-discrete")` -#' @examples -#' # Template function for creating densities grouped by a variable -#' cty_by_var <- function(var) { -#' ggplot(mpg, aes(cty, colour = factor({{var}}), fill = factor({{var}}))) + -#' geom_density(alpha = 0.2) -#' } -#' -#' # The default, scale_fill_hue(), is not colour-blind safe -#' cty_by_var(class) -#' -#' # (Temporarily) set the default to Okabe-Ito (which is colour-blind safe) -#' okabe <- c("#E69F00", "#56B4E9", "#009E73", "#F0E442", "#0072B2", "#D55E00", "#CC79A7") -#' withr::with_options( -#' list(ggplot2.discrete.fill = okabe), -#' print(cty_by_var(class)) -#' ) -#' -#' # Define a collection of palettes to alter the default based on number of levels to encode -#' discrete_palettes <- list( -#' c("skyblue", "orange"), -#' RColorBrewer::brewer.pal(3, "Set2"), -#' RColorBrewer::brewer.pal(6, "Accent") -#' ) -#' withr::with_options( -#' list(ggplot2.discrete.fill = discrete_palettes), { -#' # 1st palette is used when there 1-2 levels (e.g., year) -#' print(cty_by_var(year)) -#' # 2nd palette is used when there are 3 levels -#' print(cty_by_var(drv)) -#' # 3rd palette is used when there are 4-6 levels -#' print(cty_by_var(fl)) -#' }) -#' -scale_colour_discrete <- function(..., aesthetics = "colour", na.value = "grey50", - type = getOption("ggplot2.discrete.colour")) { - if (!is.null(type)) { - scale <- scale_backward_compatibility( - ..., na.value = na.value, scale = type, - aesthetic = "colour", type = "discrete" - ) - return(scale) - } - discrete_scale( - aesthetics, palette = NULL, na.value = na.value, - ... - ) -} - -#' @rdname scale_colour_discrete -#' @export -scale_fill_discrete <- function(..., aesthetics = "fill", na.value = "grey50", - type = getOption("ggplot2.discrete.fill")) { - if (!is.null(type)) { - scale <- scale_backward_compatibility( - ..., na.value = na.value, scale = type, - aesthetic = "fill", type = "discrete" - ) - return(scale) - } - discrete_scale( - aesthetics, palette = NULL, na.value = na.value, - ... - ) -} - scale_colour_qualitative <- function(name = waiver(), ..., type = NULL, h = c(0, 360) + 15, c = 100, l = 65, h.start = 0, direction = 1, From d9d13059fadbaa994b1e1ee335da0cf6b7baf708 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Wed, 4 Dec 2024 15:43:45 +0100 Subject: [PATCH 02/16] wrap palettes --- R/scale-colour.R | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/R/scale-colour.R b/R/scale-colour.R index 01d6d3e9d5..dc3d6418cb 100644 --- a/R/scale-colour.R +++ b/R/scale-colour.R @@ -78,7 +78,7 @@ #' v #' options(ggplot2.continuous.fill = tmp) # restore previous setting #' @export -scale_colour_continuous <- function(..., aesthetics = "colour", +scale_colour_continuous <- function(..., palette = NULL, aesthetics = "colour", guide = "colourbar", na.value = "grey50", type = getOption("ggplot2.continuous.colour")) { @@ -89,16 +89,16 @@ scale_colour_continuous <- function(..., aesthetics = "colour", ) return(scale) } - + pal <- if (!is.null(palette)) as_continuous_pal(palette) continuous_scale( - aesthetics, palette = NULL, guide = guide, na.value = na.value, + aesthetics, palette = palette, guide = guide, na.value = na.value, ... ) } #' @rdname scale_colour_continuous #' @export -scale_fill_continuous <- function(..., aesthetics = "fill", guide = "colourbar", +scale_fill_continuous <- function(..., palette = NULL, aesthetics = "fill", guide = "colourbar", na.value = "grey50", type = getOption("ggplot2.continuous.fill")) { @@ -109,16 +109,16 @@ scale_fill_continuous <- function(..., aesthetics = "fill", guide = "colourbar", ) return(scale) } - + palette <- if (!is.null(palette)) as_continuous_pal(palette) continuous_scale( - aesthetics, palette = NULL, guide = guide, na.value = na.value, + aesthetics, palette = palette, guide = guide, na.value = na.value, ... ) } #' @export #' @rdname scale_colour_continuous -scale_colour_binned <- function(..., aesthetics = "colour", guide = "coloursteps", +scale_colour_binned <- function(..., palette = NULL, aesthetics = "colour", guide = "coloursteps", na.value = "grey50", type = getOption("ggplot2.binned.colour")) { if (!is.null(type)) { @@ -128,16 +128,16 @@ scale_colour_binned <- function(..., aesthetics = "colour", guide = "coloursteps ) return(scale) } - + palette <- if (!is.null(palette)) pal_binned(as_discrete_pal(palette)) binned_scale( - aesthetics, palette = NULL, guide = guide, na.value = na.value, + aesthetics, palette = palette, guide = guide, na.value = na.value, ... ) } #' @export #' @rdname scale_colour_continuous -scale_fill_binned <- function(..., aesthetics = "fill", guide = "coloursteps", +scale_fill_binned <- function(..., palette = NULL, aesthetics = "fill", guide = "coloursteps", na.value = "grey50", type = getOption("ggplot2.binned.fill")) { if (!is.null(type)) { @@ -147,9 +147,9 @@ scale_fill_binned <- function(..., aesthetics = "fill", guide = "coloursteps", ) return(scale) } - + palette <- if (!is.null(palette)) pal_binned(as_discrete_pal(palette)) binned_scale( - aesthetics, palette = NULL, guide = guide, na.value = na.value, + aesthetics, palette = palette, guide = guide, na.value = na.value, ... ) } @@ -210,7 +210,7 @@ scale_fill_binned <- function(..., aesthetics = "fill", guide = "coloursteps", #' print(cty_by_var(fl)) #' }) #' -scale_colour_discrete <- function(..., aesthetics = "colour", na.value = "grey50", +scale_colour_discrete <- function(..., palette = NULL, aesthetics = "colour", na.value = "grey50", type = getOption("ggplot2.discrete.colour")) { if (!is.null(type)) { scale <- scale_backward_compatibility( @@ -219,15 +219,16 @@ scale_colour_discrete <- function(..., aesthetics = "colour", na.value = "grey50 ) return(scale) } + palette <- if (!is.null(palette)) as_discrete_pal(palette) discrete_scale( - aesthetics, palette = NULL, na.value = na.value, + aesthetics, palette = palette, na.value = na.value, ... ) } #' @rdname scale_colour_discrete #' @export -scale_fill_discrete <- function(..., aesthetics = "fill", na.value = "grey50", +scale_fill_discrete <- function(..., palette = NULL, aesthetics = "fill", na.value = "grey50", type = getOption("ggplot2.discrete.fill")) { if (!is.null(type)) { scale <- scale_backward_compatibility( @@ -236,8 +237,9 @@ scale_fill_discrete <- function(..., aesthetics = "fill", na.value = "grey50", ) return(scale) } + palette <- if (!is.null(palette)) as_discrete_pal(palette) discrete_scale( - aesthetics, palette = NULL, na.value = na.value, + aesthetics, palette = palette, na.value = na.value, ... ) } From 33ca805c0fa5ef3832d6035284b7c2c2b8db8870 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Wed, 4 Dec 2024 15:52:44 +0100 Subject: [PATCH 03/16] document `palette` parameter --- R/scale-colour.R | 12 ++++++++++++ man/scale_colour_continuous.Rd | 13 +++++++++++++ man/scale_colour_discrete.Rd | 13 ++++++++++++- 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/R/scale-colour.R b/R/scale-colour.R index dc3d6418cb..542cce7143 100644 --- a/R/scale-colour.R +++ b/R/scale-colour.R @@ -28,6 +28,12 @@ #' [scale_colour_gradient()] or [scale_colour_steps()]. #' #' @inheritParams continuous_scale +#' @param palette One of the following: +#' * `NULL` for the default palette stored in the theme. +#' * a character vector of colours. +#' * a single string naming a palette. +#' * a palette function that when called with a numeric vector with values +#' between 0 and 1 returns the corresponding output values. #' @param ... Additional parameters passed on to the scale type #' @param type One of the following: #' * "gradient" (the default) @@ -160,6 +166,12 @@ scale_fill_binned <- function(..., palette = NULL, aesthetics = "fill", guide = #' unless `type` (which defaults to the `ggplot2.discrete.fill`/`ggplot2.discrete.colour` options) #' is specified. #' +#' @param palette One of the following: +#' * `NULL` for the default palette stored in the theme. +#' * a character vector of colours. +#' * a single string naming a palette. +#' * a palette function that when called with a single integer argument (the +#' number of levels in the scale) returns the values that they should take. #' @param ... Additional parameters passed on to the scale type, #' @inheritParams discrete_scale #' @param type One of the following: diff --git a/man/scale_colour_continuous.Rd b/man/scale_colour_continuous.Rd index d88a74f399..c945394d2b 100644 --- a/man/scale_colour_continuous.Rd +++ b/man/scale_colour_continuous.Rd @@ -11,6 +11,7 @@ \usage{ scale_colour_continuous( ..., + palette = NULL, aesthetics = "colour", guide = "colourbar", na.value = "grey50", @@ -19,6 +20,7 @@ scale_colour_continuous( scale_fill_continuous( ..., + palette = NULL, aesthetics = "fill", guide = "colourbar", na.value = "grey50", @@ -27,6 +29,7 @@ scale_fill_continuous( scale_colour_binned( ..., + palette = NULL, aesthetics = "colour", guide = "coloursteps", na.value = "grey50", @@ -35,6 +38,7 @@ scale_colour_binned( scale_fill_binned( ..., + palette = NULL, aesthetics = "fill", guide = "coloursteps", na.value = "grey50", @@ -44,6 +48,15 @@ scale_fill_binned( \arguments{ \item{...}{Additional parameters passed on to the scale type} +\item{palette}{One of the following: +\itemize{ +\item \code{NULL} for the default palette stored in the theme. +\item a character vector of colours. +\item a single string naming a palette. +\item a palette function that when called with a numeric vector with values +between 0 and 1 returns the corresponding output values. +}} + \item{aesthetics}{The names of the aesthetics that this scale works with.} \item{guide}{A function used to create a guide or its name. See diff --git a/man/scale_colour_discrete.Rd b/man/scale_colour_discrete.Rd index ff8fe3f9e7..0c22c4672f 100644 --- a/man/scale_colour_discrete.Rd +++ b/man/scale_colour_discrete.Rd @@ -1,5 +1,5 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/scale-hue.R, R/zxx.R +% Please edit documentation in R/scale-colour.R, R/zxx.R \name{scale_colour_discrete} \alias{scale_colour_discrete} \alias{scale_fill_discrete} @@ -8,6 +8,7 @@ \usage{ scale_colour_discrete( ..., + palette = NULL, aesthetics = "colour", na.value = "grey50", type = getOption("ggplot2.discrete.colour") @@ -15,6 +16,7 @@ scale_colour_discrete( scale_fill_discrete( ..., + palette = NULL, aesthetics = "fill", na.value = "grey50", type = getOption("ggplot2.discrete.fill") @@ -23,6 +25,15 @@ scale_fill_discrete( \arguments{ \item{...}{Additional parameters passed on to the scale type,} +\item{palette}{One of the following: +\itemize{ +\item \code{NULL} for the default palette stored in the theme. +\item a character vector of colours. +\item a single string naming a palette. +\item a palette function that when called with a single integer argument (the +number of levels in the scale) returns the values that they should take. +}} + \item{aesthetics}{The names of the aesthetics that this scale works with.} \item{na.value}{If \code{na.translate = TRUE}, what aesthetic value should the From 917353cde7059ec639dfad5768de38304ba9d247 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Wed, 4 Dec 2024 15:55:14 +0100 Subject: [PATCH 04/16] fix typo --- R/scale-colour.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/scale-colour.R b/R/scale-colour.R index 542cce7143..16e84277bb 100644 --- a/R/scale-colour.R +++ b/R/scale-colour.R @@ -95,7 +95,7 @@ scale_colour_continuous <- function(..., palette = NULL, aesthetics = "colour", ) return(scale) } - pal <- if (!is.null(palette)) as_continuous_pal(palette) + palette <- if (!is.null(palette)) as_continuous_pal(palette) continuous_scale( aesthetics, palette = palette, guide = guide, na.value = na.value, ... From 0a78264b76b17cbfdc0b28d71d07922c3197c7bd Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Wed, 4 Dec 2024 16:18:10 +0100 Subject: [PATCH 05/16] avoid recursive problems --- R/utilities.R | 1 + 1 file changed, 1 insertion(+) diff --git a/R/utilities.R b/R/utilities.R index 54087eba68..a953546eae 100644 --- a/R/utilities.R +++ b/R/utilities.R @@ -195,6 +195,7 @@ waiver <- function() structure(list(), class = "waiver") is.waiver <- function(x) inherits(x, "waiver") pal_binned <- function(palette) { + force(palette) function(x) { palette(length(x)) } From b32619adca3ed896dde371fe7e01ca85e6408098 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Wed, 4 Dec 2024 16:20:35 +0100 Subject: [PATCH 06/16] only fall back when no explicit palette exists --- R/scale-colour.R | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/R/scale-colour.R b/R/scale-colour.R index 16e84277bb..7c306c518d 100644 --- a/R/scale-colour.R +++ b/R/scale-colour.R @@ -88,7 +88,7 @@ scale_colour_continuous <- function(..., palette = NULL, aesthetics = "colour", guide = "colourbar", na.value = "grey50", type = getOption("ggplot2.continuous.colour")) { - if (!is.null(type)) { + if (!is.null(type) && is.null(palette)) { scale <- scale_backward_compatibility( ..., guide = guide, na.value = na.value, scale = type, aesthetic = "colour", type = "continuous" @@ -108,7 +108,7 @@ scale_fill_continuous <- function(..., palette = NULL, aesthetics = "fill", guid na.value = "grey50", type = getOption("ggplot2.continuous.fill")) { - if (!is.null(type)) { + if (!is.null(type) && is.null(palette)) { scale <- scale_backward_compatibility( ..., guide = guide, na.value = na.value, scale = type, aesthetic = "fill", type = "continuous" @@ -127,7 +127,7 @@ scale_fill_continuous <- function(..., palette = NULL, aesthetics = "fill", guid scale_colour_binned <- function(..., palette = NULL, aesthetics = "colour", guide = "coloursteps", na.value = "grey50", type = getOption("ggplot2.binned.colour")) { - if (!is.null(type)) { + if (!is.null(type) && is.null(palette)) { scale <- scale_backward_compatibility( ..., guide = guide, na.value = na.value, scale = type, aesthetic = "colour", type = "binned" @@ -146,7 +146,7 @@ scale_colour_binned <- function(..., palette = NULL, aesthetics = "colour", guid scale_fill_binned <- function(..., palette = NULL, aesthetics = "fill", guide = "coloursteps", na.value = "grey50", type = getOption("ggplot2.binned.fill")) { - if (!is.null(type)) { + if (!is.null(type) && is.null(palette)) { scale <- scale_backward_compatibility( ..., guide = guide, na.value = na.value, scale = type, aesthetic = "fill", type = "binned" @@ -224,7 +224,7 @@ scale_fill_binned <- function(..., palette = NULL, aesthetics = "fill", guide = #' scale_colour_discrete <- function(..., palette = NULL, aesthetics = "colour", na.value = "grey50", type = getOption("ggplot2.discrete.colour")) { - if (!is.null(type)) { + if (!is.null(type) && is.null(palette)) { scale <- scale_backward_compatibility( ..., na.value = na.value, scale = type, aesthetic = "colour", type = "discrete" @@ -242,7 +242,7 @@ scale_colour_discrete <- function(..., palette = NULL, aesthetics = "colour", na #' @export scale_fill_discrete <- function(..., palette = NULL, aesthetics = "fill", na.value = "grey50", type = getOption("ggplot2.discrete.fill")) { - if (!is.null(type)) { + if (!is.null(type) && is.null(palette)) { scale <- scale_backward_compatibility( ..., na.value = na.value, scale = type, aesthetic = "fill", type = "discrete" From c0bb40950ceaea7901d5b81a953b8b674222b6ca Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Wed, 4 Dec 2024 16:21:57 +0100 Subject: [PATCH 07/16] broaden test scope --- .../{test-scale-colour-continuous.R => test-scale-colour.R} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/testthat/{test-scale-colour-continuous.R => test-scale-colour.R} (100%) diff --git a/tests/testthat/test-scale-colour-continuous.R b/tests/testthat/test-scale-colour.R similarity index 100% rename from tests/testthat/test-scale-colour-continuous.R rename to tests/testthat/test-scale-colour.R From 532d501b2778006898d312f18fa38acc88748c91 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Wed, 4 Dec 2024 16:28:34 +0100 Subject: [PATCH 08/16] add tests --- tests/testthat/test-scale-colour.R | 31 ++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/testthat/test-scale-colour.R b/tests/testthat/test-scale-colour.R index e97e3d5b01..bcdbc90892 100644 --- a/tests/testthat/test-scale-colour.R +++ b/tests/testthat/test-scale-colour.R @@ -18,3 +18,34 @@ test_that("type argument is checked for proper input", { scale_colour_continuous(type = "abc") ) }) + +test_that("palette arguments can take alternative input", { + + cols <- c("red", "gold", "green", "cyan", "blue", "magenta") + hex <- alpha(cols, 1) + + sc <- scale_colour_continuous(palette = cols) + test <- sc$palette(seq(0, 1, length.out = length(cols))) + expect_equal(alpha(test, 1), hex) + + sc <- scale_fill_continuous(palette = cols) + test <- sc$palette(seq(0, 1, length.out = length(cols))) + expect_equal(alpha(test, 1), hex) + + sc <- scale_colour_binned(palette = cols) + test <- sc$palette(seq_along(cols)) + expect_equal(alpha(test, 1), hex) + + sc <- scale_fill_binned(palette = cols) + test <- sc$palette(seq_along(cols)) + expect_equal(alpha(test, 1), hex) + + sc <- scale_colour_discrete(palette = cols) + test <- sc$palette(length(cols)) + expect_equal(alpha(test, 1), hex) + + sc <- scale_fill_discrete(palette = cols) + test <- sc$palette(length(cols)) + expect_equal(alpha(test, 1), hex) + +}) From 3b0bc919506e38ffa9960a2e2dbe99525c5ab54e Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Wed, 4 Dec 2024 16:30:38 +0100 Subject: [PATCH 09/16] add news bullet --- NEWS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NEWS.md b/NEWS.md index b7563b0dee..1ce62a4f91 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,7 @@ # ggplot2 (development version) +* The default colour and fill scales have a new `palette` argument + (@teunbrand, #6064). * New stat: `stat_manual()` for arbitrary computations (@teunbrand, #3501) * Reversal of a dimension, typically 'x' or 'y', is now controlled by the `reverse` argument in `coord_cartesian()`, `coord_fixed()`, `coord_radial()` From ab44a18b9d4267c487d420bd2c9602fa1f1e4013 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Thu, 12 Dec 2024 09:52:35 +0100 Subject: [PATCH 10/16] merging a function element gives `new` --- R/theme.R | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/R/theme.R b/R/theme.R index 2ebd892f62..7b62c22549 100644 --- a/R/theme.R +++ b/R/theme.R @@ -799,7 +799,7 @@ merge_element.default <- function(new, old) { # If old is NULL or element_blank, then just return new return(new) } else if (is.null(new) || is.character(new) || is.numeric(new) || is.unit(new) || - is.logical(new)) { + is.logical(new) || is.function(new)) { # If new is NULL, or a string, numeric vector, unit, or logical, just return it return(new) } From 20de7be09cc2d9678edc01ad4c3695347d453ce5 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Thu, 12 Dec 2024 09:52:44 +0100 Subject: [PATCH 11/16] update examples --- R/scale-colour.R | 76 ++++++++++++++-------------------- man/scale_colour_continuous.Rd | 42 ++++++++++--------- man/scale_colour_discrete.Rd | 42 +++++++------------ 3 files changed, 68 insertions(+), 92 deletions(-) diff --git a/R/scale-colour.R b/R/scale-colour.R index 7c306c518d..c4c9ec7ba6 100644 --- a/R/scale-colour.R +++ b/R/scale-colour.R @@ -63,26 +63,28 @@ #' see the [paper on the colorspace package](https://arxiv.org/abs/1903.06490) #' and references therein. #' @examples -#' v <- ggplot(faithfuld, aes(waiting, eruptions, fill = density)) + -#' geom_tile() -#' v +#' # A standard plot +#' p <- ggplot(mpg, aes(displ, hwy, colour = cty)) + +#' geom_point() #' -#' v + scale_fill_continuous(type = "gradient") -#' v + scale_fill_continuous(type = "viridis") +#' # You can use the scale to give a palette directly +#' p + scale_colour_continuous(palette = c("#FEE0D2", "#FC9272", "#DE2D26")) #' -#' # The above are equivalent to -#' v + scale_fill_gradient() -#' v + scale_fill_viridis_c() +#' # The default colours are encoded into the theme +#' p + theme(palette.colour.continuous = c("#DEEBF7", "#9ECAE1", "#3182BD")) #' -#' # To make a binned version of this plot -#' v + scale_fill_binned(type = "viridis") +#' # You can globally set default colour palette via the theme +#' old <- update_theme(palette.colour.continuous = c("#E5F5E0", "#A1D99B", "#31A354")) #' -#' # Set a different default scale using the options -#' # mechanism -#' tmp <- getOption("ggplot2.continuous.fill") # store current setting -#' options(ggplot2.continuous.fill = scale_fill_distiller) -#' v -#' options(ggplot2.continuous.fill = tmp) # restore previous setting +#' # Plot now shows new global default +#' p +#' +#' # The default binned colour scale uses the continuous palette +#' p + scale_colour_binned() + +#' theme(palette.colour.continuous = c("#EFEDF5", "#BCBDDC", "#756BB1")) +#' +#' # Restoring the previous theme +#' theme_set(old) #' @export scale_colour_continuous <- function(..., palette = NULL, aesthetics = "colour", guide = "colourbar", na.value = "grey50", @@ -190,38 +192,24 @@ scale_fill_binned <- function(..., palette = NULL, aesthetics = "fill", guide = #' @seealso #' The `r link_book("discrete colour scales section", "scales-colour#sec-colour-discrete")` #' @examples -#' # Template function for creating densities grouped by a variable -#' cty_by_var <- function(var) { -#' ggplot(mpg, aes(cty, colour = factor({{var}}), fill = factor({{var}}))) + -#' geom_density(alpha = 0.2) -#' } +#' # A standard plot +#' p <- ggplot(mpg, aes(displ, hwy, colour = class)) + +#' geom_point() +#' +#' # You can use the scale to give a palette directly +#' p + scale_colour_discrete(palette = pal_brewer(palette = "Dark2")) #' -#' # The default, scale_fill_hue(), is not colour-blind safe -#' cty_by_var(class) +#' # The default colours are encoded into the theme +#' p + theme(palette.colour.discrete = pal_grey()) #' -#' # (Temporarily) set the default to Okabe-Ito (which is colour-blind safe) -#' okabe <- c("#E69F00", "#56B4E9", "#009E73", "#F0E442", "#0072B2", "#D55E00", "#CC79A7") -#' withr::with_options( -#' list(ggplot2.discrete.fill = okabe), -#' print(cty_by_var(class)) -#' ) +#' # You can globally set default colour palette via the theme +#' old <- update_theme(palette.colour.discrete = pal_viridis()) #' -#' # Define a collection of palettes to alter the default based on number of levels to encode -#' discrete_palettes <- list( -#' c("skyblue", "orange"), -#' RColorBrewer::brewer.pal(3, "Set2"), -#' RColorBrewer::brewer.pal(6, "Accent") -#' ) -#' withr::with_options( -#' list(ggplot2.discrete.fill = discrete_palettes), { -#' # 1st palette is used when there 1-2 levels (e.g., year) -#' print(cty_by_var(year)) -#' # 2nd palette is used when there are 3 levels -#' print(cty_by_var(drv)) -#' # 3rd palette is used when there are 4-6 levels -#' print(cty_by_var(fl)) -#' }) +#' # Plot now shows new global default +#' p #' +#' # Restoring the previous theme +#' theme_set(old) scale_colour_discrete <- function(..., palette = NULL, aesthetics = "colour", na.value = "grey50", type = getOption("ggplot2.discrete.colour")) { if (!is.null(type) && is.null(palette)) { diff --git a/man/scale_colour_continuous.Rd b/man/scale_colour_continuous.Rd index c945394d2b..b7b482cb92 100644 --- a/man/scale_colour_continuous.Rd +++ b/man/scale_colour_continuous.Rd @@ -120,26 +120,28 @@ and references therein. } \examples{ -v <- ggplot(faithfuld, aes(waiting, eruptions, fill = density)) + -geom_tile() -v - -v + scale_fill_continuous(type = "gradient") -v + scale_fill_continuous(type = "viridis") - -# The above are equivalent to -v + scale_fill_gradient() -v + scale_fill_viridis_c() - -# To make a binned version of this plot -v + scale_fill_binned(type = "viridis") - -# Set a different default scale using the options -# mechanism -tmp <- getOption("ggplot2.continuous.fill") # store current setting -options(ggplot2.continuous.fill = scale_fill_distiller) -v -options(ggplot2.continuous.fill = tmp) # restore previous setting +# A standard plot +p <- ggplot(mpg, aes(displ, hwy, colour = cty)) + + geom_point() + +# You can use the scale to give a palette directly +p + scale_colour_continuous(palette = c("#FEE0D2", "#FC9272", "#DE2D26")) + +# The default colours are encoded into the theme +p + theme(palette.colour.continuous = c("#DEEBF7", "#9ECAE1", "#3182BD")) + +# You can globally set default colour palette via the theme +old <- update_theme(palette.colour.continuous = c("#E5F5E0", "#A1D99B", "#31A354")) + +# Plot now shows new global default +p + +# The default binned colour scale uses the continuous palette +p + scale_colour_binned() + + theme(palette.colour.continuous = c("#EFEDF5", "#BCBDDC", "#756BB1")) + +# Restoring the previous theme +theme_set(old) } \seealso{ \code{\link[=scale_colour_gradient]{scale_colour_gradient()}}, \code{\link[=scale_colour_viridis_c]{scale_colour_viridis_c()}}, diff --git a/man/scale_colour_discrete.Rd b/man/scale_colour_discrete.Rd index 0c22c4672f..abb4c715cf 100644 --- a/man/scale_colour_discrete.Rd +++ b/man/scale_colour_discrete.Rd @@ -61,38 +61,24 @@ unless \code{type} (which defaults to the \code{ggplot2.discrete.fill}/\code{ggp is specified. } \examples{ -# Template function for creating densities grouped by a variable -cty_by_var <- function(var) { - ggplot(mpg, aes(cty, colour = factor({{var}}), fill = factor({{var}}))) + - geom_density(alpha = 0.2) -} +# A standard plot +p <- ggplot(mpg, aes(displ, hwy, colour = class)) + + geom_point() -# The default, scale_fill_hue(), is not colour-blind safe -cty_by_var(class) +# You can use the scale to give a palette directly +p + scale_colour_discrete(palette = pal_brewer(palette = "Dark2")) -# (Temporarily) set the default to Okabe-Ito (which is colour-blind safe) -okabe <- c("#E69F00", "#56B4E9", "#009E73", "#F0E442", "#0072B2", "#D55E00", "#CC79A7") -withr::with_options( - list(ggplot2.discrete.fill = okabe), - print(cty_by_var(class)) -) +# The default colours are encoded into the theme +p + theme(palette.colour.discrete = pal_grey()) -# Define a collection of palettes to alter the default based on number of levels to encode -discrete_palettes <- list( - c("skyblue", "orange"), - RColorBrewer::brewer.pal(3, "Set2"), - RColorBrewer::brewer.pal(6, "Accent") -) -withr::with_options( - list(ggplot2.discrete.fill = discrete_palettes), { - # 1st palette is used when there 1-2 levels (e.g., year) - print(cty_by_var(year)) - # 2nd palette is used when there are 3 levels - print(cty_by_var(drv)) - # 3rd palette is used when there are 4-6 levels - print(cty_by_var(fl)) -}) +# You can globally set default colour palette via the theme +old <- update_theme(palette.colour.discrete = pal_viridis()) + +# Plot now shows new global default +p +# Restoring the previous theme +theme_set(old) } \seealso{ The \href{https://ggplot2-book.org/scales-colour#sec-colour-discrete}{discrete colour scales section} of the online ggplot2 book. From e9d98c121605c5885cd2aebca7834992ac7d6262 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Thu, 12 Dec 2024 10:38:43 +0100 Subject: [PATCH 12/16] prefix scales pkg --- R/scale-colour.R | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/R/scale-colour.R b/R/scale-colour.R index c4c9ec7ba6..45bff6b46c 100644 --- a/R/scale-colour.R +++ b/R/scale-colour.R @@ -197,13 +197,13 @@ scale_fill_binned <- function(..., palette = NULL, aesthetics = "fill", guide = #' geom_point() #' #' # You can use the scale to give a palette directly -#' p + scale_colour_discrete(palette = pal_brewer(palette = "Dark2")) +#' p + scale_colour_discrete(palette = scales::pal_brewer(palette = "Dark2")) #' #' # The default colours are encoded into the theme -#' p + theme(palette.colour.discrete = pal_grey()) +#' p + theme(palette.colour.discrete = scales::pal_grey()) #' #' # You can globally set default colour palette via the theme -#' old <- update_theme(palette.colour.discrete = pal_viridis()) +#' old <- update_theme(palette.colour.discrete = scales::pal_viridis()) #' #' # Plot now shows new global default #' p From dd1247b5398ef22cdb1ab532d7e138cf973326d8 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Thu, 12 Dec 2024 15:58:29 +0100 Subject: [PATCH 13/16] document / lol at my incompetence --- man/scale_colour_discrete.Rd | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/man/scale_colour_discrete.Rd b/man/scale_colour_discrete.Rd index abb4c715cf..c86fd7b33c 100644 --- a/man/scale_colour_discrete.Rd +++ b/man/scale_colour_discrete.Rd @@ -66,13 +66,13 @@ p <- ggplot(mpg, aes(displ, hwy, colour = class)) + geom_point() # You can use the scale to give a palette directly -p + scale_colour_discrete(palette = pal_brewer(palette = "Dark2")) +p + scale_colour_discrete(palette = scales::pal_brewer(palette = "Dark2")) # The default colours are encoded into the theme -p + theme(palette.colour.discrete = pal_grey()) +p + theme(palette.colour.discrete = scales::pal_grey()) # You can globally set default colour palette via the theme -old <- update_theme(palette.colour.discrete = pal_viridis()) +old <- update_theme(palette.colour.discrete = scales::pal_viridis()) # Plot now shows new global default p From f7ce6441cee6d8c3b0d7568cd982c76315c506c3 Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Wed, 22 Jan 2025 15:30:20 +0100 Subject: [PATCH 14/16] Fix #6289 --- R/scale-colour.R | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/R/scale-colour.R b/R/scale-colour.R index 45bff6b46c..e200dd89d6 100644 --- a/R/scale-colour.R +++ b/R/scale-colour.R @@ -90,7 +90,9 @@ scale_colour_continuous <- function(..., palette = NULL, aesthetics = "colour", guide = "colourbar", na.value = "grey50", type = getOption("ggplot2.continuous.colour")) { - if (!is.null(type) && is.null(palette)) { + has_old_args <- any(names(enexprs(...)) %in% c("low", "high")) + + if (has_old_args || !is.null(type) && is.null(palette)) { scale <- scale_backward_compatibility( ..., guide = guide, na.value = na.value, scale = type, aesthetic = "colour", type = "continuous" @@ -110,7 +112,9 @@ scale_fill_continuous <- function(..., palette = NULL, aesthetics = "fill", guid na.value = "grey50", type = getOption("ggplot2.continuous.fill")) { - if (!is.null(type) && is.null(palette)) { + has_old_args <- any(names(enexprs(...)) %in% c("low", "high")) + + if (has_old_args || !is.null(type) && is.null(palette)) { scale <- scale_backward_compatibility( ..., guide = guide, na.value = na.value, scale = type, aesthetic = "fill", type = "continuous" @@ -129,7 +133,10 @@ scale_fill_continuous <- function(..., palette = NULL, aesthetics = "fill", guid scale_colour_binned <- function(..., palette = NULL, aesthetics = "colour", guide = "coloursteps", na.value = "grey50", type = getOption("ggplot2.binned.colour")) { - if (!is.null(type) && is.null(palette)) { + + has_old_args <- any(names(enexprs(...)) %in% c("low", "high")) + + if (has_old_args || !is.null(type) && is.null(palette)) { scale <- scale_backward_compatibility( ..., guide = guide, na.value = na.value, scale = type, aesthetic = "colour", type = "binned" @@ -148,7 +155,9 @@ scale_colour_binned <- function(..., palette = NULL, aesthetics = "colour", guid scale_fill_binned <- function(..., palette = NULL, aesthetics = "fill", guide = "coloursteps", na.value = "grey50", type = getOption("ggplot2.binned.fill")) { - if (!is.null(type) && is.null(palette)) { + has_old_args <- any(names(enexprs(...)) %in% c("low", "high")) + + if (has_old_args || !is.null(type) && is.null(palette)) { scale <- scale_backward_compatibility( ..., guide = guide, na.value = na.value, scale = type, aesthetic = "fill", type = "binned" @@ -212,7 +221,10 @@ scale_fill_binned <- function(..., palette = NULL, aesthetics = "fill", guide = #' theme_set(old) scale_colour_discrete <- function(..., palette = NULL, aesthetics = "colour", na.value = "grey50", type = getOption("ggplot2.discrete.colour")) { - if (!is.null(type) && is.null(palette)) { + + has_old_args <- any(names(enexprs(...)) %in% c("h", "c", "l", "h.start", "direction")) + + if (has_old_args || !is.null(type) && is.null(palette)) { scale <- scale_backward_compatibility( ..., na.value = na.value, scale = type, aesthetic = "colour", type = "discrete" @@ -230,7 +242,10 @@ scale_colour_discrete <- function(..., palette = NULL, aesthetics = "colour", na #' @export scale_fill_discrete <- function(..., palette = NULL, aesthetics = "fill", na.value = "grey50", type = getOption("ggplot2.discrete.fill")) { - if (!is.null(type) && is.null(palette)) { + + has_old_args <- any(names(enexprs(...)) %in% c("h", "c", "l", "h.start", "direction")) + + if (has_old_args || !is.null(type) && is.null(palette)) { scale <- scale_backward_compatibility( ..., na.value = na.value, scale = type, aesthetic = "fill", type = "discrete" From 3eded4f272de8d33b6efc94e7ee17ecbc37ad507 Mon Sep 17 00:00:00 2001 From: Teun van den Brand <49372158+teunbrand@users.noreply.github.com> Date: Thu, 27 Feb 2025 10:04:52 +0100 Subject: [PATCH 15/16] Apply suggestions from code review Co-authored-by: Thomas Lin Pedersen --- R/scale-colour.R | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/R/scale-colour.R b/R/scale-colour.R index e200dd89d6..239f4d77f6 100644 --- a/R/scale-colour.R +++ b/R/scale-colour.R @@ -92,7 +92,7 @@ scale_colour_continuous <- function(..., palette = NULL, aesthetics = "colour", has_old_args <- any(names(enexprs(...)) %in% c("low", "high")) - if (has_old_args || !is.null(type) && is.null(palette)) { + if (has_old_args || (!is.null(type) && is.null(palette))) { scale <- scale_backward_compatibility( ..., guide = guide, na.value = na.value, scale = type, aesthetic = "colour", type = "continuous" @@ -114,7 +114,7 @@ scale_fill_continuous <- function(..., palette = NULL, aesthetics = "fill", guid has_old_args <- any(names(enexprs(...)) %in% c("low", "high")) - if (has_old_args || !is.null(type) && is.null(palette)) { + if (has_old_args || (!is.null(type) && is.null(palette))) { scale <- scale_backward_compatibility( ..., guide = guide, na.value = na.value, scale = type, aesthetic = "fill", type = "continuous" @@ -136,7 +136,7 @@ scale_colour_binned <- function(..., palette = NULL, aesthetics = "colour", guid has_old_args <- any(names(enexprs(...)) %in% c("low", "high")) - if (has_old_args || !is.null(type) && is.null(palette)) { + if (has_old_args || (!is.null(type) && is.null(palette))) { scale <- scale_backward_compatibility( ..., guide = guide, na.value = na.value, scale = type, aesthetic = "colour", type = "binned" @@ -157,7 +157,7 @@ scale_fill_binned <- function(..., palette = NULL, aesthetics = "fill", guide = type = getOption("ggplot2.binned.fill")) { has_old_args <- any(names(enexprs(...)) %in% c("low", "high")) - if (has_old_args || !is.null(type) && is.null(palette)) { + if (has_old_args || (!is.null(type) && is.null(palette))) { scale <- scale_backward_compatibility( ..., guide = guide, na.value = na.value, scale = type, aesthetic = "fill", type = "binned" From 937d3069f998ee231b32a2ed65a873d8a3ca88ca Mon Sep 17 00:00:00 2001 From: Teun van den Brand Date: Tue, 25 Mar 2025 09:17:38 +0100 Subject: [PATCH 16/16] be more thorough with the parentheses --- R/scale-colour.R | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/R/scale-colour.R b/R/scale-colour.R index d544e302d5..ab5981da76 100644 --- a/R/scale-colour.R +++ b/R/scale-colour.R @@ -224,7 +224,7 @@ scale_colour_discrete <- function(..., palette = NULL, aesthetics = "colour", na has_old_args <- any(names(enexprs(...)) %in% c("h", "c", "l", "h.start", "direction")) - if (has_old_args || !is.null(type) && is.null(palette)) { + if (has_old_args || (!is.null(type) && is.null(palette))) { scale <- scale_backward_compatibility( ..., na.value = na.value, scale = type, aesthetic = "colour", type = "discrete" @@ -245,7 +245,7 @@ scale_fill_discrete <- function(..., palette = NULL, aesthetics = "fill", na.val has_old_args <- any(names(enexprs(...)) %in% c("h", "c", "l", "h.start", "direction")) - if (has_old_args || !is.null(type) && is.null(palette)) { + if (has_old_args || (!is.null(type) && is.null(palette))) { scale <- scale_backward_compatibility( ..., na.value = na.value, scale = type, aesthetic = "fill", type = "discrete"