Drawing Structural Equation Models with TikZ

This post presents TikZ scripts to draw the model path diagrams presented in this website.

I use the following TikZ libraries:

and the following LaTeX packages:

There is a standardised set of symbols used in the diagrams. The key symbols are shown below.

These symbols are defined as styles in SEMstyles.tex. As well, SEMstyles.tex loads the TikZ libraries and LaTeX packages.

Click to see SEMstyles.tex

\usepackage{tikz}
\usepackage{newpxtext,newpxmath}   % upright Greek
\usepackage{amsmath}               % Maths typesetting

\renewcommand{\familydefault}{\sfdefault}   % sans sarif
\usepackage{mathastext}                     % and apply it to maths mode

\usetikzlibrary{positioning, quotes, calc, arrows.meta, shapes, math}

\tikzset{
every edge quotes/.style = {fill = white},
> = {Stealth[length = 1.5mm]},       % Stealth arrow heads
shorten > = 1pt,                     % Both ends of the arrows (variances and covariances) ...        
shorten < = 1pt,                     % ... end 1pt short of the node
manifest/.style = {draw, inner sep = 3pt, minimum width = 1cm,
   minimum height = 0.85cm, align = center},
latent/.style = {ellipse, draw, inner sep = 3pt, minimum width = 1cm,
   minimum height = 0.85cm},
residual1/.style = {circle, draw, minimum size = 5mm, inner sep = 1pt},
residual2/.style = {ellipse, minimum size = 0.5pt, inner sep = 0pt},
regression/.style = {->, shorten < = 0pt, inner sep = 1.5pt},
covariance/.style = {<->, inner sep = 1.5pt},
variance/.style = {<->, inner sep = 1pt},
interaction/.style = {-{Stealth[sep = 1pt, length = 1.5mm] . Circle[length = 4pt]},
   shorten > = -2pt},
constant/.style = {draw, inner sep = 1pt, regular polygon,
   regular polygon sides = 3, minimum size = 5mm},
group/.style = {inner sep = 2pt, minimum width = 15mm, 
   minimum height = 5mm, align = center}
}

All that is left for the individual TikZ scripts to do is to position the symbols on the canvas.

The scripts are rendered as pdf files, then converted to svg files using inkscape.

The publications and their model diagrams

  • Jose, P. (2013). Doing statistical mediation and moderation. New York, NY: Guilford Press.
Mediation model diagram
Tikz code
\documentclass[border = 10pt]{standalone}
\input{../SEMstyles.tex}

\begin{document}
\begin{tikzpicture}

%% Manifest
\node [manifest] (X) {ple};
\node [manifest] (Y) [right = 5cm of X] {shs};
\node [manifest] (M) [above = 4.5cm of $(X) !0.5! (Y)$] {grat};

%% Regressions
\path [regression] 
   (X) edge ["c$'$"] (Y)
   (X.70) edge ["a"] (M.250)
   (M.290) edge ["b"] (Y.110);

%% Residuals
\node [residual1] (e2) [above right = 4mm of Y] {e$_2$};
\node [residual1] (e1) [above right = 4mm of M] {e$_1$};
\path [regression] 
   (e2) edge (Y.north east)
   (e1) edge (M.north east);

\end{tikzpicture}
\end{document}


  • Kurbanoglu, N. & Takunyaci, M. (2021). A structural equation modeling on relationship between self-efficacy, physics laboratory anxiety and attitudes. Journal of Family, Counseling and Education, 6(1), 47-56.
Mediation model diagram
Tikz code
\documentclass[border = 10pt]{standalone}
\input{../SEMstyles.tex}

\begin{document}
\begin{tikzpicture}

%% Manifest
\node [manifest] (SE) {Self\\Efficacy};
\node [manifest] (Anx) [right = 5cm of SE] {Anxiety};
\node [manifest] (Att) [above = 4.5cm of $(SE) !0.5! (Anx)$] {Attitude};

%% Regressions
\path [regression] 
   (SE) edge ["c$'$"] (Anx)
   (SE.70) edge ["a"] (Att.250)
   (Att.290) edge ["b"] (Anx.110);

%% Residuals
\node [residual1] (e2) [above right = 4mm of Anx] {e$_2$};
\node [residual1] (e1) [above right = 4mm of Att] {e$_1$};
\path [regression] 
   (e2) edge (Anx.north east)
   (e1) edge (Att.north east);

\end{tikzpicture}
\end{document}


  • Little, T., Slegers, D., & Card, N. (2006). A non-arbitrary method of identifying and scaling latent variables in SEM and MACS models. Structural Equation Modeling, 13(1), 59-72.
