Skip to content

Commit 51eb2f0

Browse files
committed
Allow default discrete scales to be set via options in a similar style to continuous scales
Also add support for a list of color palettes
1 parent 5b4f19d commit 51eb2f0

File tree

3 files changed

+163
-20
lines changed

3 files changed

+163
-20
lines changed

R/scale-hue.r

Lines changed: 98 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
#' Evenly spaced colours for discrete data
22
#'
3-
#' This is the default colour scale for categorical variables. It maps each
4-
#' level to an evenly spaced hue on the colour wheel. It does not generate
5-
#' colour-blind safe palettes.
3+
#' Maps each level to an evenly spaced hue on the colour wheel.
4+
#' It does not generate colour-blind safe palettes.
65
#'
76
#' @param na.value Colour to use for missing values
87
#' @inheritDotParams discrete_scale -aesthetics
@@ -65,22 +64,80 @@ scale_fill_hue <- function(..., h = c(0, 360) + 15, c = 100, l = 65, h.start = 0
6564
}
6665

6766

67+
#' Discrete colour scales
68+
#'
69+
#' Colour scales for discrete data default to the values of the `ggplot2.discrete.fill`
70+
#' and `ggplot2.discrete.colour` options. By default these scales attempt to use
71+
#' a colour-blind safe (or a custom) palette, but if the number of levels is
72+
#' large, they fallback to [scale_fill_hue()]/[scale_colour_hue()].
73+
#'
74+
#' @param ... Additional parameters passed on to the scale type,
75+
#' @param type One of the following:
76+
#' * A character vector of color codes. The codes are used for a 'manual' color
77+
#' scale as long as the number of codes exceeds the number of data levels
78+
#' (if there are more levels than codes, [scale_colour_hue()]/[scale_fill_hue()]
79+
#' are used to construct the default scale).
80+
#' * A list of character vectors of color codes. The minimum length vector that exceeds the
81+
#' number of data levels is chosen for the color scaling. This is useful if you
82+
#' want to change the color palette based on the number of levels.
83+
#' * A function that returns a discrete colour/fill scale (e.g., [scale_fill_hue()],
84+
#' [scale_fill_brewer()], etc).
6885
#' @export
69-
#' @inheritParams scale_colour_hue
70-
#' @param codes a vector of color codes defining a qualitative color palette. These codes
71-
#' are used so long as the number of codes is greater or equal to the number of values they encode.
72-
scale_colour_discrete <- function(..., codes = okabeIto, h = c(0, 360) + 15, c = 100, l = 65, h.start = 0,
73-
direction = 1, na.value = "grey50", aesthetics = "colour") {
86+
#' @rdname
87+
#' @examples
88+
#' # Template function for creating densities grouped by a variable
89+
#' cty_by_var <- function(var) {
90+
#' ggplot(mpg, aes(cty, colour = factor({{var}}), fill = factor({{var}}))) +
91+
#' geom_density(alpha = 0.2)
92+
#' }
93+
#' # The default color scale for three levels
94+
#' cty_by_var(class)
95+
#'
96+
#' # Define custom palettes for when there are 1-2, 3, or 4-6 levels
97+
#' opts <- options(
98+
#' ggplot2.discrete.fill = list(
99+
#' c("skyblue", "orange"),
100+
#' RColorBrewer::brewer.pal(3, "Set2"),
101+
#' RColorBrewer::brewer.pal(6, "Accent")
102+
#' )
103+
#' )
104+
#' cty_by_var(year)
105+
#' cty_by_var(drv)
106+
#' cty_by_var(fl)
107+
#' cty_by_var(class)
108+
#' options(opts)
109+
#'
110+
scale_colour_discrete <- function(..., type = getOption("ggplot2.discrete.colour", getOption("ggplot2.discrete.fill"))) {
111+
type <- type %||% okabeIto
112+
if (is.function(type)) {
113+
type(...)
114+
} else {
115+
scale_colour_qualitative(..., codes = type)
116+
}
117+
}
118+
119+
#' @rdname scale_colour_discrete
120+
#' @export
121+
scale_fill_discrete <- function(..., type = getOption("ggplot2.discrete.fill", getOption("ggplot2.discrete.colour"))) {
122+
type <- type %||% okabeIto
123+
if (is.function(type)) {
124+
type(...)
125+
} else {
126+
scale_fill_qualitative(..., codes = type)
127+
}
128+
}
129+
130+
# TODO: export?
131+
scale_colour_qualitative <- function(..., codes = NULL, h = c(0, 360) + 15, c = 100, l = 65, h.start = 0,
132+
direction = 1, na.value = "grey50", aesthetics = "colour") {
74133
discrete_scale(
75134
aesthetics, "qualitative", qualitative_pal(codes, h, c, l, h.start, direction),
76135
na.value = na.value, ...
77136
)
78137
}
79138

80-
#' @export
81-
#' @inheritParams scale_colour_discrete
82-
scale_fill_discrete <- function(..., codes = okabeIto, h = c(0, 360) + 15, c = 100, l = 65, h.start = 0,
83-
direction = 1, na.value = "grey50", aesthetics = "fill") {
139+
scale_fill_qualitative <- function(..., codes = NULL, h = c(0, 360) + 15, c = 100, l = 65, h.start = 0,
140+
direction = 1, na.value = "grey50", aesthetics = "fill") {
84141
discrete_scale(
85142
aesthetics, "qualitative", qualitative_pal(codes, h, c, l, h.start, direction),
86143
na.value = na.value, ...
@@ -89,12 +146,36 @@ scale_fill_discrete <- function(..., codes = okabeIto, h = c(0, 360) + 15, c = 1
89146

90147
qualitative_pal <- function(codes, h, c, l, h.start, direction) {
91148
function(n) {
92-
if (n <= length(codes)) {
93-
codes[seq_len(n)]
94-
} else {
95-
scales::hue_pal(h, c, l, h.start, direction)(n)
149+
if (!length(codes)) {
150+
return(scales::hue_pal(h, c, l, h.start, direction)(n))
151+
}
152+
codes_list <- if (!is.list(codes)) list(codes) else codes
153+
if (!all(vapply(codes_list, is.character, logical(1)))) {
154+
stop("codes must be a character vector or a list of character vectors", call. = FALSE)
155+
}
156+
codes_lengths <- vapply(codes_list, length, integer(1))
157+
# If there are more levels than color codes default to hue_pal()
158+
if (max(codes_lengths) < n) {
159+
return(scales::hue_pal(h, c, l, h.start, direction)(n))
160+
}
161+
# Use the minimum length vector that exceeds the number of levels (n)
162+
codes_list <- codes_list[order(codes_lengths)]
163+
i <- 1
164+
while (length(codes_list[[i]]) < n) {
165+
i <- i + 1
96166
}
167+
codes_list[[i]][seq_len(n)]
97168
}
98169
}
99170

100-
okabeIto <- c("#E69F00", "#009E73", "#0072B2", "#CC79A7", "#999999", "#D55E00", "#F0E442", "#56B4E9")
171+
# prismatic::check_color_blindness(okabeIto)
172+
okabeIto <- c(
173+
"#E69F00",
174+
"#56B4E9",
175+
"#009E73",
176+
"#F0E442",
177+
"#0072B2",
178+
"#D55E00",
179+
"#CC79A7",
180+
"#999999"
181+
)

man/scale_colour_discrete.Rd

Lines changed: 63 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/scale_hue.Rd

Lines changed: 2 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)