-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Make make_labels() more consistent #2981
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make make_labels() more consistent #2981
Conversation
I think it makes sense, but the abbreviation of the calls may cause trouble in revdeps and production plots. Should you maybe run the revdeps to get a sense of how this impacts backward compatibility? Or you could implement |
Thanks, let's care about backward compatibility.
In this case, it's not symbols; I want to convert quosures of a symbol to strings (my description above is a bit confusing, sorry). What is the supposed way to use |
This seems to work. make_labels2 <- function(mapping) {
default_label <- function(aesthetic, mapping) {
# e.g., geom_smooth(aes(colour = "loess"))
if (is.atomic(mapping)) {
aesthetic
} else if (rlang::quo_is_symbol(mapping)) {
rlang::as_string(ggplot2:::strip_dots(rlang::get_expr(mapping)))
} else {
x <- rlang::quo_text(ggplot2:::strip_dots(mapping))
if (length(x) > 1) {
x <- paste0(x[[1]], "...")
}
x
}
}
Map(default_label, names(mapping), mapping)
}
m <- ggplot2::aes(`a b`)
ggplot2:::make_labels(m)
#> $x
#> [1] "`a b`"
make_labels2(m)
#> $x
#> [1] "a b"
m <- ggplot2::aes(..x..)
ggplot2:::make_labels(m)
#> $x
#> [1] "x"
make_labels2(m)
#> $x
#> [1] "x"
m <- ggplot2::aes(15)
ggplot2:::make_labels(m)
#> $x
#> [1] "x"
make_labels2(m)
#> $x
#> [1] "x"
m <- ggplot2::aes(2*x*exp(`coef 1`*x^2))
ggplot2:::make_labels(m)
#> $x
#> [1] "2 * x * exp(`coef 1` * x^2)"
make_labels2(m)
#> $x
#> [1] "2 * x * exp(`coef 1` * x^2)" Created on 2018-11-05 by the reprex package (v0.2.1) |
Instead of |
Thanks! I would use the BTW, is it OK to remove this if (length(x) > 1) {
x <- paste0(x[[1]], "...")
} |
It seems that the current implementation introduces newlines instead of returning a vector of strings. So maybe check for the presence of a newline and if one is there split the string there, take the first part, and add "..."? make_labels2 <- function(mapping) {
default_label <- function(aesthetic, mapping) {
# e.g., geom_smooth(aes(colour = "loess"))
if (is.atomic(mapping)) {
aesthetic
} else if (rlang::quo_is_symbol(mapping)) {
rlang::as_string(ggplot2:::strip_dots(rlang::quo_get_expr(mapping)))
} else {
x <- rlang::quo_text(ggplot2:::strip_dots(mapping))
if (grepl("\\n", x)) {
x <- paste0(strsplit(x, "\\n")[[1]][1], "...")
}
x
}
}
Map(default_label, names(mapping), mapping)
}
m <- ggplot2::aes(2*x*exp(`coef 1`*x^2)*2*x*exp(`coef 1`*x^2)*2*x*exp(`coef 1`*x^2)*2*x*exp(`coef 1`*x^2)*2*x*exp(`coef 1`*x^2)*2*x*exp(`coef 1`*x^2)*2*x*exp(`coef 1`*x^2)*2*x*exp(`coef 1`*x^2))
ggplot2:::make_labels(m)
#> $x
#> [1] "2 * x * exp(`coef 1` * x^2) * 2 * x * exp(`coef 1` * x^2) * 2 * \n x * exp(`coef 1` * x^2) * 2 * x * exp(`coef 1` * x^2) * 2 * \n x * exp(`coef 1` * x^2) * 2 * x * exp(`coef 1` * x^2) * 2 * \n x * exp(`coef 1` * x^2) * 2 * x * exp(`coef 1` * x^2)"
make_labels2(m)
#> $x
#> [1] "2 * x * exp(`coef 1` * x^2) * 2 * x * exp(`coef 1` * x^2) * 2 * ..." Created on 2018-11-05 by the reprex package (v0.2.1) |
I edited my previous comment to include an example implementation of what I meant. |
In rlang I also see: name <- quo_text(expr)
name <- gsub("\n.*$", "...", name) |
Yes, that's cleaner. Though I don't understand whether it's make_labels2 <- function(mapping) {
default_label <- function(aesthetic, mapping) {
# e.g., geom_smooth(aes(colour = "loess"))
if (is.atomic(mapping)) {
aesthetic
} else if (rlang::quo_is_symbol(mapping)) {
rlang::as_string(ggplot2:::strip_dots(rlang::quo_get_expr(mapping)))
} else {
x <- rlang::quo_text(ggplot2:::strip_dots(mapping))
gsub("\n.*$", "...", x)
}
}
Map(default_label, names(mapping), mapping)
}
m <- ggplot2::aes(2*x*exp(`coef 1`*x^2)*2*x*exp(`coef 1`*x^2)*2*x*exp(`coef 1`*x^2)*2*x*exp(`coef 1`*x^2)*2*x*exp(`coef 1`*x^2)*2*x*exp(`coef 1`*x^2)*2*x*exp(`coef 1`*x^2)*2*x*exp(`coef 1`*x^2))
ggplot2:::make_labels(m)
#> $x
#> [1] "2 * x * exp(`coef 1` * x^2) * 2 * x * exp(`coef 1` * x^2) * 2 * \n x * exp(`coef 1` * x^2) * 2 * x * exp(`coef 1` * x^2) * 2 * \n x * exp(`coef 1` * x^2) * 2 * x * exp(`coef 1` * x^2) * 2 * \n x * exp(`coef 1` * x^2) * 2 * x * exp(`coef 1` * x^2)"
make_labels2(m)
#> $x
#> [1] "2 * x * exp(`coef 1` * x^2) * 2 * x * exp(`coef 1` * x^2) * 2 * ..." Created on 2018-11-05 by the reprex package (v0.2.1) |
Ah, got it! Thanks. I didn't know the difference between Note that, to be precise about the backward compatibility, it seems that # Convert aesthetic mapping into text labels
make_labels <- function(mapping) {
remove_dots <- function(x) {
gsub(match_calculated_aes, "\\1", x)
}
default_label <- function(aesthetic, mapping) {
# e.g., geom_smooth(aes(colour = "loess"))
if (is.character(mapping)) {
aesthetic
} else {
remove_dots(deparse(mapping))
}
}
Map(default_label, names(mapping), mapping)
} I don't follow the codes detailedly, but it seems only the first element is used for labeling. library(ggplot2)
packageVersion("ggplot2")
#> [1] '2.2.1'
d <- data.frame(a = 1)
ggplot(d, aes(a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a + a)) +
geom_bar() Created on 2018-11-06 by the reprex package (v0.2.1) But, using |
For For library(ggplot2)
patchwork::wrap_plots(
qplot(x = 1, geom = "vline"),
qplot(x = 1, y = NULL, geom = "vline"),
ggplot(mapping = aes(x = 1)) + geom_vline(),
ggplot(mapping = aes(x = 1, y = NULL)) + geom_vline()
) Created on 2018-11-06 by the reprex package (v0.2.1) |
Hmm, it seems the handling of |
Perhaps this patch should make minimal changes and we'll solve the problem more fully as part of r-lib/rlang#636 |
At a minimum, we should write down the labelling principles that ggplot2 wants, so we can make sure to enforce them in rlang. I think the reason that |
I'm not fully sure, but I think the test cases I added (hopefully) cover the principles minimally, and, in order to pass them, the changes would be this size. Of course I can wait for r-lib/rlang#636 if necessary :) |
I think |
Now I gradually feel you are right, but let me confirm what you mean by "before". Currently, ggplot2 handles
Do you mean 1. is the supposed behaviour? (I think we can agree 3. is not the answer at least) library(ggplot2)
patchwork::wrap_plots(
ggplot(mapping = aes(x = 1, y = NULL)) + geom_vline() + ggtitle("make_labels()"),
ggplot() + aes(x = 1, y = NULL) + geom_vline() + ggtitle("ggplot_add.uneval()"),
qplot(x = 1, y = NULL, geom = "vline") + ggtitle("qplot()")
) Created on 2018-11-07 by the reprex package (v0.2.1) |
I'd consider 1 to be the desired behaviour. 3 is bad, but it's not necessary to fix it (unless it's easy) |
Yes, I think option 1 is the most likely to make sense in an ambiguous situation. library(ggplot2)
ggplot(mapping = aes(x = 1, y = NULL)) + geom_point(data = data.frame(x = 1, y = 1), mapping = aes(x, y)) ggplot() + aes(x = 1, y = NULL) + geom_point(data = data.frame(x = 1, y = 1), mapping = aes(x, y)) Created on 2018-11-06 by the reprex package (v0.2.1) |
Thanks! OK, I'll modify the codes to follow option 1. |
This reverts commit f75440b.
I updated the description of this PR. Does this correctly reflect our discussion so far? |
Yes, I think so. |
tests/testthat/test-aes-calculated.r
Outdated
expect_identical(make_labels(aes(x = `a b`)), list(x = "a b")) | ||
# long expression is abbreviated with ... | ||
expect_identical(make_labels(aes(x = 2 * x * exp(`coef 1` * x^2) * 2 * x * exp(`coef 1` * x^2) * 2 * x)), | ||
list(x = "2 * x * exp(`coef 1` * x^2) * 2 * x * exp(`coef 1` * x^2) * 2 * ...")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you grep for ...
instead please? This way the test does't depend on how quo_text()
deparses complex expressions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure.
@clauswilke @dpseidel May I merge this now? If you are planning a quick release with the fix for the transformation of secondary axis, I think I should wait. Though this PR is only about fixing a minor bug, worries about breaking revdeps should be minimized... |
I think this should be merged now. The change in rlang had much bigger implications for revdeps, and this PR mostly undoes that change. |
Thanks, makes sense! |
This old issue has been automatically locked. If you believe you have found a related problem, please file a new issue (with reprex) and link to this issue. https://reprex.tidyverse.org/ |
Fix #2975
Problem
Currently, ggplot2 makes labels in three different ways:
make_labels()
usesquo_text()
. This handles the cases whereaes()
is passed toggplot()
orgeom_*()
.ggplot_add.uneval()
usesquo_name()
. This handles the case whereaes()
is+
ed to the plot.qplot()
usesquo_name()
, (but the implementation is yet different fromggplot_add.uneval()
). This handlesqplot()
.Consequently, we see some inconsistencies among them. For example,
make_labels()
adds backticks to invalid symbols (e.g.`a b`
), while others don't.NULL
,make_labels()
uses the aesthetic name,ggplot_add.uneval()
removes the label, andqplot()
uses"NULL"
.Scope of this PR
The backticks can be easily removed if we stop using
quo_text()
for symbols. So, at least we can and should fixmake_labels()
.Next, ideally, we should use
make_label()
for making labels everywhere. But, in order to keep this PR small, this PR does only these:make_labels()
to follow the disirable behaviours (below).make_labels()
inggplot_add.uneval()
.and I leave these issues:
qplot()
uses different implementationdeparse()
(ggplot adds backticks to labels (regression?) #2975 (comment))Desirable Behaviours
as_string(quo_get_expr())
for this....
. Usequo_text()
andgsub()
for this.NULL
, use the aesthetic name instead.