Model diagram for Reference-Group Method
Tikz code
\documentclass[border = 10pt]{standalone}
\input{../SEMstyles.tex}

\begin{document}
\begin{tikzpicture}

%%%% Grade 7
%% pos manifest
\node [manifest] (pos71) {$pos_1$};
\node [manifest] (pos72) [right = 1.5cm of pos71] {$pos_2$};
\node [manifest] (pos73) [right = 1.5cm of pos72] {$pos_3$};

%% neg manifest
\node [manifest] (neg71) [right = 3cm of pos73] {$neg_1$};
\node [manifest] (neg72) [right = 1.5cm of neg71] {$neg_2$};
\node [manifest] (neg73) [right = 1.5cm of neg72] {$neg_3$};

%% POS and NEG latents
\node [latent] (POS7) [above = 3cm of pos73] {$POS$};
\node [latent] (NEG7) [above = 3cm of neg71] {$NEG$};

%% Latent variances and covariance
\path [covariance] (POS7.35) edge ["$\upphi_{7,12}$", bend left = 40] (NEG7.145);
\path [variance] (POS7.120) edge ["$\upphi_{7,11}\!=\!1$" {above = 1pt},
   bend left = 100, looseness = 2.5] (POS7.60);
\path [variance] (NEG7.120) edge ["$\upphi_{7,22}\!=\!1$" {above = 1pt},
   bend left = 100, looseness = 2.5] (NEG7.60);

%% Latent means
\node [constant, black!30] (M7) [below = 1.5cm of $(pos73) !0.5! (neg71)$] {1};
\path [regression, black!30] (M7) edge ["$\upkappa_{7,1}\!=\!0$", pos = 0.6] (POS7);
\path [regression, black!30] (M7) edge ["$\upkappa_{7,2}\!=\!0$", pos = 0.6] (NEG7);

%%% POS factor
\foreach \i [count = \j] in {170, 150, 130} {
%% Loadings
\path [regression] (POS7) edge ["$\uplambda_\j$"] (pos7\j.90);

%% Residuals
\node [residual2] (e7\j) [above left = 4mm of pos7\j, xshift = 1mm] {$\uptheta_{7,\j}$};
\path [regression] (e7\j) edge (pos7\j.north west);

%% Intercepts
\path [regression, black!30] (M7.\i) edge ["$\uptau_\j$"] (pos7\j.south east);
}

%%% NEG factor
\foreach \i [count = \j, evaluate = \j as \k using int(\j + 3)] in {50, 30, 10} {
%% Loadings
\path [regression] (NEG7) edge ["$\uplambda_\k$"] (neg7\j.90);

%% Residuals
\node [residual2] (e7\k) [above right = 4mm of neg7\j, xshift = -1mm] {$\uptheta_{7,\k}$};
\path [regression] (e7\k) edge (neg7\j.north east);

%% Intercepts
\path [regression, black!30] (M7.\i) edge ["$\uptau_\k$"] (neg7\j.south west);
}


%%%% Grade 8
%% pos manifests
\node [manifest] (pos81) [below = 7.5cm of pos71] {$pos_1$};
\node [manifest] (pos82) [right = 1.5cm of pos81] {$pos_2$};
\node [manifest] (pos83) [right = 1.5cm of pos82] {$pos_3$};

%% neg manifests
\node [manifest] (neg81) [right = 3cm of pos83] {$neg_1$};
\node [manifest] (neg82) [right = 1.5cm of neg81] {$neg_2$};
\node [manifest] (neg83) [right = 1.5cm of neg82] {$neg_3$};

%% POS and NEG latents
\node [latent] (POS8) [above = 3cm of pos83] {$POS$};
\node [latent] (NEG8) [above = 3cm of neg81] {$NEG$};

%% Latent variances and covariance
\path [covariance] (POS8.35) edge ["$\upphi_{8,12}$", bend left = 40] (NEG8.145);
\path [variance] (POS8.120) edge ["$\upphi_{8,11}$" {name = C1, above = 1pt},
   bend left = 100, looseness = 2.5] (POS8.60);
\path [variance] (NEG8.120) edge ["$\upphi_{8,22}$" {name = C2, above = 1pt},
   bend left = 100, looseness = 2.5] (NEG8.60);   % C1 and C2 used later to position Grades and dashed line

%% latent means
\node [constant, black!30] (M8) [below = 1.5cm of $(pos83) !0.5! (neg81)$] {1};
\path [regression, black!30] (M8) edge ["$\upkappa_{8,1}$", pos = 0.6] (POS8);
\path [regression, black!30] (M8) edge ["$\upkappa_{8,2}$", pos = 0.6] (NEG8);

