# Not repeating myself so much in TikZ diagrams

I am writing a document which defines a network protocol. I illustrate key message exchanges with diagrams like this:

The TikZ source for this diagram is:

\documentclass{article}
\usepackage{tikz}
\usepackage{amsmath}
\usetikzlibrary{arrows}
\begin{document}
\begin{tikzpicture}
\node (C0) at (0.2,7.7) {$C$} ;
\node [coordinate] (Cn) at (0.2,0)   {} ;
\node (S0) at (4.8,7.7) {$S$} ;
\node [coordinate] (Sn) at (4.8,0)   {} ;

\begin{scope}[shape=coordinate]
\draw (C0) -- (Cn)
node[pos=.10] (C1) {}
node[pos=.19] (C2) {}
node[pos=.32] (C3) {}
node[pos=.45] (C4) {}
node[pos=.58] (C5) {}
node[pos=.67] (C6) {}
node[pos=.80] (C7) {}
;

\draw (S0) -- (Sn)
node[pos=.10] (S1) {}
node[pos=.19] (S2) {}
node[pos=.32] (S3) {}
node[pos=.45] (S4) {}
node[pos=.58] (S5) {}
node[pos=.67] (S6) {}
node[pos=.80] (S7) {}
;
\end{scope}
\begin{scope}[above,->,>=angle 60] \footnotesize
\draw (C1) -- node {$C^\prime_\alpha[\alpha,99,l_r,,\cdots]$} (S1) ;
\draw (C2) -- node {$C^\prime_\alpha[\alpha,100,l_s,\text{\textsc{fin}},\cdots]$}
(S2) ;
\draw (S3) -- node{$S^\prime_\alpha[\alpha,60,l_t,,\cdots]$} (C3) ;
\draw (C4) -- node{$C^\prime_\alpha[\alpha,101,0,,]$}        (S4) ;
\draw (S5) -- node{$S^\prime_\alpha[\alpha,61,l_u,,\cdots]$} (C5) ;
\draw (S6) -- node{$S^\prime_\alpha[\alpha,62,l_v,,\cdots]$} (C6) ;
\draw (C7) -- node{$C^\prime_\alpha[\alpha,102,0,,]$}        (S7) ;
\end{scope}
\end{tikzpicture}
\end{document}


This feels a bit repetitive already, but worse than that, there are six of these diagrams and the source is almost the same for each, except for the labels on the arrows, how many there are, and which way they're going. (Two of the diagrams have arrows that are not just horizontal lines -- they turn and cross another arrow -- but it is okay if I have to do something special for those.)

Please suggest ways to reduce the repetitiveness both within the diagram and between successive diagrams. I could also very much use a way to position the arrow endpoints that was an absolute distance along the relevant line, rather than a relative distance; having coded all the diagrams like this, it turns out that they're all too tall, but I can't chop off the extra space at the bottom without changing all the [pos=X] annotations as well.

-
Not a solution but, do you know Message Sequence Chart package? – Ignasi Nov 14 '11 at 19:21
Instead of posting code snippet, it is always best to compose a compilable MWE that illustrates the problem including the \documentclass and the appropriate packages so that those trying to help don't have to recreate it. – Peter Grill Nov 14 '11 at 20:27
For chopping off the extra space at the bottom, you can have a look at clipping, section 2.11 Clipping a Path in the manual. – Torbjørn T. Nov 14 '11 at 20:46
@Ignasi Thanks for the tip, I'll look at that. – Zack Nov 14 '11 at 23:08
@PeterGrill The example is now a complete document. – Zack Nov 14 '11 at 23:08

You don't even need the calc library here. Since you can use \foreach inside of a \draw sequence:

\node (C0) at (0.2,0) {$C$};
\draw (C0)
\foreach [count=\name] \y in {0.77, 0.693, 1.001, 1.001, 1.001, 0.693, 1.001, 1.54}
{
-- ++(0,-\y) coordinate (C\name)
};


The [count=\name] syntax I learned from Torbjørn T. (thanks!). The benefit is that you don't have to calculate the whole length, because you only give the difference between two coordinates. Now we can wrap this code easily into a macro:

