[Tex/LaTex] Drawing path diagrams with minimal effort

diagrams

I wonder how to draw such path diagrams with minimal effort.

enter image description here

Best Answer

Here is another solution using TiKZ, but without operating on a grid. [It has been edited somewhat to simplify the code, based on the comments.]

TiKZ is an awesome package for making diagrams. Because you do it programmatically rather than interactively, you can make images very precisely, and with higher-level concepts; but it involves some forethought if you want to do it quickly. I will describe the workflow, in order to describe not just what code would produce this image, but to indicate how you can go about designing code to produce this image.

Obligatory pre-amble

Use something like this at the beginning of your document.

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{calc,shapes}

\begin{document}

The command \usetikzlibrary should always accompany \usepackage{tikz}, because there's always some library you're going to want to use. In our case, we want calc to compute relative positions, and shapes to get e.g. elliptical nodes. The excellent documentation for TiKZ and PGF will always let you know what libraries you need to put things in your picture.

Defining styles

Your picture has several nodes (boxes, ellipses) with common features. We define different styles of nodes which more-or-less duplicate them. You should name them more intelligently than I have. All of these are settings which describe how they ought to look. (The "inner sep" parameter defines how far the border must be from the text from the default; all of the values were achieved by trial and error.)

\tikzstyle{input}=[font=\scriptsize\sffamily\bfseries]
\tikzstyle{yelbox}=[rectangle,draw=black,fill=yellow,
                    font=\scriptsize\sffamily\bfseries,inner sep=6pt]
\tikzstyle{yelball}=[ellipse,draw=black,fill=yellow,
                     font=\scriptsize\sffamily\bfseries,inner sep=-1pt]
\tikzstyle{whiteball}=[ellipse,draw=black,fill=white,
                       font=\scriptsize\sffamily\bfseries,inner sep=-1pt]