%%% POS factor
\foreach \i [count = \j] in {170, 150, 130} {
%% Loadings
\path [regression] (POS8) edge ["$\uplambda_\j$"] (pos8\j.90);

%% Residuals
\node [residual2] (e8\j) [above left = 4mm of pos8\j, xshift = 1mm] {$\uptheta_{8,\j}$};
\path [regression] (e8\j) edge (pos8\j.north west);

%% Intercepts
\path [regression, black!30] (M8.\i) edge ["$\uptau_\j$"] (pos8\j.south east);
}

%%% NEG factor
\foreach \i [count = \j, evaluate = \j as \k using int(\j + 3)] in {50, 30, 10} {
%% Loadings
\path [regression] (NEG8) edge ["$\uplambda_\k$"] (neg8\j.90);

%% Residuals
\node [residual2] (e8\k) [above right = 4mm of neg8\j, xshift = -1mm] {$\uptheta_{8,\k}$};
\path [regression] (e8\k) edge (neg8\j.north east);

%% Intercepts
\path [regression, black!30] (M8.\i) edge ["$\uptau_\k$"] (neg8\j.south west);
}

%% Grades
\coordinate (mid) at ($(M7) !0.5! ($(C1) !0.5! (C2)$)$);
\coordinate (C3) at (pos81 |- mid);   
\coordinate (C4) at (neg83 |- mid);   

\node [above = 0.5cm of C3] {Grade 7};
\node [below = 0.5cm of C3] {Grade 8};

\draw[densely dashed] ($(C3) !-1.5cm! (C4)$) -- ($(C4) !-1.5cm! (C3)$);

\end{tikzpicture}
\end{document}

Model diagram for Marker-Variable Method
Tikz code
\documentclass[border = 10pt]{standalone}
\input{../SEMstyles.tex}

\begin{document}
\begin{tikzpicture}

%%%% Grade 7
%% pos manifest
\node [manifest] (pos71) {$pos_1$};
\node [manifest] (pos72) [right = 1.5cm of pos71] {$pos_2$};
\node [manifest] (pos73) [right = 1.5cm of pos72] {$pos_3$};

%% neg manifest
\node [manifest] (neg71) [right = 3cm of pos73] {$neg_1$};
\node [manifest] (neg72) [right = 1.5cm of neg71] {$neg_2$};
\node [manifest] (neg73) [right = 1.5cm of neg72] {$neg_3$};

%% POS and NEG latents
\node [latent] (POS7) [above = 3cm of pos73] {$POS$};
\node [latent] (NEG7) [above = 3cm of neg71] {$NEG$};

%% Latent variances and covariance
\path [covariance] (POS7.35) edge ["$\upphi_{7,12}$", bend left = 40] (NEG7.145);
\path [variance] (POS7.120) edge ["$\upphi_{7,11}$" {above = 1pt},
   bend left = 100, looseness = 2.5] (POS7.60);
\path [variance] (NEG7.120) edge ["$\upphi_{7,22}$" {above = 1pt},
   bend left = 100, looseness = 2.5] (NEG7.60);

%% Latent means
\node [constant, black!30] (M7) [below = 1.5cm of $(pos73)!0.5!(neg71)$] {1};
\path [regression, black!30] (M7) edge ["$\upkappa_{7,1}$", pos = 0.6] (POS7);
\path [regression, black!30] (M7) edge ["$\upkappa_{7,2}$", pos = 0.6] (NEG7);

%%% POS factor
\foreach \i [count = \j] in {170, 150, 130} {
%% Loadings
\path [regression] (POS7) edge ["\ifnum \j = 3 {$\uplambda_\j\!=\!1$} \else {$\uplambda_\j$} \fi"] (pos7\j.90);

%% Residuals
\node [residual2] (e7\j) [above left = 4mm of pos7\j, xshift = 1mm] {$\uptheta_{7,\j}$};
\path [regression] (e7\j) edge (pos7\j.north west);

%% Intercepts
\path [regression, black!30] (M7.\i) edge ["\ifnum \j = 3 {$\uptau_\j\!=\!0$} \else {$\uptau_\j$} \fi"] (pos7\j.south east);
}

%%% NEG factor
\foreach \i [count = \j, evaluate = \j as \k using int(\j + 3)] in {50, 30, 10} {
%% Loadings
\path [regression] (NEG7) edge ["\ifnum \j = 1 {$\uplambda_\k\!=\!1$} \else {$\uplambda_\k$} \fi"] (neg7\j.90);

%% Residuals
\node [residual2] (e7\k) [above right = 4mm of neg7\j, xshift = -1mm] {$\uptheta_{7,\k}$};
\path [regression] (e7\k) edge (neg7\j.north east);

%% Intercepts
\path [regression, black!30] (M7.\i) edge ["\ifnum \j = 1 {$\uptau_\k\!=\!0$} \else {$\uptau_\k$} \fi"] (neg7\j.south west);
}

