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

tests/testthat/_snaps/geom-ribbon.md

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,23 @@
11
# geom_ribbon() checks the aesthetics
22

3-
Either xmin or xmax must be given as an aesthetic.
3+
Problem while setting up geom.
4+
i Error occurred in the 1st layer.
5+
Caused by error in `setup_data()`:
6+
! Either xmin or xmax must be given as an aesthetic.
47

58
---
69

7-
Either ymin or ymax must be given as an aesthetic.
10+
Problem while setting up geom.
11+
i Error occurred in the 1st layer.
12+
Caused by error in `setup_data()`:
13+
! Either ymin or ymax must be given as an aesthetic.
814

915
---
1016

11-
Aesthetics can not vary along a ribbon
17+
Problem while converting geom to grob.
18+
i Error occurred in the 1st layer.
19+
Caused by error in `draw_group()`:
20+
! Aesthetics can not vary along a ribbon
1221

1322
---
1423

tests/testthat/_snaps/geom-sf.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
# errors are correctly triggered
22

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

58
---
69

tests/testthat/_snaps/geom-violin.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
# quantiles fails outside 0-1 bound
22

3-
`draw_quantiles` must be between 0 and 1
3+
Problem while converting geom to grob.
4+
i Error occurred in the 1st layer.
5+
Caused by error in `draw_group()`:
6+
! `draw_quantiles` must be between 0 and 1
47

58
---
69

7-
`draw_quantiles` must be between 0 and 1
10+
Problem while converting geom to grob.
11+
i Error occurred in the 1st layer.
12+
Caused by error in `draw_group()`:
13+
! `draw_quantiles` must be between 0 and 1
814

tests/testthat/_snaps/guides.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,3 @@
1-
# binning scales understand the different combinations of limits, breaks, labels, and show.limits
2-
3-
`show.limits` is ignored when `labels` are given as a character vector
4-
i Either add the limits to `breaks` or provide a function for `labels`
5-
6-
---
7-
8-
`show.limits` is ignored when `labels` are given as a character vector
9-
i Either add the limits to `breaks` or provide a function for `labels`
10-
111
# axis_label_element_overrides errors when angles are outside the range [0, 90]
122

133
Unrecognized `axis_position`: "test"
@@ -68,6 +58,16 @@
6858
Breaks not formatted correctly for a bin legend.
6959
i Use `(<lower>, <upper>]` format to indicate bins
7060

61+
# binning scales understand the different combinations of limits, breaks, labels, and show.limits
62+
63+
`show.limits` is ignored when `labels` are given as a character vector
64+
i Either add the limits to `breaks` or provide a function for `labels`
65+
66+
---
67+
68+
`show.limits` is ignored when `labels` are given as a character vector
69+
i Either add the limits to `breaks` or provide a function for `labels`
70+
7171
# a warning is generated when guides(<scale> = FALSE) is specified
7272

7373
`guide = FALSE` is deprecated

tests/testthat/_snaps/layer.md

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,29 +29,41 @@
2929

3030
# invalid aesthetics throws errors
3131

32-
Aesthetics are not valid data columns.
32+
Problem while computing aesthetics.
33+
i Error occurred in the 1st layer.
34+
Caused by error in `compute_aesthetics()`:
35+
! Aesthetics are not valid data columns.
3336
x The following aesthetics are invalid:
3437
x `fill = data`
3538
i Did you mistype the name of a data column or forget to add `after_stat()`?
3639

3740
---
3841

39-
Aesthetics are not valid computed stats.
42+
Problem while mapping stat to aesthetics.
43+
i Error occurred in the 1st layer.
44+
Caused by error in `map_statistic()`:
45+
! Aesthetics must be valid computed stats.
4046
x The following aesthetics are invalid:
4147
x `fill = after_stat(data)`
4248
i Did you map your stat in the wrong layer?
4349

4450
# function aesthetics are wrapped with stat()
4551

46-
Aesthetics are not valid data columns.
52+
Problem while computing aesthetics.
53+
i Error occurred in the 1st layer.
54+
Caused by error in `compute_aesthetics()`:
55+
! Aesthetics are not valid data columns.
4756
x The following aesthetics are invalid:
4857
x `colour = NULL`
4958
x `fill = NULL`
5059
i Did you mistype the name of a data column or forget to add `after_stat()`?
5160

5261
# computed stats are in appropriate layer
5362

54-
Aesthetics are not valid computed stats.
63+
Problem while mapping stat to aesthetics.
64+
i Error occurred in the 1st layer.
65+
Caused by error in `map_statistic()`:
66+
! Aesthetics must be valid computed stats.
5567
x The following aesthetics are invalid:
5668
x `colour = NULL`
5769
x `fill = NULL`
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
# position_jitterdodge() fails with meaningful error
22

3-
`position_jitterdodge()` requires at least one aesthetic to dodge by
3+
Problem while computing position.
4+
i Error occurred in the 1st layer.
5+
Caused by error in `setup_params()`:
6+
! `position_jitterdodge()` requires at least one aesthetic to dodge by
47

0 commit comments

Comments
 (0)