Skip to content

Commit 5d1d773

Browse files
idno0001thomasp85
authored andcommitted
Add value matching to breaks in manual_scale (#3579)
1 parent 98c49fe commit 5d1d773

File tree

4 files changed

+84
-33
lines changed

4 files changed

+84
-33
lines changed

NEWS.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,12 @@
103103

104104
* `geom_sf()` now applies alpha to linestring geometries (#3589, @yutannihilation).
105105

106+
* `manual_scale()` now matches `values` with the order of `breaks` whenever
107+
`values` is an unnamed vector. Previously, unnamed `values` would match with
108+
the limits of the scale and ignore the order of any `breaks` provided. Note
109+
that this may change the appearance of plots that previously relied on the
110+
unordered behaviour (#2429, @idno0001).
111+
106112
# ggplot2 3.2.1
107113

108114
This is a patch release fixing a few regressions introduced in 3.2.0 as well as

R/scale-manual.r

Lines changed: 38 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,17 @@
1717
#' name(s) of the aesthetic(s) that this scale works with. This can be useful, for
1818
#' example, to apply colour settings to the `colour` and `fill` aesthetics at the
1919
#' same time, via `aesthetics = c("colour", "fill")`.
20-
#' @param values a set of aesthetic values to map data values to. If this
21-
#' is a named vector, then the values will be matched based on the names.
22-
#' If unnamed, values will be matched in order (usually alphabetical) with
23-
#' the limits of the scale. Any data values that don't match will be
24-
#' given `na.value`.
20+
#' @param values a set of aesthetic values to map data values to. The values
21+
#' will be matched in order (usually alphabetical) with the limits of the
22+
#' scale, or with `breaks` if provided. If this is a named vector, then the
23+
#' values will be matched based on the names instead. Data values that don't
24+
#' match will be given `na.value`.
25+
#' @param breaks One of:
26+
#' - `NULL` for no breaks
27+
#' - `waiver()` for the default breaks (the scale limits)
28+
#' - A character vector of breaks
29+
#' - A function that takes the limits as input and returns breaks
30+
#' as output
2531
#' @section Color Blindness:
2632
#' Many color palettes derived from RGB combinations (like the "rainbow" color
2733
#' palette) are not suitable to support all viewers, especially those with
@@ -74,48 +80,48 @@ NULL
7480

7581
#' @rdname scale_manual
7682
#' @export
77-
scale_colour_manual <- function(..., values, aesthetics = "colour") {
78-
manual_scale(aesthetics, values, ...)
83+
scale_colour_manual <- function(..., values, aesthetics = "colour", breaks = waiver()) {
84+
manual_scale(aesthetics, values, breaks, ...)
7985
}
8086

8187
#' @rdname scale_manual
8288
#' @export
83-
scale_fill_manual <- function(..., values, aesthetics = "fill") {
84-
manual_scale(aesthetics, values, ...)
89+
scale_fill_manual <- function(..., values, aesthetics = "fill", breaks = waiver()) {
90+
manual_scale(aesthetics, values, breaks, ...)
8591
}
8692

8793
#' @rdname scale_manual
8894
#' @export
89-
scale_size_manual <- function(..., values) {
90-
manual_scale("size", values, ...)
95+
scale_size_manual <- function(..., values, breaks = waiver()) {
96+
manual_scale("size", values, breaks, ...)
9197
}
9298

9399
#' @rdname scale_manual
94100
#' @export
95-
scale_shape_manual <- function(..., values) {
96-
manual_scale("shape", values, ...)
101+
scale_shape_manual <- function(..., values, breaks = waiver()) {
102+
manual_scale("shape", values, breaks, ...)
97103
}
98104

99105
#' @rdname scale_manual
100106
#' @export
101-
scale_linetype_manual <- function(..., values) {
102-
manual_scale("linetype", values, ...)
107+
scale_linetype_manual <- function(..., values, breaks = waiver()) {
108+
manual_scale("linetype", values, breaks, ...)
103109
}
104110

105111
#' @rdname scale_manual
106112
#' @export
107-
scale_alpha_manual <- function(..., values) {
108-
manual_scale("alpha", values, ...)
113+
scale_alpha_manual <- function(..., values, breaks = waiver()) {
114+
manual_scale("alpha", values, breaks, ...)
109115
}
110116

111117
#' @rdname scale_manual
112118
#' @export
113-
scale_discrete_manual <- function(aesthetics, ..., values) {
114-
manual_scale(aesthetics, values, ...)
119+
scale_discrete_manual <- function(aesthetics, ..., values, breaks = waiver()) {
120+
manual_scale(aesthetics, values, breaks, ...)
115121
}
116122

117123

118-
manual_scale <- function(aesthetic, values = NULL, ...) {
124+
manual_scale <- function(aesthetic, values = NULL, breaks = waiver(), ...) {
119125
# check for missing `values` parameter, in lieu of providing
120126
# a default to all the different scale_*_manual() functions
121127
if (is_missing(values)) {
@@ -124,12 +130,23 @@ manual_scale <- function(aesthetic, values = NULL, ...) {
124130
force(values)
125131
}
126132

133+
# order values according to breaks
134+
if (is.vector(values) && is.null(names(values)) && !is.waive(breaks) &&
135+
!is.null(breaks)) {
136+
if (length(breaks) != length(values)) {
137+
stop("Differing number of values and breaks in manual scale. ",
138+
length(values), " values provided compared to ", length(breaks),
139+
" breaks.", call. = FALSE)
140+
}
141+
names(values) <- breaks
142+
}
143+
127144
pal <- function(n) {
128145
if (n > length(values)) {
129146
stop("Insufficient values in manual scale. ", n, " needed but only ",
130147
length(values), " provided.", call. = FALSE)
131148
}
132149
values
133150
}
134-
discrete_scale(aesthetic, "manual", pal, ...)
151+
discrete_scale(aesthetic, "manual", pal, breaks = breaks, ...)
135152
}

man/scale_manual.Rd

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

tests/testthat/test-scale-manual.r

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,21 @@ test_that("generic scale can be used in place of aesthetic-specific scales", {
6767

6868
expect_equal(layer_data(p1), layer_data(p2))
6969
})
70+
71+
test_that("named values do not match with breaks in manual scales", {
72+
s <- scale_fill_manual(
73+
values = c("data_red" = "red", "data_black" = "black"),
74+
breaks = c("data_black", "data_red")
75+
)
76+
s$train(c("data_black", "data_red"))
77+
expect_equal(s$map(c("data_red", "data_black")), c("red", "black"))
78+
})
79+
80+
test_that("unnamed values match breaks in manual scales", {
81+
s <- scale_fill_manual(
82+
values = c("red", "black"),
83+
breaks = c("data_red", "data_black")
84+
)
85+
s$train(c("data_red", "data_black"))
86+
expect_equal(s$map(c("data_red", "data_black")), c("red", "black"))
87+
})

0 commit comments

Comments
 (0)