%%%% Grade 8
%% pos manifest
\node [manifest] (pos81) [below = 7.5cm of pos71] {$pos_1$};
\node [manifest] (pos82) [right = 1.5cm of pos81] {$pos_2$};
\node [manifest] (pos83) [right = 1.5cm of pos82] {$pos_3$};

%% neg manifest
\node [manifest] (neg81) [right = 3cm of pos83] {$neg_1$};
\node [manifest] (neg82) [right = 1.5cm of neg81] {$neg_2$};
\node [manifest] (neg83) [right = 1.5cm of neg82] {$neg_3$};

%% POS and NEG latents
\node [latent] (POS8) [above = 3cm of pos83] {$POS$};
\node [latent] (NEG8) [above = 3cm of neg81] {$NEG$};

%% Latent variances and covariance
\path [covariance] (POS8.35) edge ["$\upphi_{8,12}$", bend left = 40] (NEG8.145);
\path [variance] (POS8.120) edge ["$\upphi_{8,11}$" {name = C1, above = 1pt},
   bend left = 100, looseness = 2.5] (POS8.60);
\path [variance] (NEG8.120) edge ["$\upphi_{8,22}$" {name = C2, above = 1pt},
   bend left = 100, looseness = 2.5] (NEG8.60);

%% Latent means
\node [constant, black!30] (M8) [below = 1.5cm of $(pos83)!0.5!(neg81)$] {1};
\path [regression, black!30] (M8) edge ["$\upkappa_{8,1}$", pos = 0.6] (POS8);
\path [regression, black!30] (M8) edge ["$\upkappa_{8,2}$", pos = 0.6] (NEG8);

%%% POS factor
\foreach \i [count = \j] in {170, 150, 130} {
%% Loadings
\path [regression] (POS8) edge ["\ifnum \j = 3 {$\uplambda_\j\!=\!1$} \else {$\uplambda_\j$} \fi"] (pos8\j.90);

%% Residuals
\node [residual2] (e8\j) [above left = 4mm of pos8\j, xshift = 1mm] {$\uptheta_{8,\j}$};
\path [regression] (e8\j) edge (pos8\j.north west);

%% Intercepts
\path [regression, black!30] (M8.\i) edge ["\ifnum \j = 3 {$\uptau_\j\!=\!0$} \else {$\uptau_\j$} \fi"] (pos8\j.south east);
}

%%% NEG factor
\foreach \i [count = \j, evaluate = \j as \k using int(\j + 3)] in {50, 30, 10} {
%% Loadings
\path [regression] (NEG8) edge ["\ifnum \j = 1 {$\uplambda_\k\!=\!1$} \else {$\uplambda_\k$} \fi"] (neg8\j.90);

%% Residuals
\node [residual2] (e8\k) [above right = 4mm of neg8\j, xshift = -1mm] {$\uptheta_{8,\k}$};
\path [regression] (e8\k) edge (neg8\j.north east);

%% Intercepts
\path [regression, black!30] (M8.\i) edge ["\ifnum \j = 1 {$\uptau_\k\!=\!0$} \else {$\uptau_\k$} \fi"] (neg8\j.south west);
}

%% Grades
\coordinate (mid) at ($(M7) !0.5! ($(C1) !0.5! (C2)$)$);
\coordinate (C3) at (pos81 |- mid);   
\coordinate (C4) at (neg83 |- mid);   

\node [above = 0.5cm of C3] {Grade 7};
\node [below = 0.5cm of C3] {Grade 8};

\draw[densely dashed] ($(C3) !-1.5cm! (C4)$) -- ($(C4) !-1.5cm! (C3)$);

\end{tikzpicture}
\end{document}

Model diagram for Effects-Scaling Method
Tikz code
\documentclass[border = 10pt]{standalone}
\input{../SEMstyles.tex}
\usetikzlibrary{matrix}

\begin{document}
\begin{tikzpicture}

%%%% Grade 7
%% pos manifest
\node [manifest] (pos71) {$pos_1$};
\node [manifest] (pos72) [right = 1.5cm of pos71] {$pos_2$};
\node [manifest] (pos73) [right = 1.5cm of pos72] {$pos_3$};

%% neg manifest
\node [manifest] (neg71) [right = 3cm of pos73] {$neg_1$};
\node [manifest] (neg72) [right = 1.5cm of neg71] {$neg_2$};
\node [manifest] (neg73) [right = 1.5cm of neg72] {$neg_3$};

