From 061d8dcd2c6d154ee89f48d31b9ca436dbb6c4a8 Mon Sep 17 00:00:00 2001 From: Gauarv Chaudhary Date: Wed, 29 Oct 2025 10:50:04 +0530 Subject: [PATCH 01/19] Add test for download status table size columns Test verifies animint.js generates total_MB and mean_MB columns in download status table. Currently fails, demonstrating these size columns are not present in the table. Addresses #249 --- tests/testthat/test-download-status-table.R | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 tests/testthat/test-download-status-table.R diff --git a/tests/testthat/test-download-status-table.R b/tests/testthat/test-download-status-table.R new file mode 100644 index 000000000..40a2231e2 --- /dev/null +++ b/tests/testthat/test-download-status-table.R @@ -0,0 +1,8 @@ +test_that("download status table should show file sizes", { + js_file <- system.file("htmljs", "animint.js", package="animint2") + js_text <- paste(readLines(js_file), collapse=" ") + cat("\n=== Issue #249: Download status table size info ===\n") + has_mb_columns <- grepl("total_MB|mean_MB", js_text) + cat(sprintf("animint.js has MB column code: %s\n", has_mb_columns)) + expect_true(has_mb_columns, label="animint.js should generate total_MB and mean_MB columns in download status table") +}) From 0316abd5dcd0af7315398d58b4ae316b119be7c4 Mon Sep 17 00:00:00 2001 From: Gauarv Chaudhary Date: Wed, 29 Oct 2025 21:12:43 +0530 Subject: [PATCH 02/19] Now using grep() and expect_gt() for informative test failures --- tests/testthat/test-download-status-table.R | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/tests/testthat/test-download-status-table.R b/tests/testthat/test-download-status-table.R index 40a2231e2..6e86858b1 100644 --- a/tests/testthat/test-download-status-table.R +++ b/tests/testthat/test-download-status-table.R @@ -1,8 +1,6 @@ test_that("download status table should show file sizes", { - js_file <- system.file("htmljs", "animint.js", package="animint2") - js_text <- paste(readLines(js_file), collapse=" ") - cat("\n=== Issue #249: Download status table size info ===\n") - has_mb_columns <- grepl("total_MB|mean_MB", js_text) - cat(sprintf("animint.js has MB column code: %s\n", has_mb_columns)) - expect_true(has_mb_columns, label="animint.js should generate total_MB and mean_MB columns in download status table") + js_file = system.file("htmljs", "animint.js", package="animint2") + js_text = paste(readLines(js_file), collapse=" ") + mb_columns = grep("total_MB", js_text) + expect_gt(length(mb_columns), 0) }) From 038edd1f031191c0226fde8b750c6700e453f85f Mon Sep 17 00:00:00 2001 From: Gauarv Chaudhary Date: Thu, 30 Oct 2025 21:11:21 +0530 Subject: [PATCH 03/19] Added the download size info to status table --- inst/htmljs/animint.js | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/inst/htmljs/animint.js b/inst/htmljs/animint.js index 02c8d760a..f7dd664d0 100644 --- a/inst/htmljs/animint.js +++ b/inst/htmljs/animint.js @@ -228,6 +228,9 @@ var animint = function (to_select, json_file) { 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", "total_MB").text("0.00"); + g_info.tr.append("td").attr("class", "mean_MB").text("0.00"); + g_info.tr.append("td").attr("class", "rows").text("0"); g_info.tr.append("td").attr("class", "status").text("initialized"); // load chunk tsv @@ -1008,6 +1011,37 @@ var animint = function (to_select, json_file) { // 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"); + + // Calculate sizes for this geom + var total_bytes = 0; + var total_rows = 0; + var chunk_count = 0; + for(var chunk_id in g_info.data){ + var chunk_data = g_info.data[chunk_id]; + if(chunk_data){ + if(Array.isArray(chunk_data)){ + total_rows += chunk_data.length; + var chunk_string = JSON.stringify(chunk_data); + total_bytes += chunk_string.length; + chunk_count++; + }else if(typeof chunk_data === 'object'){ + for(var key in chunk_data){ + if(Array.isArray(chunk_data[key])){ + total_rows += chunk_data[key].length; + } + } + var chunk_string = JSON.stringify(chunk_data); + total_bytes += chunk_string.length; + chunk_count++; + } + } + } + var total_MB = (total_bytes / 1048576).toFixed(2); + var mean_MB = chunk_count > 0 ? (total_bytes / chunk_count / 1048576).toFixed(2) : "0.00"; + g_info.tr.select("td.total_MB").text(total_MB); + g_info.tr.select("td.mean_MB").text(mean_MB); + g_info.tr.select("td.rows").text(total_rows); + var svg = SVGs[g_info.classed]; // derive the plot name from the geometry name var g_names = g_info.classed.split("_"); @@ -2375,6 +2409,9 @@ var animint = function (to_select, json_file) { 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", "total_MB").text("total_MB"); + tr.append("th").attr("class", "mean_MB").text("mean_MB"); + tr.append("th").attr("class", "rows").text("rows"); tr.append("th").attr("class", "status").text("status"); // Add geoms and construct nest operators. From 1c016bc3bb21c57f82c09578f9b46f48a4c819e8 Mon Sep 17 00:00:00 2001 From: Gauarv Chaudhary Date: Fri, 31 Oct 2025 14:05:25 +0530 Subject: [PATCH 04/19] Calculate chunk sizes in R, display in download table - Calculate file sizes using file.size() in saveChunks() - Store chunk_info (bytes, rows) in plot.json - Update table cells after download (not in draw_geom) - Remove 36-line JavaScript calculation loop --- R/z_animint.R | 4 +++ R/z_animintHelpers.R | 14 ++++++++++- inst/htmljs/animint.js | 57 ++++++++++++++++++------------------------ 3 files changed, 41 insertions(+), 34 deletions(-) diff --git a/R/z_animint.R b/R/z_animint.R index 3beb7dd7c..a594ac6ab 100644 --- a/R/z_animint.R +++ b/R/z_animint.R @@ -212,8 +212,12 @@ storeLayer <- function(meta, g, g.data.varied){ ## Save each variable chunk to a separate tsv file. meta$chunk.i <- 1L meta$g <- g + meta$chunk_info <- list() # Initialize chunk info storage g$chunks <- saveChunks(g.data.varied, meta) g$total <- length(unlist(g$chunks)) + + ## Add chunk size information to geom + g$chunk_info <- meta$chunk_info ## Finally save to the master geom list. meta$geoms[[g$classed]] <- g diff --git a/R/z_animintHelpers.R b/R/z_animintHelpers.R index f36864547..1de93360b 100644 --- a/R/z_animintHelpers.R +++ b/R/z_animintHelpers.R @@ -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)){ diff --git a/inst/htmljs/animint.js b/inst/htmljs/animint.js index f7dd664d0..4363331a9 100644 --- a/inst/htmljs/animint.js +++ b/inst/htmljs/animint.js @@ -228,10 +228,15 @@ var animint = function (to_select, json_file) { 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", "total_MB").text("0.00"); - g_info.tr.append("td").attr("class", "mean_MB").text("0.00"); - g_info.tr.append("td").attr("class", "rows").text("0"); + g_info.td_total_MB = g_info.tr.append("td").attr("class", "total_MB"); + g_info.td_mean_MB = g_info.tr.append("td").attr("class", "mean_MB"); + g_info.td_rows = g_info.tr.append("td").attr("class", "rows"); g_info.tr.append("td").attr("class", "status").text("initialized"); + + // Initialize size tracking + g_info.total_bytes = 0; + g_info.total_rows = 0; + g_info.downloaded_chunks = 0; // load chunk tsv g_info.data = {}; @@ -1002,6 +1007,22 @@ var animint = function (to_select, json_file) { 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 + 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 + var total_MB = (g_info.total_bytes / 1048576).toFixed(2); + var mean_MB = (g_info.total_bytes / g_info.downloaded_chunks / 1048576).toFixed(2); + g_info.td_total_MB.text(total_MB); + g_info.td_mean_MB.text(mean_MB); + g_info.td_rows.text(g_info.total_rows); + } + funAfter(chunk); }); }); @@ -1012,36 +1033,6 @@ var animint = function (to_select, json_file) { var draw_geom = function(g_info, chunk, selector_name, PANEL){ g_info.tr.select("td.status").text("displayed"); - // Calculate sizes for this geom - var total_bytes = 0; - var total_rows = 0; - var chunk_count = 0; - for(var chunk_id in g_info.data){ - var chunk_data = g_info.data[chunk_id]; - if(chunk_data){ - if(Array.isArray(chunk_data)){ - total_rows += chunk_data.length; - var chunk_string = JSON.stringify(chunk_data); - total_bytes += chunk_string.length; - chunk_count++; - }else if(typeof chunk_data === 'object'){ - for(var key in chunk_data){ - if(Array.isArray(chunk_data[key])){ - total_rows += chunk_data[key].length; - } - } - var chunk_string = JSON.stringify(chunk_data); - total_bytes += chunk_string.length; - chunk_count++; - } - } - } - var total_MB = (total_bytes / 1048576).toFixed(2); - var mean_MB = chunk_count > 0 ? (total_bytes / chunk_count / 1048576).toFixed(2) : "0.00"; - g_info.tr.select("td.total_MB").text(total_MB); - g_info.tr.select("td.mean_MB").text(mean_MB); - g_info.tr.select("td.rows").text(total_rows); - var svg = SVGs[g_info.classed]; // derive the plot name from the geometry name var g_names = g_info.classed.split("_"); From 5d335856338d4cbacf65bcc07949a4f1feef082e Mon Sep 17 00:00:00 2001 From: Gauarv Chaudhary Date: Fri, 31 Oct 2025 14:14:00 +0530 Subject: [PATCH 05/19] Bump version to 2025.10.31 and add NEWS entry for PR#272 --- DESCRIPTION | 2 +- NEWS.md | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index 95ed405f8..0ba379a55 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: animint2 Title: Animated Interactive Grammar of Graphics -Version: 2025.10.27 +Version: 2025.10.31 URL: https://animint.github.io/animint2 BugReports: https://github.com/animint/animint2/issues Authors@R: c( diff --git a/NEWS.md b/NEWS.md index d75223336..e7f5c0066 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,7 @@ +# Changes in version 2025.10.31 (PR#272) + +- Download status table now displays three new columns: `total_MB` (total disk space used by all downloaded chunks), `mean_MB` (average chunk size), and `rows` (total number of data rows). Chunk sizes are calculated in R using `file.size()` and exported via plot.json for efficient display updates after each chunk download. + # Changes in version 2025.10.27 (PR#269) - `geom_point()` default shape changed from 19 to 21 to enable both color and fill aesthetics for more consistent static rendering. From d5e661d64cc9a9ac1231140dc51c40d2c99c07e5 Mon Sep 17 00:00:00 2001 From: Gauarv Chaudhary Date: Fri, 31 Oct 2025 19:36:23 +0530 Subject: [PATCH 06/19] Use spaces in column headers: 'total MB' and 'mean MB' --- inst/htmljs/animint.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/inst/htmljs/animint.js b/inst/htmljs/animint.js index 4363331a9..2148eaf40 100644 --- a/inst/htmljs/animint.js +++ b/inst/htmljs/animint.js @@ -2400,8 +2400,8 @@ var animint = function (to_select, json_file) { 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", "total_MB").text("total_MB"); - tr.append("th").attr("class", "mean_MB").text("mean_MB"); + tr.append("th").attr("class", "total_MB").text("total MB"); + tr.append("th").attr("class", "mean_MB").text("mean MB"); tr.append("th").attr("class", "rows").text("rows"); tr.append("th").attr("class", "status").text("status"); From bf360fa9efc01df5268f82695cf18b693deb4943 Mon Sep 17 00:00:00 2001 From: Gauarv Chaudhary Date: Fri, 31 Oct 2025 21:47:00 +0530 Subject: [PATCH 07/19] Add download size info to status table (#249) - Remove 'selected chunk', 'status', and 'mean MB' columns - Add combined 'files', 'MB', and 'rows' columns (downloaded/total format) - Right-justify all numeric columns - Fix common chunk tracking: count in both downloaded and total counts - Track size (bytes/rows) for common chunks in R code - Filter chunk_info to only include each geom's own chunks - Count only .tsv files for accurate totals in JavaScript - Update tests to verify new column structure --- R/geom-.r | 8 +++ R/z_animint.R | 15 ++++- inst/htmljs/animint.js | 71 ++++++++++++++------- tests/testthat/test-download-status-table.R | 8 ++- 4 files changed, 74 insertions(+), 28 deletions(-) diff --git a/R/geom-.r b/R/geom-.r index 92e2b8a3b..f7707b322 100644 --- a/R/geom-.r +++ b/R/geom-.r @@ -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) diff --git a/R/z_animint.R b/R/z_animint.R index a594ac6ab..539589701 100644 --- a/R/z_animint.R +++ b/R/z_animint.R @@ -212,12 +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 - meta$chunk_info <- list() # Initialize chunk info storage + # 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 - g$chunk_info <- meta$chunk_info + ## 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 diff --git a/inst/htmljs/animint.js b/inst/htmljs/animint.js index 2148eaf40..510921356 100644 --- a/inst/htmljs/animint.js +++ b/inst/htmljs/animint.js @@ -225,18 +225,31 @@ 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.td_total_MB = g_info.tr.append("td").attr("class", "total_MB"); - g_info.td_mean_MB = g_info.tr.append("td").attr("class", "mean_MB"); - g_info.td_rows = g_info.tr.append("td").attr("class", "rows"); - g_info.tr.append("td").attr("class", "status").text("initialized"); + g_info.td_files = g_info.tr.append("td").attr("class", "files").style("text-align", "right"); + g_info.td_MB = g_info.tr.append("td").attr("class", "MB").style("text-align", "right"); + g_info.td_rows = 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; + } // load chunk tsv g_info.data = {}; @@ -251,6 +264,21 @@ 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 + if(g_info.chunk_info && g_info.chunk_info[common_tsv]){ + var info = g_info.chunk_info[common_tsv]; + g_info.total_bytes += info.bytes; + g_info.total_rows += info.rows; + g_info.downloaded_chunks += 1; + // Update display + var downloaded_count = g_info.downloaded_chunks; + var total_count = g_info.total_possible_chunks; + var downloaded_MB = (g_info.total_bytes / 1e6).toFixed(2); + var possible_MB = (g_info.possible_bytes / 1e6).toFixed(2); + g_info.td_files.text(downloaded_count + " / " + total_count); + g_info.td_MB.text(downloaded_MB + " / " + possible_MB); + g_info.td_rows.text(g_info.total_rows + " / " + g_info.possible_rows); + } }); } else { g_info.common_tsv = null; @@ -1005,7 +1033,6 @@ 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 @@ -1015,12 +1042,16 @@ var animint = function (to_select, json_file) { g_info.total_rows += info.rows; g_info.downloaded_chunks += 1; - // Update display - var total_MB = (g_info.total_bytes / 1048576).toFixed(2); - var mean_MB = (g_info.total_bytes / g_info.downloaded_chunks / 1048576).toFixed(2); - g_info.td_total_MB.text(total_MB); - g_info.td_mean_MB.text(mean_MB); - g_info.td_rows.text(g_info.total_rows); + // 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); + + var downloaded_MB = (g_info.total_bytes / 1048576).toFixed(2); + var possible_MB = (g_info.possible_bytes / 1048576).toFixed(2); + g_info.td_MB.text(downloaded_MB + " / " + possible_MB); + + g_info.td_rows.text(g_info.total_rows + " / " + g_info.possible_rows); } funAfter(chunk); @@ -1031,8 +1062,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("_"); @@ -2397,13 +2426,9 @@ var animint = function (to_select, json_file) { 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", "total_MB").text("total MB"); - tr.append("th").attr("class", "mean_MB").text("mean MB"); - tr.append("th").attr("class", "rows").text("rows"); - tr.append("th").attr("class", "status").text("status"); + tr.append("th").attr("class", "files").style("text-align", "right").text("files"); + tr.append("th").attr("class", "MB").style("text-align", "right").text("MB"); + 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) { diff --git a/tests/testthat/test-download-status-table.R b/tests/testthat/test-download-status-table.R index 6e86858b1..ab3a447a9 100644 --- a/tests/testthat/test-download-status-table.R +++ b/tests/testthat/test-download-status-table.R @@ -1,6 +1,10 @@ test_that("download status table should show file sizes", { js_file = system.file("htmljs", "animint.js", package="animint2") js_text = paste(readLines(js_file), collapse=" ") - mb_columns = grep("total_MB", js_text) - expect_gt(length(mb_columns), 0) + mb_column = grep('attr\\("class", "MB"\\)', js_text) + expect_gt(length(mb_column), 0) + files_column = grep('attr\\("class", "files"\\)', js_text) + expect_gt(length(files_column), 0) + rows_column = grep('attr\\("class", "rows"\\)', js_text) + expect_gt(length(rows_column), 0) }) From 81cd1e1be27b73bfc92bc441c943d85883a3903f Mon Sep 17 00:00:00 2001 From: Gauarv Chaudhary Date: Mon, 10 Nov 2025 12:47:19 +0530 Subject: [PATCH 08/19] Fix MB calculation inconsistency in download table Use 1048576 (1024*1024) consistently for MB conversion instead of mixing 1e6 (1000000) and 1048576. This ensures consistent size reporting for both common chunks and regular chunks in the download status table. --- inst/htmljs/animint.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/inst/htmljs/animint.js b/inst/htmljs/animint.js index 510921356..be204f362 100644 --- a/inst/htmljs/animint.js +++ b/inst/htmljs/animint.js @@ -273,8 +273,8 @@ var animint = function (to_select, json_file) { // Update display var downloaded_count = g_info.downloaded_chunks; var total_count = g_info.total_possible_chunks; - var downloaded_MB = (g_info.total_bytes / 1e6).toFixed(2); - var possible_MB = (g_info.possible_bytes / 1e6).toFixed(2); + var downloaded_MB = (g_info.total_bytes / 1048576).toFixed(2); + var possible_MB = (g_info.possible_bytes / 1048576).toFixed(2); g_info.td_files.text(downloaded_count + " / " + total_count); g_info.td_MB.text(downloaded_MB + " / " + possible_MB); g_info.td_rows.text(g_info.total_rows + " / " + g_info.possible_rows); From db9139b9fd7c096a1d719d4a9dc2582da5b9b35f Mon Sep 17 00:00:00 2001 From: Gauarv Chaudhary Date: Tue, 11 Nov 2025 21:23:39 +0530 Subject: [PATCH 09/19] Update download table tests for new files column Switch test-download-status-table.R to validate rendered HTML instead of the JS source. Verify headers and right-justified numeric columns via XPath. Update test-renderer1-variable-value.R to read the combined column ("downloaded / total") so the chunk count checks work again. Remove the extra blank line in NEWS.md. --- NEWS.md | 1 - tests/testthat/test-download-status-table.R | 36 ++++++++++++++----- .../testthat/test-renderer1-variable-value.R | 6 ++-- 3 files changed, 31 insertions(+), 12 deletions(-) diff --git a/NEWS.md b/NEWS.md index adcac8412..299ccde79 100644 --- a/NEWS.md +++ b/NEWS.md @@ -6,7 +6,6 @@ - `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. - # Changes in version 2025.10.27 (PR#269) - `geom_point()` default shape changed from 19 to 21 to enable both color and fill aesthetics for more consistent static rendering. diff --git a/tests/testthat/test-download-status-table.R b/tests/testthat/test-download-status-table.R index ab3a447a9..27d1d448e 100644 --- a/tests/testthat/test-download-status-table.R +++ b/tests/testthat/test-download-status-table.R @@ -1,10 +1,28 @@ -test_that("download status table should show file sizes", { - js_file = system.file("htmljs", "animint.js", package="animint2") - js_text = paste(readLines(js_file), collapse=" ") - mb_column = grep('attr\\("class", "MB"\\)', js_text) - expect_gt(length(mb_column), 0) - files_column = grep('attr\\("class", "files"\\)', js_text) - expect_gt(length(files_column), 0) - rows_column = grep('attr\\("class", "rows"\\)', js_text) - expect_gt(length(rows_column), 0) +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[@id="download_status"]//th') + header_text <- sapply(table_headers, xmlValue) + expect_true("geom" %in% header_text) + expect_true("files" %in% header_text) + expect_true("MB" %in% header_text) + expect_true("rows" %in% header_text) + expect_false("status" %in% header_text) + expect_false("mean MB" %in% header_text) + expect_false("selected chunk" %in% header_text) +}) +test_that("numeric columns are right-justified", { + files_header <- getNodeSet(info$html, '//table[@id="download_status"]//th[@class="files"]') + mb_header <- getNodeSet(info$html, '//table[@id="download_status"]//th[@class="MB"]') + rows_header <- getNodeSet(info$html, '//table[@id="download_status"]//th[@class="rows"]') + expect_equal(length(files_header), 1) + expect_equal(length(mb_header), 1) + expect_equal(length(rows_header), 1) + expect_match(xmlGetAttr(files_header[[1]], "style"), "text-align.*right") + expect_match(xmlGetAttr(mb_header[[1]], "style"), "text-align.*right") + expect_match(xmlGetAttr(rows_header[[1]], "style"), "text-align.*right") }) diff --git a/tests/testthat/test-renderer1-variable-value.R b/tests/testthat/test-renderer1-variable-value.R index 13aa8480f..1da7fb01a 100644 --- a/tests/testthat/test-renderer1-variable-value.R +++ b/tests/testthat/test-renderer1-variable-value.R @@ -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", { From 24919ed470f26df94a72ddbdec2b8d04d9ba2b6f Mon Sep 17 00:00:00 2001 From: Gauarv Chaudhary Date: Sat, 29 Nov 2025 08:14:30 +0530 Subject: [PATCH 10/19] fix download status table: add id attribute and initialize display values --- inst/htmljs/animint.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/inst/htmljs/animint.js b/inst/htmljs/animint.js index 671ae7533..6dcbfce67 100644 --- a/inst/htmljs/animint.js +++ b/inst/htmljs/animint.js @@ -251,6 +251,12 @@ var animint = function (to_select, json_file) { g_info.total_possible_chunks = tsv_count; } + // Set initial display values + var possible_MB = (g_info.possible_bytes / 1048576).toFixed(2); + g_info.td_files.text("0 / " + g_info.total_possible_chunks); + g_info.td_MB.text("0.00 / " + possible_MB); + g_info.td_rows.text("0 / " + g_info.possible_rows); + // load chunk tsv g_info.data = {}; g_info.download_status = {}; @@ -2435,6 +2441,7 @@ var animint = function (to_select, json_file) { } }); var loading = widget_td.append("table") + .attr("id", "download_status") .style("display", "none"); Widgets["loading"] = loading; var tr = loading.append("tr"); From 46af47e8104090ac04e5b3e16929c25552896c01 Mon Sep 17 00:00:00 2001 From: Gauarv Chaudhary Date: Wed, 3 Dec 2025 21:07:09 +0530 Subject: [PATCH 11/19] download status table: clean up tests and whitespace only --- inst/htmljs/animint.js | 5 ----- tests/testthat/test-download-status-table.R | 9 ++------- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/inst/htmljs/animint.js b/inst/htmljs/animint.js index aceedce38..d35126838 100644 --- a/inst/htmljs/animint.js +++ b/inst/htmljs/animint.js @@ -1036,26 +1036,21 @@ var animint = function (to_select, json_file) { var chunk = nest.map(response); g_info.data[tsv_name] = chunk; g_info.download_status[tsv_name] = "saved"; - // Update size information after download 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); - var downloaded_MB = (g_info.total_bytes / 1048576).toFixed(2); var possible_MB = (g_info.possible_bytes / 1048576).toFixed(2); g_info.td_MB.text(downloaded_MB + " / " + possible_MB); - g_info.td_rows.text(g_info.total_rows + " / " + g_info.possible_rows); } - funAfter(chunk); }); }); diff --git a/tests/testthat/test-download-status-table.R b/tests/testthat/test-download-status-table.R index 27d1d448e..88945515c 100644 --- a/tests/testthat/test-download-status-table.R +++ b/tests/testthat/test-download-status-table.R @@ -7,13 +7,8 @@ info <- animint2HTML(viz) test_that("table has correct column headers", { table_headers <- getNodeSet(info$html, '//table[@id="download_status"]//th') header_text <- sapply(table_headers, xmlValue) - expect_true("geom" %in% header_text) - expect_true("files" %in% header_text) - expect_true("MB" %in% header_text) - expect_true("rows" %in% header_text) - expect_false("status" %in% header_text) - expect_false("mean MB" %in% header_text) - expect_false("selected chunk" %in% header_text) + expected <- c("geom", "files", "MB", "rows") + expect_true(all(expected %in% header_text)) }) test_that("numeric columns are right-justified", { files_header <- getNodeSet(info$html, '//table[@id="download_status"]//th[@class="files"]') From 53d1ad91f120f0dccafefd5ffb566c003ee5a2d2 Mon Sep 17 00:00:00 2001 From: Gauarv Chaudhary Date: Wed, 10 Dec 2025 20:43:19 +0530 Subject: [PATCH 12/19] fix duplicate ID issue for knit-print compatibility - Change download_status table ID from fixed 'download_status' to viz_id + '_download_status' for unique IDs when multiple animints are on the same page - Update test XPath selectors to use contains() for flexibility --- inst/htmljs/animint.js | 2 +- tests/testthat/test-download-status-table.R | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/inst/htmljs/animint.js b/inst/htmljs/animint.js index 7806951e7..b83a64150 100644 --- a/inst/htmljs/animint.js +++ b/inst/htmljs/animint.js @@ -2436,7 +2436,7 @@ var animint = function (to_select, json_file) { } }); var loading = widget_td.append("table") - .attr("id", "download_status") + .attr("id", viz_id + "_download_status") .style("display", "none"); Widgets["loading"] = loading; var tr = loading.append("tr"); diff --git a/tests/testthat/test-download-status-table.R b/tests/testthat/test-download-status-table.R index 88945515c..919f11c99 100644 --- a/tests/testthat/test-download-status-table.R +++ b/tests/testthat/test-download-status-table.R @@ -5,15 +5,15 @@ viz <- animint( ) info <- animint2HTML(viz) test_that("table has correct column headers", { - table_headers <- getNodeSet(info$html, '//table[@id="download_status"]//th') + table_headers <- getNodeSet(info$html, '//table[contains(@id,"_download_status")]//th') header_text <- sapply(table_headers, xmlValue) expected <- c("geom", "files", "MB", "rows") expect_true(all(expected %in% header_text)) }) test_that("numeric columns are right-justified", { - files_header <- getNodeSet(info$html, '//table[@id="download_status"]//th[@class="files"]') - mb_header <- getNodeSet(info$html, '//table[@id="download_status"]//th[@class="MB"]') - rows_header <- getNodeSet(info$html, '//table[@id="download_status"]//th[@class="rows"]') + files_header <- getNodeSet(info$html, '//table[contains(@id,"_download_status")]//th[@class="files"]') + mb_header <- getNodeSet(info$html, '//table[contains(@id,"_download_status")]//th[@class="MB"]') + rows_header <- getNodeSet(info$html, '//table[contains(@id,"_download_status")]//th[@class="rows"]') expect_equal(length(files_header), 1) expect_equal(length(mb_header), 1) expect_equal(length(rows_header), 1) From 4b10010c015f3301192c3ce97ccaa1194a85ea75 Mon Sep 17 00:00:00 2001 From: Gauarv Chaudhary Date: Wed, 10 Dec 2025 22:18:43 +0530 Subject: [PATCH 13/19] Update version to 2025.12.4 for PR#272 --- DESCRIPTION | 2 +- NEWS.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index cdd8e0a33..e35dd810d 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -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( diff --git a/NEWS.md b/NEWS.md index 84742d385..a3bbee258 100644 --- a/NEWS.md +++ b/NEWS.md @@ -10,7 +10,7 @@ - `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.10.31 (PR#272) +# Changes in version 2025.12.4 (PR#272) - Download status table now displays three new columns: `total_MB` (total disk space used by all downloaded chunks), `mean_MB` (average chunk size), and `rows` (total number of data rows). Chunk sizes are calculated in R using `file.size()` and exported via plot.json for efficient display updates after each chunk download. From 52c7d6c979b4e804bd7d3f2ffdd677873c6c0c22 Mon Sep 17 00:00:00 2001 From: Gauarv Chaudhary Date: Fri, 12 Dec 2025 21:03:06 +0530 Subject: [PATCH 14/19] improve download status table: commas for rows, KiB/MiB for disk sizes - Add commas to large row numbers (e.g., 4,321 instead of 4321) - Show disk sizes in KiB or MiB based on size (binary 1024 divisor) - Rename column from 'MB' to 'disk' since values now include units - Update NEWS.md entry to reflect current features --- NEWS.md | 2 +- inst/htmljs/animint.js | 48 +++++++++++++++------ tests/testthat/test-download-status-table.R | 8 ++-- 3 files changed, 40 insertions(+), 18 deletions(-) diff --git a/NEWS.md b/NEWS.md index a3bbee258..59b54ce27 100644 --- a/NEWS.md +++ b/NEWS.md @@ -12,7 +12,7 @@ # Changes in version 2025.12.4 (PR#272) -- Download status table now displays three new columns: `total_MB` (total disk space used by all downloaded chunks), `mean_MB` (average chunk size), and `rows` (total number of data rows). Chunk sizes are calculated in R using `file.size()` and exported via plot.json for efficient display updates after each chunk download. +- 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) diff --git a/inst/htmljs/animint.js b/inst/htmljs/animint.js index b83a64150..5f11d2a88 100644 --- a/inst/htmljs/animint.js +++ b/inst/htmljs/animint.js @@ -9,6 +9,33 @@ 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"; + } + } + function wait_until_then(timeout, condFun, readyFun) { var args=arguments function checkFun() { @@ -226,7 +253,7 @@ var animint = function (to_select, json_file) { g_info.tr = Widgets["loading"].append("tr"); g_info.tr.append("td").text(g_name); g_info.td_files = g_info.tr.append("td").attr("class", "files").style("text-align", "right"); - g_info.td_MB = g_info.tr.append("td").attr("class", "MB").style("text-align", "right"); + g_info.td_disk = g_info.tr.append("td").attr("class", "disk").style("text-align", "right"); g_info.td_rows = g_info.tr.append("td").attr("class", "rows").style("text-align", "right"); // Initialize size tracking @@ -252,10 +279,9 @@ var animint = function (to_select, json_file) { } // Set initial display values - var possible_MB = (g_info.possible_bytes / 1048576).toFixed(2); g_info.td_files.text("0 / " + g_info.total_possible_chunks); - g_info.td_MB.text("0.00 / " + possible_MB); - g_info.td_rows.text("0 / " + g_info.possible_rows); + 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 = {}; @@ -279,11 +305,9 @@ var animint = function (to_select, json_file) { // Update display var downloaded_count = g_info.downloaded_chunks; var total_count = g_info.total_possible_chunks; - var downloaded_MB = (g_info.total_bytes / 1048576).toFixed(2); - var possible_MB = (g_info.possible_bytes / 1048576).toFixed(2); g_info.td_files.text(downloaded_count + " / " + total_count); - g_info.td_MB.text(downloaded_MB + " / " + possible_MB); - g_info.td_rows.text(g_info.total_rows + " / " + g_info.possible_rows); + 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)); } }); } else { @@ -1046,10 +1070,8 @@ var animint = function (to_select, json_file) { var downloaded_count = g_info.downloaded_chunks; var total_count = g_info.total_possible_chunks; g_info.td_files.text(downloaded_count + " / " + total_count); - var downloaded_MB = (g_info.total_bytes / 1048576).toFixed(2); - var possible_MB = (g_info.possible_bytes / 1048576).toFixed(2); - g_info.td_MB.text(downloaded_MB + " / " + possible_MB); - g_info.td_rows.text(g_info.total_rows + " / " + g_info.possible_rows); + 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)); } funAfter(chunk); }); @@ -2442,7 +2464,7 @@ var animint = function (to_select, json_file) { var tr = loading.append("tr"); tr.append("th").text("geom"); tr.append("th").attr("class", "files").style("text-align", "right").text("files"); - tr.append("th").attr("class", "MB").style("text-align", "right").text("MB"); + tr.append("th").attr("class", "disk").style("text-align", "right").text("disk"); tr.append("th").attr("class", "rows").style("text-align", "right").text("rows"); // Add geoms and construct nest operators. diff --git a/tests/testthat/test-download-status-table.R b/tests/testthat/test-download-status-table.R index 919f11c99..fdd924fab 100644 --- a/tests/testthat/test-download-status-table.R +++ b/tests/testthat/test-download-status-table.R @@ -7,17 +7,17 @@ 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", "MB", "rows") + 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"]') - mb_header <- getNodeSet(info$html, '//table[contains(@id,"_download_status")]//th[@class="MB"]') + 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(mb_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(mb_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") }) From ae45eb74eaf8f0d39791c3bd904e857691e5b39c Mon Sep 17 00:00:00 2001 From: Gauarv Chaudhary Date: Mon, 15 Dec 2025 16:29:22 +0530 Subject: [PATCH 15/19] Trigger CI re-run From bc12ff853596e74fd6f8aedebfb8bb8169d81a47 Mon Sep 17 00:00:00 2001 From: Gauarv Chaudhary Date: Mon, 15 Dec 2025 22:01:56 +0530 Subject: [PATCH 16/19] refactor download status table: add helper functions and content test - Add updateDownloadStatus() function to eliminate code duplication - Add formatWithCommas() for row numbers (4,321 instead of 4321) - Add formatBytes() for KiB/MiB display using 1024 divisor - Add test to verify table content format (files/disk/rows) --- inst/htmljs/animint.js | 43 +++++++++------------ tests/testthat/test-download-status-table.R | 21 ++++++++++ 2 files changed, 40 insertions(+), 24 deletions(-) diff --git a/inst/htmljs/animint.js b/inst/htmljs/animint.js index 5f11d2a88..2bd15d23a 100644 --- a/inst/htmljs/animint.js +++ b/inst/htmljs/animint.js @@ -36,6 +36,23 @@ var animint = function (to_select, json_file) { } } + // 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() { @@ -297,18 +314,7 @@ var animint = function (to_select, json_file) { 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 - if(g_info.chunk_info && g_info.chunk_info[common_tsv]){ - var info = g_info.chunk_info[common_tsv]; - g_info.total_bytes += info.bytes; - g_info.total_rows += info.rows; - g_info.downloaded_chunks += 1; - // Update display - 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)); - } + updateDownloadStatus(g_info, common_tsv); }); } else { g_info.common_tsv = null; @@ -1061,18 +1067,7 @@ var animint = function (to_select, json_file) { g_info.data[tsv_name] = chunk; g_info.download_status[tsv_name] = "saved"; // Update size information after download - 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)); - } + updateDownloadStatus(g_info, tsv_name); funAfter(chunk); }); }); diff --git a/tests/testthat/test-download-status-table.R b/tests/testthat/test-download-status-table.R index fdd924fab..dda50e296 100644 --- a/tests/testthat/test-download-status-table.R +++ b/tests/testthat/test-download-status-table.R @@ -21,3 +21,24 @@ test_that("numeric columns are right-justified", { 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 <- "^\\d+ / \\d+$" + 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 <- "\\d+(\\.\\d+)? (KiB|MiB) / \\d+(\\.\\d+)? (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) + rows_pattern <- "^[\\d,]+ / [\\d,]+$" + rows_cells <- grep(rows_pattern, cell_text, value = TRUE) + expect_true(length(rows_cells) > 0, "Should have rows column with numeric format") +}) From 65d5ffe20e5c9b7203febf5c6875a4d72fed23d4 Mon Sep 17 00:00:00 2001 From: Gauarv Chaudhary Date: Mon, 15 Dec 2025 22:45:52 +0530 Subject: [PATCH 17/19] fix regex patterns in download status table test Replace \d with [0-9] in regex patterns - \d is not recognized in R's base regex engine --- tests/testthat/test-download-status-table.R | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/testthat/test-download-status-table.R b/tests/testthat/test-download-status-table.R index dda50e296..998c8f579 100644 --- a/tests/testthat/test-download-status-table.R +++ b/tests/testthat/test-download-status-table.R @@ -28,17 +28,18 @@ test_that("download status table displays correct content format", { # Should have cells for: geom name, files (1 / 1), disk (KiB/MiB), rows (with commas) # Files column should show "downloaded / total" format - files_pattern <- "^\\d+ / \\d+$" + 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 <- "\\d+(\\.\\d+)? (KiB|MiB) / \\d+(\\.\\d+)? (KiB|MiB)" + 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) - rows_pattern <- "^[\\d,]+ / [\\d,]+$" + # 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") }) From 69bc4c74942ca94c0d7053d57669151ded098ad0 Mon Sep 17 00:00:00 2001 From: Gauarv Chaudhary Date: Tue, 16 Dec 2025 08:29:31 +0530 Subject: [PATCH 18/19] remove empty lines and add table borders --- inst/htmljs/animint.js | 22 ++++++++++----------- tests/testthat/test-download-status-table.R | 3 --- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/inst/htmljs/animint.js b/inst/htmljs/animint.js index 2bd15d23a..128b1a412 100644 --- a/inst/htmljs/animint.js +++ b/inst/htmljs/animint.js @@ -268,16 +268,14 @@ 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.td_files = g_info.tr.append("td").attr("class", "files").style("text-align", "right"); - g_info.td_disk = g_info.tr.append("td").attr("class", "disk").style("text-align", "right"); - g_info.td_rows = g_info.tr.append("td").attr("class", "rows").style("text-align", "right"); - + g_info.tr.append("td").text(g_name).style("border", "1px solid #ddd").style("padding", "4px"); + g_info.td_files = g_info.tr.append("td").attr("class", "files").style("text-align", "right").style("border", "1px solid #ddd").style("padding", "4px"); + g_info.td_disk = g_info.tr.append("td").attr("class", "disk").style("text-align", "right").style("border", "1px solid #ddd").style("padding", "4px"); + g_info.td_rows = g_info.tr.append("td").attr("class", "rows").style("text-align", "right").style("border", "1px solid #ddd").style("padding", "4px"); // 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; @@ -2454,13 +2452,15 @@ var animint = function (to_select, json_file) { }); var loading = widget_td.append("table") .attr("id", viz_id + "_download_status") - .style("display", "none"); + .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", "files").style("text-align", "right").text("files"); - tr.append("th").attr("class", "disk").style("text-align", "right").text("disk"); - tr.append("th").attr("class", "rows").style("text-align", "right").text("rows"); + tr.append("th").text("geom").style("border", "1px solid #ddd").style("padding", "4px"); + tr.append("th").attr("class", "files").style("text-align", "right").style("border", "1px solid #ddd").style("padding", "4px").text("files"); + tr.append("th").attr("class", "disk").style("text-align", "right").style("border", "1px solid #ddd").style("padding", "4px").text("disk"); + tr.append("th").attr("class", "rows").style("text-align", "right").style("border", "1px solid #ddd").style("padding", "4px").text("rows"); // Add geoms and construct nest operators. for (var g_name in response.geoms) { diff --git a/tests/testthat/test-download-status-table.R b/tests/testthat/test-download-status-table.R index 998c8f579..b87bbaffa 100644 --- a/tests/testthat/test-download-status-table.R +++ b/tests/testthat/test-download-status-table.R @@ -25,18 +25,15 @@ 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,]+$" From a44e99434dfb0085638e0fa05f01833f0b9df92d Mon Sep 17 00:00:00 2001 From: Gauarv Chaudhary Date: Fri, 19 Dec 2025 15:58:27 +0530 Subject: [PATCH 19/19] added applyCellStyles helper to avoid style repetition --- inst/htmljs/animint.js | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/inst/htmljs/animint.js b/inst/htmljs/animint.js index 128b1a412..6c6fcfe97 100644 --- a/inst/htmljs/animint.js +++ b/inst/htmljs/animint.js @@ -36,6 +36,11 @@ var animint = function (to_select, json_file) { } } + // 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) { @@ -268,10 +273,10 @@ 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).style("border", "1px solid #ddd").style("padding", "4px"); - g_info.td_files = g_info.tr.append("td").attr("class", "files").style("text-align", "right").style("border", "1px solid #ddd").style("padding", "4px"); - g_info.td_disk = g_info.tr.append("td").attr("class", "disk").style("text-align", "right").style("border", "1px solid #ddd").style("padding", "4px"); - g_info.td_rows = g_info.tr.append("td").attr("class", "rows").style("text-align", "right").style("border", "1px solid #ddd").style("padding", "4px"); + 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; @@ -2457,10 +2462,10 @@ var animint = function (to_select, json_file) { .style("border", "1px solid #ddd"); Widgets["loading"] = loading; var tr = loading.append("tr"); - tr.append("th").text("geom").style("border", "1px solid #ddd").style("padding", "4px"); - tr.append("th").attr("class", "files").style("text-align", "right").style("border", "1px solid #ddd").style("padding", "4px").text("files"); - tr.append("th").attr("class", "disk").style("text-align", "right").style("border", "1px solid #ddd").style("padding", "4px").text("disk"); - tr.append("th").attr("class", "rows").style("text-align", "right").style("border", "1px solid #ddd").style("padding", "4px").text("rows"); + 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) {