lvmPlot draws publication-ready diagrams for latent
variable models. lavaan is the main workflow, but the
package now uses a common lvm_graph grammar so
blavaan/lavaan.mi, semPlot,
mirt, eRm, OpenMx,
psych, poLCA, mclust,
flexmix, lcmm, tidyLPA, and
MplusAutomation-style outputs can share the same RStudio,
SVG, PDF, PNG, and TikZ rendering system. The supported model families
include SEM/CFA, multilevel SEM, bifactor and higher-order models,
latent class and profile models, IRT/MIRT, Rasch, OpenMx RAM models, and
Mplus-style parameter output.
The package is meant to cover the everyday strengths of common SEM drawing tools while reducing the amount of manual cleanup needed for publication:
semPlot::semPaths()-style model awareness, layout
presets, rotations, and parameter labelstidySEM-style editable layouts and data-frame-friendly
graph objectslavaanPlot-style simple plotting from
lavaan outputsemptools-style attention to factor/indicator placement
and loading labelslvmPlot adds a common LVM graph grammar, TikZ-first
export, publication and presentation themes, orientation-aware label
placement, multilevel layer bands, and adapters for SEM, multilevel SEM,
LCA/LPA, IRT/MIRT, Rasch, OpenMx, and Mplus-style parameter tables.
Automatic diagrams prioritize a clean publication view. When a model
contains geometry that cannot be shown well as a straight-edge path
diagram, such as a 60-item single-factor battery, dense latent
structural regressions, dense LCA probability matrices, or a covariance
edge parallel to a directed path, diagram = "auto"
summarizes the display. Use diagram = "all" when you need
the complete parameter graph for audit or manual editing.
From the package directory:
install.packages("devtools")
devtools::install()or without devtools:
R CMD INSTALL .library(lavaan)
library(lvmPlot)
model <- '
visual =~ x1 + x2 + x3
textual =~ x4 + x5 + x6
speed =~ x7 + x8 + x9
textual ~ visual
speed ~ visual + textual
'
fit <- sem(model, data = HolzingerSwineford1939)
plot_lvm(fit, label = "std")
save_lvm_svg(fit, "holzinger-swineford.svg", width = 9.2, height = 5.4)
write_lvm_tikz(fit, "holzinger-swineford.tex", label = "std")The older SEM-specific helpers still work:
plot_sem(fit, label = "std")
write_sem_tikz(fit, "holzinger-swineford.tex")For day-to-day work in RStudio, draw directly into the Plots pane:
plot_lvm(fit, label = "std")In RStudio, the package also installs Addins. Select a model object
name or an expression in the editor, then use Preview lvmPlot
Diagram to draw it in the Plots pane or Export lvmPlot
TikZ to write a .tex file.
Export the same diagram as vector artwork:
save_lvm_svg(fit, "holzinger-swineford.svg")
save_lvm_pdf(fit, "holzinger-swineford.pdf")
save_lvm_png(fit, "holzinger-swineford.png", res = 300)The LVM save helpers use width = "auto" and
height = "auto" by default. lvm_canvas_size()
gives the recommended inches before export, and explicit numeric
width/height values still override it.
When an automatic layout needs final human judgment, open the browser editor, drag nodes and coefficient labels into place, and download the final figure or reusable layout:
lvmPlot_editor(fit, label = "std", theme = "journal")The editor supports click/shift-click node selection, selected-node
label editing, grid snapping, arrow-key nudging, locked nodes, node
dragging, draggable coefficient labels, undo/redo, multi-node alignment,
horizontal or vertical distribution, smart polishing for selected nodes,
one-click layout repair, X/Y compact/expand controls, and live preview
updates when edge labels, themes, style presets, colors, font sizes,
node sizes, and line widths change. Double-click a coefficient label to
return it to automatic placement. Style edits are exported through the
same lvm_style() system used by plot_lvm() and
the save helpers, so the browser preview is not a separate cosmetic
layer. It is meant for the final publishing pass after the automatic
layout has done the heavy structural work.
For reproducible editing sessions, download State JSON to save the full editor state, including edited node labels, manual coefficient-label positions, and locked/selected nodes, and load it later to continue editing. Download Figure R for a script that reconstructs the final layout, node labels, style, theme, edge labels, edge-label positions, and export calls inside an analysis project.
The editor keeps publication export inside lvmPlot: SVG/PDF/PNG
downloads use the same renderer as save_lvm_svg(),
save_lvm_pdf(), and save_lvm_png(), and layout
downloads can be reused with
plot_lvm(object, layout = layout).
If a TeX engine is installed, compile the file:
write_lvm_tikz(fit, "holzinger-swineford.tex", compile = TRUE)For manuscript workflows, export_lvm_bundle() writes the
whole figure artifact set in one call: PDF, PNG, SVG, standalone TikZ,
node and edge tables, diagnostics, a quality score, session metadata,
and a Markdown report.
bundle <- export_lvm_bundle(
fit,
dir = "figures/holzinger-swineford",
name = "figure-cfa",
label = "std",
theme = "journal",
check = TRUE,
optimize = TRUE,
optimize_orientation = c("top-down", "left-right")
)
bundleThis makes the diagram auditable: the exported folder contains both
the figure and the data/diagnostics needed to reproduce or review it.
When optimize = TRUE, the bundle also contains
layout-selection.csv, which records each candidate
orientation/layout/routing combination and the selected quality
score.
You can run the same selection step without exporting files:
selection <- select_lvm_layout(
fit,
orientation = c("top-down", "left-right", "bottom-up", "right-left"),
label = "std"
)
selection
plot_lvm(selection$graph, label = "std")All adapters return the same lvm_graph object:
graph <- as_lvm_graph(fit)
plot_lvm(graph)Supported adapters include:
lavaan fit objects and lavaan-style parameter
tableslavaan multilevel models with level
informationblavaan and lavaan.mi lavaan-like objects
when their parameter estimates can be read through the lavaan-compatible
APIsemPlot::semPlotModel objects, which makes lvmPlot
usable as a high-quality layout/export backend for models already
understood by semPlotmirt SingleGroupClass objects for
IRT/MIRTeRm Rasch modelsmclust latent profile / mixture modelsflexmix and lcmm-style mixture /
longitudinal latent class schematicspoLCA latent class objectstidyLPA objects and tidyLPA-style profile datapsych::fa, stats::loadings, and EFA-style
loading matricesOpenMx RAM modelsMplusAutomation mplus.model and
result-wrapper parameter tables with BY, ON,
and WITHYou can also build diagrams directly:
nodes <- data.frame(
name = c("Theta", "i1", "i2", "i3"),
role = c("trait", "item", "item", "item"),
type = c("latent", "observed", "observed", "observed")
)
edges <- data.frame(
from = "Theta",
to = c("i1", "i2", "i3"),
type = "loading",
edge_label = c("a=.80", "a=1.10", "a=.65")
)
plot_lvm(lvm_graph(nodes, edges, model_type = "irt", layout_family = "irt"))The main plotting functions expose the same grammar-level choices:
plot_lvm(
fit,
layout_family = "sem", # sem, bifactor, irt, mixture, growth, multilevel, circle
orientation = "left-right", # top-down, bottom-up, left-right, right-left
diagram = "measurement", # all, measurement, structural, paths, covariances
theme = "classic", # see lvm_themes()
label = "std",
aspect = "balanced", # balanced, preserve, fill
min_abs = .10,
significant = TRUE
)Built-in themes are:
lvm_themes()The defaults remain conservative (journal), with extra
presets for minimal, classic,
apa, nature, colorblind,
poster, compact, slides, and
blueprint.
The default aspect = "balanced" keeps diagrams from
looking oddly stretched in wide RStudio plot panes or exported
PNG/SVG/PDF files. Use aspect = "preserve" when equal x/y
coordinate units matter, or aspect = "fill" when you
explicitly want the older full-panel stretch. For dense diagrams, the
default themes also make small automatic typography and line-width
adjustments so labels remain legible; explicit lvm_style()
choices always take precedence. The default label = "auto"
keeps the diagram itself clean by hiding automatically estimated
parameters while preserving explicit custom edge labels on ordinary
diagrams. Use label = "std", label = "est", or
label = "both" when you want coefficients printed on the
paths, and label = "none" to silence all edge labels
including custom edge_label values.
Style overrides cover the usual publication tweaks:
plot_lvm(
fit,
label = "std",
style = lvm_style(
scale = 1.08, # whole figure polish
font_scale = 0.95, # keep labels modest after enlarging nodes/lines
node_font_size = 12,
edge_font_size = 9,
font_family = "Helvetica",
latent_size = 17,
observed_width = 22,
observed_height = 10,
node_line_width = 1.1,
edge_line_width = 1.0,
node_fill = "#F8FAFC",
edge_color = "#334155",
label_fill = "#FFFFFF"
)
)For publication cleanup, use a layout matrix and local node/edge styling. The same controls work in the RStudio Plots pane and in TikZ output:
params <- data.frame(
lhs = c("f", "f", "y"),
op = c("=~", "=~", "~"),
rhs = c("x1", "x2", "f"),
est = c(1, .8, .4),
std.all = c(.7, .6, .35)
)
layout <- matrix(
c(NA, "y", NA,
NA, "f", NA,
"x1", ".", "x2"),
nrow = 3,
byrow = TRUE
)
plot_lvm(
params,
layout = layout,
label = "std",
node_style = data.frame(
name = c("f", "y"),
label = c("Factor", "Outcome"),
shape = c("diamond", "rounded"),
fill = c("#EEF2FF", "#ECFDF5"),
color = c("#3730A3", "#047857"),
font_size = c(12, 10)
),
edge_style = data.frame(
from = c("f", "f"),
to = c("x2", "y"),
label = c("lambda2", "beta"),
color = c("#B91C1C", "#0F766E"),
line_width = c(1.6, 1.4),
linetype = c("dashed", "solid"),
curvature = c(.20, -.18),
label_size = c(8, 9),
label_fill = c("#FFF7ED", "#F0FDFA")
)
)Straight routing is the default, so model edges follow conventional
path-diagram geometry. layout_diagnostics() reports node
overlaps, edge/node collisions, and crossings before export. It can also
estimate edge label boxes so long labels such as .46*** are
checked separately from node layout:
diagnostics <- layout_diagnostics(as_lvm_graph(params))
diagnostics
layout_diagnostics(as_lvm_graph(params), label = "std", stars = "always")
layout_quality(as_lvm_graph(params), label = "std")
check_lvm_layout(params, layout = layout, label = "std")
plot_lvm(params, routing = "smart")
lvm_tikz(params, routing = "smart")check_lvm_layout() is assertion-style: by default it
errors if the diagram is not ready, if the score is below
92, or if node/edge/label collisions remain. Use
action = "none" to return the quality object without
stopping, or minimum_status = "review" when a script should
permit human-review cases.
If a user explicitly wants automatic curved avoidance,
routing = "smart" is still available.
For dense LCA/LPA probability matrices, diagram = "auto"
uses a compact representative view so the default plot stays readable.
Use diagram = "all" when you explicitly want every
class-by-item probability edge.
For dense IRT/MIRT item axes, automatic diagrams use representative
item blocks and compact display labels such as i12 when
long common prefixes would make nodes collide. The original variable
names remain in the graph tables, and node_labels can
override the display labels when needed.
Default automatic layouts keep ordinary CFA and IRT indicators aligned on clean semantic layers. When a strict row would force straight edges through nearby nodes, dense bifactor, growth, LCA/LPA, and MIRT diagrams use shallow path-diagram layers or arcs to preserve straight edges without node collisions.
Custom layouts can also use a data frame with name,
x, and y:
layout <- data.frame(name = c("F", "x1", "x2"), x = c(0, -1, 1), y = c(0, -2, -2))
plot_lvm(graph, layout = layout)lvmPlot also works with a plain lavaan-style table. This
keeps it useful in scripts where you want to cache model output
first.
params <- data.frame(
lhs = c("visual", "visual", "visual", "textual", "textual", "textual",
"textual", "speed", "speed", "speed", "speed"),
op = c("=~", "=~", "=~", "=~", "=~", "=~", "~", "=~", "=~", "=~", "~"),
rhs = c("x1", "x2", "x3", "x4", "x5", "x6", "visual",
"x7", "x8", "x9", "textual"),
est = c(1, 0.55, 0.73, 1, 1.11, 0.93, 0.41, 1, 1.18, 1.08, 0.36),
std.all = c(0.77, 0.42, 0.58, 0.85, 0.81, 0.74, 0.39,
0.62, 0.71, 0.66, 0.34),
pvalue = c(NA, .001, .001, NA, .001, .001, .002, NA, .001, .001, .004)
)
plot_lvm(params)
lvm_tikz(params, standalone = FALSE)For lavaan multilevel models, lvmPlot keeps
same-named variables separate across levels and draws light
Within/Between bands:
model <- '
level: 1
fw =~ y1 + y2 + y3
out ~ fw
level: 2
fb =~ y1 + y2 + y3
out ~ fb + z
'
fit <- sem(model, data = dat, cluster = "school")
plot_lvm(fit, label = "std")
save_lvm_svg(fit, "multilevel-sem.svg", width = 9.4, height = 6.6)The same works from cached parameter tables containing a
level column.
The automatic layout is intentionally simple and SEM-shaped: latent variables and structural variables sit on a main row, while indicators are placed below their latent factor. For full control, pass coordinates:
layout <- data.frame(
name = c("visual", "textual", "speed", "x1", "x2", "x3",
"x4", "x5", "x6", "x7", "x8", "x9"),
x = c(-3, 0, 3, -4, -3, -2, -1, 0, 1, 2, 3, 4),
y = c(0, 0, 0, -2, -2, -2, -2, -2, -2, -2, -2, -2)
)
sem_tikz(params, layout = layout)Or use the compact matrix form:
sem_tikz(
params,
layout = matrix(c("visual", "textual", "speed",
"x1", "x4", "x7",
"x2", "x5", "x8",
"x3", "x6", "x9"),
nrow = 4, byrow = TRUE)
)=~), structural regressions
(~), and covariances (~~) are supported for
SEM-style models.diagram = "auto" and can be expanded with
diagram = "all".residuals = TRUE, but are hidden by default to keep
diagrams readable.
Need a high-speed mirror for your open-source project?
Contact our mirror admin team at info@clientvps.com.
This archive is provided as a free public service to the community.
Proudly supported by infrastructure from VPSPulse , RxServers , BuyNumber , UnitVPS , OffshoreName and secure payment technology by ArionPay.