Skip to content
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
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Package: animint2
Title: Animated Interactive Grammar of Graphics
Version: 2026.9.5
Version: 2026.9.28
URL: https://animint.github.io/animint2
BugReports: https://github.com/animint/animint2/issues
Authors@R: c(
Expand Down
4 changes: 4 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# Changes in version 2026.9.28 (PR#285)

- Clearer facet error messages when a facet variable is missing from the data, or when `facet_wrap(. ~ var)` uses invalid notation (issue #168).

# Changes in version 2026.9.5

- geom_text() renders with .text(), instead of setMultilineText(), fixing interactive updates.
Expand Down
30 changes: 29 additions & 1 deletion R/facet-layout.r
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,20 @@ layout_wrap <- function(data, vars = NULL, nrow = NULL, ncol = NULL,
vars <- as.quoted(vars)
if (length(vars) == 0) return(layout_null())

# Notation error only when real vars exist; otherwise layout_base reports missing columns.
if ("." %in% names(vars)) {
real_vars <- names(vars)[names(vars) != "."]
all_data_vars <- unique(unlist(lapply(data, names)))
missing_vars <- setdiff(real_vars, all_data_vars)
if (length(missing_vars) == 0) {
stop(sprintf(
"Invalid facet_wrap formula (. ~ var). Use facet_wrap(~%s) or facet_wrap(\"%s\")",
paste(real_vars, collapse = " + "),
paste(real_vars, collapse = "\", \"")
), call. = FALSE)
}
}

base <- plyr::unrowname(layout_base(data, vars, drop = drop))

id <- plyr::id(base, drop = TRUE)
Expand Down Expand Up @@ -93,8 +107,22 @@ layout_base <- function(data, vars = NULL, drop = TRUE) {

# Form the base data frame which contains all combinations of facetting
# variables that appear in the data
has_all <- unlist(plyr::llply(values, length)) == length(vars)
has_all <- unlist(lapply(values, length)) == length(vars)
if (!any(has_all)) {
all_data_vars <- unique(unlist(lapply(data, names)))
missing_vars <- setdiff(names(vars)[names(vars) != "."], all_data_vars)
if (length(missing_vars) > 0) {
stop(sprintf(
"Facet variable%s not found in data: %s\nAvailable columns: %s",
if (length(missing_vars) > 1) "s" else "",
paste(missing_vars, collapse = ", "),
paste(all_data_vars, collapse = ", ")
))
}
# Reached when all facet variables exist somewhere in the data, but no
# single layer has all of them together. For example, facet_grid(X ~ Y)
# where layer 1 only has column X and layer 2 only has column Y — neither
# layer alone satisfies all facetting variables, so has_all is FALSE for every layer.
stop("At least one layer must contain all variables used for facetting")
}

Expand Down
24 changes: 23 additions & 1 deletion tests/testthat/test-compiler-ghpages.R
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,28 @@ reset_test_repo <- function(){
gh::gh("POST /orgs/animint-test/repos", name = test_repo_name)
Sys.sleep(3)
}
clone_test_repo <- function(github_url, new_clone){
max_attempts <- 5
wait_seconds <- 3
last_error <- NULL
for (attempt in seq_len(max_attempts)) {
result <- tryCatch({
clone <- gert::git_clone(github_url, new_clone)
last_error <- NULL
clone
}, error = function(err){
last_error <<- err
NULL
})
if (is.null(last_error)) return(result)
if (!grepl("status code: 404", conditionMessage(last_error), fixed = TRUE)) {
stop(last_error)
}
unlink(new_clone, recursive = TRUE, force = TRUE)
if (attempt < max_attempts) Sys.sleep(wait_seconds)
}
stop(last_error)
}
test_that("animint2pages(chromote_sleep_seconds=3) creates Capture.PNG", {
reset_test_repo()
## first run of animint2pages creates new data viz.
Expand Down Expand Up @@ -96,7 +118,7 @@ test_that("animint2pages(chromote_sleep_seconds=NULL) does not create Capture.PN
## clone and add Capture.PNG
new_clone <- tempfile()
github_url <- paste0("https://github.com/", result_list$owner_repo, ".git")
gert::git_clone(github_url, new_clone)
clone_test_repo(github_url, new_clone)
branch_name <- gert::git_branch(new_clone)
expect_identical(branch_name, "gh-pages", info=paste(branch_name, "should be gh-pages"))
cat("FOO", file=file.path(new_clone, "Capture.PNG"))
Expand Down
79 changes: 79 additions & 0 deletions tests/testthat/test-renderer-facet-error-messages.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
acontext("Facet error messages")
missing_var_error <- paste(
"Facet variable not found in data: NonExistentColumn",
"Available columns: Sepal.Length, Sepal.Width, Petal.Length, Petal.Width, Species",
sep = "\n"
)
missing_var_string_error <- paste(
"Facet variable not found in data: MissingVar",
"Available columns: Sepal.Length, Sepal.Width, Petal.Length, Petal.Width, Species",
sep = "\n"
)
bad_notation_error <- "Invalid facet_wrap formula (. ~ var). Use facet_wrap(~Species) or facet_wrap(\"Species\")"
test_that("facet_wrap string missing variable gives clear error", {
viz <- list(
scatter = ggplot() +
facet_wrap("MissingVar") +
geom_point(aes(Sepal.Length, Petal.Length), data = iris)
)
expect_error({
animint2dir(viz)
}, missing_var_string_error, fixed = TRUE)
})
test_that("facet_wrap formula with missing variable gives clear error", {
viz <- list(
scatter = ggplot() +
facet_wrap(. ~ NonExistentColumn) +
Comment thread
ANAMASGARD marked this conversation as resolved.
geom_point(aes(Sepal.Length, Petal.Length), data = iris)
)
expect_error({
animint2dir(viz)
}, missing_var_error, fixed = TRUE)
})
test_that("facet_wrap bad notation when variable exists gives clear error", {
viz <- list(
scatter = ggplot() +
facet_wrap(. ~ Species) +
geom_point(aes(Sepal.Length, Petal.Length), data = iris)
)
expect_error({
animint2dir(viz)
}, bad_notation_error, fixed = TRUE)
})
test_that("facet_grid missing variable gives clear error", {
viz <- list(
scatter = ggplot() +
facet_grid(. ~ MissingVar) +
geom_point(aes(Sepal.Length, Petal.Length), data = iris)
)
expect_error({
animint2dir(viz)
}, missing_var_string_error, fixed = TRUE)
})
test_that("facet_wrap tilde notation works", {
viz <- list(
scatter = ggplot() +
facet_wrap(~Species) +
geom_point(aes(Sepal.Length, Petal.Length), data = iris)
)
info <- animint2dir(viz, open.browser = FALSE)
expect_true(file.exists(file.path(info$out.dir, "index.html")))
})
test_that("facet_wrap string notation works", {
viz <- list(
scatter = ggplot() +
facet_wrap("Species") +

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this work or error?

Suggested change
facet_wrap("Species") +
facet_wrap(~Species) +

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

???

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tdhock yes Sir it works. facet_wrap(~Species) is valid (same as "Species"). The error is only for facet_wrap(. ~ Species). Covered in the "facet_wrap tilde notation works" test.

geom_point(aes(Sepal.Length, Petal.Length), data = iris)
)
info <- animint2dir(viz, open.browser = FALSE)
expect_true(file.exists(file.path(info$out.dir, "index.html")))
})
test_that("facet_grid formula notation works", {
viz <- list(
scatter = ggplot() +
facet_grid(. ~ Species) +
geom_point(aes(Sepal.Length, Petal.Length), data = iris)
)
info <- animint2dir(viz, open.browser = FALSE)
expect_true(file.exists(file.path(info$out.dir, "index.html")))
})
1 change: 1 addition & 0 deletions tests/testthat/test-renderer3-knit-print.R
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ click_center <- function(id){
## https://github.com/rstudio/chromote/issues/32
do.call(remDr$Input$dispatchMouseEvent, L)
}
Sys.sleep(1)
}

djs.init.list <- driverjs_get(html)
Expand Down
Loading