Skip to content

Commit 2478406

Browse files
teunbrandthomasp85
andauthored
Flexible palettes (#6216)
* collect all default colour scales in one place * wrap palettes * document `palette` parameter * fix typo * avoid recursive problems * only fall back when no explicit palette exists * broaden test scope * add tests * add news bullet * merging a function element gives `new` * update examples * prefix scales pkg * document / lol at my incompetence * Fix #6289 * Apply suggestions from code review Co-authored-by: Thomas Lin Pedersen <[email protected]> * be more thorough with the parentheses --------- Co-authored-by: Thomas Lin Pedersen <[email protected]>
1 parent d1b519d commit 2478406

9 files changed

+258
-195
lines changed

NEWS.md

+2
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,8 @@
336336
* Improved consistency of curve direction in `geom_curve()` (@teunbrand, #5069)
337337
* `linetype = NA` is now interpreted to mean 'no line' instead of raising errors
338338
(@teunbrand, #6269).
339+
* The default colour and fill scales have a new `palette` argument
340+
(@teunbrand, #6064).
339341

340342
# ggplot2 3.5.1
341343

R/scale-colour.R

+142-36
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@
2828
#' [scale_colour_gradient()] or [scale_colour_steps()].
2929
#'
3030
#' @inheritParams continuous_scale
31+
#' @param palette One of the following:
32+
#' * `NULL` for the default palette stored in the theme.
33+
#' * a character vector of colours.
34+
#' * a single string naming a palette.
35+
#' * a palette function that when called with a numeric vector with values
36+
#' between 0 and 1 returns the corresponding output values.
3137
#' @param ... Additional parameters passed on to the scale type
3238
#' @param type One of the following:
3339
#' * "gradient" (the default)
@@ -57,98 +63,198 @@
5763
#' see the [paper on the colorspace package](https://arxiv.org/abs/1903.06490)
5864
#' and references therein.
5965
#' @examples
60-
#' v <- ggplot(faithfuld, aes(waiting, eruptions, fill = density)) +
61-
#' geom_tile()
62-
#' v
63-
#'
64-
#' v + scale_fill_continuous(type = "gradient")
65-
#' v + scale_fill_continuous(type = "viridis")
66-
#'
67-
#' # The above are equivalent to
68-
#' v + scale_fill_gradient()
69-
#' v + scale_fill_viridis_c()
70-
#'
71-
#' # To make a binned version of this plot
72-
#' v + scale_fill_binned(type = "viridis")
73-
#'
74-
#' # Set a different default scale using the options
75-
#' # mechanism
76-
#' tmp <- getOption("ggplot2.continuous.fill") # store current setting
77-
#' options(ggplot2.continuous.fill = scale_fill_distiller)
78-
#' v
79-
#' options(ggplot2.continuous.fill = tmp) # restore previous setting
66+
#' # A standard plot
67+
#' p <- ggplot(mpg, aes(displ, hwy, colour = cty)) +
68+
#' geom_point()
69+
#'
70+
#' # You can use the scale to give a palette directly
71+
#' p + scale_colour_continuous(palette = c("#FEE0D2", "#FC9272", "#DE2D26"))
72+
#'
73+
#' # The default colours are encoded into the theme
74+
#' p + theme(palette.colour.continuous = c("#DEEBF7", "#9ECAE1", "#3182BD"))
75+
#'
76+
#' # You can globally set default colour palette via the theme
77+
#' old <- update_theme(palette.colour.continuous = c("#E5F5E0", "#A1D99B", "#31A354"))
78+
#'
79+
#' # Plot now shows new global default
80+
#' p
81+
#'
82+
#' # The default binned colour scale uses the continuous palette
83+
#' p + scale_colour_binned() +
84+
#' theme(palette.colour.continuous = c("#EFEDF5", "#BCBDDC", "#756BB1"))
85+
#'
86+
#' # Restoring the previous theme
87+
#' theme_set(old)
8088
#' @export
81-
scale_colour_continuous <- function(..., aesthetics = "colour",
89+
scale_colour_continuous <- function(..., palette = NULL, aesthetics = "colour",
8290
guide = "colourbar", na.value = "grey50",
8391
type = getOption("ggplot2.continuous.colour")) {
84-
if (!is.null(type)) {
92+
93+
has_old_args <- any(names(enexprs(...)) %in% c("low", "high"))
94+
95+
if (has_old_args || (!is.null(type) && is.null(palette))) {
8596
scale <- scale_backward_compatibility(
8697
..., guide = guide, na.value = na.value, scale = type,
8798
aesthetic = "colour", type = "continuous"
8899
)
89100
return(scale)
90101
}
91-
102+
palette <- if (!is.null(palette)) as_continuous_pal(palette)
92103
continuous_scale(
93-
aesthetics, palette = NULL, guide = guide, na.value = na.value,
104+
aesthetics, palette = palette, guide = guide, na.value = na.value,
94105
...
95106
)
96107
}
97108

98109
#' @rdname scale_colour_continuous
99110
#' @export
100-
scale_fill_continuous <- function(..., aesthetics = "fill", guide = "colourbar",
111+
scale_fill_continuous <- function(..., palette = NULL, aesthetics = "fill", guide = "colourbar",
101112
na.value = "grey50",
102113
type = getOption("ggplot2.continuous.fill")) {
103114

104-
if (!is.null(type)) {
115+
has_old_args <- any(names(enexprs(...)) %in% c("low", "high"))
116+
117+
if (has_old_args || (!is.null(type) && is.null(palette))) {
105118
scale <- scale_backward_compatibility(
106119
..., guide = guide, na.value = na.value, scale = type,
107120
aesthetic = "fill", type = "continuous"
108121
)
109122
return(scale)
110123
}
111-
124+
palette <- if (!is.null(palette)) as_continuous_pal(palette)
112125
continuous_scale(
113-
aesthetics, palette = NULL, guide = guide, na.value = na.value,
126+
aesthetics, palette = palette, guide = guide, na.value = na.value,
114127
...
115128
)
116129
}
117130

118131
#' @export
119132
#' @rdname scale_colour_continuous
120-
scale_colour_binned <- function(..., aesthetics = "colour", guide = "coloursteps",
133+
scale_colour_binned <- function(..., palette = NULL, aesthetics = "colour", guide = "coloursteps",
121134
na.value = "grey50",
122135
type = getOption("ggplot2.binned.colour")) {
123-
if (!is.null(type)) {
136+
137+
has_old_args <- any(names(enexprs(...)) %in% c("low", "high"))
138+
139+
if (has_old_args || (!is.null(type) && is.null(palette))) {
124140
scale <- scale_backward_compatibility(
125141
..., guide = guide, na.value = na.value, scale = type,
126142
aesthetic = "colour", type = "binned"
127143
)
128144
return(scale)
129145
}
130-
146+
palette <- if (!is.null(palette)) pal_binned(as_discrete_pal(palette))
131147
binned_scale(
132-
aesthetics, palette = NULL, guide = guide, na.value = na.value,
148+
aesthetics, palette = palette, guide = guide, na.value = na.value,
133149
...
134150
)
135151
}
136152

137153
#' @export
138154
#' @rdname scale_colour_continuous
139-
scale_fill_binned <- function(..., aesthetics = "fill", guide = "coloursteps",
155+
scale_fill_binned <- function(..., palette = NULL, aesthetics = "fill", guide = "coloursteps",
140156
na.value = "grey50",
141157
type = getOption("ggplot2.binned.fill")) {
142-
if (!is.null(type)) {
158+
has_old_args <- any(names(enexprs(...)) %in% c("low", "high"))
159+
160+
if (has_old_args || (!is.null(type) && is.null(palette))) {
143161
scale <- scale_backward_compatibility(
144162
..., guide = guide, na.value = na.value, scale = type,
145163
aesthetic = "fill", type = "binned"
146164
)
147165
return(scale)
148166
}
149-
167+
palette <- if (!is.null(palette)) pal_binned(as_discrete_pal(palette))
150168
binned_scale(
151-
aesthetics, palette = NULL, guide = guide, na.value = na.value,
169+
aesthetics, palette = palette, guide = guide, na.value = na.value,
170+
...
171+
)
172+
}
173+
174+
#' Discrete colour scales
175+
#'
176+
#' The default discrete colour scale. Defaults to [scale_fill_hue()]/[scale_fill_brewer()]
177+
#' unless `type` (which defaults to the `ggplot2.discrete.fill`/`ggplot2.discrete.colour` options)
178+
#' is specified.
179+
#'
180+
#' @param palette One of the following:
181+
#' * `NULL` for the default palette stored in the theme.
182+
#' * a character vector of colours.
183+
#' * a single string naming a palette.
184+
#' * a palette function that when called with a single integer argument (the
185+
#' number of levels in the scale) returns the values that they should take.
186+
#' @param ... Additional parameters passed on to the scale type,
187+
#' @inheritParams discrete_scale
188+
#' @param type One of the following:
189+
#' * A character vector of color codes. The codes are used for a 'manual' color
190+
#' scale as long as the number of codes exceeds the number of data levels
191+
#' (if there are more levels than codes, [scale_colour_hue()]/[scale_fill_hue()]
192+
#' are used to construct the default scale). If this is a named vector, then the color values
193+
#' will be matched to levels based on the names of the vectors. Data values that
194+
#' don't match will be set as `na.value`.
195+
#' * A list of character vectors of color codes. The minimum length vector that exceeds the
196+
#' number of data levels is chosen for the color scaling. This is useful if you
197+
#' want to change the color palette based on the number of levels.
198+
#' * A function that returns a discrete colour/fill scale (e.g., [scale_fill_hue()],
199+
#' [scale_fill_brewer()], etc).
200+
#' @export
201+
#' @seealso
202+
#' The `r link_book("discrete colour scales section", "scales-colour#sec-colour-discrete")`
203+
#' @examples
204+
#' # A standard plot
205+
#' p <- ggplot(mpg, aes(displ, hwy, colour = class)) +
206+
#' geom_point()
207+
#'
208+
#' # You can use the scale to give a palette directly
209+
#' p + scale_colour_discrete(palette = scales::pal_brewer(palette = "Dark2"))
210+
#'
211+
#' # The default colours are encoded into the theme
212+
#' p + theme(palette.colour.discrete = scales::pal_grey())
213+
#'
214+
#' # You can globally set default colour palette via the theme
215+
#' old <- update_theme(palette.colour.discrete = scales::pal_viridis())
216+
#'
217+
#' # Plot now shows new global default
218+
#' p
219+
#'
220+
#' # Restoring the previous theme
221+
#' theme_set(old)
222+
scale_colour_discrete <- function(..., palette = NULL, aesthetics = "colour", na.value = "grey50",
223+
type = getOption("ggplot2.discrete.colour")) {
224+
225+
has_old_args <- any(names(enexprs(...)) %in% c("h", "c", "l", "h.start", "direction"))
226+
227+
if (has_old_args || (!is.null(type) && is.null(palette))) {
228+
scale <- scale_backward_compatibility(
229+
..., na.value = na.value, scale = type,
230+
aesthetic = "colour", type = "discrete"
231+
)
232+
return(scale)
233+
}
234+
palette <- if (!is.null(palette)) as_discrete_pal(palette)
235+
discrete_scale(
236+
aesthetics, palette = palette, na.value = na.value,
237+
...
238+
)
239+
}
240+
241+
#' @rdname scale_colour_discrete
242+
#' @export
243+
scale_fill_discrete <- function(..., palette = NULL, aesthetics = "fill", na.value = "grey50",
244+
type = getOption("ggplot2.discrete.fill")) {
245+
246+
has_old_args <- any(names(enexprs(...)) %in% c("h", "c", "l", "h.start", "direction"))
247+
248+
if (has_old_args || (!is.null(type) && is.null(palette))) {
249+
scale <- scale_backward_compatibility(
250+
..., na.value = na.value, scale = type,
251+
aesthetic = "fill", type = "discrete"
252+
)
253+
return(scale)
254+
}
255+
palette <- if (!is.null(palette)) as_discrete_pal(palette)
256+
discrete_scale(
257+
aesthetics, palette = palette, na.value = na.value,
152258
...
153259
)
154260
}

R/scale-hue.R

-89
Original file line numberDiff line numberDiff line change
@@ -78,95 +78,6 @@ scale_fill_hue <- function(name = waiver(), ..., h = c(0, 360) + 15, c = 100,
7878
)
7979
}
8080

81-
82-
#' Discrete colour scales
83-
#'
84-
#' The default discrete colour scale. Defaults to [scale_fill_hue()]/[scale_fill_brewer()]
85-
#' unless `type` (which defaults to the `ggplot2.discrete.fill`/`ggplot2.discrete.colour` options)
86-
#' is specified.
87-
#'
88-
#' @param ... Additional parameters passed on to the scale type,
89-
#' @inheritParams discrete_scale
90-
#' @param type One of the following:
91-
#' * A character vector of color codes. The codes are used for a 'manual' color
92-
#' scale as long as the number of codes exceeds the number of data levels
93-
#' (if there are more levels than codes, [scale_colour_hue()]/[scale_fill_hue()]
94-
#' are used to construct the default scale). If this is a named vector, then the color values
95-
#' will be matched to levels based on the names of the vectors. Data values that
96-
#' don't match will be set as `na.value`.
97-
#' * A list of character vectors of color codes. The minimum length vector that exceeds the
98-
#' number of data levels is chosen for the color scaling. This is useful if you
99-
#' want to change the color palette based on the number of levels.
100-
#' * A function that returns a discrete colour/fill scale (e.g., [scale_fill_hue()],
101-
#' [scale_fill_brewer()], etc).
102-
#' @export
103-
#' @seealso
104-
#' The `r link_book("discrete colour scales section", "scales-colour#sec-colour-discrete")`
105-
#' @examples
106-
#' # Template function for creating densities grouped by a variable
107-
#' cty_by_var <- function(var) {
108-
#' ggplot(mpg, aes(cty, colour = factor({{var}}), fill = factor({{var}}))) +
109-
#' geom_density(alpha = 0.2)
110-
#' }
111-
#'
112-
#' # The default, scale_fill_hue(), is not colour-blind safe
113-
#' cty_by_var(class)
114-
#'
115-
#' # (Temporarily) set the default to Okabe-Ito (which is colour-blind safe)
116-
#' okabe <- c("#E69F00", "#56B4E9", "#009E73", "#F0E442", "#0072B2", "#D55E00", "#CC79A7")
117-
#' withr::with_options(
118-
#' list(ggplot2.discrete.fill = okabe),
119-
#' print(cty_by_var(class))
120-
#' )
121-
#'
122-
#' # Define a collection of palettes to alter the default based on number of levels to encode
123-
#' discrete_palettes <- list(
124-
#' c("skyblue", "orange"),
125-
#' RColorBrewer::brewer.pal(3, "Set2"),
126-
#' RColorBrewer::brewer.pal(6, "Accent")
127-
#' )
128-
#' withr::with_options(
129-
#' list(ggplot2.discrete.fill = discrete_palettes), {
130-
#' # 1st palette is used when there 1-2 levels (e.g., year)
131-
#' print(cty_by_var(year))
132-
#' # 2nd palette is used when there are 3 levels
133-
#' print(cty_by_var(drv))
134-
#' # 3rd palette is used when there are 4-6 levels
135-
#' print(cty_by_var(fl))
136-
#' })
137-
#'
138-
scale_colour_discrete <- function(..., aesthetics = "colour", na.value = "grey50",
139-
type = getOption("ggplot2.discrete.colour")) {
140-
if (!is.null(type)) {
141-
scale <- scale_backward_compatibility(
142-
..., na.value = na.value, scale = type,
143-
aesthetic = "colour", type = "discrete"
144-
)
145-
return(scale)
146-
}
147-
discrete_scale(
148-
aesthetics, palette = NULL, na.value = na.value,
149-
...
150-
)
151-
}
152-
153-
#' @rdname scale_colour_discrete
154-
#' @export
155-
scale_fill_discrete <- function(..., aesthetics = "fill", na.value = "grey50",
156-
type = getOption("ggplot2.discrete.fill")) {
157-
if (!is.null(type)) {
158-
scale <- scale_backward_compatibility(
159-
..., na.value = na.value, scale = type,
160-
aesthetic = "fill", type = "discrete"
161-
)
162-
return(scale)
163-
}
164-
discrete_scale(
165-
aesthetics, palette = NULL, na.value = na.value,
166-
...
167-
)
168-
}
169-
17081
scale_colour_qualitative <- function(name = waiver(), ..., type = NULL,
17182
h = c(0, 360) + 15, c = 100, l = 65,
17283
h.start = 0, direction = 1,

R/theme.R

+1-1
Original file line numberDiff line numberDiff line change
@@ -836,7 +836,7 @@ merge_element.default <- function(new, old) {
836836
# If old is NULL or element_blank, then just return new
837837
return(new)
838838
} else if (is.null(new) || is.character(new) || is.numeric(new) || is.unit(new) ||
839-
is.logical(new)) {
839+
is.logical(new) || is.function(new)) {
840840
# If new is NULL, or a string, numeric vector, unit, or logical, just return it
841841
return(new)
842842
}

R/utilities.R

+1
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ waiver <- function() structure(list(), class = "waiver")
195195
is.waiver <- function(x) inherits(x, "waiver")
196196

197197
pal_binned <- function(palette) {
198+
force(palette)
198199
function(x) {
199200
palette(length(x))
200201
}

0 commit comments

Comments
 (0)