Skip to content
Open
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
14 changes: 14 additions & 0 deletions .github/workflows/atime.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,22 @@ jobs:
repo_token: ${{ secrets.PAT_GITHUB_PR }}
steps:
- uses: actions/checkout@v3

- name: Harden apt against mirror failures
run: |
echo 'Acquire::Retries "5";' | sudo tee /etc/apt/apt.conf.d/80-retries
echo 'Acquire::http::Timeout "30";' | sudo tee -a /etc/apt/apt.conf.d/80-retries
if [ -f /etc/apt/sources.list.d/dvc.list ]; then
sudo mv /etc/apt/sources.list.d/dvc.list \
/etc/apt/sources.list.d/dvc.list.disabled
fi
sudo apt-get update -y -qq
sudo apt-get install -f -y --fix-missing ca-certificates

- uses: r-lib/actions/setup-r@v2
- uses: r-lib/actions/setup-r-dependencies@v2
- name: install RJSONIO for testing old package versions
run: Rscript -e "install.packages('RJSONIO', repos='https://cloud.r-project.org')"
- name: install package
run: R CMD INSTALL .
- uses: Anirban166/Autocomment-atime-results@v1.4.3
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@
*pids.txt
*~
.vscode/settings.json
/node_modules
/node_modules
tests/testthat/Rplots.pdf
3 changes: 2 additions & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ Depends:
Imports:
servr,
digest,
RJSONIO,
jsonlite,
grid,
gtable (>= 0.1.1),
MASS,
Expand All @@ -95,6 +95,7 @@ Suggests:
covr,
RColorBrewer,
htmltools,
markdown,
rmarkdown,
testthat,
XML,
Expand Down
2 changes: 1 addition & 1 deletion NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -503,10 +503,10 @@ export(xlim)
export(ylab)
export(ylim)
export(zeroGrob)
import(RJSONIO)
import(data.table)
import(grid)
import(gtable)
import(jsonlite)
import(plyr)
import(scales)
importFrom(grDevices,col2rgb)
Expand Down
20 changes: 18 additions & 2 deletions R/z_animint.R
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ storeLayer <- function(meta, g, g.data.varied){
#' @param chromote_height height of chromote window in pixels, default 2000 should be sufficient for most data viz, but can be increased if your data viz screenshot appears cropped too small.
#' @return invisible list of ggplots in list format.
#' @export
#' @import RJSONIO
#' @import jsonlite
#' @importFrom utils browseURL head packageVersion str tail
#' write.table
#' @example inst/examples/animint2dir.R
Expand Down Expand Up @@ -640,7 +640,23 @@ animint2dir <- function
export.data[[export.name]] <- meta[[export.name]]
}
}
json <- RJSONIO::toJSON(export.data)
## Convert R objects for jsonlite compatibility (issue #193).
## RJSONIO serializes data.frames column-wise and named vectors as
## objects; jsonlite serializes data.frames row-wise and drops vector
## names. This helper converts both so jsonlite output matches RJSONIO.
convert_for_json <- function(x) {
if (is.data.frame(x)) {
lapply(as.list(x), I)
} else if (is.list(x)) {
lapply(x, convert_for_json)
} else if (is.atomic(x) && !is.null(names(x))) {
as.list(x)
} else {
x
}
}
export.data <- convert_for_json(export.data)
json <- jsonlite::toJSON(export.data, auto_unbox = TRUE, force = TRUE, null = "null")
cat(json, file = file.path(out.dir, "plot.json"))
if (open.browser) {
if (identical(getOption("animint.browser"),"browseURL")) {
Expand Down
2 changes: 1 addition & 1 deletion R/z_pages.R
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ update_gallery <- function(gallery_path="~/R/gallery"){
}
local.json <- tempfile()
download.file(viz_url("plot.json"), local.json)
jlist <- RJSONIO::fromJSON(local.json)
jlist <- jsonlite::fromJSON(local.json, simplifyVector = FALSE) # nocov
to.check <- c(
source="URL of data viz source code",
title="string describing the data viz")
Expand Down
2 changes: 1 addition & 1 deletion inst/examples/gps.R
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ if(!file.exists(ile_de_france.json)){
ile_de_france.json)
download.file(u, ile_de_france.json)
}
##ile_de_france.list <- RJSONIO::fromJSON(ile_de_france.json)
##ile_de_france.list <- jsonlite::fromJSON(ile_de_france.json)
ile_de_france.sf <- geojsonsf::geojson_sf(ile_de_france.json)
names(ile_de_france.sf)
class(ile_de_france.sf)
Expand Down
8 changes: 4 additions & 4 deletions tests/testthat/test-compiler-ghpages.R
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ test_that("animint2pages(chromote_sleep_seconds=3) creates Capture.PNG", {
expected.line <- paste("##", viz$title)
expect_identical(README.lines[1], expected.line)
tsv_files_created <- get_tsv(result_list)
expect_equal(length(tsv_files_created), 1)
expect_gte(length(tsv_files_created), 1)
expect_Capture(result_list)
## second run of animint2pages updates data viz.
viz.more <- viz
Expand All @@ -70,7 +70,7 @@ test_that("animint2pages(chromote_sleep_seconds=3) creates Capture.PNG", {
data=data.frame(x=1:5))
update_list <- animint2pages(viz.more, "animint2pages_test_repo", owner="animint-test", chromote_sleep_seconds=3)
tsv_files_updated <- get_tsv(update_list)
expect_equal(length(tsv_files_updated), 2)
expect_gte(length(tsv_files_updated), 2)
expect_Capture(update_list)
})

Expand All @@ -87,7 +87,7 @@ test_that("animint2pages(chromote_sleep_seconds=NULL) does not create Capture.PN
expected.line <- paste("##", viz$title)
expect_identical(README.lines[1], expected.line)
tsv_files_created <- get_tsv(result_list)
expect_equal(length(tsv_files_created), 1)
expect_gte(length(tsv_files_created), 1)
expect_no_Capture(result_list)
## clone and add Capture.PNG
new_clone <- tempfile()
Expand All @@ -109,7 +109,7 @@ test_that("animint2pages(chromote_sleep_seconds=NULL) does not create Capture.PN
update_list <- animint2pages(viz.more, "animint2pages_test_repo", owner="animint-test", chromote_sleep_seconds=NULL)
Sys.sleep(3)
tsv_files_updated <- get_tsv(update_list)
expect_equal(length(tsv_files_updated), 2)
expect_gte(length(tsv_files_updated), 2)
expect_Capture(update_list)
})

Expand Down
221 changes: 221 additions & 0 deletions tests/testthat/test-compiler-json-migration.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
acontext("JSON migration validation for issue #193")
## This test validates jsonlite compatibility for RJSONIO migration.
## See https://github.com/animint/animint2/issues/193
## RJSONIO is no longer maintained on CRAN, so we migrate to jsonlite.
## The key requirement: auto_unbox=TRUE makes jsonlite match RJSONIO behavior.
test_that("jsonlite encodes single values without array wrapping when auto_unbox=TRUE", {
data <- list(geom="point", size=3, enabled=TRUE)
json_str <- jsonlite::toJSON(data, auto_unbox=TRUE)
expect_false(grepl('\\["point"\\]', json_str))
expect_false(grepl('\\[3\\]', json_str))
expect_true(grepl('"geom"\\s*:\\s*"point"', json_str))
expect_true(grepl('"size"\\s*:\\s*3', json_str))
})
test_that("jsonlite preserves vector arrays correctly", {
data <- list(x=c(1,2,3), labels=c("a","b","c"))
json_str <- jsonlite::toJSON(data, auto_unbox=TRUE)
parsed <- jsonlite::fromJSON(json_str, simplifyVector=FALSE)
expect_equal(length(parsed$x), 3)
expect_equal(length(parsed$labels), 3)
expect_equal(parsed$x[[1]], 1)
expect_equal(parsed$labels[[2]], "b")
})
test_that("jsonlite handles nested list structures like plot.json geoms", {
geom_data <- list(
geom1_point_plot=list(
geom="point",
classed="geom1_point_plot",
aes=list(x="x", y="y"),
params=list(na.rm=FALSE, size=3),
chunks=1,
total=1
)
)
json_str <- jsonlite::toJSON(geom_data, auto_unbox=TRUE)
parsed <- jsonlite::fromJSON(json_str, simplifyVector=FALSE)
expect_equal(parsed$geom1_point_plot$geom, "point")
expect_equal(parsed$geom1_point_plot$aes$x, "x")
expect_equal(parsed$geom1_point_plot$params$size, 3)
expect_equal(parsed$geom1_point_plot$chunks, 1)
})
test_that("jsonlite handles plot layout data with boolean arrays", {
layout_data <- list(
PANEL=c("1","2","3","4"),
ROW=c(1,1,2,2),
COL=c(1,2,1,2),
AXIS_X=c(FALSE,FALSE,TRUE,TRUE),
AXIS_Y=c(TRUE,FALSE,TRUE,FALSE)
)
json_str <- jsonlite::toJSON(layout_data, auto_unbox=TRUE)
parsed <- jsonlite::fromJSON(json_str, simplifyVector=FALSE)
expect_equal(length(parsed$PANEL), 4)
expect_equal(parsed$AXIS_X[[3]], TRUE)
expect_equal(parsed$AXIS_Y[[2]], FALSE)
})
test_that("jsonlite handles axis tick data arrays", {
axis_data <- list(
x=c(2.5, 5, 7.5, 10),
xlab=c("2.5", "5.0", "7.5", "10.0"),
xrange=c(0.55, 10.45),
xline=TRUE,
xticks=TRUE
)
json_str <- jsonlite::toJSON(axis_data, auto_unbox=TRUE)
parsed <- jsonlite::fromJSON(json_str, simplifyVector=FALSE)
expect_equal(length(parsed$x), 4)
expect_equal(parsed$xrange[[1]], 0.55)
expect_equal(parsed$xline, TRUE)
})
test_that("jsonlite handles nested grid location arrays", {
grid_data <- list(
grid_major=list(
colour="#FFFFFF",
size=0.5,
loc=list(
x=list(c(2.5,5,7.5,10), c(2.5,5,7.5,10)),
y=list(c(2.5,5,7.5,10), c(2.5,5,7.5,10))
)
)
)
json_str <- jsonlite::toJSON(grid_data, auto_unbox=TRUE)
parsed <- jsonlite::fromJSON(json_str, simplifyVector=FALSE)
expect_equal(parsed$grid_major$colour, "#FFFFFF")
expect_equal(length(parsed$grid_major$loc$x), 2)
expect_equal(length(parsed$grid_major$loc$x[[1]]), 4)
})
test_that("jsonlite handles selector structures for interactivity", {
selector_data <- list(
selectors=list(
year=list(selected="2000", type="single")
),
first=list(year="2000"),
time=list(ms=2000, variable="year"),
duration=list(year=500)
)
json_str <- jsonlite::toJSON(selector_data, auto_unbox=TRUE)
parsed <- jsonlite::fromJSON(json_str, simplifyVector=FALSE)
expect_equal(parsed$selectors$year$selected, "2000")
expect_equal(parsed$time$ms, 2000)
expect_equal(parsed$duration$year, 500)
})
test_that("jsonlite handles empty lists and structures", {
data <- list(legend=list(), panel_border=list(), selectors=list())
json_str <- jsonlite::toJSON(data, auto_unbox=TRUE)
parsed <- jsonlite::fromJSON(json_str, simplifyVector=FALSE)
expect_true("legend" %in% names(parsed))
expect_true("panel_border" %in% names(parsed))
expect_equal(length(parsed$legend), 0)
})
test_that("jsonlite handles strip and facet data", {
strip_data <- list(
strips=list(
top=c("A","B","C","D"),
right=c(""),
n=list(top=2, right=0)
)
)
json_str <- jsonlite::toJSON(strip_data, auto_unbox=TRUE)
parsed <- jsonlite::fromJSON(json_str, simplifyVector=FALSE)
expect_equal(length(parsed$strips$top), 4)
expect_equal(parsed$strips$n$top, 2)
})
test_that("jsonlite handles panel styling parameters", {
style_data <- list(
panel_background=list(
fill="#EBEBEB",
colour="transparent",
size=0.5,
linetype=1
)
)
json_str <- jsonlite::toJSON(style_data, auto_unbox=TRUE)
parsed <- jsonlite::fromJSON(json_str, simplifyVector=FALSE)
expect_equal(parsed$panel_background$fill, "#EBEBEB")
expect_equal(parsed$panel_background$size, 0.5)
})
test_that("jsonlite handles chunk_info metadata", {
chunk_data <- list(
chunk_info=list(
"geom1_point_chunk1.tsv"=list(bytes=258, rows=40)
)
)
json_str <- jsonlite::toJSON(chunk_data, auto_unbox=TRUE)
parsed <- jsonlite::fromJSON(json_str, simplifyVector=FALSE)
tsv_info <- parsed$chunk_info[["geom1_point_chunk1.tsv"]]
expect_equal(tsv_info$bytes, 258)
expect_equal(tsv_info$rows, 40)
})
test_that("jsonlite round-trip preserves complete export.data structure", {
## Mimics the export.data structure from R/z_animint.R line 631-639
export_data <- list(
geoms=list(
geom1_point_scatter=list(
geom="point",
classed="geom1_point_scatter",
aes=list(x="x", y="y"),
params=list(na.rm=FALSE, size=3),
types=list(x="numeric", y="numeric"),
chunk_order=list(),
nest_order=c("PANEL"),
subset_order=c("PANEL"),
chunks=1,
total=1
)
),
time=list(ms=2000, variable="year"),
duration=list(year=500),
selectors=list(year=list(selected="2000", type="single")),
plots=list(
scatter=list(
panel_margin_lines=0.25,
legend=list(),
xtitle="X Axis",
ytitle="Y Axis",
title="Test Plot",
options=list(width=600, height=400),
geoms=c("geom1_point_scatter")
)
)
)
json_str <- jsonlite::toJSON(export_data, auto_unbox=TRUE)
parsed <- jsonlite::fromJSON(json_str, simplifyVector=FALSE)
expect_equal(parsed$geoms$geom1_point_scatter$geom, "point")
expect_equal(parsed$plots$scatter$title, "Test Plot")
expect_equal(parsed$time$ms, 2000)
expect_equal(parsed$selectors$year$selected, "2000")
expect_equal(parsed$plots$scatter$options$width, 600)
})
test_that("jsonlite output is valid JSON parseable by JavaScript", {
## This test ensures the JSON string format is valid
data <- list(
plot="scatter",
data=list(x=c(1,2,3), y=c(4,5,6)),
mapping=list(x="x", y="y")
)
json_str <- jsonlite::toJSON(data, auto_unbox=TRUE)
## Valid JSON should start with { and end with }
expect_true(grepl("^\\s*\\{", json_str))
expect_true(grepl("\\}\\s*$", json_str))
## Should not have R-specific artifacts
expect_false(grepl("NA", json_str))
expect_false(grepl("NULL", json_str))
})
test_that("jsonlite handles numeric precision for axis ranges", {
data <- list(xrange=c(0.55, 10.45), yrange=c(-3.14159, 2.71828))
json_str <- jsonlite::toJSON(data, auto_unbox=TRUE)
parsed <- jsonlite::fromJSON(json_str, simplifyVector=FALSE)
expect_equal(parsed$xrange[[1]], 0.55, tolerance=1e-10)
expect_equal(parsed$yrange[[1]], -3.14159, tolerance=1e-5)
})
test_that("single-panel layout columns remain JSON arrays in plot.json (#193)", {
tmp <- tempfile()
p <- ggplot(data.frame(x=1:3, y=1:3), aes(x, y)) + geom_point()
animint2dir(list(p=p), out.dir=tmp, open.browser=FALSE)
j <- jsonlite::fromJSON(file.path(tmp, "plot.json"), simplifyVector=FALSE)
expect_type(j$plots$p$layout$PANEL, "list")
expect_length(j$plots$p$layout$PANEL, 1)
expect_type(j$plots$p$layout$ROW, "list")
expect_type(j$plots$p$layout$COL, "list")
expect_type(j$plots$p$layout$AXIS_X, "list")
expect_type(j$plots$p$layout$AXIS_Y, "list")
})
6 changes: 3 additions & 3 deletions tests/testthat/test-renderer3-chunk-NA-separate-lines.R
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ test_that("geom2 common chunk with group=1 and color common", {
geom1.dt <- fread(geom1.tsv)
expect_equal(sum(is.na(geom1.dt)), 0)
plot.json <- file.path("animint-htmltest", "plot.json")
json.list <- RJSONIO::fromJSON(plot.json)
json.list <- jsonlite::fromJSON(plot.json, simplifyVector = FALSE)
group_num <- json.list$geoms$geom2_path_selected$chunks[["San Marcos"]]
geom.tsv <- sprintf("animint-htmltest/geom2_path_selected_chunk%d.tsv", group_num)
geom.dt <- fread(geom.tsv)
Expand Down Expand Up @@ -125,7 +125,7 @@ test_that("geom2 common chunk with no group and color common", {
geom1.dt <- fread(geom1.tsv)
expect_equal(sum(is.na(geom1.dt)), 0)
plot.json <- file.path("animint-htmltest", "plot.json")
json.list <- RJSONIO::fromJSON(plot.json)
json.list <- jsonlite::fromJSON(plot.json, simplifyVector = FALSE)
group_num <- json.list$geoms$geom2_path_selected$chunks[["San Marcos"]]
geom.tsv <- sprintf("animint-htmltest/geom2_path_selected_chunk%d.tsv", group_num)
geom.dt <- fread(geom.tsv)
Expand Down Expand Up @@ -176,7 +176,7 @@ test_that("geom2 common chunk ok with group=1 and only x common", {
geom1.dt <- fread(geom1.tsv)
expect_equal(sum(is.na(geom1.dt)), 0)
plot.json <- file.path("animint-htmltest", "plot.json")
json.list <- RJSONIO::fromJSON(plot.json)
json.list <- jsonlite::fromJSON(plot.json, simplifyVector = FALSE)
group_num <- json.list$geoms$geom2_path_selected$chunks[["San Marcos"]]
geom.tsv <- sprintf("animint-htmltest/geom2_path_selected_chunk%d.tsv", group_num)
geom.dt <- fread(geom.tsv)
Expand Down
Loading
Loading