4

I want to draw diagrams that look a bit like this. Mathematically, you could describe them as directed acyclic graphs with dangling arcs. They're somewhat reminiscent of route diagrams.

Desired output

enter image description here

Source here. There are four lines dangling in at the top of diagram, then four nodes that operate on these lines, and finally three lines dangling out of the bottom of the diagram. I know how to create this particular diagram in TikZ, but I want to make a package for creating them in general.

Desired input

It is important to me that:

  • the user is required to specify as little as possible, so that it is easy to create and modify big diagrams, and that
  • the diagram is composed in a row-by-row fashion.

This is the code I would like my user to type, in order to generate the picture above (I explain the code in the next subsection):

\begin{wickersondiagram}[%
  initial lines={a/{pos=10}, b/{pos=30}, c/{pos=50}, d/{pos=70}}]
\step[left=0, right=40, text=decide,%
  incoming lines={a,b},%
  outgoing lines={e/{pos=10,label=yes}, f/{pos=30,label=no}}]
\\
\step[left=20, right=60,%
  pale,%
  incoming lines={f,c},%
  outgoing lines={g/{pos=30},h/{pos=50}}]
\\
\step[left=0, right=40, text=join,%
  incoming lines={e,g},%
  outgoing lines={i/{pos=20}}]
\step[left=60, right=80,%
  incoming lines={d},%
  outgoing lines={j/{pos=70}}]
\\
\end{wickersondiagram}

By 'row-by-row', I mean that if I removed the last few rows from this code, I would still obtain a perfectly valid picture, and it would look just like the one above but with the lower portion missing.

Explanation of desired input

The user starts the diagram by specifying the horizontal placements of the initial lines. Each line is assigned a name, here a, b, c and d. While constructing the diagram, my package should maintain an implicit 'state', containing the names of the lines and their placements. At this point in the construction, the state is

{a ↦ 10, b ↦ 30, c ↦ 50, d ↦ 70}.

Then the user creates a \step. The horizontal position is specified with the left and right keys. There is optional text on the step, which is 'decide' in this case. The incoming lines are removed from the state, and the outgoing lines are added to the state. At this point the state becomes:

{e ↦ 10, f ↦ 30, c ↦ 50, d ↦ 70}.

The point of maintaining the state is so lines that are not involved in a particular step are drawn in without the user having to keep specifying them. For instance, the package should implicitly draw the lines c and d to the right-hand side of the 'decide' step.

Occasionally two or more steps occur in parallel, as seen at the bottom of the picture above. I propose to deal with this in the syntax by using \\ to explicitly start a new 'row' of steps.

My approach so far

My approach so far has been to try to store the state in a series of macros, e.g.:

\def\state@line@a{10}
\def\state@line@b{30}
\def\state@line@c{50}
\def\state@line@d{70}
\def\state@line@e{10}
\def\state@line@f{30}

and then maintain a comma-separated list of those lines which are currently in the state, e.g.:

\def\activelines{e,f,c,d}

My previous two questions (here and here), have resulted from my trying to get this approach to work. In those questions, I have condensed my code down to minimal working examples to show the particular technical problem I was facing. I thank the answerers for addressing those problems so thoroughly and considerately. However, since the same people are answering my questions each time, I suspect it is probably better if I just cut to the chase and show what I'm really working on. I have still idealised my problem a bit, to make it easier to explain, but I hope to have preserved most of the technical challenges.

Extensions

I would like eventually to extend my diagrams to much more complicated ones than the example shown above. For instance:

  • I want my lines to have customisable thickness (so the elements of the 'state' should really be tuples rather than single integers).

  • I want to add a command to 'twist' two or more lines over each other, swapping their horizontal positions.

  • The user must currently specify a left and right position for each step, but I want eventually to calculate these automatically from the positions of the incoming and outgoing lines.

  • I want to add special 'compound' nodes that contain diagrams within them.

  • I want to add another type of line.

  • I want to add a command to take a snapshot of the current 'state', and a command to restore a previous 'state'.

Therefore, it is important to me that your solutions are robust and understandable enough so that I can fiddle with them a lot!

8
  • What about using pgfgantt? That's not the purpose of the package of course, but could be a valid solution/starting point. May 15, 2013 at 14:13
  • Since your interface is largely keyval and your implementation is TikZ, have you considered using pgfkeys for maintaining state? May 15, 2013 at 14:14
  • 1
    Also, does everybody call these things Wickerson diagrams or just you? In my field one treads lightly when considering autoeponymity. May 15, 2013 at 14:20
  • 1
    That makes sense. But you're planning a package that others will use, so at some point you need a better name. :-) May 15, 2013 at 14:28
  • 1
    This looks like a combination of the already mentioned pgfgantt (for the rather independent boxes) and tikz-cd (for the connections which needs to be built in the background). — I wonder about the vertical lines though. In the example you specify a x value at which they start and end. It might be easier to first draw them to a specific y value and then draw filled boxes over it; especially considering other shapes than rectangles. If you want to do this with other shapes, you will otherwise need the intersections library to find at least a point on the border. May 15, 2013 at 18:49

1 Answer 1

3

With the help of several sub-questions [1,2,3,4,5], I have managed to cobble together a reasonable solution.

The user input has been slightly modified: one now types \row at the start of each row, rather than \\ at the end.

Here is my code:

\documentclass{article}

\usepackage{tikz}
\usepackage{etextools}

\makeatletter