%% POS and NEG latents
\node [latent] (POS7) [above = 3cm of pos73] {$POS$};
\node [latent] (NEG7) [above = 3cm of neg71] {$NEG$};

%% Latent variances and covariance
\path [covariance] (POS7.35) edge ["$\upphi_{7,12}$", bend left = 40] (NEG7.145);
\path [variance] (POS7.120) edge ["$\upphi_{7,11}$" {above = 1pt},
   bend left = 100, looseness = 2.5] (POS7.60);
\path [variance] (NEG7.120) edge ["$\upphi_{7,22}$" {above = 1pt},
   bend left = 100, looseness = 2.5] (NEG7.60);

%% Latent means
\node [constant, black!30] (M7) [below = 1.5cm of $(pos73)!0.5!(neg71)$] {1};
\path [regression, black!30] (M7) edge ["$\upkappa_{7,1}$", pos = 0.6] (POS7);
\path [regression, black!30] (M7) edge ["$\upkappa_{7,2}$", pos = 0.6] (NEG7);

%%% POS factor
\foreach \i [count = \j] in {170, 150, 130} {
%% Loadings
\path [regression] (POS7) edge ["$\uplambda_\j$"] (pos7\j.90);

%% Residuals
\node [residual2] (e7\j) [above left = 4mm of pos7\j, xshift = 1mm] {$\uptheta_{7,\j}$};
\path [regression] (e7\j) edge (pos7\j.north west);

%% Intercepts
\path [regression, black!30] (M7.\i) edge ["$\uptau_\j$"] (pos7\j.south east);
}

%% NEG factor
\foreach \i [count = \j, evaluate = \j as \k using int(\j + 3)] in {50, 30, 10} {
\path [regression] (NEG7) edge ["$\uplambda_\k$"] (neg7\j.90);

%% Residuals
\node [residual2] (e7\k) [above right = 4mm of neg7\j, xshift = -1mm] {$\uptheta_{7,\k}$};
\path [regression] (e7\k) edge (neg7\j.north east);

%% Intercepts
\path [regression, black!30] (M7.\i) edge ["$\uptau_\k$"] (neg7\j.south west);
}

%%%% Grade 8
%% pos manifests
\node [manifest] (pos81) [below = 7.5cm of pos71] {$pos_1$};
\node [manifest] (pos82) [right = 1.5cm of pos81] {$pos_2$};
\node [manifest] (pos83) [right = 1.5cm of pos82] {$pos_3$};

%% neg manifests
\node [manifest] (neg81) [right = 3cm of pos83] {$neg_1$};
\node [manifest] (neg82) [right = 1.5cm of neg81] {$neg_2$};
\node [manifest] (neg83) [right = 1.5cm of neg82] {$neg_3$};

%% POS and NEG latents
\node [latent] (POS8) [above = 3cm of pos83] {$POS$};
\node [latent] (NEG8) [above = 3cm of neg81] {$NEG$};

%% Latent variances and covariance
\path [covariance] (POS8.35) edge ["$\upphi_{8,12}$", bend left = 40] (NEG8.145);
\path [variance] (POS8.120) edge ["$\upphi_{8,11}$" {name = C1, above = 1pt},
   bend left = 100, looseness = 2.5] (POS8.60);
\path [variance] (NEG8.120) edge ["$\upphi_{8,22}$" {name = C2, above = 1pt},
   bend left = 100, looseness = 2.5] (NEG8.60);

%% Latent means
\node [constant, black!30] (M8) [below = 1.5cm of $(pos83) !0.5! (neg81)$] {1};
\path [regression, black!30] (M8) edge ["$\upkappa_{8,1}$", pos = 0.6] (POS8);
\path [regression, black!30] (M8) edge ["$\upkappa_{8,2}$", pos = 0.6] (NEG8);

%%% POS factor
\foreach \i [count = \j] in {170, 150, 130} {
%% Loadings
\path [regression] (POS8) edge ["$\uplambda_\j$"] (pos8\j.90);

%% Residuals
\node [residual2] (e8\j) [above left = 4mm of pos8\j, xshift = 1mm] {$\uptheta_{8,\j}$};
\path [regression] (e8\j) edge (pos8\j.north west);

%% Intercepts
\path [regression, black!30] (M8.\i) edge ["$\uptau_\j$"] (pos8\j.south east);
}