\newcommand{\drawparties}[1]{
\node (C0) at (0.2,0) {$C$} ;
\draw (C0)
\foreach [count=\name] \y in {#1}
{
-- ++(0,-\y) coordinate (C\name)
};
\node (S0) at (4.8,0) {$S$};
\draw (S0)
\foreach [count=\name] \y in {#1}
{
-- ++(0,-\y) coordinate (S\name)
};
}


Note: this macro does not depend on the numbers of the drawn nodes. We can also shorten your arrow code, if it is really such repetitive as in your example to:

\newcommand{\drawsend}[2]{\draw[->,>=angle 60] (C#1) -- node[above, font={\footnotesize}] {$C^\prime_\alpha[\alpha,#2]$} (S#1);}
\newcommand{\drawreceive}[2]{\draw[<-,>=angle 60] (S#1) -- node[above, font={\footnotesize}]{$S^\prime_\alpha[\alpha,#2]$} (C#1);}


Then the whole code cooks down to:

\begin{tikzpicture}
\drawparties{0.77, 0.693, 1.001, 1.001, 1.001, 0.693, 1.001, 1.54}

\drawsend{1}{99,l_r,,\cdots}
\drawsend{2}{100,l_s,\text{\textsc{fin}},\cdots}
\drawsend{4}{101,0,,}
\drawsend{7}{102,0,,}
\end{tikzpicture}


I personally don't like to use macros (like the one above) in tikz because they feel like an alien body inside of a tikzpicture. But in this case you can really save some typing in particular if you are using it more than one time.

-

You can use for loops to repeat stuff in TikZ. This is described in chapter 56, Repeating Things: The Foreach Statement in the manual.

For example, you could draw the vertical lines and make the coordinate nodes with

\foreach \x/\y/\n in {C0/Cn/C, S0/Sn/S}
{\draw (\x) -- (\y)
\foreach [count=\name] \p in {0.10,0.19,0.32,0.45,0.58,0.67,0.8}
{node[pos=\p] (\n\name) {}};
};


This will go through the loop for each of the \x/\y/\n combinations. So the first time through it will draw the line from C0 to Cn, then go through the "inner" loop, where the coordinate nodes are placed along this line. The count=\name option allows you to access the index of the item in the list. Hence, \n\name will give you C1, C2 etc.

You also asked for a way to position arrow endpoints an absolute distance along the line. There are probably several ways of doing this, the following uses the calc library for coordinate calculations:

\foreach [count=\name] \y in {1,2,...,7} {
\coordinate (C\name) at ($(C0) + (0,-\y)$);
\coordinate (S\name) at ($(S0) + (0,-\y)$);
};


1,2,...,7 is the same as 1,2,3,4,5,6,7. This places coordinates a distance \y below the C0 and S0 nodes. Of course, this method doesn't work well for non vertical (or non horizontal) lines.

-

I think you can get away with a TikZ matrix as follows:

\documentclass{article}
\usepackage{amsmath,tikz}
\usetikzlibrary{arrows,matrix}
\begin{document}
\begin{tikzpicture}[>=angle 60,every left delimiter/.style={xshift=1.5ex},%
every right delimiter/.style={xshift=-1.5ex}]
\matrix[matrix of math nodes,nodes in empty cells,row sep={9mm,between origins},%
nodes={minimum width=4cm},left delimiter=|,right delimiter=|,%
label={north east:$S$},label={north west:$C$}] (listmat) at (0,4)
{\\
C^\prime_\alpha[\alpha,99,l_r,,\cdots]\\
C^\prime_\alpha[\alpha,100,l_s,\text{\textsc{fin}},\cdots]\\
S^\prime_\alpha[\alpha,60,l_t,,\cdots] \\
C^\prime_\alpha[\alpha,101,0,,]\\
S^\prime_\alpha[\alpha,61,l_u,,\cdots]\\
S^\prime_\alpha[\alpha,62,l_v,,\cdots]\\
C^\prime_\alpha[\alpha,102,0,,]\\
\\
};
\def\alef{<-}
\def\arig{->}
\foreach \x/\ddir in {2/\arig,3/\arig,4/\alef,5/\arig,6/\alef,7/\alef,8/\arig}{
\draw[\ddir] (listmat-\x-1.south west) --  (listmat-\x-1.south east);
}
\end{tikzpicture}
\end{document}


Here is the result

I am committing a few TeX crimes by defining things in a very bad location etc. Moreover, the numbers a bit hand-tuned. The lame variable names are to avoid conflicts. I didn't spend more time on it since I am not even sure if this is what you wanted. If you wish to use, please let me know which part needs tweaking or a remake.

-
 Why are you \defing \alef and \arig here? Writing -> and <- directly in the foreach is shorter, clearer and doesn't require additional definitions. – Roelof Spijker Nov 15 '11 at 8:26 @wh1t3 Not a big deal actually. Just for the ease of typing. When I tried, it became pretty tedious to follow dashes, > and commas. – percusse Nov 15 '11 at 10:38