diff --git a/NEWS.md b/NEWS.md index 539dee258c..5337ccdbc9 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). * `scale_{x/y}_discrete(continuous.limits)` is a new argument to control the display range of discrete scales (@teunbrand, #4174, #6259). * `geom_ribbon()` now appropriately warns about, and removes, missing values diff --git a/R/scale-colour.R b/R/scale-colour.R index a17d872dbe..e200dd89d6 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) @@ -57,99 +63,198 @@ #' 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 -#' -#' 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) #' @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")) { - if (!is.null(type)) { + 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" ) 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, ... ) } #' @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")) { - if (!is.null(type)) { + 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" ) 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)) { + + 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" ) 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)) { + 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" ) 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, + ... + ) +} + +#' 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 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: +#' * 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 +#' # 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 = scales::pal_brewer(palette = "Dark2")) +#' +#' # The default colours are encoded into the theme +#' p + theme(palette.colour.discrete = scales::pal_grey()) +#' +#' # You can globally set default colour palette via the theme +#' old <- update_theme(palette.colour.discrete = scales::pal_viridis()) +#' +#' # 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")) { + + 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" + ) + return(scale) + } + palette <- if (!is.null(palette)) as_discrete_pal(palette) + discrete_scale( + aesthetics, palette = palette, na.value = na.value, + ... + ) +} + +#' @rdname scale_colour_discrete +#' @export +scale_fill_discrete <- function(..., palette = NULL, aesthetics = "fill", na.value = "grey50", + type = getOption("ggplot2.discrete.fill")) { + + 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" + ) + return(scale) + } + palette <- if (!is.null(palette)) as_discrete_pal(palette) + discrete_scale( + aesthetics, palette = palette, na.value = na.value, ... ) } 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, 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) } 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)) } diff --git a/man/scale_colour_continuous.Rd b/man/scale_colour_continuous.Rd index d88a74f399..b7b482cb92 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 @@ -107,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 ff8fe3f9e7..c86fd7b33c 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 @@ -50,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 = scales::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 = scales::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 = scales::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. diff --git a/tests/testthat/test-scale-colour-continuous.R b/tests/testthat/test-scale-colour-continuous.R deleted file mode 100644 index e97e3d5b01..0000000000 --- a/tests/testthat/test-scale-colour-continuous.R +++ /dev/null @@ -1,20 +0,0 @@ -test_that("type argument is checked for proper input", { - expect_snapshot_error( - scale_colour_continuous(type = function() "abc") - ) - expect_snapshot_error( - suppressWarnings(scale_fill_continuous(type = geom_point)) - ) - expect_snapshot_error( - scale_colour_binned(type = function(...) scale_colour_binned(aesthetics = c("fill", "point_colour"))) - ) - expect_snapshot_error( - scale_fill_binned(type = scale_fill_brewer) - ) - expect_snapshot_error( - scale_fill_continuous(type = "abc") - ) - expect_snapshot_error( - scale_colour_continuous(type = "abc") - ) -}) diff --git a/tests/testthat/test-scale-colour.R b/tests/testthat/test-scale-colour.R new file mode 100644 index 0000000000..bcdbc90892 --- /dev/null +++ b/tests/testthat/test-scale-colour.R @@ -0,0 +1,51 @@ +test_that("type argument is checked for proper input", { + expect_snapshot_error( + scale_colour_continuous(type = function() "abc") + ) + expect_snapshot_error( + suppressWarnings(scale_fill_continuous(type = geom_point)) + ) + expect_snapshot_error( + scale_colour_binned(type = function(...) scale_colour_binned(aesthetics = c("fill", "point_colour"))) + ) + expect_snapshot_error( + scale_fill_binned(type = scale_fill_brewer) + ) + expect_snapshot_error( + scale_fill_continuous(type = "abc") + ) + expect_snapshot_error( + 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) + +})