%%% NEG factor
\foreach \i [count = \j, evaluate = \j as \k using int(\j + 3)] in {50, 30, 10} {
%% Loadings
\path [regression] (NEG8) edge ["$\uplambda_\k$"] (neg8\j.90);

%% Residuals
\node [residual2] (e8\k) [above right = 4mm of neg8\j, xshift = -1mm] {$\uptheta_{8,\k}$};
\path [regression] (e8\k) edge (neg8\j.north east);

%% Intercepts
\path [regression, black!30] (M8.\i) edge ["$\uptau_\k$"] (neg8\j.south west);
}

%% Constraints
\matrix [matrix of nodes, draw, cells = {anchor = west}] at (0, 4) {
   \bf{Constraints}\\
   $\uplambda_1 + \uplambda_2 + \uplambda_3 = 3$ \\
   $\uplambda_4 + \uplambda_5 + \uplambda_6 = 3$ \\ [2ex]

   $\uptau_1 + \uptau_2 + \uptau_3 = 0$ \\
   $\uptau_4 + \uptau_5 + \uptau_6 = 0$ \\
};

%% Grades
\coordinate (mid) at ($(M7) !0.5! ($(C1) !0.5! (C2)$)$);
\coordinate (C3) at (pos81 |- mid);   
\coordinate (C4) at (neg83 |- mid);   

\node [above = 0.5cm of C3] {Grade 7};
\node [below = 0.5cm of C3] {Grade 8};

\draw[densely dashed] ($(C3) !-1.5cm! (C4)$) -- ($(C4) !-1.5cm! (C3)$);

\end{tikzpicture}
\end{document}


  • Thompson, M., Liu, Y. & Green, S. (2023). Flexible structural equation modeling approaches for analyzing means. In R. Hoyle (Ed.), Handbook of structural equation modeling (2nd ed., pp. 385-408). New York, NY: Guilford Press.
one-way ANOVA model diagram
Tikz code
\documentclass[border = 10pt]{standalone}
\input{../SEMstyles.tex}

\begin{document}
\begin{tikzpicture}

\def \gap {2cm}                                 % separation between dependents
\def \dep {Life\\Satisfaction}                  % dependent variable
\def \groups {No strategy, Discussion, Exercise}   % groups

%% Dependents
\node [manifest] (y1) {\dep};
\node [manifest] (y2) [right = \gap of y1] {\dep};
\node [manifest] (y3) [right = \gap of y2] {\dep};

\foreach \j [count = \i, remember = \i as \lasti] in \groups {
   %% Residuals
   \node [residual2] (e\i) [below right = 4mm of y\i] {e};
   \path [regression] (e\i) edge (y\i.south east);

   %% Means
   \node [constant] (const\i) [above = 1.5cm of y\i] {1};
   \path [regression] (const\i) edge ["a$_\i$"] (y\i);

   %% Groups
   \node [group] (gp\i) [above = 0.5cm of const\i] {Group \i:\\\j};
   
   %% Separators
   \ifnum \i > 1 
      \coordinate (m1) at ($(y\i.south) !0.5! (y\lasti.south)$);
      \coordinate (m2) at ($(gp\i.north) !0.5! (gp\lasti.north)$);      
      \draw [densely dashed] ($(m1) !0.6cm!270: (y\i)$) -- ($(m2) !0.2cm!90: (gp\i)$);
   \else \relax
  \fi
}

\end{tikzpicture}
\end{document}

one-way ANCOVA model diagram
Tikz code
\documentclass[border = 10pt]{standalone}
\input{../SEMstyles.tex}

\begin{document}
\begin{tikzpicture}

\def \gap {2cm}                     % separation between dependents
\def \dep {Life\\Satisfaction}      % dependent variable
\def \groups {No strategy, Discussion, Exercise}

%% Dependents
\node [manifest] (y1) {\dep};
\node [manifest] (y2) [right = \gap of y1] {\dep};
\node [manifest] (y3) [right = \gap of y2] {\dep};

\foreach \j [count = \i, remember = \i as \lasti] in \groups {
  %% Covariates
   \node [manifest] (pre\i) [below = 1.5cm of y\i] {pre$_C$};
   \path [regression] (pre\i) edge ["b"] (y\i);

   %% Residuals
   \node [residual2] (e\i) [below right = 4mm of y\i] {e};
   \path [regression] (e\i) edge (y\i.south east);

   %% Means
   \node [constant] (const\i) [above = 1.5cm of y\i] {1};
   \path [regression] (const\i) edge ["a$_\i$"] (y\i);

   %% Groups
   \node [group] (gp\i) [above = 0.5cm of const\i] {Group \i:\\\j};
   
   %% Separators
   \ifnum \i > 1 
      \coordinate (m1) at ($(pre\i.south) !0.5! (pre\lasti.south)$);
      \coordinate (m2) at ($(gp\i.north) !0.5! (gp\lasti.north)$);      
      \draw [densely dashed] ($(m1) !0.2cm!270: (y\i)$) -- ($(m2) !0.2cm!90: (gp\i)$);
   \else \relax   
   \fi
}

