Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
07f3835
This fixes issue #234 where coord_equal() was producing unnecessarily
ANAMASGARD Oct 4, 2025
ab19433
Improve tests for coord_equal fix (issue #234)
ANAMASGARD Oct 6, 2025
7b53778
some test code
Oct 15, 2025
208161c
test ratio greater than 2
Oct 15, 2025
f1b8aac
Merge branch 'coord_equal-issue-#234' of https://github.com/ANAMASGAR…
Oct 15, 2025
b29d411
rm info
Oct 15, 2025
680957f
Merge branch 'master' into test-coord_equal
ANAMASGARD Oct 15, 2025
5fcd6a1
Merge branch 'master' into test-coord_equal
ANAMASGARD Oct 22, 2025
9ac872b
Merge branch 'master' into test-coord_equal
ANAMASGARD Oct 23, 2025
b88d304
Merge branch 'master' into test-coord_equal
ANAMASGARD Nov 13, 2025
7b02aa4
Merge branch 'master' into test-coord_equal
ANAMASGARD Nov 27, 2025
c104a55
Merge branch 'master' into test-coord_equal
ANAMASGARD Mar 11, 2026
6277a93
Fix coord_equal shrinking (issue #234) and adjust test tolerances
ANAMASGARD Mar 11, 2026
929e42e
Merge branch 'master' into test-coord_equal
ANAMASGARD May 18, 2026
56a52eb
Merge branch 'master' into test-coord_equal
ANAMASGARD May 26, 2026
109e061
Merge branch 'master' into test-coord_equal
ANAMASGARD May 29, 2026
608ae1c
fix(ci): update atime Slow/Fast SHAs to installable commits
ANAMASGARD May 29, 2026
bdcbf3f
fix(ci): restore atime #238 getCommonChunk baseline SHAs
ANAMASGARD May 29, 2026
707738a
fix(ci): simplify atime #238 test config and stub old geom_dotplot Rd
ANAMASGARD May 29, 2026
6120537
Reverted back to previous values
ANAMASGARD May 29, 2026
39369a8
fix(test): align coord_equal renderer test with issue #234 scope
ANAMASGARD Jun 11, 2026
ff3b03d
Merge branch 'master' into test-coord_equal
ANAMASGARD Jun 12, 2026
2f50368
Merge branch 'master' into test-coord_equal
ANAMASGARD Jun 29, 2026
a601fb0
fix: coord_equal scale-to-fit for non-square viewports (#234)
ANAMASGARD Jul 29, 2026
31f7dae
Merge branch 'master' into test-coord_equal
ANAMASGARD Jul 30, 2026
cae57f6
docs(test): address tdhock NEWS and width/height questions
ANAMASGARD Jul 30, 2026
08a7939
Merge branch 'master' into test-coord_equal
ANAMASGARD Aug 14, 2026
c99e59f
Merge branch 'master' into test-coord_equal
ANAMASGARD Aug 20, 2026
6818162
Simplifed coord_equal scale-to-fit row/column loops.
ANAMASGARD Aug 20, 2026
81ca7ec
Fix CI flake in coord_fixed shrinking x-axis test.
ANAMASGARD Aug 20, 2026
c6aed56
Merge branch 'master' into test-coord_equal
ANAMASGARD Aug 25, 2026
99547e8
Isolate JS_coverage GitHub Pages test repository
ANAMASGARD Aug 26, 2026
8cf2681
Merge branch 'master' into test-coord_equal
ANAMASGARD Sep 4, 2026
abd7bcd
Extract max_group_sum for coord scale-to-fit; fix NEWS version header.
ANAMASGARD Sep 4, 2026
fa7f131
Merge branch 'master' into test-coord_equal
ANAMASGARD Sep 12, 2026
99ad2e6
Merge branch 'master' into test-coord_equal
ANAMASGARD Sep 28, 2026
d4dfaca
Merge branch 'master' into test-coord_equal
ANAMASGARD Sep 28, 2026
d6353ff
Merge branch 'master' into test-coord_equal
ANAMASGARD Sep 28, 2026
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.28
Version: 2026.9.5
URL: https://animint.github.io/animint2
BugReports: https://github.com/animint/animint2/issues
Authors@R: c(
Expand Down
8 changes: 2 additions & 6 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
# Changes in version 2026.9.29 (PR#288)
# Changes in version 2026.7.29 (PR#253)

- Plot SVGs use `display: block` (issue #279). Each ggplot is one SVG. Browsers treat an SVG as inline and leave space under it for text descenders, which is the extra whitespace below a facet_wrap plot. That gap is under the SVG, not between facet panels. This is required; without it the rendered gap is about 5px. Thanks @ANAMASGARD.

# 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).
- Fixed `coord_equal()` and `coord_fixed()` to properly fill available plotting space. Issue #234 showed the problem when increasing plot width (800 vs default 400); the same JavaScript bug also affected tall viewports when height should fill. Previously `Math.min(1, aspect)` was applied independently to width and height proportions, shrinking both when the graph pixel aspect did not match the data aspect. The renderer now adjusts panel proportions for non-square graph cells and scale-to-fits so at least one dimension fills the available space while preserving the data aspect ratio.

# Changes in version 2026.9.5

Expand Down
30 changes: 1 addition & 29 deletions R/facet-layout.r
Original file line number Diff line number Diff line change
Expand Up @@ -48,20 +48,6 @@ 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)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

can you please look at Files changed tab before asking me to review again?

this section, and another below, delete error messages about facets, unrelated to coord_equal.
please undo or explain?

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 @@ -107,22 +93,8 @@ 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(lapply(values, length)) == length(vars)
has_all <- unlist(plyr::llply(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
3 changes: 3 additions & 0 deletions R/z_facets.R
Original file line number Diff line number Diff line change
Expand Up @@ -150,5 +150,8 @@ fixed_spaces <- function(ranges, ratio = 1) {
function(z) diff(z$y.range) / diff(z$x.range) * ratio)
spaces <- list(y = aspect)
spaces$x <- 1/spaces$y
# Clamp each proportion to at most 1. Desired data aspect is spaces$y/spaces$x;
# the JS renderer then scale-to-fits these proportions into the available
# (possibly non-square) graph pixel space so at least one side fills.
lapply(spaces, function(z) min(z, 1))
}
35 changes: 26 additions & 9 deletions inst/htmljs/animint.js
Original file line number Diff line number Diff line change
Expand Up @@ -401,9 +401,7 @@ var setMultilineText = function(textElement, text) {
var svg = tdLeft.append("svg")
.attr("id", p_info.plot_id)
.attr("height", p_info.options.height)
.attr("width", p_info.options.width)
// block avoids inline SVG baseline/descender gap below the plot (issue #279)
.style("display", "block");
.attr("width", p_info.options.width);

// divvy up width/height based on the panel layout
var nrows = Math.max.apply(null, p_info.layout.ROW);
Expand Down Expand Up @@ -540,6 +538,14 @@ var setMultilineText = function(textElement, text) {
}
return cumsum;
}
function max_group_sum(group, values){
var sums = [];
for(var i=0; i<values.length; i++){
var g = group[i] - 1;
sums[g] = (sums[g] || 0) + values[i];
}
return d3.max(sums) || 0;
}
var cum_height_per_row = cumsum_array(row_strip_heights);
var cum_width_per_col = cumsum_array(col_strip_widths);
var strip_width = d3.max(cum_width_per_col);
Expand All @@ -559,17 +565,28 @@ var setMultilineText = function(textElement, text) {
// proportions calculated by the compiler. This has to be done on the
// rendering side since the precomputed proportions apply to the *graph*
// and the graph size depends upon results of measureText()
//
// R fixed_spaces() gives proportions for a square panel cell. Available
// cells may be non-square; aspect is cell_height/cell_width in pixels.
// Dividing heights by aspect makes panel pixel aspect match the data
// aspect (issue #234). Then scale so the largest row/column still fits.
if (p_info.layout.coord_fixed[0]) {
var aspect = (graph_height / nrows) / (graph_width / ncols);
} else {
var aspect = 1;
}
var wp = p_info.layout.width_proportion.map(function(x){
return x * Math.min(1, aspect);
})
var hp = p_info.layout.height_proportion.map(function(x){
return x * Math.min(1, 1/aspect);
})
var wp = p_info.layout.width_proportion.slice();
var hp = p_info.layout.height_proportion.map(function(y){
return y / aspect;
});
var fit_prop = Math.max(
max_group_sum(p_info.layout.ROW, wp),
max_group_sum(p_info.layout.COL, hp)
);
if (fit_prop > 0) {
wp = wp.map(function(x) { return x / fit_prop; });
Comment on lines +578 to +587

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

this is repetitive, please simplify

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

for loop over ROW and COL?

hp = hp.map(function(y) { return y / fit_prop; });
}

// track the proportion of the graph that should be 'blank'
// this is mainly used to implement coord_fixed()
Expand Down
19 changes: 19 additions & 0 deletions tests/testthat/test-compiler-coord-equal-fix.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
acontext("coord_equal fix for issue #234")

test_that("fixed_spaces keeps at least one dimension filling space", {
# Test with iris petal data ranges (issue #234 example)
ranges <- list(list(x.range = c(1, 7), y.range = c(0, 2.5)))
spaces <- animint2:::fixed_spaces(ranges, ratio = 1)

# At least one dimension should be 1 (fills available space on a square cell)
max_prop <- max(spaces$x, spaces$y)
expect_equal(max_prop, 1)

# Both proportions should be positive and <= 1
expect_gt(spaces$x, 0)
expect_lte(spaces$x, 1)
expect_gt(spaces$y, 0)
expect_lte(spaces$y, 1)
# Data aspect y/x preserved for the JS scale-to-fit step
expect_equal(spaces$y / spaces$x, 2.5 / 6)
})
24 changes: 1 addition & 23 deletions tests/testthat/test-compiler-ghpages.R
Original file line number Diff line number Diff line change
Expand Up @@ -51,28 +51,6 @@ 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 @@ -118,7 +96,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")
clone_test_repo(github_url, new_clone)
gert::git_clone(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
43 changes: 0 additions & 43 deletions tests/testthat/test-issue-279-facet-wrap-custom-height-spacing.R

This file was deleted.

49 changes: 49 additions & 0 deletions tests/testthat/test-renderer-coord_equal.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
acontext("coord_equal renderer tests")

test_that("coord_equal fills available space (issue #234)", {
# Create the iris petal plot from issue #234
# This test ensures coord_equal() doesn't shrink the plot unnecessarily
library(data.table)
data.mat <- as.matrix(iris[,c("Petal.Width","Petal.Length")])

# Create the visualization with a specific size
viz <- animint(
plot1=ggplot(data=data.table(data.mat), aes(Petal.Length, Petal.Width))+
geom_point()+
coord_equal()+
theme_animint(width=500, height=500)
)

# Render the plot using animint2HTML (not animint2dir)
info <- animint2HTML(viz)

# Extract SVG plotting area dimensions
# Get the SVG element to check overall plot dimensions
svg.nodes <- getNodeSet(info$html, '//svg[@id="plot_plot1"]')
expect_equal(length(svg.nodes), 1)
svg.attrs <- xmlAttrs(svg.nodes[[1]])

# Extract width and height attributes
svg.width <- as.numeric(svg.attrs[["width"]])
svg.height <- as.numeric(svg.attrs[["height"]])

# The key test: with coord_equal(), at least one dimension should
# fill the available space (close to 500px as specified in theme_animint)
# On the old buggy code (before the fix in commit e4b9634b), BOTH dimensions
# would be unnecessarily shrunk below the available space.
# With the fix, at least one dimension uses nearly all available space.

# The iris petal data has aspect ratio of ~0.417 (y_range=2.5, x_range=6)
# So with coord_equal (ratio=1), the x-axis fills space (width≈500)
# and y-axis is constrained by aspect ratio (height≈200)

# Test that at least ONE dimension is close to requested size (500)
max_dimension <- max(svg.width, svg.height)
expect_gt(max_dimension, 450)

# Both dimensions should be positive
expect_gt(svg.width, 0)
expect_gt(svg.height, 0)
# Issue #234 acceptance: fill available space. Aspect-ratio math is covered by
# test-compiler-coord-equal-fix.R and test-renderer1-coord.R (normDiffs).
})
79 changes: 0 additions & 79 deletions tests/testthat/test-renderer-facet-error-messages.R

This file was deleted.

Loading
Loading