\pgfkeys{/wickerson/.cd,
  execute style/.style = {#1},
  execute macro/.style = {execute style/.expand once=#1},
  pos/.code=           {\xdef\wickerson@pos{#1}},
  left/.code=          {\xdef\wickerson@left{#1}},
  right/.code=         {\xdef\wickerson@right{#1}},
  label/.code=         {\xdef\wickerson@label{#1}},
  text/.code=          {\xdef\wickerson@text{#1}},
  pale/.code=          {\xdef\wickerson@color{red!50}},
  scale/.code=         {\xdef\wickerson@scale{#1}},
  initial lines/.code= {\xdef\wickerson@initiallines{#1}},
  incoming lines/.code={\xdef\wickerson@incominglines{#1}},
  outgoing lines/.code={\xdef\wickerson@outgoinglines{#1}},
}
\def\wickerson@color{red}

\newcommand*\@defineLine[2]{%
  %\typeout{Defining wickerson@#1@pos = #2}
  \expandafter\xdef\csname wickerson@#1@pos\endcsname{#2}
}

% Project the pos-field from the line with the given
% identifier. For instance, if \wickerson@foo@pos=35 then
%   \@getLinePos{foo}
% will give 35.
\gdef\@getLinePos#1{\csname wickerson@#1@pos\endcsname}


% For tracking the distance from the top of the diagram.
\newcounter{VCursor}

% Default step height
\newcommand{\stepHeight}{10}

% Height of the current row.
\newcounter{RowHeight}


% A comma-separated list of active lines. Each element is a line identifier. 
\gdef\@activeLines{}

% Add the given identifier to the list of active lines.
\newcommand*\@addToActiveLines[1]{%
  \ifx\@activeLines\@empty\else\g@addto@macro\@activeLines{,}\fi
  \g@addto@macro\@activeLines{#1}
}

\newcommand*\@removeFromActiveLines[1]{%
  \@expandtwoargs\@removeelement{#1}\@activeLines\@activeLines
  \global\let\@activeLines=\@activeLines
}

\newcommand*\row[1][20]{%
  \setcounter{RowHeight}{#1}
  \addtocounter{VCursor}{\theRowHeight}%
  \typeout{Starting row. Drawing lines \@activeLines}
  \foreach\i in \@activeLines {%
    \draw[black] (\@getLinePos\i,\theVCursor) 
      -- (\@getLinePos\i,\theVCursor-\theRowHeight);
    \node[text=black!25,anchor=west] at
      (\@getLinePos\i,\theVCursor-\theRowHeight+5) {\strut \i};
  }
}

\newcommand*\@printState{%
  \typeout{Current state:}
  \foreach\i in \@activeLines {%
    \typeout{  (\i, \@getLinePos\i)}
  }
  \typeout{Current activeLines:}
  \show\@activeLines
}

\newcommand*\@drawLineLabel[2]{%
  \node[text=black,anchor=east] at
    (#1,\theVCursor+5) {\strut #2};
}

\newcommand*\step[1][]{%
  \typeout{Starting step.}
  \pgfkeys{/wickerson/.cd,left=0,right=0,text={},%
    incoming lines={},outgoing lines={},#1}
  \fill[\wickerson@color] (\wickerson@left,\theVCursor-\stepHeight) 
    rectangle (\wickerson@right,\theVCursor);
  \gdef\wickerson@color{red}
  \node at (\wickerson@left*0.5 + \wickerson@right*0.5, 
    \theVCursor - 0.5*\stepHeight) {\wickerson@text};
  \foreach\i in \wickerson@incominglines {
    \typeout{Removing line \i}
    \ExpandNext\@removeFromActiveLines\i
  }
  \foreach \i/\values in \wickerson@outgoinglines {
    \pgfkeys{/wickerson/.cd,pos=0,label={},execute macro=\values}
    \typeout{Adding (\i,\wickerson@pos) to the state.}
    \ExpandNextTwo\@defineLine\i\wickerson@pos
    \ExpandNext\@addToActiveLines\i
    \ExpandNextTwo\@drawLineLabel\wickerson@pos\wickerson@label
  } 
  \@printState
}

\newenvironment{wickersondiagram}[1][]{%
  \typeout{Starting diagram.}
  \pgfkeys{/wickerson/.cd,scale=1,initial lines={},#1}
  \setcounter{VCursor}{0}%
  \begin{tikzpicture}[x=1mm,y=-1mm]
    \foreach \i/\values in \wickerson@initiallines {
      \pgfkeys{/wickerson/.cd,pos=0,label={},execute macro=\values}
      \typeout{Adding (\i,\wickerson@pos) to the state.}
      \ExpandNextTwo\@defineLine\i\wickerson@pos
      \ExpandNext\@addToActiveLines\i
      \@printState
    } 
}{%
  \end{tikzpicture}
}

\makeatother

\begin{document}

\begin{wickersondiagram}[%
  initial lines={a/{pos=10}, b/{pos=30}, c/{pos=50}, d/{pos=70}}]
\row
\step[left=0, right=40, text=decide,%
  incoming lines={a,b},%
  outgoing lines={e/{pos=10,label=yes}, f/{pos=30,label=no}}]
\row
\step[left=20, right=60,%
  pale,%
  incoming lines={f,c},%
  outgoing lines={g/{pos=30},h/{pos=50}}]
\row
\step[left=0, right=40, text=join,%
  incoming lines={e,g},%
  outgoing lines={i/{pos=20}}]
\step[left=60, right=80,%
  incoming lines={d},%
  outgoing lines={j/{pos=70}}]
\row[10]
\end{wickersondiagram}

\end{document}

Here is the output. The letters in grey are the internal names given to the lines, and are shown for debugging purposes.

enter image description here

I would encourage anybody to improve this code if they would like. As such, I've made this answer community wiki.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .