Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
e08850a
Initial plan
Copilot Nov 23, 2025
a9d7c45
Fix title centering in nice_lavaanPlot by adding ALIGN="CENTER" to HT…
Copilot Nov 23, 2025
168df0d
Update documentation after code styling changes
Copilot Nov 23, 2025
f714d23
document [skip ci]
rempsyc Nov 23, 2025
d3df4b1
no need to specify dimensions optionally since NULL also works [skip ci]
rempsyc Nov 23, 2025
fa61210
Add labeljust='c' to fix horizontal centering in exported plots (PNG/…
Copilot Nov 23, 2025
900e6e4
Add BALIGN="CENTER" to TABLE element for proper table-level centering
Copilot Nov 23, 2025
552102b
Add center="true" to center the entire graph drawing on the page
Copilot Nov 23, 2025
b564b6d
correct typo [skip ci]
rempsyc Nov 23, 2025
48590d8
Fix PNG/JPG centering by using SVG viewBox dimensions for rasterization
Copilot Nov 23, 2025
b18a980
Disable add_svg_padding to preserve Graphviz centering
Copilot Nov 24, 2025
6fc611f
Fix title centering in nice_lavaanPlot for all output formats
Copilot Nov 24, 2025
e02e16f
Refactor nice_lavaanPlot to use buildCall() + grViz() for proper cent…
Copilot Nov 24, 2025
a119289
partial fix [skip ci]
rempsyc Nov 24, 2025
b6b0652
Add webshot engine option for pixel-perfect browser-based rendering
Copilot Nov 25, 2025
222048b
Address code review: fix misleading comment and document magic numbers
Copilot Nov 25, 2025
dfe570f
doc [skip ci]
rempsyc Nov 25, 2025
d4e0d15
use chatgpt save_with_webshot2() [skip ci]
rempsyc Nov 25, 2025
fc561c8
Switch from webshot/PhantomJS to webshot2/Chrome and use use_webshot=…
Copilot Nov 25, 2025
f229f16
Fix webshot2 white margins by injecting CSS and using selector
Copilot Nov 25, 2025
07495c0
Fix whitespace by extracting SVG dimensions for viewport and forcing …
Copilot Nov 25, 2025
fed1d18
air format [skip ci]
rempsyc Nov 25, 2025
f7272bd
Fix PDF cropping by converting through PNG with grid.raster
Copilot Nov 25, 2025
e946e2c
some conditional installs aren't necessary in this function [skip ci]
rempsyc Nov 25, 2025
f99be97
Fix PDF to be vector-based with exact page size via webshot2 pdf_options
Copilot Nov 25, 2025
2cd962b
air format [skip ci]
rempsyc Nov 25, 2025
6b41dcd
improve save plot
rempsyc Nov 29, 2025
4a39cbf
Address code review feedback: add grid.newpage(), consolidate Diagram…
Copilot Nov 29, 2025
208dac2
Fix failing tests: update ALIGN count expectations and use_webshot=FA…
Copilot Nov 29, 2025
572ae09
Fix R CMD check: replace non-ASCII chars and use tempfile in examples
Copilot Nov 30, 2025
c530519
document, wordlist
rempsyc Nov 30, 2025
e96cb4f
use strategy to not rely on :::
rempsyc Nov 30, 2025
4413f93
Change use_webshot default to FALSE to avoid CI connection issues
Copilot Nov 30, 2025
8173039
doc [skip ci]
rempsyc Nov 30, 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
6 changes: 4 additions & 2 deletions DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Package: lavaanExtra
Title: Convenience Functions for Package 'lavaan'
Version: 0.2.2.4
Version: 0.2.2.5
Date: 2025-11-23
Authors@R:
person("Rémi", "Thériault", , "remi.theriault@mail.mcgill.ca", role = c("aut", "cre"),
Expand All @@ -21,11 +21,13 @@ Suggests:
rempsyc (> 0.1.6),
flextable,
lavaanPlot,
DiagrammeR,
DiagrammeRsvg,
rsvg,
ggplot2,
png,
webshot,
webshot2,
htmlwidgets,
tidySEM,
tmvnsim,
knitr,
Expand Down
11 changes: 11 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@

* In progress

## lavaanExtra 0.2.2.5
* `nice_lavaanPlot()`: Fixed title centering in all output formats (PNG, JPG, PDF, SVG) by:
- Adding Graphviz centering attributes (ALIGN="CENTER", BALIGN="CENTER", labeljust="c", center="true")
- Refactoring to use `lavaanPlot:::buildCall()` + `DiagrammeR::grViz()` for direct control over DOT diagram generation
* `save_plot()`: Added `use_webshot` parameter (default `TRUE`) for browser-based rendering:
- When `TRUE`, uses webshot2 package (headless Chrome via chromote) for pixel-perfect rendering that matches RStudio viewer exactly
- Avoids librsvg font substitution issues (Helvetica → DejaVu Sans) that cause text misalignment
- Supports PNG, JPG, and PDF formats; SVG uses rsvg directly
- Set `use_webshot = FALSE` to use rsvg rendering instead (faster but may have font differences)
* `save_plot()`: Re-enabled `add_svg_padding()` with horizontal-only padding to provide margin around plot content

## lavaanExtra 0.2.2.4
* Add `save_plot()` function to export/save plots from `nice_lavaanPlot()` and `nice_tidySEM()` with multiple format options (PNG, PDF, SVG, JPG)

Expand Down
68 changes: 48 additions & 20 deletions R/nice_lavaanPlot.R
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,17 @@
#'
#' # With robust estimator showing multiple fit statistic types
#' fit_robust <- cfa(HS.model, HolzingerSwineford1939, estimator = "MLR")
#' nice_lavaanPlot(fit_robust, title = "CFA Model", fit_stats = TRUE,
#' fit_stats_type = c("regular", "scaled", "robust"))
#' nice_lavaanPlot(fit_robust,
#' title = "CFA Model", fit_stats = TRUE,
#' fit_stats_type = c("regular", "scaled", "robust")
#' )
#'
#' # With custom font sizes (useful for large diagrams)
#' nice_lavaanPlot(fit, title = "Three-Factor CFA Model",
#' fit_stats = TRUE,
#' title_size = 18, fit_stats_size = 12)
#' nice_lavaanPlot(fit,
#' title = "Three-Factor CFA Model",
#' fit_stats = TRUE,
#' title_size = 18, fit_stats_size = 12
#' )
#'
#' # With automatic text wrapping for long titles
#' long_title <- "A Very Long Title That Would Otherwise Be Cut Off When Displayed"
Expand All @@ -95,10 +99,10 @@
#' # Save plot to file
#' \dontrun{
#' plot <- nice_lavaanPlot(fit)
#' save_plot(plot, "my_plot.png") # PNG format
#' save_plot(plot, "my_plot.pdf") # PDF format (lossless)
#' save_plot(plot, "my_plot.svg") # SVG format (lossless)
#' save_plot(plot, "my_plot.jpg") # JPG format
#' save_plot(plot, "my_plot.png") # PNG format
#' save_plot(plot, "my_plot.pdf") # PDF format (lossless)
#' save_plot(plot, "my_plot.svg") # SVG format (lossless)
#' save_plot(plot, "my_plot.jpg") # JPG format
#' }
#' @section Illustrations:
#'
Expand Down Expand Up @@ -128,9 +132,7 @@ nice_lavaanPlot <- function(
c(
"lavaanPlot",
"DiagrammeRsvg",
"rsvg",
"png",
"webshot"
"DiagrammeR"
),
reason = "for this function."
)
Expand Down Expand Up @@ -316,7 +318,7 @@ nice_lavaanPlot <- function(
html_rows <- c(
html_rows,
paste0(
"<TR><TD><FONT POINT-SIZE=\"",
"<TR><TD ALIGN=\"CENTER\"><FONT POINT-SIZE=\"",
title_size,
"\"><B>",
title_text,
Expand All @@ -335,7 +337,7 @@ nice_lavaanPlot <- function(
html_rows <- c(
html_rows,
paste0(
"<TR><TD><FONT POINT-SIZE=\"",
"<TR><TD ALIGN=\"CENTER\"><FONT POINT-SIZE=\"",
note_size,
"\">",
note_text,
Expand Down Expand Up @@ -363,7 +365,7 @@ nice_lavaanPlot <- function(
html_rows <- c(
html_rows,
paste0(
"<TR><TD><FONT POINT-SIZE=\"",
"<TR><TD ALIGN=\"CENTER\"><FONT POINT-SIZE=\"",
fit_stats_size,
"\">",
wrapped_line,
Expand All @@ -374,26 +376,52 @@ nice_lavaanPlot <- function(
}

# Combine into full HTML table
# BALIGN="CENTER" centers the table itself within the label space
graph_options$label <- paste0(
"<<TABLE BORDER=\"0\" CELLBORDER=\"0\" CELLSPACING=\"0\">",
"<<TABLE BORDER=\"0\" CELLBORDER=\"0\" CELLSPACING=\"0\" BALIGN=\"CENTER\">",
paste(html_rows, collapse = ""),
"</TABLE>>"
)

# Position label at top if title is present, otherwise at bottom
graph_options$labelloc <- if (has_title) "t" else "b"
# Center the label horizontally
graph_options$labeljust <- "c"
# Center the entire graph drawing on the page
graph_options$center <- "true"
}

lavaanPlot::lavaanPlot(
# --- buildCall + grViz backend ------------------------------------------

# Optional: allow width/height for HTML display (does NOT affect export_svg)
extra_args <- list(...)
widget_width <- if ("width" %in% names(extra_args)) extra_args$width else NULL
widget_height <- if ("height" %in% names(extra_args)) {
extra_args$height
} else {
NULL
}

# Use lavaanPlot's internal buildCall() to generate Graphviz DOT syntax
buildCall <- utils::getFromNamespace("buildCall", "lavaanPlot")
plot_call <- buildCall(
model = model,
name = "plot",
labels = NULL, # let lavaanPlot handle default labeling
node_options = node_options,
edge_options = edge_options,
coefs = coefs,
stand = stand,
covs = covs,
stars = stars,
graph_options = graph_options,
sig = sig,
...
graph_options = graph_options, # includes our title/note/fit_stats label
sig = sig
)

# Render as a grViz htmlwidget
DiagrammeR::grViz(

Copilot AI Nov 29, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code uses DiagrammeR::grViz() but DiagrammeR is not listed in the DESCRIPTION file's Suggests section. This will cause the package check to fail. Add DiagrammeR to the Suggests field in DESCRIPTION.

Copilot uses AI. Check for mistakes.
plot_call,
width = widget_width,
height = widget_height
)
}
35 changes: 20 additions & 15 deletions R/nice_tidySEM.R
Original file line number Diff line number Diff line change
Expand Up @@ -69,25 +69,27 @@
#' # Save plot to file
#' \dontrun{
#' plot <- nice_tidySEM(fit, layout = structure)
#' save_plot(plot, "my_plot.png") # PNG format
#' save_plot(plot, "my_plot.pdf") # PDF format
#' save_plot(plot, "my_plot.png") # PNG format
#' save_plot(plot, "my_plot.pdf") # PDF format
#' }
#' @section Illustrations:
#'
#' \if{html}{\figure{nice_tidySEM.png}{options: width="400"}}

nice_tidySEM <- function(fit,
layout = NULL,
hide_nonsig_edges = FALSE,
hide_var = TRUE,
hide_cov = FALSE,
hide_mean = TRUE,
est_std = TRUE,
label,
label_location = NULL,
reduce_items = NULL,
plot = TRUE,
...) {
nice_tidySEM <- function(
fit,
layout = NULL,
hide_nonsig_edges = FALSE,
hide_var = TRUE,
hide_cov = FALSE,
hide_mean = TRUE,
est_std = TRUE,
label,
label_location = NULL,
reduce_items = NULL,
plot = TRUE,
...
) {
insight::check_if_installed(c("tidySEM", "tmvnsim"))

# We are forced to reimport some tidySEM functions manually here...
Expand All @@ -101,7 +103,10 @@ nice_tidySEM <- function(fit,

# Function starts here
structure <- layout
if (all(c("IV", "M", "DV") %in% names(structure)) && length(names(structure)) == 3) {
if (
all(c("IV", "M", "DV") %in% names(structure)) &&
length(names(structure)) == 3
) {
sx <- function(x) {
rep("", x)
}
Expand Down
Loading