Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Flexible palettes #6216

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# ggplot2 (development version)

* The default colour and fill scales have a new `palette` argument
(@teunbrand, #6064).
* `guide_*()` can now accept two inside legend theme elements:
`legend.position.inside` and `legend.justification.inside`, allowing inside
legends to be placed at different positions. Only inside legends with the same
Expand Down
162 changes: 126 additions & 36 deletions R/scale-colour.R
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -57,99 +63,183 @@
#' 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)) {
if (!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)) {
if (!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)) {
if (!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)) {
if (!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")) {
if (!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")) {
if (!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,
...
)
}
Expand Down
89 changes: 0 additions & 89 deletions R/scale-hue.R
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion R/theme.R
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
1 change: 1 addition & 0 deletions R/utilities.R
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
Expand Down
Loading
Loading