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
3 changes: 2 additions & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ Suggests:
rmarkdown,
sf,
svglite,
testthat
testthat,
xml2
VignetteBuilder:
knitr
Encoding: UTF-8
Expand Down
50 changes: 27 additions & 23 deletions R/animate.R
Original file line number Diff line number Diff line change
Expand Up @@ -246,32 +246,21 @@ prerender <- function(plot, nframes) {
draw_frames <- function(plot, frames, device, ref_frame, ...) {
stream <- device == 'current'

dims <- try_fetch(
plot_dims(plot, ref_frame),
error = function(e) {
cli::cli_warn('Cannot get dimensions of plot table. Plot region might not be fixed', parent = e)
list(widths = NULL, heights = NULL)
}
)

dir <- tempfile(pattern = '')
dir.create(dir, showWarnings = FALSE)
files <- file.path(dir, sprintf('gganim_plot%04d', seq_along(frames)))
files <- switch(
ext <- switch(
tolower(device),
ragg_png = ,
png = paste0(files, '.png'),
png = '.png',
jpg = ,
jpeg = paste0(files, '.jpg'),
jpeg = '.jpg',
tif = ,
tiff = paste0(files, '.tif'),
bmp = paste0(files, '.bmp'),
tiff = '.tif',
bmp = '.bmp',
svglite = ,
svg = paste0(files, '.svg'),
current = files,
svg = '.svg',
current = '',
cli::cli_abort('Unsupported device: {device}')
)
device <- switch(
device_fn <- switch(
device,
ragg_png = ragg::agg_png,
png = png,
Expand All @@ -281,10 +270,25 @@ draw_frames <- function(plot, frames, device, ref_frame, ...) {
tiff = tiff,
bmp = bmp,
svg = svg,
svglite = svglite::svglite
svglite = svglite::svglite,
current = png
)
args <- list(...)
args <- args[names(args) %in% names(formals(device))]
args <- args[names(args) %in% names(formals(device_fn))]

dims <- try_fetch(
plot_dims(plot, ref_frame, device = device_fn, dev_args = args),
error = function(e) {
cli::cli_warn('Cannot get dimensions of plot table. Plot region might not be fixed', parent = e)
list(widths = NULL, heights = NULL)
}
)

dir <- tempfile(pattern = '')
dir.create(dir, showWarnings = FALSE)
files <- file.path(dir, sprintf('gganim_plot%04d', seq_along(frames)))
files <- if (stream) files else paste0(files, ext)
device <- device_fn

pb <- progress_bar$new(
'Rendering [:bar] at :fps fps ~ eta: :eta',
Expand Down Expand Up @@ -316,9 +320,9 @@ draw_frames <- function(plot, frames, device, ref_frame, ...) {
frame_vars
}
# Get dimensions of plot based on a reference frame
plot_dims <- function(plot, ref_frame) {
plot_dims <- function(plot, ref_frame, device = png, dev_args = list()) {
tmpf <- tempfile()
png(tmpf)
inject(device(tmpf, !!!dev_args))
on.exit({
dev.off()
unlink(tmpf)
Expand Down
2 changes: 1 addition & 1 deletion R/scene.R
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ Scene <- ggproto('Scene', NULL,
})
if (orig_call) {
new_label[[1]]
} else if (is_expression(label)) {
} else if (is.expression(label)) {
as.expression(new_label)
} else {
unlist(new_label)
Expand Down
68 changes: 68 additions & 0 deletions tests/testthat/test-ggplot-align.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
context("ggplot alignment")

# Helper: parse y-coordinates of text elements from an SVG file.
# Returns a named list with entries for elements matching each selector pattern.
svg_text_y <- function(svg_path) {
xml <- xml2::read_xml(svg_path)
xml2::xml_ns_strip(xml)
nodes <- xml2::xml_find_all(xml, "//text")
y_vals <- as.numeric(xml2::xml_attr(nodes, "y"))
y_vals[!is.na(y_vals)]
}

test_that("animate and ggsave produce the same layout with svg device", {
skip_if_not_installed("xml2")
skip_if_not_installed("svglite")

df <- data.frame(
x = rep(1:5, 2),
y = c(1:5, 5:1),
state = rep(1:2, each = 5)
)

base_plot <- ggplot(df, aes(x, y)) +
labs(title = "Title", subtitle = "Subtitle", caption = "Caption") +
theme_minimal(base_size = 14) +
scale_x_continuous(limits = c(0, 6)) +
scale_y_continuous(limits = c(0, 6))

# ggsave reference: render state 1 directly
ggplot_svg <- tempfile(fileext = ".svg")
ggplot2::ggsave(
plot = ggplot(subset(df, state == 1), aes(x, y)) +
geom_point() +
labs(title = "Title", subtitle = "Subtitle", caption = "Caption") +
theme_minimal(base_size = 14) +
scale_x_continuous(limits = c(0, 6)) +
scale_y_continuous(limits = c(0, 6)),
filename = ggplot_svg,
device = svglite::svglite,
width = 5, height = 5
)

# animate: render state 1 frame via gganimate with svglite device
anim <- base_plot +
geom_point() +
transition_states(state, wrap = FALSE)

frame_paths <- animate(
anim,
renderer = function(frames, fps) frames,
nframes = 2,
fps = 1,
device = "svglite",
width = 5,
height = 5,
units = "in"
)

gganimate_svg <- frame_paths[[1]]

ggplot_y <- svg_text_y(ggplot_svg)
gganimate_y <- svg_text_y(gganimate_svg)

# Both renders should produce the same number of text elements at the same
# y-positions (within floating-point tolerance).
expect_equal(length(ggplot_y), length(gganimate_y))
expect_equal(ggplot_y, gganimate_y, tolerance = 0.5)
})