Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
061d8dc
Add test for download status table size columns
ANAMASGARD Oct 29, 2025
0316abd
Now using grep() and expect_gt() for informative test failures
ANAMASGARD Oct 29, 2025
038edd1
Added the download size info to status table
ANAMASGARD Oct 30, 2025
a1721ff
Merge branch 'master' into add-download-size-info
ANAMASGARD Oct 31, 2025
1c016bc
Calculate chunk sizes in R, display in download table
ANAMASGARD Oct 31, 2025
5d33585
Bump version to 2025.10.31 and add NEWS entry for PR#272
ANAMASGARD Oct 31, 2025
5419abf
Merge branch 'master' into add-download-size-info
ANAMASGARD Oct 31, 2025
d5e661d
Use spaces in column headers: 'total MB' and 'mean MB'
ANAMASGARD Oct 31, 2025
bf360fa
Add download size info to status table (#249)
ANAMASGARD Oct 31, 2025
073d7b7
Merge branch 'master' into add-download-size-info
ANAMASGARD Nov 9, 2025
81cd1e1
Fix MB calculation inconsistency in download table
ANAMASGARD Nov 10, 2025
db9139b
Update download table tests for new files column
ANAMASGARD Nov 11, 2025
d8aa60b
Merge branch 'master' into add-download-size-info
ANAMASGARD Nov 27, 2025
24919ed
fix download status table: add id attribute and initialize display va…
ANAMASGARD Nov 29, 2025
bf35913
Merge branch 'master' into add-download-size-info
ANAMASGARD Dec 3, 2025
46af47e
download status table: clean up tests and whitespace only
ANAMASGARD Dec 3, 2025
ab008f7
Merge branch 'master' into add-download-size-info
ANAMASGARD Dec 5, 2025
53d1ad9
fix duplicate ID issue for knit-print compatibility
ANAMASGARD Dec 10, 2025
4b10010
Update version to 2025.12.4 for PR#272
ANAMASGARD Dec 10, 2025
52c7d6c
improve download status table: commas for rows, KiB/MiB for disk sizes
ANAMASGARD Dec 12, 2025
ae45eb7
Trigger CI re-run
ANAMASGARD Dec 15, 2025
bc12ff8
refactor download status table: add helper functions and content test
ANAMASGARD Dec 15, 2025
65d5ffe
fix regex patterns in download status table test
ANAMASGARD Dec 15, 2025
69bc4c7
remove empty lines and add table borders
ANAMASGARD Dec 16, 2025
a44e994
added applyCellStyles helper to avoid style repetition
ANAMASGARD Dec 19, 2025
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: 2025.12.3
Version: 2025.12.4
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
Expand Up @@ -10,6 +10,10 @@

- `update_axes`: Fixed issue #273 where axis tick text font-size was inconsistent between plots with and without `update_axes`. Previously, plots using `theme_animint(update_axes="x")` would lose `theme(axis.text = element_text(size=...))` styling after axis updates.

# Changes in version 2025.12.4 (PR#272)

- Download status table now shows `files`, `disk`, and `rows` columns in "downloaded / total" format. Row counts display with comma separators for readability. Disk sizes show KiB or MiB units (using binary 1024 divisor, consistent with `man du`). Chunk sizes are calculated in R using `file.size()` and exported via plot.json.

# Changes in version 2025.10.31 (PR#271)

- `geom_point()` now warns when shape parameter is set to a value other than 21, since animint2 web rendering only supports shape=21 for proper display of both color and fill aesthetics.
Expand Down
8 changes: 8 additions & 0 deletions R/geom-.r
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,14 @@ Geom <- gganimintproto("Geom",
data.table::fwrite(
data.or.null$common, file = tsv.path,
row.names = FALSE, sep = "\t")
# Track common chunk size and rows
if(!exists("chunk_info", envir=meta)) {
meta$chunk_info <- list()
}
meta$chunk_info[[tsv.name]] <- list(
bytes = file.size(tsv.path),
rows = nrow(data.or.null$common)
)
data.or.null$varied
}
list(g=g, g.data.varied=g.data.varied, timeValues=AnimationInfo$timeValues)
Expand Down
13 changes: 13 additions & 0 deletions R/z_animint.R
Original file line number Diff line number Diff line change
Expand Up @@ -212,8 +212,21 @@ storeLayer <- function(meta, g, g.data.varied){
## Save each variable chunk to a separate tsv file.
meta$chunk.i <- 1L
meta$g <- g
# Initialize chunk_info only if it doesn't exist (common chunk may have been saved)
if(!exists("chunk_info", envir=meta)) {
meta$chunk_info <- list()
}
g$chunks <- saveChunks(g.data.varied, meta)
g$total <- length(unlist(g$chunks))

## Add chunk size information to geom - filter to only this geom's chunks
g$chunk_info <- list()
geom_prefix <- paste0(g$classed, "_chunk")
for(chunk_name in names(meta$chunk_info)) {
if(startsWith(chunk_name, geom_prefix)) {
g$chunk_info[[chunk_name]] <- meta$chunk_info[[chunk_name]]
}
}

## Finally save to the master geom list.
meta$geoms[[g$classed]] <- g
Expand Down
14 changes: 13 additions & 1 deletion R/z_animintHelpers.R
Original file line number Diff line number Diff line change
Expand Up @@ -917,9 +917,21 @@ saveChunks <- function(x, meta){
# fwrite defaults ensure fields are quoted so that embedded
# newlines or tabs in string fields do not break the TSV format
# when read by d3.tsv.
csv.path <- file.path(meta$out.dir, csv.name)
data.table::fwrite(
na.omit(x), file.path(meta$out.dir, csv.name),
na.omit(x), csv.path,
row.names=FALSE, sep="\t")
# Calculate chunk size and row count
chunk_bytes <- file.size(csv.path)
chunk_rows <- nrow(na.omit(x))
# Store chunk info
if(!exists("chunk_info", envir=meta)) {
meta$chunk_info <- list()
}
meta$chunk_info[[csv.name]] <- list(
bytes = chunk_bytes,
rows = chunk_rows
)
meta$chunk.i <- meta$chunk.i + 1L
this.i
}else if(is.list(x)){
Expand Down
103 changes: 90 additions & 13 deletions inst/htmljs/animint.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,55 @@ var animint = function (to_select, json_file) {
var default_axis_px = 16;
var grid_layout = false;
var grid_layout_table;

// Helper function to format numbers with commas (e.g., 4321 -> "4,321")
function formatWithCommas(num) {
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}

// Helper function to format bytes as KiB or MiB with appropriate precision
// Uses binary units (1024) consistent with "man du" documentation
function formatBytes(bytes) {
if (bytes === 0) return "0";
var kib = bytes / 1024;
if (kib < 1024) {
// Less than 1 MiB, show in KiB
if (kib < 10) {
return kib.toFixed(2) + " KiB";
} else if (kib < 100) {
return kib.toFixed(1) + " KiB";
} else {
return Math.round(kib) + " KiB";
}
} else {
// 1 MiB or more, show in MiB
var mib = kib / 1024;
return mib.toFixed(2) + " MiB";
}
}

// Helper function to apply consistent border and padding styles to table cells
function applyCellStyles(cell) {
return cell.style("border", "1px solid #ddd").style("padding", "4px");
}

// Helper function to update download status display after a chunk is downloaded
// Used by both common chunk and regular chunk download handlers
function updateDownloadStatus(g_info, tsv_name) {
if(g_info.chunk_info && g_info.chunk_info[tsv_name]){
var info = g_info.chunk_info[tsv_name];
g_info.total_bytes += info.bytes;
g_info.total_rows += info.rows;
g_info.downloaded_chunks += 1;
// Update display with "downloaded / total" format
var downloaded_count = g_info.downloaded_chunks;
var total_count = g_info.total_possible_chunks;
g_info.td_files.text(downloaded_count + " / " + total_count);
g_info.td_disk.text(formatBytes(g_info.total_bytes) + " / " + formatBytes(g_info.possible_bytes));
g_info.td_rows.text(formatWithCommas(g_info.total_rows) + " / " + formatWithCommas(g_info.possible_rows));
}
}

function wait_until_then(timeout, condFun, readyFun) {
var args=arguments
function checkFun() {
Expand Down Expand Up @@ -224,11 +273,35 @@ var animint = function (to_select, json_file) {
}
// Add a row to the loading table.
g_info.tr = Widgets["loading"].append("tr");
g_info.tr.append("td").text(g_name);
g_info.tr.append("td").attr("class", "chunk");
g_info.tr.append("td").attr("class", "downloaded").text(0);
g_info.tr.append("td").text(g_info.total);
g_info.tr.append("td").attr("class", "status").text("initialized");
applyCellStyles(g_info.tr.append("td").text(g_name));
g_info.td_files = applyCellStyles(g_info.tr.append("td").attr("class", "files").style("text-align", "right"));
g_info.td_disk = applyCellStyles(g_info.tr.append("td").attr("class", "disk").style("text-align", "right"));
g_info.td_rows = applyCellStyles(g_info.tr.append("td").attr("class", "rows").style("text-align", "right"));
// Initialize size tracking
g_info.total_bytes = 0;
g_info.total_rows = 0;
g_info.downloaded_chunks = 0;
// Calculate total possible bytes and rows from chunk_info
g_info.possible_bytes = 0;
g_info.possible_rows = 0;
g_info.total_possible_chunks = g_info.total;
if(g_info.chunk_info){
var tsv_count = 0;
for(var chunk_name in g_info.chunk_info){
if(chunk_name.endsWith('.tsv')){
g_info.possible_bytes += g_info.chunk_info[chunk_name].bytes;
g_info.possible_rows += g_info.chunk_info[chunk_name].rows;
tsv_count++;
}
}
// chunk_info includes the common chunk, so total_possible_chunks should include it
g_info.total_possible_chunks = tsv_count;
}

// Set initial display values
g_info.td_files.text("0 / " + g_info.total_possible_chunks);
g_info.td_disk.text("0 / " + formatBytes(g_info.possible_bytes));
g_info.td_rows.text("0 / " + formatWithCommas(g_info.possible_rows));

// load chunk tsv
g_info.data = {};
Expand All @@ -243,6 +316,8 @@ var animint = function (to_select, json_file) {
d3.tsv(common_path, function (error, response) {
var converted = convert_R_types(response, g_info.types);
g_info.data[common_tsv] = nest_by_group.map(converted);
// Track common chunk download for size information
updateDownloadStatus(g_info, common_tsv);
});
} else {
g_info.common_tsv = null;
Expand Down Expand Up @@ -993,8 +1068,9 @@ var animint = function (to_select, json_file) {
});
var chunk = nest.map(response);
g_info.data[tsv_name] = chunk;
g_info.tr.select("td.downloaded").text(d3.keys(g_info.data).length);
g_info.download_status[tsv_name] = "saved";
// Update size information after download
updateDownloadStatus(g_info, tsv_name);
funAfter(chunk);
});
});
Expand All @@ -1003,7 +1079,6 @@ var animint = function (to_select, json_file) {
// update_geom is responsible for obtaining a chunk of downloaded
// data, and then calling draw_geom to actually draw it.
var draw_geom = function(g_info, chunk, selector_name, PANEL){
g_info.tr.select("td.status").text("displayed");
var svg = SVGs[g_info.classed];
// derive the plot name from the geometry name
var g_names = g_info.classed.split("_");
Expand Down Expand Up @@ -2381,14 +2456,16 @@ var animint = function (to_select, json_file) {
}
});
var loading = widget_td.append("table")
.style("display", "none");
.attr("id", viz_id + "_download_status")
.style("display", "none")
.style("border-collapse", "collapse")
.style("border", "1px solid #ddd");
Widgets["loading"] = loading;
var tr = loading.append("tr");
tr.append("th").text("geom");
tr.append("th").attr("class", "chunk").text("selected chunk");
tr.append("th").attr("class", "downloaded").text("downloaded");
tr.append("th").attr("class", "total").text("total");
tr.append("th").attr("class", "status").text("status");
applyCellStyles(tr.append("th").text("geom"));
applyCellStyles(tr.append("th").attr("class", "files").style("text-align", "right")).text("files");
applyCellStyles(tr.append("th").attr("class", "disk").style("text-align", "right")).text("disk");
applyCellStyles(tr.append("th").attr("class", "rows").style("text-align", "right")).text("rows");

// Add geoms and construct nest operators.
for (var g_name in response.geoms) {
Expand Down
42 changes: 42 additions & 0 deletions tests/testthat/test-download-status-table.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
acontext("download status table")
viz <- animint(
ggplot()+
geom_point(aes(Sepal.Length, Sepal.Width), data=iris)
)
info <- animint2HTML(viz)
test_that("table has correct column headers", {
table_headers <- getNodeSet(info$html, '//table[contains(@id,"_download_status")]//th')
header_text <- sapply(table_headers, xmlValue)
expected <- c("geom", "files", "disk", "rows")
expect_true(all(expected %in% header_text))
})
test_that("numeric columns are right-justified", {
files_header <- getNodeSet(info$html, '//table[contains(@id,"_download_status")]//th[@class="files"]')
disk_header <- getNodeSet(info$html, '//table[contains(@id,"_download_status")]//th[@class="disk"]')
rows_header <- getNodeSet(info$html, '//table[contains(@id,"_download_status")]//th[@class="rows"]')
expect_equal(length(files_header), 1)
expect_equal(length(disk_header), 1)
expect_equal(length(rows_header), 1)
expect_match(xmlGetAttr(files_header[[1]], "style"), "text-align.*right")
expect_match(xmlGetAttr(disk_header[[1]], "style"), "text-align.*right")
expect_match(xmlGetAttr(rows_header[[1]], "style"), "text-align.*right")
})
test_that("download status table displays correct content format", {
# Get all table cells from the download status table
table_cells <- getNodeSet(info$html, '//table[contains(@id,"_download_status")]//td')
cell_text <- sapply(table_cells, xmlValue)
# Should have cells for: geom name, files (1 / 1), disk (KiB/MiB), rows (with commas)
# Files column should show "downloaded / total" format
files_pattern <- "^[0-9]+ / [0-9]+$"
files_cells <- grep(files_pattern, cell_text, value = TRUE)
expect_true(length(files_cells) > 0, "Should have files column with 'downloaded / total' format")
# Disk column should show bytes with KiB or MiB units
disk_pattern <- "[0-9]+(\\.[0-9]+)? (KiB|MiB) / [0-9]+(\\.[0-9]+)? (KiB|MiB)"
disk_cells <- grep(disk_pattern, cell_text, value = TRUE)
expect_true(length(disk_cells) > 0, "Should have disk column with KiB/MiB units")
# Rows column should show numbers (may have commas for large numbers)
# Pattern allows digits with optional commas: 150 or 10,066
rows_pattern <- "^[0-9,]+ / [0-9,]+$"
rows_cells <- grep(rows_pattern, cell_text, value = TRUE)
expect_true(length(rows_cells) > 0, "Should have rows column with numeric format")
})
6 changes: 4 additions & 2 deletions tests/testthat/test-renderer1-variable-value.R
Original file line number Diff line number Diff line change
Expand Up @@ -269,8 +269,10 @@ test_that("Widgets for regular selectors", {

chunk.counts <- function(html=getHTML()){
node.set <-
getNodeSet(html, '//td[@class="downloaded"]')
as.integer(sapply(node.set, xmlValue))
getNodeSet(html, '//td[@class="files"]')
text.vec <- sapply(node.set, xmlValue)
downloaded.vec <- sapply(strsplit(text.vec, " / "), "[[", 1)
as.integer(downloaded.vec)
}

test_that("counts of chunks downloaded or not at first", {
Expand Down
Loading