Skip to content

Commit 76bb2cd

Browse files
authored
Default labels from attributes (option 2) (#5879)
* resolve layers in `ggplot_build()` * remove label updates from `ggplot_add()` methods * pre-build for label tests * fix bug * add test * `get_alt_text()` applies to build plot
1 parent cd9410c commit 76bb2cd

File tree

7 files changed

+80
-28
lines changed

7 files changed

+80
-28
lines changed

R/labels.R

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,56 @@ update_labels <- function(p, labels) {
1616
p
1717
}
1818

19+
# Called in `ggplot_build()` to set default labels not specified by user.
20+
setup_plot_labels <- function(plot, layers, data) {
21+
# Initiate from user-defined labels
22+
labels <- plot$labels
23+
24+
# Find labels from every layer
25+
for (i in seq_along(layers)) {
26+
layer <- layers[[i]]
27+
mapping <- layer$computed_mapping
28+
mapping <- strip_stage(mapping)
29+
mapping <- strip_dots(mapping, strip_pronoun = TRUE)
30+
31+
# Acquire default labels
32+
mapping_default <- make_labels(mapping)
33+
stat_default <- lapply(
34+
make_labels(layer$stat$default_aes),
35+
function(l) {
36+
attr(l, "fallback") <- TRUE
37+
l
38+
}
39+
)
40+
default <- defaults(mapping_default, stat_default)
41+
42+
# Search for label attribute in symbolic mappings
43+
symbolic <- vapply(
44+
mapping, FUN.VALUE = logical(1),
45+
function(x) is_quosure(x) && quo_is_symbol(x)
46+
)
47+
symbols <- intersect(names(mapping)[symbolic], names(data[[i]]))
48+
attribs <- lapply(setNames(nm = symbols), function(x) {
49+
attr(data[[i]][[x]], "label", exact = TRUE)
50+
})
51+
attribs <- attribs[lengths(attribs) > 0]
52+
layer_labels <- defaults(attribs, default)
53+
54+
# Set label priority:
55+
# 1. Existing labels that aren't fallback labels
56+
# 2. The labels of this layer, including fallback labels
57+
# 3. Existing fallback labels
58+
current <- labels
59+
fallbacks <- vapply(current, function(l) isTRUE(attr(l, "fallback")), logical(1))
60+
61+
labels <- defaults(current[!fallbacks], layer_labels)
62+
if (any(fallbacks)) {
63+
labels <- defaults(labels, current)
64+
}
65+
}
66+
labels
67+
}
68+
1969
#' Modify axis, legend, and plot labels
2070
#'
2171
#' Good labels are critical for making your plots accessible to a wider
@@ -144,8 +194,13 @@ get_alt_text <- function(p, ...) {
144194
#' @export
145195
get_alt_text.ggplot <- function(p, ...) {
146196
alt <- p$labels[["alt"]] %||% ""
197+
if (!is.function(alt)) {
198+
return(alt)
199+
}
147200
p$labels[["alt"]] <- NULL
148-
if (is.function(alt)) alt(p) else alt
201+
build <- ggplot_build(p)
202+
build$plot$labels[["alt"]] <- alt
203+
get_alt_text(build)
149204
}
150205
#' @export
151206
get_alt_text.ggplot_built <- function(p, ...) {

R/plot-build.R

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ ggplot_build.ggplot <- function(plot) {
6060

6161
# Compute aesthetics to produce data with generalised variable names
6262
data <- by_layer(function(l, d) l$compute_aesthetics(d, plot), layers, data, "computing aesthetics")
63+
plot$labels <- setup_plot_labels(plot, layers, data)
6364
data <- .ignore_data(data)
6465

6566
# Transform all scales

R/plot-construction.R

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -133,10 +133,7 @@ ggplot_add.uneval <- function(object, plot, object_name) {
133133
plot$mapping <- defaults(object, plot$mapping)
134134
# defaults() doesn't copy class, so copy it.
135135
class(plot$mapping) <- class(object)
136-
137-
labels <- make_labels(object)
138-
names(labels) <- names(object)
139-
update_labels(plot, labels)
136+
plot
140137
}
141138
#' @export
142139
ggplot_add.Coord <- function(object, plot, object_name) {
@@ -167,19 +164,5 @@ ggplot_add.by <- function(object, plot, object_name) {
167164
#' @export
168165
ggplot_add.Layer <- function(object, plot, object_name) {
169166
plot$layers <- append(plot$layers, object)
170-
171-
# Add any new labels
172-
mapping <- make_labels(object$mapping)
173-
default <- lapply(make_labels(object$stat$default_aes), function(l) {
174-
attr(l, "fallback") <- TRUE
175-
l
176-
})
177-
new_labels <- defaults(mapping, default)
178-
current_labels <- plot$labels
179-
current_fallbacks <- vapply(current_labels, function(l) isTRUE(attr(l, "fallback")), logical(1))
180-
plot$labels <- defaults(current_labels[!current_fallbacks], new_labels)
181-
if (any(current_fallbacks)) {
182-
plot$labels <- defaults(plot$labels, current_labels)
183-
}
184167
plot
185168
}

R/plot.R

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,6 @@ ggplot.default <- function(data = NULL, mapping = aes(), ...,
133133
layout = ggproto(NULL, Layout)
134134
), class = c("gg", "ggplot"))
135135

136-
p$labels <- make_labels(mapping)
137-
138136
set_last_plot(p)
139137
p
140138
}

tests/testthat/_snaps/labels.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
Code
44
get_alt_text(p)
55
Output
6-
[1] "A plot showing class on the x-axis and count on the y-axis using a bar layer."
6+
[1] "A plot showing class on a discrete x-axis and count on a continuous y-axis using a bar layer."
77

88
# plot.tag.position rejects invalid input
99

tests/testthat/test-aes.R

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,12 +94,14 @@ test_that("assignment methods pull unwrap constants from quosures", {
9494

9595
test_that("quosures are squashed when creating default label for a mapping", {
9696
p <- ggplot(mtcars) + aes(!!quo(identity(!!quo(cyl))))
97-
expect_identical(p$labels$x, "identity(cyl)")
97+
labels <- ggplot_build(p)$plot$labels
98+
expect_identical(labels$x, "identity(cyl)")
9899
})
99100

100101
test_that("labelling doesn't cause error if aesthetic is NULL", {
101102
p <- ggplot(mtcars) + aes(x = NULL)
102-
expect_identical(p$labels$x, "x")
103+
labels <- ggplot_build(p)$plot$labels
104+
expect_identical(labels$x, "x")
103105
})
104106

105107
test_that("aes standardises aesthetic names", {

tests/testthat/test-labels.R

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,26 @@ test_that("setting guide labels works", {
5252
test_that("Labels from default stat mapping are overwritten by default labels", {
5353
p <- ggplot(mpg, aes(displ, hwy)) +
5454
geom_density2d()
55+
labels <- ggplot_build(p)$plot$labels
5556

56-
expect_equal(p$labels$colour[1], "colour")
57-
expect_true(attr(p$labels$colour, "fallback"))
57+
expect_equal(labels$colour[1], "colour")
58+
expect_true(attr(labels$colour, "fallback"))
5859

59-
p <- p + geom_smooth(aes(color = drv))
60+
p <- p + geom_smooth(aes(color = drv), method = "lm", formula = y ~ x)
61+
labels <- ggplot_build(p)$plot$labels
6062

61-
expect_equal(p$labels$colour, "drv")
63+
expect_equal(labels$colour, "drv")
64+
})
65+
66+
test_that("Labels can be extracted from attributes", {
67+
df <- mtcars
68+
attr(df$mpg, "label") <- "Miles per gallon"
69+
70+
p <- ggplot(df, aes(mpg, disp)) + geom_point()
71+
labels <- ggplot_build(p)$plot$labels
72+
73+
expect_equal(labels$x, "Miles per gallon")
74+
expect_equal(labels$y, "disp")
6275
})
6376

6477
test_that("alt text is returned", {

0 commit comments

Comments
 (0)