\end{tikzpicture}
\end{document}

two-way ANOVA model diagram
Tikz code
\documentclass[border = 10pt]{standalone}
\input{../SEMstyles.tex}

\begin{document}
\begin{tikzpicture}

\def \gap {2cm}            % separation between dependents
\def \strat {{"No strategy", "Discussion", "Exercise"}}  % strategy groups
\def \gend {{"Male", "Female"}}                          % gender groups

%% Dependents
\node [manifest] (y1) {Y};
\foreach \i [remember = \i as \lasti (initially 1)] in {2,...,6} {
    \node [manifest] (y\i) [right = \gap of y\lasti] {Y};
}

\foreach \i [remember = \i as \lasti] in {1,...,6} {
   %% Residuals
   \node [residual2] (e\i) [below right = 4mm of y\i] {e};
   \path [regression] (e\i) edge (y\i.south east) ;

   %% Means
   \node [constant] (const\i) [above = 1.5cm of y\i] {1};
   \path [regression] (const\i) edge ["a$_\i$"] (y\i);

   %% Groups
   \tikzmath{\firstrow = \strat[mod((\i-1), 3)]; \secondrow = \gend[int((\i-1)/3)];}
   \node [group] (gp\i) [above = 0.5cm of const\i] {Group \i:\\\firstrow\\\secondrow};
   
   %% Separators
   \ifnum \i > 1 
      \coordinate (m1) at ($(y\i.south) !0.5! (y\lasti.south)$);
      \coordinate (m2) at ($(gp\i.north) !0.5! (gp\lasti.north)$);      
      \draw [densely dashed] ($(m1) !0.6cm!270: (y\i)$) -- ($(m2) !0.2cm!90: (gp\i)$);
  \fi
}

\end{tikzpicture}
\end{document}

one-way MANOVA model diagram
Tikz code
\documentclass[border = 10pt]{standalone}
\input{../SEMstyles.tex}
%\usetikzlibrary{bending}
\begin{document}
\begin{tikzpicture}

%% Three groups of four variables.
%% Labels in each group are Y1 to Y4.
%% Names are y1 to y12.

\def \groups {No strategy, Discussion, Exercise}
\def \gaps {0.4cm}     % small gap between two variables within groups
\def \gapb {1cm}       % large gap between two variables between groups
\def \angles {240, 260, 280, 300}    % angeles out of constant

%% Covariances in and out angles and bend
\def \eout {{350, 335, 310}}     % out angle
\def \ein {{190, 205, 230}}    % in angle
\def \bend {{40, 55, 60}}      % bend

%% Indicators and their residuals
\node [manifest] (y1) {Y$_1$};
\node [residual2] (e1) [below = 4mm of y1] {e$_{Y_1}$};
\path [regression] (e1) edge (y1);

\foreach \i [remember = \i as \lasti (initially 1)] in {2,...,12}{
   \tikzmath{\labnum = int(mod(\i - 1, 4) + 1);}

   \ifnum \labnum = 1      %% Use label number to determine size of gap
      \def \gap {\gapb} 
   \else 
      \def \gap {\gaps} 
   \fi   

   \node [manifest] (y\i) [right = \gap of y\lasti] {Y$_\labnum$};
   \node [residual2] (e\i) [below = 4mm of y\i] {e$_{Y_\labnum}$};
   \path [regression] (e\i) edge (y\i);  
}

