Skip to content

Chained errors with contextual info in layers #4856

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

Merged
merged 8 commits into from
May 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions R/ggproto.r
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ fetch_ggproto <- function(x, name) {
return(res)
}

make_proto_method(x, res)
make_proto_method(x, res, name)
}

#' @export
Expand All @@ -163,20 +163,23 @@ fetch_ggproto <- function(x, name) {
return(res)
}

make_proto_method(.subset2(x, "self"), res)
make_proto_method(.subset2(x, "self"), res, name)
}

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

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

class(fun) <- "ggproto_method"
fun
Expand Down
6 changes: 5 additions & 1 deletion R/layer-sf.R
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ layer_sf <- function(geom = NULL, stat = NULL,
position = NULL, params = list(),
inherit.aes = TRUE, check.aes = TRUE, check.param = TRUE,
show.legend = NA) {
call_env <- caller_env()
if (is.character(show.legend)) {
legend_key_type <- show.legend
show.legend <- TRUE
Expand All @@ -21,7 +22,10 @@ layer_sf <- function(geom = NULL, stat = NULL,
}

# inherit from LayerSf class to add `legend_key_type` slot
layer_class <- ggproto(NULL, LayerSf, legend_key_type = legend_key_type)
layer_class <- ggproto(NULL, LayerSf,
constructor = frame_call(call_env),
legend_key_type = legend_key_type
)

layer(
geom = geom, stat = stat, data = data, mapping = mapping,
Expand Down
6 changes: 5 additions & 1 deletion R/layer.r
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,10 @@ layer <- function(geom = NULL, stat = NULL,
# adjust the legend draw key if requested
geom <- set_draw_key(geom, key_glyph)

fr_call <- layer_class$constructor %||% frame_call(call_env)

ggproto("LayerInstance", layer_class,
constructor = fr_call,
geom = geom,
geom_params = geom_params,
stat = stat,
Expand Down Expand Up @@ -169,6 +172,7 @@ validate_mapping <- function(mapping, call = caller_env()) {
}

Layer <- ggproto("Layer", NULL,
constructor = NULL,
geom = NULL,
geom_params = NULL,
stat = NULL,
Expand Down Expand Up @@ -328,7 +332,7 @@ Layer <- ggproto("Layer", NULL,
issues <- paste0("{.code ", nondata_stat_cols, " = ", as_label(aesthetics[[nondata_stat_cols]]), "}")
names(issues) <- rep("x", length(issues))
cli::cli_abort(c(
"Aesthetics are not valid computed stats.",
"Aesthetics must be valid computed stats.",
"x" = "The following aesthetics are invalid:",
issues,
"i" = "Did you map your stat in the wrong layer?"
Expand Down
43 changes: 25 additions & 18 deletions R/plot-build.r
Original file line number Diff line number Diff line change
Expand Up @@ -35,27 +35,19 @@ ggplot_build.ggplot <- function(plot) {
data <- rep(list(NULL), length(layers))

scales <- plot$scales
# Apply function to layer and matching data
by_layer <- function(f) {
out <- vector("list", length(data))
for (i in seq_along(data)) {
out[[i]] <- f(l = layers[[i]], d = data[[i]])
}
out
}

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

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

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

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

# Apply and map statistics
data <- by_layer(function(l, d) l$compute_statistic(d, layout))
data <- by_layer(function(l, d) l$map_statistic(d, plot))
data <- by_layer(function(l, d) l$compute_statistic(d, layout), layers, data, "computing stat")
data <- by_layer(function(l, d) l$map_statistic(d, plot), layers, data, "mapping stat to aesthetics")

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

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

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

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

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

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

# Let Layout modify data before rendering
data <- layout$finish_data(data)
Expand Down Expand Up @@ -168,7 +160,7 @@ ggplot_gtable.ggplot_built <- function(data) {
data <- data$data
theme <- plot_theme(plot)

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

Expand Down Expand Up @@ -419,3 +411,18 @@ ggplot_gtable.ggplot_built <- function(data) {
ggplotGrob <- function(x) {
ggplot_gtable(ggplot_build(x))
}

# Apply function to layer and matching data
by_layer <- function(f, layers, data, step = NULL) {
ordinal <- label_ordinal()
out <- vector("list", length(data))
try_fetch(
for (i in seq_along(data)) {
out[[i]] <- f(l = layers[[i]], d = data[[i]])
},
error = function(cnd) {
cli::cli_abort(c("Problem while {step}.", "i" = "Error occurred in the {ordinal(i)} layer."), call = I(layers[[i]]$constructor), parent = cnd)
}
)
out
}
10 changes: 8 additions & 2 deletions tests/testthat/_snaps/annotate.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
# annotation_raster() and annotation_custom() requires cartesian coordinates

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

---

`annotation_custom()` only works with `coord_cartesian()`
Problem while converting geom to grob.
i Error occurred in the 1st layer.
Caused by error in `draw_panel()`:
! `annotation_custom()` only works with `coord_cartesian()`

# annotation_map() checks the input data

Expand Down
5 changes: 4 additions & 1 deletion tests/testthat/_snaps/geom-.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# aesthetic checking in geom throws correct errors

Aesthetic modifiers returned invalid values
Problem while setting up geom aesthetics.
i Error occurred in the 1st layer.
Caused by error in `use_defaults()`:
! Aesthetic modifiers returned invalid values
x The following mappings are invalid
x `colour = after_scale(data)`
i Did you map the modifier in the wrong layer?
Expand Down
4 changes: 2 additions & 2 deletions tests/testthat/_snaps/geom-dotplot.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
# weight aesthetic is checked

Computation failed in `stat_bindot()`
Caused by error in `f()`:
Caused by error in `compute_group()`:
! Weights must be nonnegative integers.

---

Computation failed in `stat_bindot()`
Caused by error in `f()`:
Caused by error in `compute_group()`:
! Weights must be nonnegative integers.

5 changes: 4 additions & 1 deletion tests/testthat/_snaps/geom-linerange.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
# geom_linerange request the right aesthetics

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

5 changes: 4 additions & 1 deletion tests/testthat/_snaps/geom-path.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
# geom_path() throws meaningful error on bad combination of varying aesthetics

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

5 changes: 4 additions & 1 deletion tests/testthat/_snaps/geom-raster.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,8 @@

---

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

15 changes: 12 additions & 3 deletions tests/testthat/_snaps/geom-ribbon.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
# geom_ribbon() checks the aesthetics

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

---

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

---

Aesthetics can not vary along a ribbon
Problem while converting geom to grob.
i Error occurred in the 1st layer.
Caused by error in `draw_group()`:
! Aesthetics can not vary along a ribbon

---

Expand Down
5 changes: 4 additions & 1 deletion tests/testthat/_snaps/geom-sf.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# errors are correctly triggered

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

---

Expand Down
10 changes: 8 additions & 2 deletions tests/testthat/_snaps/geom-violin.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
# quantiles fails outside 0-1 bound

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

---

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

20 changes: 10 additions & 10 deletions tests/testthat/_snaps/guides.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,3 @@
# binning scales understand the different combinations of limits, breaks, labels, and show.limits

`show.limits` is ignored when `labels` are given as a character vector
i Either add the limits to `breaks` or provide a function for `labels`

---

`show.limits` is ignored when `labels` are given as a character vector
i Either add the limits to `breaks` or provide a function for `labels`

# axis_label_element_overrides errors when angles are outside the range [0, 90]

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

# binning scales understand the different combinations of limits, breaks, labels, and show.limits

`show.limits` is ignored when `labels` are given as a character vector
i Either add the limits to `breaks` or provide a function for `labels`

---

`show.limits` is ignored when `labels` are given as a character vector
i Either add the limits to `breaks` or provide a function for `labels`

# a warning is generated when guides(<scale> = FALSE) is specified

`guide = FALSE` is deprecated
Expand Down
20 changes: 16 additions & 4 deletions tests/testthat/_snaps/layer.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,29 +29,41 @@

# invalid aesthetics throws errors

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

---

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

# function aesthetics are wrapped with stat()

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

# computed stats are in appropriate layer

Aesthetics are not valid computed stats.
Problem while mapping stat to aesthetics.
i Error occurred in the 1st layer.
Caused by error in `map_statistic()`:
! Aesthetics must be valid computed stats.
x The following aesthetics are invalid:
x `colour = NULL`
x `fill = NULL`
Expand Down
5 changes: 4 additions & 1 deletion tests/testthat/_snaps/position-jitterdodge.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
# position_jitterdodge() fails with meaningful error

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

Loading