diff --git a/NEWS.md b/NEWS.md index aa6350d2..52d12279 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,9 @@ +# Changes in version 2026.9.4 (PR#288) + +- Plot SVGs use `display: block` so browsers do not leave baseline whitespace below the plot (issue #279). Thanks @ANAMASGARD. + +# Changes in development (PR#339) + # Changes in version 2026.9.5 - geom_text() renders with .text(), instead of setMultilineText(), fixing interactive updates. diff --git a/inst/htmljs/animint.js b/inst/htmljs/animint.js index 0e24ec52..9d215877 100644 --- a/inst/htmljs/animint.js +++ b/inst/htmljs/animint.js @@ -401,7 +401,9 @@ 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); + .attr("width", p_info.options.width) + // block avoids inline SVG baseline/descender gap below the plot (issue #279) + .style("display", "block"); // divvy up width/height based on the panel layout var nrows = Math.max.apply(null, p_info.layout.ROW); diff --git a/tests/testthat/test-issue-279-facet-wrap-custom-height-spacing.R b/tests/testthat/test-issue-279-facet-wrap-custom-height-spacing.R new file mode 100644 index 00000000..86d8b217 --- /dev/null +++ b/tests/testthat/test-issue-279-facet-wrap-custom-height-spacing.R @@ -0,0 +1,43 @@ +test_that("facet_wrap SVG height is proportional to theme_animint height, no excess space (#279)", { + task_data <- data.frame( + x = rep(1:5, 5), + y = rep(1:5, 5), + task_id = rep(c("sonar", "spam", "vowel", "waveform", "zip"), each = 5) + ) + n_facets <- length(unique(task_data$task_id)) + default_theme_height <- 400 + custom_theme_height <- 600 + svg_xpath <- "//svg[@id='plot_plot']" + svg_sel <- "svg#plot_plot" + parent_sel <- "td:has(> svg#plot_plot)" + base_plot <- ggplot() + + geom_point(aes(x, y), data = task_data) + + facet_wrap(~ task_id, ncol = 1) + + theme_bw() + viz_list <- list( + default = animint(plot = base_plot), + custom = animint(plot = base_plot + theme_animint(height = custom_theme_height)) + ) + heights <- list() + for (viz_name in names(viz_list)) { + info <- animint2HTML(viz_list[[viz_name]]) + svg_node <- getNodeSet(info$html, svg_xpath) + expect_equal(length(svg_node), 1L) + svg_attrs <- xmlAttrs(svg_node[[1]]) + display <- getStyleValue(info$html, svg_xpath, "display") + expect_equal(display, "block", + label = "plot SVG should have inline display:block to prevent whitespace below it") + svg_box <- get_element_bbox(svg_sel) + parent_box <- get_element_bbox(parent_sel) + bottom_gap <- (parent_box$top + parent_box$height) - (svg_box$top + svg_box$height) + expect_lte(bottom_gap, 1, + label = "no excess rendered gap below plot SVG (issue #279)") + heights[[viz_name]] <- as.numeric(svg_attrs[["height"]]) + } + expect_lt(heights$default, default_theme_height * n_facets) + expect_lt(heights$custom, custom_theme_height * n_facets, + label = sprintf( + "SVG height should not be %d*num_facets — regression from issue #279", + custom_theme_height)) + expect_gt(heights$custom, heights$default) +}) diff --git a/tests/testthat/test-renderer3-knit-print.R b/tests/testthat/test-renderer3-knit-print.R index e732b42c..8d236a22 100644 --- a/tests/testthat/test-renderer3-knit-print.R +++ b/tests/testthat/test-renderer3-knit-print.R @@ -44,10 +44,9 @@ test_that("segments and breakpoints are rendered", { }) test_that("svg id property is unique", { - svg.list <- getNodeSet(html, "//svg") - attr.mat <- sapply(svg.list, xmlAttrs) - id.counts <- table(attr.mat["id",]) - expect_true(all(id.counts==1)) + id.vec <- getPropertyValue(html, "//svg", "id") + id.counts <- table(id.vec) + expect_equal(max(id.counts), 1L) }) all.list <- getNodeSet(html, "//*")