\foreach \j [count = \i, remember = \i as \lasti] in \groups {
   \tikzmath{\lasty = int(4*\i);           % label number for last Y in each group
             \firsty = int(\lasty - 3);}   % label number for first Y in each group

   %% Means (above the middle of each group)
   \tikzmath{\xa = int(\firsty + 1); \xb = int(\firsty + 2;}
   \node [constant] (const\i) [above = 3cm of $(y\xa) !0.5! (y\xb)$] {1};
   \foreach \c [count = \b] in \angles {
      \tikzmath{\d = int((\i - 1)*4 + \b);} 
      \path [regression] (const\i.\c) edge ["a$_{\i{Y_\b}}$", pos = 0.65] (y\d.90);
   }

   %% Groups (above the constant)
   \node [group] (gp\i) [above = 0.5cm of const\i] {Group \i:\\\j};

   %% Separators
   \ifnum \i > 1 
      \tikzmath{\prevy = (int(\firsty - 1);}
      \coordinate (m1) at ($(y\firsty.south) !0.5! (y\prevy.south)$);
      \coordinate (m2) at ($(gp\i.north) !0.5! (gp\lasti.north)$);   
      \draw [densely dashed] ($(m1) !1.5cm!90: (y\prevy.south)$) -- ($(m2) !0.2cm!90: (gp\i.north)$);
  \fi
}

%% Covariances
\foreach \g in {1,5,9} {              % three groups of four variables - label for first in each group
   \foreach \i in {1,2,3} {                           
      \foreach \j [parse = true] in {\i+1,...,4}{     
        \tikzmath{\idash = int(\g + \i - 1);  \jdash = int(\g + \j - 1);}    % out and in labels for e
        \tikzmath{\index = \j - \i - 1;}                                     % array element
        \tikzmath{\first = \eout[\index]; \second = \ein[\index]; \third = \bend[\index];}
        \path [covariance, black!40,  bend right = \third, looseness = 0.75] (e\idash.\first) edge (e\jdash.\second); 
}}}

\end{tikzpicture}
\end{document}

one-way LATENT model diagram
Tikz code
\documentclass[tikz, border = 10pt]{standalone}
\input{../SEMstyles.tex}

\begin{document}
\begin{tikzpicture}

%% Three groups of four variables.
%% Labels in each group are Y1 to Y4.
%% Names are y1 to y12.

\def \groups {No strategy, Discussion, Exercise}
\def \gaps {0.4cm}     % small gap between indicators
\def \gapb {1cm}       % large gap between indicators
\def \angles {270/240, 282/253, 294/263, 306/270}  % angles for intercepts and loadings
                                                   % out of constant and latent

%% Indicators and their residuals
\node [manifest] (y1) {Y$_1$};
\node [residual2] (e1) [below = 4mm of y1] {e$_{Y_1}$};
\path [regression] (e1) edge (y1);

\foreach \i [remember = \i as \lasti (initially 1)] in {2,...,12}{
   \tikzmath{\labnum = int(mod(\i - 1, 4) + 1);}

   \ifnum \labnum = 1      %% Use label number to determine size of gap
      \def \gap {\gapb} 
   \else 
      \def \gap {\gaps} 
   \fi   

   \node [manifest] (y\i) [right = \gap of y\lasti] {Y$_\labnum$};
   \node [residual2] (e\i) [below = 4mm of y\i] {e$_{Y_\labnum}$};
   \path [regression] (e\i) edge (y\i);  
}

\foreach \j [count = \i, remember = \i as \lasti] in \groups {
   \tikzmath{\lasty = int(4*\i);           % label number for last Y in each group
             \firsty = int(\lasty - 3);}   % label number for first Y in each group

   %% Latents and their variances (above last y in each group)
   \node [latent] (latent\i) [above = 3cm of y\lasty] {LS};
   \path [variance, bend left = 110, looseness = 3] (latent\i.125) edge ["d"] (latent\i.60);

   %% Means (above first y in each group)
   \coordinate (cc) at (y\firsty |- latent\i);   % coordinates for the constant
   \node [constant] (const\i) at (cc) {1};   
   \path[regression] (const\i) edge ["\ifnum \i = 1 {0} \else {a$_\i$} \fi"] (latent\i);
   
   \foreach \c/\d [count = \k] in \angles {
      %% Intercepts
      \tikzmath{\e = int((\i - 1)*4 + \k);} 
      \path [regression] (const\i.\c) edge ["$\uptau_\k$", pos = 0.5 - 1/(6*(5-\k))] (y\e.90);

      %% Loadings
      \path[regression] (latent\i.\d) edge ["\ifnum \k = 1 {1} \else {$\uplambda_\k$} \fi", 
      pos = 0.5 - 1/(6*\k)] (y\e.90);
   }

   %% Groups (above middle of each group of variables)
   \tikzmath{\xa = int(\firsty + 1); \xb = int(\firsty + 2;}
   \node [group] (gp\i) [above = 5cm of $(y\xa) !0.5! (y\xb)$] {Group \i:\\\j};

   %% Separators
   \ifnum \i > 1
      \tikzmath{\prevy = (int(\firsty - 1);}
      \coordinate (m1) at ($(y\firsty.south) !0.5! (y\prevy.south)$);
      \coordinate (m2) at ($(gp\i.north) !0.5! (gp\lasti.north)$);   
      \draw [densely dashed] ($(m1) !0.7cm!90: (y\prevy.south)$) -- ($(m2) !0.2cm!90: (gp\i.north)$);
   \fi
}  
      
\end{tikzpicture}
\end{document}