Skip to content

Commit 7ac9e42

Browse files
clauswilkehadley
authored andcommitted
Nonstandard aesthetics (#2555)
* enable guide_colorbar to work with nonstandard aesthetics * remove hardcoded aesthetics setting for scales; adjust guides so they can make full use of multiple aesthetics for one scale. * update NEWS * add regression tests * exported discrete manual scale. * add generic manual and identity scales * add test cases for new generic scales. * improve docs
1 parent 22691ab commit 7ac9e42

26 files changed

+461
-115
lines changed

NAMESPACE

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,9 @@ export(scale_colour_manual)
443443
export(scale_colour_ordinal)
444444
export(scale_colour_viridis_c)
445445
export(scale_colour_viridis_d)
446+
export(scale_continuous_identity)
447+
export(scale_discrete_identity)
448+
export(scale_discrete_manual)
446449
export(scale_fill_brewer)
447450
export(scale_fill_continuous)
448451
export(scale_fill_date)

NEWS.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,10 @@ up correct aspect ratio, and draws a graticule.
186186
use matrix-columns. These are rarely used but are produced by `scale()`;
187187
to continue use `scale()` you'll need to wrap it with `as.numeric()`,
188188
e.g. `as.numeric(scale(x))`.
189+
190+
* The function `guide_train()` now has an optional parameter `aesthetic`
191+
that allows to override the `aesthetic` setting in the scale. This change
192+
will only affect code that implements custom guides. (@clauswilke)
189193

190194
## Minor bug fixes and improvements
191195

@@ -220,6 +224,15 @@ up correct aspect ratio, and draws a graticule.
220224

221225
* Legends no longer try and use set aesthetics that are not length one (#1932).
222226

227+
* All colour and fill scales now have an `aesthetics` argument that can
228+
be used to set the aesthetic(s) the scale works with. This makes it
229+
possible to apply a colour scale to both colour and fill aesthetics
230+
at the same time, via `aesthetics = c("colour", "fill"). (@clauswilke)
231+
232+
* Three generic scales were added that work with any aesthetic or set of
233+
aesthetics: `scale_continuous_identity()`, `scale_discrete_identity()`,
234+
`scale_discrete_manual()`. (@clauswilke)
235+
223236
### Layers
224237

225238
* `geom_label` no longer produces an undesired border around labels when

R/guide-colorbar.r

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@
4444
#' for `barwidth` and `barheight`.
4545
#' @param reverse logical. If `TRUE` the colorbar is reversed. By default,
4646
#' the highest value is on the top and the lowest value is on the bottom
47+
#' @param available_aes A vector of charater strings listing the aesthetics
48+
#' for which a colorbar can be drawn.
4749
#' @param ... ignored.
4850
#' @return A guide object
4951
#' @export
@@ -134,6 +136,7 @@ guide_colourbar <- function(
134136
default.unit = "line",
135137
reverse = FALSE,
136138
order = 0,
139+
available_aes = c("colour", "color", "fill"),
137140

138141
...) {
139142

@@ -180,17 +183,20 @@ guide_colourbar <- function(
180183
order = order,
181184

182185
# parameter
183-
available_aes = c("colour", "color", "fill"), ..., name = "colorbar"),
186+
available_aes = available_aes,
187+
...,
188+
name = "colorbar"),
184189
class = c("guide", "colorbar")
185190
)
186191
}
187192

188193
#' @export
189-
guide_train.colorbar <- function(guide, scale) {
194+
guide_train.colorbar <- function(guide, scale, aesthetic = NULL) {
190195

191196
# do nothing if scale are inappropriate
192-
if (length(intersect(scale$aesthetics, c("color", "colour", "fill"))) == 0) {
193-
warning("colorbar guide needs colour or fill scales.")
197+
if (length(intersect(scale$aesthetics, guide$available_aes)) == 0) {
198+
warning("colorbar guide needs appropriate scales: ",
199+
paste(guide$available_aes, collapse = ", "))
194200
return(NULL)
195201
}
196202
if (scale$is_discrete()) {
@@ -204,7 +210,7 @@ guide_train.colorbar <- function(guide, scale) {
204210
if (length(breaks) == 0 || all(is.na(breaks)))
205211
return()
206212

207-
ticks <- as.data.frame(setNames(list(scale$map(breaks)), scale$aesthetics[1]))
213+
ticks <- as.data.frame(setNames(list(scale$map(breaks)), aesthetic %||% scale$aesthetics[1]))
208214
ticks$.value <- breaks
209215
ticks$.label <- scale$get_labels(breaks)
210216

R/guide-legend.r

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -215,14 +215,14 @@ guide_legend <- function(# title
215215
}
216216

217217
#' @export
218-
guide_train.legend <- function(guide, scale) {
218+
guide_train.legend <- function(guide, scale, aesthetic = NULL) {
219219
breaks <- scale$get_breaks()
220220
if (length(breaks) == 0 || all(is.na(breaks))) {
221221
return()
222222
}
223223

224224
key <- as.data.frame(
225-
setNames(list(scale$map(breaks)), scale$aesthetics[1]),
225+
setNames(list(scale$map(breaks)), aesthetic %||% scale$aesthetics[1]),
226226
stringsAsFactors = FALSE
227227
)
228228
key$.label <- scale$get_labels(breaks)

R/guides-.r

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -162,37 +162,38 @@ guides_train <- function(scales, theme, guides, labels) {
162162

163163
gdefs <- list()
164164
for (scale in scales$scales) {
165+
for (output in scale$aesthetics) {
165166

166-
# guides(XXX) is stored in guides[[XXX]],
167-
# which is prior to scale_ZZZ(guide=XXX)
168-
# guide is determined in order of:
169-
# + guides(XXX) > + scale_ZZZ(guide=XXX) > default(i.e., legend)
170-
output <- scale$aesthetics[1]
171-
guide <- guides[[output]] %||% scale$guide
167+
# guides(XXX) is stored in guides[[XXX]],
168+
# which is prior to scale_ZZZ(guide=XXX)
169+
# guide is determined in order of:
170+
# + guides(XXX) > + scale_ZZZ(guide=XXX) > default(i.e., legend)
171+
guide <- guides[[output]] %||% scale$guide
172172

173-
# this should be changed to testing guide == "none"
174-
# scale$legend is backward compatibility
175-
# if guides(XXX=FALSE), then scale_ZZZ(guides=XXX) is discarded.
176-
if (guide == "none" || (is.logical(guide) && !guide)) next
173+
# this should be changed to testing guide == "none"
174+
# scale$legend is backward compatibility
175+
# if guides(XXX=FALSE), then scale_ZZZ(guides=XXX) is discarded.
176+
if (guide == "none" || (is.logical(guide) && !guide)) next
177177

178-
# check the validity of guide.
179-
# if guide is character, then find the guide object
180-
guide <- validate_guide(guide)
178+
# check the validity of guide.
179+
# if guide is character, then find the guide object
180+
guide <- validate_guide(guide)
181181

182-
# check the consistency of the guide and scale.
183-
if (guide$available_aes != "any" && !scale$aesthetics %in% guide$available_aes)
184-
stop("Guide '", guide$name, "' cannot be used for '", scale$aesthetics, "'.")
182+
# check the consistency of the guide and scale.
183+
if (guide$available_aes != "any" && !scale$aesthetics %in% guide$available_aes)
184+
stop("Guide '", guide$name, "' cannot be used for '", scale$aesthetics, "'.")
185185

186-
guide$title <- scale$make_title(guide$title %|W|% scale$name %|W|% labels[[output]])
186+
guide$title <- scale$make_title(guide$title %|W|% scale$name %|W|% labels[[output]])
187187

188-
# direction of this grob
189-
guide$direction <- guide$direction %||% theme$legend.direction
188+
# direction of this grob
189+
guide$direction <- guide$direction %||% theme$legend.direction
190190

191-
# each guide object trains scale within the object,
192-
# so Guides (i.e., the container of guides) need not to know about them
193-
guide <- guide_train(guide, scale)
191+
# each guide object trains scale within the object,
192+
# so Guides (i.e., the container of guides) need not to know about them
193+
guide <- guide_train(guide, scale, output)
194194

195-
if (!is.null(guide)) gdefs[[length(gdefs) + 1]] <- guide
195+
if (!is.null(guide)) gdefs[[length(gdefs) + 1]] <- guide
196+
}
196197
}
197198
gdefs
198199
}
@@ -309,7 +310,7 @@ NULL
309310

310311
#' @export
311312
#' @rdname guide-exts
312-
guide_train <- function(guide, scale) UseMethod("guide_train")
313+
guide_train <- function(guide, scale, aesthetic = NULL) UseMethod("guide_train")
313314

314315
#' @export
315316
#' @rdname guide-exts

R/scale-brewer.r

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -65,37 +65,37 @@
6565
#' v
6666
#' v + scale_fill_distiller()
6767
#' v + scale_fill_distiller(palette = "Spectral")
68-
scale_colour_brewer <- function(..., type = "seq", palette = 1, direction = 1) {
69-
discrete_scale("colour", "brewer", brewer_pal(type, palette, direction), ...)
68+
scale_colour_brewer <- function(..., type = "seq", palette = 1, direction = 1, aesthetics = "colour") {
69+
discrete_scale(aesthetics, "brewer", brewer_pal(type, palette, direction), ...)
7070
}
7171

7272
#' @export
7373
#' @rdname scale_brewer
74-
scale_fill_brewer <- function(..., type = "seq", palette = 1, direction = 1) {
75-
discrete_scale("fill", "brewer", brewer_pal(type, palette, direction), ...)
74+
scale_fill_brewer <- function(..., type = "seq", palette = 1, direction = 1, aesthetics = "fill") {
75+
discrete_scale(aesthetics, "brewer", brewer_pal(type, palette, direction), ...)
7676
}
7777

7878
#' @export
7979
#' @rdname scale_brewer
80-
scale_colour_distiller <- function(..., type = "seq", palette = 1, direction = -1, values = NULL, space = "Lab", na.value = "grey50", guide = "colourbar") {
80+
scale_colour_distiller <- function(..., type = "seq", palette = 1, direction = -1, values = NULL, space = "Lab", na.value = "grey50", guide = "colourbar", aesthetics = "colour") {
8181
# warn about using a qualitative brewer palette to generate the gradient
8282
type <- match.arg(type, c("seq", "div", "qual"))
8383
if (type == "qual") {
8484
warning("Using a discrete colour palette in a continuous scale.\n Consider using type = \"seq\" or type = \"div\" instead", call. = FALSE)
8585
}
86-
continuous_scale("colour", "distiller",
86+
continuous_scale(aesthetics, "distiller",
8787
gradient_n_pal(brewer_pal(type, palette, direction)(6), values, space), na.value = na.value, guide = guide, ...)
8888
# NB: 6 colours per palette gives nice gradients; more results in more saturated colours which do not look as good
8989
}
9090

9191
#' @export
9292
#' @rdname scale_brewer
93-
scale_fill_distiller <- function(..., type = "seq", palette = 1, direction = -1, values = NULL, space = "Lab", na.value = "grey50", guide = "colourbar") {
93+
scale_fill_distiller <- function(..., type = "seq", palette = 1, direction = -1, values = NULL, space = "Lab", na.value = "grey50", guide = "colourbar", aesthetics = "fill") {
9494
type <- match.arg(type, c("seq", "div", "qual"))
9595
if (type == "qual") {
9696
warning("Using a discrete colour palette in a continuous scale.\n Consider using type = \"seq\" or type = \"div\" instead", call. = FALSE)
9797
}
98-
continuous_scale("fill", "distiller",
98+
continuous_scale(aesthetics, "distiller",
9999
gradient_n_pal(brewer_pal(type, palette, direction)(6), values, space), na.value = na.value, guide = guide, ...)
100100
}
101101

R/scale-gradient.r

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -54,15 +54,17 @@
5454
#' scale_colour_gradient(low = "white", high = "black")
5555
#' # Avoid red-green colour contrasts because ~10% of men have difficulty
5656
#' # seeing them
57-
scale_colour_gradient <- function(..., low = "#132B43", high = "#56B1F7", space = "Lab", na.value = "grey50", guide = "colourbar") {
58-
continuous_scale("colour", "gradient", seq_gradient_pal(low, high, space),
57+
scale_colour_gradient <- function(..., low = "#132B43", high = "#56B1F7", space = "Lab",
58+
na.value = "grey50", guide = "colourbar", aesthetics = "colour") {
59+
continuous_scale(aesthetics, "gradient", seq_gradient_pal(low, high, space),
5960
na.value = na.value, guide = guide, ...)
6061
}
6162

6263
#' @rdname scale_gradient
6364
#' @export
64-
scale_fill_gradient <- function(..., low = "#132B43", high = "#56B1F7", space = "Lab", na.value = "grey50", guide = "colourbar") {
65-
continuous_scale("fill", "gradient", seq_gradient_pal(low, high, space),
65+
scale_fill_gradient <- function(..., low = "#132B43", high = "#56B1F7", space = "Lab",
66+
na.value = "grey50", guide = "colourbar", aesthetics = "fill") {
67+
continuous_scale(aesthetics, "gradient", seq_gradient_pal(low, high, space),
6668
na.value = na.value, guide = guide, ...)
6769
}
6870

@@ -71,16 +73,20 @@ scale_fill_gradient <- function(..., low = "#132B43", high = "#56B1F7", space =
7173
#' Defaults to 0.
7274
#' @rdname scale_gradient
7375
#' @export
74-
scale_colour_gradient2 <- function(..., low = muted("red"), mid = "white", high = muted("blue"), midpoint = 0, space = "Lab", na.value = "grey50", guide = "colourbar") {
75-
continuous_scale("colour", "gradient2",
76+
scale_colour_gradient2 <- function(..., low = muted("red"), mid = "white", high = muted("blue"),
77+
midpoint = 0, space = "Lab", na.value = "grey50", guide = "colourbar",
78+
aesthetics = "colour") {
79+
continuous_scale(aesthetics, "gradient2",
7680
div_gradient_pal(low, mid, high, space), na.value = na.value, guide = guide, ...,
7781
rescaler = mid_rescaler(mid = midpoint))
7882
}
7983

8084
#' @rdname scale_gradient
8185
#' @export
82-
scale_fill_gradient2 <- function(..., low = muted("red"), mid = "white", high = muted("blue"), midpoint = 0, space = "Lab", na.value = "grey50", guide = "colourbar") {
83-
continuous_scale("fill", "gradient2",
86+
scale_fill_gradient2 <- function(..., low = muted("red"), mid = "white", high = muted("blue"),
87+
midpoint = 0, space = "Lab", na.value = "grey50", guide = "colourbar",
88+
aesthetics = "fill") {
89+
continuous_scale(aesthetics, "gradient2",
8490
div_gradient_pal(low, mid, high, space), na.value = na.value, guide = guide, ...,
8591
rescaler = mid_rescaler(mid = midpoint))
8692
}
@@ -95,17 +101,19 @@ mid_rescaler <- function(mid) {
95101
#' @param colours,colors Vector of colours to use for n-colour gradient.
96102
#' @rdname scale_gradient
97103
#' @export
98-
scale_colour_gradientn <- function(..., colours, values = NULL, space = "Lab", na.value = "grey50", guide = "colourbar", colors) {
104+
scale_colour_gradientn <- function(..., colours, values = NULL, space = "Lab", na.value = "grey50",
105+
guide = "colourbar", aesthetics = "colour", colors) {
99106
colours <- if (missing(colours)) colors else colours
100107

101-
continuous_scale("colour", "gradientn",
108+
continuous_scale(aesthetics, "gradientn",
102109
gradient_n_pal(colours, values, space), na.value = na.value, guide = guide, ...)
103110
}
104111
#' @rdname scale_gradient
105112
#' @export
106-
scale_fill_gradientn <- function(..., colours, values = NULL, space = "Lab", na.value = "grey50", guide = "colourbar", colors) {
113+
scale_fill_gradientn <- function(..., colours, values = NULL, space = "Lab", na.value = "grey50",
114+
guide = "colourbar", aesthetics = "fill", colors) {
107115
colours <- if (missing(colours)) colors else colours
108116

109-
continuous_scale("fill", "gradientn",
117+
continuous_scale(aesthetics, "gradientn",
110118
gradient_n_pal(colours, values, space), na.value = na.value, guide = guide, ...)
111119
}

R/scale-grey.r

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,14 @@
2525
#' ggplot(mtcars, aes(mpg, wt)) +
2626
#' geom_point(aes(colour = miss)) +
2727
#' scale_colour_grey(na.value = "green")
28-
scale_colour_grey <- function(..., start = 0.2, end = 0.8, na.value = "red") {
29-
discrete_scale("colour", "grey", grey_pal(start, end),
28+
scale_colour_grey <- function(..., start = 0.2, end = 0.8, na.value = "red", aesthetics = "colour") {
29+
discrete_scale(aesthetics, "grey", grey_pal(start, end),
3030
na.value = na.value, ...)
3131
}
3232

3333
#' @rdname scale_grey
3434
#' @export
35-
scale_fill_grey <- function(..., start = 0.2, end = 0.8, na.value = "red") {
36-
discrete_scale("fill", "grey", grey_pal(start, end),
35+
scale_fill_grey <- function(..., start = 0.2, end = 0.8, na.value = "red", aesthetics = "fill") {
36+
discrete_scale(aesthetics, "grey", grey_pal(start, end),
3737
na.value = na.value, ...)
3838
}

R/scale-hue.r

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@
55
#' colour-blind safe palettes.
66
#'
77
#' @param na.value Colour to use for missing values
8-
#' @inheritDotParams discrete_scale
8+
#' @inheritDotParams discrete_scale -aesthetics
9+
#' @param aesthetics Character string or vector of character strings listing the
10+
#' name(s) of the aesthetic(s) that this scale works with. This can be useful, for
11+
#' example, to apply colour settings to the `colour` and `fill` aesthetics at the
12+
#' same time, via `aesthetics = c("colour", "fill")`.
913
#' @inheritParams scales::hue_pal
1014
#' @rdname scale_hue
1115
#' @export
@@ -46,14 +50,16 @@
4650
#' geom_point(aes(colour = miss)) +
4751
#' scale_colour_hue(na.value = "black")
4852
#' }
49-
scale_colour_hue <- function(..., h = c(0, 360) + 15, c = 100, l = 65, h.start = 0, direction = 1, na.value = "grey50") {
50-
discrete_scale("colour", "hue", hue_pal(h, c, l, h.start, direction),
53+
scale_colour_hue <- function(..., h = c(0, 360) + 15, c = 100, l = 65, h.start = 0,
54+
direction = 1, na.value = "grey50", aesthetics = "colour") {
55+
discrete_scale(aesthetics, "hue", hue_pal(h, c, l, h.start, direction),
5156
na.value = na.value, ...)
5257
}
5358

5459
#' @rdname scale_hue
5560
#' @export
56-
scale_fill_hue <- function(..., h = c(0, 360) + 15, c = 100, l = 65, h.start = 0, direction = 1, na.value = "grey50") {
57-
discrete_scale("fill", "hue", hue_pal(h, c, l, h.start, direction),
61+
scale_fill_hue <- function(..., h = c(0, 360) + 15, c = 100, l = 65, h.start = 0,
62+
direction = 1, na.value = "grey50", aesthetics = "fill") {
63+
discrete_scale(aesthetics, "hue", hue_pal(h, c, l, h.start, direction),
5864
na.value = na.value, ...)
5965
}

0 commit comments

Comments
 (0)