There are also different kinds of arrows which are repeated: we define styles for those as well. (Here, "latex" is just the name of the triangular arrow-head; I would prefer to use "stealth" myself, but I'm duplicating your image in this case.)

\tikzstyle{arr}=[-latex,black,line width=1pt]
\tikzstyle{doublearr}=[latex-latex,black,line width=1pt]
\tikzstyle{bluearr}=[-latex,blue,line width=1pt]

Ad-hoc commands

Some of your nodes have features such as labels with multiple lines. We're going to make a macro to produce those labels nicely. The default width of 3em was achieved by trial-and-error.

\newcommand\nodelabel[3][3em]{\parbox{#1}{\centering #2 \\ #3 }}

In order to more quickly prototype the image, I want fast control over the spacing of the nodes vertically, which is where there is the most variation (and the most non-uniformity). I will define a scaling factor that I can adjust for this (set to a negative number, to change the normal TiKZ convention so that larger numbers correspond to larger distances downward).

\newcommand\vertspacing{-1.5}

Building the picture

Here we go:

\begin{tikzpicture}[scale=0.8]

The scale was found by trial-and-error to get it to fit on a single page. You can also independently control the x-scaling and y-scaling, but I didn't do it that way, because I wanted to reverse the directions for co-ordinates anyway, and could do the scaling another way. (You can do this with a negative y-scaling, but then it messes up with the corners of the nodes, such as .north and .south which I will use below.)

While you're building this, you should be re-compiling all the time to find out whether your placement is working well, and to adjust the styles of the nodes to make their appearance work better. But don't spend too much time making everything perfect; get things just almost-right first, and then make them perfect at the end.

Building the E nodes

These are the nodes with E labels on the left and right. We will give them names so that we can refer to the nodes when drawing arrows.

I eye-balled the relative vertical spacing of the nodes; wherever there was a larger gap, I settled on an incremental spacing of 0.3 or 0.4. The horizontal positions were also eye-balled (and tweaked slightly), using negative/positive values to ensure symmetry.

\foreach \label/\row in
    {E16/1, E17/2.3, E14/3.3, E15/4.7, E18/5.7, E19/7} 
        \node [input] (\label) at ($(-7.5,\vertspacing*\row)$) {\label};

\foreach \label/\row in
    {E10/1, E11/2.3, E8/3.3, E9/4.7, E12/5.7, E13/7}
        \node [input] (\label) at ($(7.5,\vertspacing*\row)$) {\label};

The (\label) assigns the name of the node which we will use later; the {\label} tells what label the nodes should be printed with.

Building the V nodes

These are the nodes with V labels, which will be the names of the nodes. They also have separate titles, which are the Axxx#-style part of their printed labels. They have similar vertical positioning as the input nodes. We produce their labels using the \nodelabel macro we defined earlier.

\foreach \title/\label/\row in
    {GSSC1/V16/1, GSCR1/V17/2.3, ASCC1/V14/3.3, ASCB1/V15/4.7, AAET1/V18/5.7, AAMA1/V19/7}
        \node [yelbox] (\label) at ($(-5,\vertspacing*\row)$) {\nodelabel{\title}{\label}};

\foreach \title/\label/\row in
    {GSSC2/V10/1, GSCR2/V11/2.3, ASCC2/V8/3.3, ASCB2/V9/4.7, AAET2/V12/5.7, AAMA2/V13/7}
        \node [yelbox] (\label) at ($(5,\vertspacing*\row)$) {\nodelabel{\title}{\label}};

We use the V# part of the label to name the nodes, because it's shorter, and because they match up with the names of the neighboring E nodes; this will be convenient later.

Building the F nodes

These are the elliptical nodes with the F labels. This is again similar to the V nodes. Their vertical positioning is basically at the gaps of the V nodes; this was computed by hand, though if you were slightly fancier you could have TiKZ do it for you. The width of 3.5em is chosen to force the ellipse to have about the same shape as in the original diagram.

\foreach \title/\label/\row in
    {GSC/F1/1.65/4, ASC/F2/4/4, AA/F3/6.35/4}
        \node [whiteball] (\label) at ($(-2,\vertspacing*\row)$) 
                          {\nodelabel[3.5em]{\title \\ I}{\label}};

\foreach \title/\label/\row in
    {GSC/F4/1.65, ASC/F5/4, AA/F6/6.35}
        \node [yelball] (\label) at ($(2,\vertspacing*\row)$) 
                          {\nodelabel[3.5em]{\title \\ II}{\label}};

Building the D nodes

For each of the F nodes on the right-hand side, there is a D node above and to the right. We place a D node for each one at a fixed position relative to the corresponding F node.

\foreach \label/\parent/\row in
    {D4/F4/.7, D5/F5/3, D6/F6/5.4}
        \node [input] (\label) at ($(\parent) + (0.5,-1*\vertspacing)$) {\label};

If you were to end the picture here by adding \end{tikzpicture}\end{document}, it would look like this:

Partially completed diagram

Arrow drawing

Here, we take advantage of the labelling scheme for the nodes. In this case, because the labels are fairly uniform throughout the diagram, we basically just need to say "connect each E node with the appropriate V node", and similarly for the D nodes to the F nodes (with the appropriate style of arrow).

\foreach \j in {16, 17, 14, 15, 18, 19, 10, 11, 8, 9, 12, 13}
    \draw [bluearr] (E\j) to (V\j);

\foreach \j in {4, 5, 6}
    \draw [bluearr] (D\j) to (F\j);

The V and F nodes don't have such a simple naming scheme, but that's okay; we just specify the full node names. Also, we would like the arrows to terminate precisely at the middle of the vertical sides of the boxes; we do this by specifying the "east" or "west" anchors for these nodes, as appropriate.

\foreach \F/\Va/\Vb in  {F1/V16/V17, F2/V14/V15, F3/V18/V19}
        { \draw [arr] (\F) to (\Va.east); \draw [arr] (\F) to (\Vb.east); }

\foreach \F/\Va/\Vb in  {F4/V10/V11, F5/V8/V9, F6/V12/V13}
        { \draw [arr] (\F) to (\Va.west); \draw [arr] (\F) to (\Vb.west); }

The most complicated arrow to do in the diagram is between F1 and F3; we want this arrow to be anchored precisely at the centre-right, and to curve. We can do this by specifying the "east" anchor, and by specifying the angles (relative to due east) that the arrow should arc from. The angles were chosen by eye-balling.

\draw [doublearr, out=-60, in=60] (F1.east) to (F3.east);

The other arrows are fairly simple, and use the techniques described thus far.

\draw [doublearr] (F1) to (F2);
\draw [doublearr] (F2) to (F3);
\draw [arr]       (F1) to (F5.north);
\draw [arr]       (F1) to (F6.north);
\draw [arr]       (F2) to (F4.west);
\draw [arr]       (F2) to (F6.west);
\draw [arr]       (F3) to (F4.south);
\draw [arr]       (F3) to (F5.south);

Finishing up

And that's it!

\end{tikzpicture}
\end{document}

The resulting figure:

The complete diagram

It's a pretty faithful representation of the original, if I so say so myself; And I made it in one hour, starting from nothing but the original to go by — some of which was spent looking up forgotten bits of TiKZ as I went!