Skip to content

Commit 15b14d3

Browse files
authored
Chained errors with contextual info in layers (#4856)
1 parent 411f17b commit 15b14d3

23 files changed

+167
-68
lines changed

R/ggproto.r

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ fetch_ggproto <- function(x, name) {
153153
return(res)
154154
}
155155

156-
make_proto_method(x, res)
156+
make_proto_method(x, res, name)
157157
}
158158

159159
#' @export
@@ -163,20 +163,23 @@ fetch_ggproto <- function(x, name) {
163163
return(res)
164164
}
165165

166-
make_proto_method(.subset2(x, "self"), res)
166+
make_proto_method(.subset2(x, "self"), res, name)
167167
}
168168

169-
make_proto_method <- function(self, f) {
169+
make_proto_method <- function(self, f, name) {
170170
args <- formals(f)
171171
# is.null is a fast path for a common case; the %in% check is slower but also
172172
# catches the case where there's a `self = NULL` argument.
173173
has_self <- !is.null(args[["self"]]) || "self" %in% names(args)
174174

175+
# We assign the method with its correct name and construct a call to it to
176+
# make errors reported as coming from the method name rather than `f()`
177+
assign(name, f, envir = environment())
178+
args <- list(quote(...))
175179
if (has_self) {
176-
fun <- function(...) f(..., self = self)
177-
} else {
178-
fun <- function(...) f(...)
180+
args$self <- quote(self)
179181
}
182+
fun <- inject(function(...) !!call2(name, !!!args))
180183

181184
class(fun) <- "ggproto_method"
182185
fun

R/layer-sf.R

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ layer_sf <- function(geom = NULL, stat = NULL,
1313
position = NULL, params = list(),
1414
inherit.aes = TRUE, check.aes = TRUE, check.param = TRUE,
1515
show.legend = NA) {
16+
call_env <- caller_env()
1617
if (is.character(show.legend)) {
1718
legend_key_type <- show.legend
1819
show.legend <- TRUE
@@ -21,7 +22,10 @@ layer_sf <- function(geom = NULL, stat = NULL,
2122
}
2223

2324
# inherit from LayerSf class to add `legend_key_type` slot
24-
layer_class <- ggproto(NULL, LayerSf, legend_key_type = legend_key_type)
25+
layer_class <- ggproto(NULL, LayerSf,
26+
constructor = frame_call(call_env),
27+
legend_key_type = legend_key_type
28+
)
2529

2630
layer(
2731
geom = geom, stat = stat, data = data, mapping = mapping,

R/layer.r

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,10 @@ layer <- function(geom = NULL, stat = NULL,
140140
# adjust the legend draw key if requested
141141
geom <- set_draw_key(geom, key_glyph)
142142

143+
fr_call <- layer_class$constructor %||% frame_call(call_env)
144+
143145
ggproto("LayerInstance", layer_class,
146+
constructor = fr_call,
144147
geom = geom,
145148
geom_params = geom_params,
146149
stat = stat,
@@ -169,6 +172,7 @@ validate_mapping <- function(mapping, call = caller_env()) {
169172
}
170173

171174
Layer <- ggproto("Layer", NULL,
175+
constructor = NULL,
172176
geom = NULL,
173177
geom_params = NULL,
174178
stat = NULL,
@@ -328,7 +332,7 @@ Layer <- ggproto("Layer", NULL,
328332
issues <- paste0("{.code ", nondata_stat_cols, " = ", as_label(aesthetics[[nondata_stat_cols]]), "}")
329333
names(issues) <- rep("x", length(issues))
330334
cli::cli_abort(c(
331-
"Aesthetics are not valid computed stats.",
335+
"Aesthetics must be valid computed stats.",
332336
"x" = "The following aesthetics are invalid:",
333337
issues,
334338
"i" = "Did you map your stat in the wrong layer?"

R/plot-build.r

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -35,27 +35,19 @@ ggplot_build.ggplot <- function(plot) {
3535
data <- rep(list(NULL), length(layers))
3636

3737
scales <- plot$scales
38-
# Apply function to layer and matching data
39-
by_layer <- function(f) {
40-
out <- vector("list", length(data))
41-
for (i in seq_along(data)) {
42-
out[[i]] <- f(l = layers[[i]], d = data[[i]])
43-
}
44-
out
45-
}
4638

4739
# Allow all layers to make any final adjustments based
4840
# on raw input data and plot info
49-
data <- by_layer(function(l, d) l$layer_data(plot$data))
50-
data <- by_layer(function(l, d) l$setup_layer(d, plot))
41+
data <- by_layer(function(l, d) l$layer_data(plot$data), layers, data, "computing layer data")
42+
data <- by_layer(function(l, d) l$setup_layer(d, plot), layers, data, "setting up layer")
5143

5244
# Initialise panels, add extra data for margins & missing faceting
5345
# variables, and add on a PANEL variable to data
5446
layout <- create_layout(plot$facet, plot$coordinates)
5547
data <- layout$setup(data, plot$data, plot$plot_env)
5648

5749
# Compute aesthetics to produce data with generalised variable names
58-
data <- by_layer(function(l, d) l$compute_aesthetics(d, plot))
50+
data <- by_layer(function(l, d) l$compute_aesthetics(d, plot), layers, data, "computing aesthetics")
5951

6052
# Transform all scales
6153
data <- lapply(data, scales_transform_df, scales = scales)
@@ -69,17 +61,17 @@ ggplot_build.ggplot <- function(plot) {
6961
data <- layout$map_position(data)
7062

7163
# Apply and map statistics
72-
data <- by_layer(function(l, d) l$compute_statistic(d, layout))
73-
data <- by_layer(function(l, d) l$map_statistic(d, plot))
64+
data <- by_layer(function(l, d) l$compute_statistic(d, layout), layers, data, "computing stat")
65+
data <- by_layer(function(l, d) l$map_statistic(d, plot), layers, data, "mapping stat to aesthetics")
7466

7567
# Make sure missing (but required) aesthetics are added
7668
scales_add_missing(plot, c("x", "y"), plot$plot_env)
7769

7870
# Reparameterise geoms from (e.g.) y and width to ymin and ymax
79-
data <- by_layer(function(l, d) l$compute_geom_1(d))
71+
data <- by_layer(function(l, d) l$compute_geom_1(d), layers, data, "setting up geom")
8072

8173
# Apply position adjustments
82-
data <- by_layer(function(l, d) l$compute_position(d, layout))
74+
data <- by_layer(function(l, d) l$compute_position(d, layout), layers, data, "computing position")
8375

8476
# Reset position scales, then re-train and map. This ensures that facets
8577
# have control over the range of a plot: is it generated from what is
@@ -97,10 +89,10 @@ ggplot_build.ggplot <- function(plot) {
9789
}
9890

9991
# Fill in defaults etc.
100-
data <- by_layer(function(l, d) l$compute_geom_2(d))
92+
data <- by_layer(function(l, d) l$compute_geom_2(d), layers, data, "setting up geom aesthetics")
10193

10294
# Let layer stat have a final say before rendering
103-
data <- by_layer(function(l, d) l$finish_statistics(d))
95+
data <- by_layer(function(l, d) l$finish_statistics(d), layers, data, "finishing layer stat")
10496

10597
# Let Layout modify data before rendering
10698
data <- layout$finish_data(data)
@@ -168,7 +160,7 @@ ggplot_gtable.ggplot_built <- function(data) {
168160
data <- data$data
169161
theme <- plot_theme(plot)
170162

171-
geom_grobs <- Map(function(l, d) l$draw_geom(d, layout), plot$layers, data)
163+
geom_grobs <- by_layer(function(l, d) l$draw_geom(d, layout), plot$layers, data, "converting geom to grob")
172164
layout$setup_panel_guides(plot$guides, plot$layers, plot$mapping)
173165
plot_table <- layout$render(geom_grobs, data, theme, plot$labels)
174166

@@ -419,3 +411,18 @@ ggplot_gtable.ggplot_built <- function(data) {
419411
ggplotGrob <- function(x) {
420412
ggplot_gtable(ggplot_build(x))
421413
}
414+
415+
# Apply function to layer and matching data
416+
by_layer <- function(f, layers, data, step = NULL) {
417+
ordinal <- label_ordinal()
418+
out <- vector("list", length(data))
419+
try_fetch(
420+
for (i in seq_along(data)) {
421+
out[[i]] <- f(l = layers[[i]], d = data[[i]])
422+
},
423+
error = function(cnd) {
424+
cli::cli_abort(c("Problem while {step}.", "i" = "Error occurred in the {ordinal(i)} layer."), call = I(layers[[i]]$constructor), parent = cnd)
425+
}
426+
)
427+
out
428+
}

tests/testthat/_snaps/annotate.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
# annotation_raster() and annotation_custom() requires cartesian coordinates
22

3-
`annotation_raster()` only works with `coord_cartesian()`
3+
Problem while converting geom to grob.
4+
i Error occurred in the 1st layer.
5+
Caused by error in `draw_panel()`:
6+
! `annotation_raster()` only works with `coord_cartesian()`
47

58
---
69

7-
`annotation_custom()` only works with `coord_cartesian()`
10+
Problem while converting geom to grob.
11+
i Error occurred in the 1st layer.
12+
Caused by error in `draw_panel()`:
13+
! `annotation_custom()` only works with `coord_cartesian()`
814

915
# annotation_map() checks the input data
1016

tests/testthat/_snaps/geom-.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
# aesthetic checking in geom throws correct errors
22

3-
Aesthetic modifiers returned invalid values
3+
Problem while setting up geom aesthetics.
4+
i Error occurred in the 1st layer.
5+
Caused by error in `use_defaults()`:
6+
! Aesthetic modifiers returned invalid values
47
x The following mappings are invalid
58
x `colour = after_scale(data)`
69
i Did you map the modifier in the wrong layer?

tests/testthat/_snaps/geom-dotplot.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55
# weight aesthetic is checked
66

77
Computation failed in `stat_bindot()`
8-
Caused by error in `f()`:
8+
Caused by error in `compute_group()`:
99
! Weights must be nonnegative integers.
1010

1111
---
1212

1313
Computation failed in `stat_bindot()`
14-
Caused by error in `f()`:
14+
Caused by error in `compute_group()`:
1515
! Weights must be nonnegative integers.
1616

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
# geom_linerange request the right aesthetics
22

3-
`geom_linerange()` requires the following missing aesthetics: ymax or xmin and xmax
3+
Problem while setting up geom.
4+
i Error occurred in the 1st layer.
5+
Caused by error in `compute_geom_1()`:
6+
! `geom_linerange()` requires the following missing aesthetics: ymax or xmin and xmax
47

tests/testthat/_snaps/geom-path.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
# geom_path() throws meaningful error on bad combination of varying aesthetics
22

3-
`geom_path()` can't have varying colour, size, and/or alpha along the line when linetype isn't solid
3+
Problem while converting geom to grob.
4+
i Error occurred in the 1st layer.
5+
Caused by error in `draw_panel()`:
6+
! `geom_path()` can't have varying colour, size, and/or alpha along the line when linetype isn't solid
47

tests/testthat/_snaps/geom-raster.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,8 @@
1616

1717
---
1818

19-
`geom_raster()` only works with `coord_cartesian()`
19+
Problem while converting geom to grob.
20+
i Error occurred in the 1st layer.
21+
Caused by error in `draw_panel()`:
22+
! `geom_raster()` only works with `coord_cartesian()`
2023

0 commit comments

Comments
 (0)