Skip to content

Commit 3f93180

Browse files
slowkowclauswilke
authored andcommitted
replace parse() with parse_safe() in geom_text() (#2867)
Closes issue #2864 * use parse_safe() in geom_text() The built in `parse()` function silently drops items: length(parse(text = c("alpha", "", "gamma"))) We expect the length to be 3, but instead it is 2. So, we add a new function `parse_safe()` that keeps all items. * update parse_safe() Use Hadley's code to parse the expressions once instead of twice. Use `parse(..., keep.source = FALSE)`, as Claus recommended. Add an example to demonstrate why `parse_safe()` is needed. * add tests for parse_safe() * add note about parse(text = NULL) * use parse_safe() in sf.R This patch fixes an example that triggers an error: library(ggplot2) nc <- sf::st_read(system.file("shape/nc.shp", package = "sf"), quiet = TRUE) ggplot(nc) + geom_sf(aes(fill = AREA)) + scale_y_continuous( breaks = c(34, 35, 36), labels = c("34*degree*N", "", "36*degree*N") ) #> Error in parse(text = x)[[1]]: subscript out of bounds See #2867 for more details. * move parse_safe() tests to test-utilities.r * change parse_safe(text = x) to parse_safe(x) * modify comment for parse_safe() add URL to GitHub issue #2864 * fix parsing degree labels in geom_sf() * add news item about parse_safe() * fix typo * move parse() into fixup_graticule_labels() * change description of parse_safe() * modify news item about parse_safe() * add stopifnot() to parse_safe() * simplify parsing code in sf.R * improve news item about parse_safe() * do not test parse_safe(factor(...)) `parse_safe()` takes a character vector, not a factor
1 parent 6e545dc commit 3f93180

File tree

6 files changed

+78
-6
lines changed

6 files changed

+78
-6
lines changed

NEWS.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,12 @@
5959
* `position_nudge()` is now more robust and nudges only in the direction
6060
requested. This enables, for example, the horizontal nudging of boxplots
6161
(@clauswilke, #2733).
62+
63+
* `geom_text(..., parse = TRUE)` now correctly renders the expected number of
64+
items instead of silently dropping items that are empty expressions, e.g.
65+
the empty string "". If an expression spans multiple lines, we take just
66+
the first line and drop the rest. This same issue is also fixed for
67+
`geom_label()` and the axis labels for `geom_sf()` (@slowkow, #2867).
6268

6369
# ggplot2 3.0.0
6470

R/geom-label.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ GeomLabel <- ggproto("GeomLabel", Geom,
6363
label.size = 0.25) {
6464
lab <- data$label
6565
if (parse) {
66-
lab <- parse(text = as.character(lab))
66+
lab <- parse_safe(as.character(lab))
6767
}
6868

6969
data <- coord$transform(data, panel_params)

R/geom-text.r

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,6 @@ geom_text <- function(mapping = NULL, data = NULL,
159159
)
160160
}
161161

162-
163162
#' @rdname ggplot2-ggproto
164163
#' @format NULL
165164
#' @usage NULL
@@ -176,7 +175,7 @@ GeomText <- ggproto("GeomText", Geom,
176175
na.rm = FALSE, check_overlap = FALSE) {
177176
lab <- data$label
178177
if (parse) {
179-
lab <- parse(text = as.character(lab))
178+
lab <- parse_safe(as.character(lab))
180179
}
181180

182181
data <- coord$transform(data, panel_params)

R/sf.R

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -476,9 +476,13 @@ CoordSf <- ggproto("CoordSf", CoordCartesian,
476476
if (!is.null(graticule$plot12))
477477
graticule$degree_label[!graticule$plot12] <- NA
478478

479-
# parse labels into expressions if required
480-
if (any(grepl("degree", graticule$degree_label)))
481-
graticule$degree_label <- lapply(graticule$degree_label, function(x) parse(text = x)[[1]])
479+
# Convert the string 'degree' to the degree symbol
480+
has_degree <- grepl("\\bdegree\\b", graticule$degree_label)
481+
if (any(has_degree)) {
482+
labels <- as.list(graticule$degree_label)
483+
labels[has_degree] <- parse_safe(graticule$degree_label[has_degree])
484+
graticule$degree_label <- labels
485+
}
482486

483487
graticule
484488
},

R/utilities.r

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,3 +430,22 @@ is_column_vec <- function(x) {
430430
dims <- dim(x)
431431
length(dims) == 2L && dims[[2]] == 1L
432432
}
433+
434+
# Parse takes a vector of n lines and returns m expressions.
435+
# See https://github.com/tidyverse/ggplot2/issues/2864 for discussion.
436+
#
437+
# parse(text = c("alpha", "", "gamma"))
438+
# #> expression(alpha, gamma)
439+
#
440+
# parse_safe(text = c("alpha", "", "gamma"))
441+
# #> expression(alpha, NA, gamma)
442+
#
443+
parse_safe <- function(text) {
444+
stopifnot(is.character(text))
445+
out <- vector("expression", length(text))
446+
for (i in seq_along(text)) {
447+
expr <- parse(text = text[[i]])
448+
out[[i]] <- if (length(expr) == 0) NA else expr[[1]]
449+
}
450+
out
451+
}

tests/testthat/test-utilities.r

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,47 @@ test_that("find_args behaves correctly", {
4343
# Defaults are overwritten
4444
expect_true(test_fun(arg2 = TRUE)$arg2)
4545
})
46+
47+
test_that("parse_safe works with simple expressions", {
48+
expect_equal(
49+
parse_safe(c("", " ", " ")),
50+
expression(NA, NA, NA)
51+
)
52+
53+
expect_equal(
54+
parse_safe(c("A", "B", "C")),
55+
expression(A, B, C)
56+
)
57+
58+
expect_equal(
59+
parse_safe(c("alpha", "", "gamma", " ")),
60+
expression(alpha, NA, gamma, NA)
61+
)
62+
63+
expect_equal(
64+
parse_safe(c(NA, "a", NA, "alpha")),
65+
expression(NA, a, NA, alpha)
66+
)
67+
})
68+
69+
test_that("parse_safe works with multi expressions", {
70+
expect_equal(
71+
parse_safe(c(" \n", "\n ", " \n \n ")),
72+
expression(NA, NA, NA)
73+
)
74+
75+
expect_equal(
76+
parse_safe(c("alpha ~ beta", "beta \n gamma", "")),
77+
expression(alpha ~ beta, beta, NA)
78+
)
79+
80+
expect_equal(
81+
parse_safe(c("alpha ~ beta", " ", "integral(f(x) * dx, a, b)")),
82+
expression(alpha ~ beta, NA, integral(f(x) * dx, a, b))
83+
)
84+
85+
expect_equal(
86+
parse_safe(c(NA, 1, 2, "a \n b")),
87+
expression(NA, 1, 2, a)
88+
)
89+
})

0 commit comments

Comments
 (0)