# automatically connect horizontal same color lines

How do you automate drawings like the one below?

These drawings follow some rules. As shown above there is a page with a list of numbered two axes fields. Each field is an individual \begin{tikzpicture}\blank ... \connectlogically\end{tikzpicture}.

(a) In each field there are horizontal arrows. Each arrow is drawn by the macro \pointX #1,#2|#3|#4|#5 (black) or \pointK #1,#2|#3|#4|#5 (gray). This draws an arrow which start at coordinate (#1,#2), is #3 units long (this number is also labeled above the arrow, centered) and below or above there is a dot with coordinates(#1 + 0.5*#3 ,#4), and a label #5 to the right of the arrow tip. A definition example of such command is on the MWE.

(b) If possible, two other commands, namely \pointXo and \pointKo, should be made. These commands take 4 not 5 arguments. They use as its own x coord the x coordinate of the arrow tip defined by the last issued \point<K/X> plus the number placed on the arrow tip label of that last issued \point<K/X>. This would be equivalent to a coordinate like (#1+#3+#5,<y coord>) where the arguments are retrived from the last issued \point<K/X> and the <y coord> is the first argument of the \point<K/X>o.

(c) The main issue: lets assume there's an arrow Ki at y position yKi which its tail is at xKi-1 and tip at xKi-2. There are also other arrows around this one, namely arrows Kj, following the same structure. Now, these arrows should be connected by vertical lines to its vertical closer one (min(abs(yKi - yKj))) that meets this condition: xKi-1 <= xKj-1 < xKi-2. I words: if there are arrows j in which the j tail is between an i arrow tail and head, the one closest to i is verttically connected. The goal is to have a command that does this automatically as when you have lots of arrows and lots of drawings this becomes extremely exhaustive, this command is here referred to as \connectlogically

(d) Ideally, you should also be able to manually instruct LaTeX to draw a connection between line segments of the same color, by giving the starting node coordinates of a pair of steps you want connected. However, this connects their centers by a bent line.

* All lengths and positions can be discrete, if that is easiest to implement. Then integer multiples of 1/8 is all that is required, such as 4 3/4 or -2 1/8.

Below is a MWE with a definition of the \pointX macro and a test case that covers all (I think) possible scenarios, below it there's the current output and the desired output after issuing the command \connectlogically.

\documentclass[border=2mm]{standalone}
\usepackage{tikz}

\tikzset{dot/.style={circle, fill, minimum size=.4em, outer sep=0pt, inner sep=0pt}}

\def\pointX #1,#2|#3|#4|#5.{%
\draw[black, ->] (#1,#2) -- node[above]{#3} (#1+#3,#2) node[right]{#5} (#1+#3/2,#2+#4) node[dot]{};
}

\def\pointK #1,#2|#3|#4|#5.{%
\draw[gray, ->] (#1,#2) -- node[above]{#3} (#1+#3,#2) node[right]{#5} (#1+#3/2,#2+#4) node[dot]{};
}

\newcount\blankcount
\def\blank{%
\draw[black!64,->](0,0)--node[left]{$x\atop y$} (0,8);
\draw[black!32](0,3)--(16,3)node{};
\draw[black!64,->](0,0)--node[below]{$a\atop b$} (16,0);
\node (x\the\blankcount) at (16,8) {\the\blankcount};
}

\begin{document}
\begin{tikzpicture}
\blank
\pointX 1/2,1|3|1|0.
\pointX 7/2,1/2|2|2|-1/2. % This should be \pointXo 1/2|2|2|-1/2.
\pointX 5,2|2|-1|0.       % This should be \pointXo 2|2|-1|0.
\pointX 7,3/2|1|3|0.      % This should be \pointXo 3/2|1|3|0.
\pointX 1/2,2|5/2|1|19/4.
\pointK 2,4|2|-2|-3/4.
\pointK 13/4,5|1|-4|0.    % This should be \pointKo 5|1|-4|0.
\pointX 31/4,3|3|3|3.     % This should be \pointXo 3|3|3|3.
%\connectlogically        % This should connect everything automatically
\end{tikzpicture}
\end{document}


EDIT: There are two great answers; 50 (E.D.) and 100 (G.S.) both deserve a bounty.

• I really don't understand the description. And couldn't make the code compile. MAybe a screenshot of what needs to be done? – percusse Nov 15 '16 at 11:37
• What's K? Are grey only connected to grey and black only to black? Why isn't the 1/2 connected to anything? It overlaps with a line below which is also the same colour (if that's the rule). It would be easier not to use X which is, after all, rather like x, if X refers to black lines or what have you. (Note: it would be easier for the example even if X is standard notation and clear to you given your disciplinary background.) It might help a bit to explain briefly what this is representing so people can try to make sense of it. – cfr Nov 15 '16 at 15:17
• Still, why is 1/2 not connected to anything? Shouldn't it be connected to the black segment (without a length, by the way) above the grey 4? – Eric Domenjoud Nov 15 '16 at 16:51
• Are the possible lengths and positions of the horizontal lines continuous or discrete? How fine-grained is this? – cfr Nov 15 '16 at 23:24
• @GuidoJorg, your code is still unable to be compiled. If you could at least provide a code to draw all the dots and arrows (without auto conection) it would be nice. so then we can test a solution to the actual problem. – Guilherme Zanotelli Nov 16 '16 at 13:05

Eric is right when he says this is an algorithm issue. And as so, there are several ways of solving the problem. Here I'll present a TikZ + etoolbox way, using extensively etoolbox's generic test capabilities and TikZ loop statements.

The logic behind is to test, for each arrow i of kind K (Ki) if it's tail x coordinate (Ki-1) is between the tail and head other arrows j of kind K (Kj,j≠i - Kj-1 is the tail and Kj-2 the head). If there is Kj that fits, test the vertical distance between them (abs(yi-yj)) and compare with the mininum vertical distance minvdist (initially 100cm) if the current vertical distance is less than minvdist than \let\minvdist{abs(yi-yj)} and also \let\j\closerj. Doing that for every arrow j, after the loop ends then just \draw (Kcloserj-1) |- (Ki-1);. That's what \autoconnectK does.

Aside Ki-1 and 2 coordinates there is also the Ki coordinate which refers to the arrow's center Position and the Ki-node coordinate which refers to the the arrow tip position plus the number given as last input of the command, this coordinate is used to implement the \pointKo command.

The macro \pointKo uses the last defined Ki-node x coordinate as input for its own <x coord>, that's why it doesn't have the first input as \pointK. Furthermore, what's tricky about the code is that inside the \foreach loops the variables are completely obliteraded after each Iteration, so when some variable needs to be stored for next iterations it must be prefixed by \global or defined globally, o else the change will be meaningless for the next iterations.

As a request from the OP the arrows are drawn by a macros called \pointK which takes 5 arguments and \pointKo which takes four:

\pointK <x coord>,<y coord>|<h lenght>|<over dot v lenght>|<right label>.
\pointKo <y coord>|<h lenght>|<over dot v lenght>|<right label>.


Bringing the problem to a new extent, a \newpoint{<K>}{tikz style} macro was created. This macro creates the former mentioned macros alltogether plus a macro named \showKs which show the names of all kind K arrows at the arrow tip in font \tiny and red Color, that's to help manually draw the bent Connections. Here's a full example that shows how everything works and ist output:

\documentclass[11pt]{article}\usepackage{geometry,xcolor,tikz,etoolbox}\usetikzlibrary{calc}
\geometry{paperwidth=16in,paperheight=10in,left=1in,right=1in,top=1in,bottom=1in}
\colorlet{AXEScolor}{blue!64!red!32}\colorlet{LINEcolor}{red!32}
\newcount\blankcount\blankcount=0
\draw[AXEScolor,line cap=round,->](0,0)--node[left]{$a\atop b$}(0,16);
\draw[AXEScolor,line cap=round,->](0,0)--node[below]{${}\atop time$}(32,0);
\draw[LINEcolor](0,6)--(32,6)node{};
\node (x\the\blankcount) at (-1/2,-1/2) {\LARGE\textbf{\the\blankcount}};}

\tikzset{dot/.style={circle,fill,minimum size=3pt}, inner sep=0pt, outer sep=2pt}
\newdimen\xlast\newdimen\ylast
\newcommand*{\ExtractCoordinate}[1]{\path (#1); \pgfgetlastxy{\xlast}{\ylast}}

\newcommand*{\closerj}{}
\newcommand*{\findcloserj}[1]{% Serves as input for second autoconnect loop (finds the closer arrow)
\ifnumequal{\j}{\i}{}% If is \j the same arrow as \i do nothing, else:
{\ExtractCoordinate{$(#1\j-1)$};\let\jtail\xlast%
\ExtractCoordinate{$(#1\j-2)$};\let\jhead\xlast%
\ifboolexpr{test {\ifboolexpr{test {\ifdimcomp{\itail}{>}{\jtail}} or test {\ifdimcomp{\itail}{=}{\jtail}}}}% If itail is after jtail
and%
}% If both are true do:
{\ifdimgreater{\yi}{\ylast}% Checks if yj is above or below yi
{\ifdimless{\yi-\ylast}{\minvdist}{\dimgdef\minvdist{\yi-\ylast}\global\let\closerj\j}{}}% If above, checks if yi-yj < minvdist
{\ifdimless{\ylast-\yi}{\minvdist}{\dimgdef\minvdist{\ylast-\yi}\global\let\closerj\j}{}}}% If below, checks if yj-yi < minvdist
{}% If any fails, do nothing
}% end of if i=j
}

\newcommand{\newpoint}[2]{% Creates new \point#1 commands and its friends (\point#1o and \autoconnect#1)
\newcounter{point#1}\setcounter{point#1}{-1}\tikzset{#1/.style={#2}}% Sets counter and style of the point
\expandafter\def\csname point#1\endcsname ##1,##2|##3|##4|##5.{% Creates the \point#1 command which draws the arrows
\stepcounter{point#1}% Steps point counter
\draw[#1] (##1,##2) coordinate (#1\csname thepoint#1\endcsname-1) % Arrow start position
(##1+##3/2,##4) node[dot]{} % Goes right halfway the arrow lenght (##3/2) and ##4 up, draws the dot
+(##3+##5,0) coordinate (#1\csname thepoint#1\endcsname-node) % Places a coordinate ##5 in front of the arrow
(#1\csname thepoint#1\endcsname-1) -- node[above]{$##3$} coordinate (#1\csname thepoint#1\endcsname) ++(##3,0) node[right]{$##5$} coordinate (#1\csname thepoint#1\endcsname-2); % Draws the arrow
}%
\expandafter\def\csname point#1o\endcsname ##1|##2|##3|##4.{% Creates the variant \point#1o
\ExtractCoordinate{$(#1\csname thepoint#1\endcsname-node)$};% Extract last point#1-node coordinate
\stepcounter{point#1}% Steps the point counter
\draw[#1] (\xlast,##1) coordinate (#1\csname thepoint#1\endcsname-1) % Arrow x start position comes from last point#1-node coordinate
(\xlast+##2/2,##3) node[dot]{} % Goes right halfway the arrow lenght (##2/2) and ##3 up, draws the dot
+(##2+##4,0) coordinate (#1\csname thepoint#1\endcsname-node) % Places a coordinate ##4 in front of the arrow
(#1\csname thepoint#1\endcsname-1) -- node[above]{$##2$} coordinate (#1\csname thepoint#1\endcsname) ++(##2,0) node[right]{$##4$} coordinate (#1\csname thepoint#1\endcsname-2); % Draws the arrow
}%
\expandafter\def\csname autoconnect#1\endcsname{% Sistematically checks for the closest tail above (if any) and connects it
\ifnumgreater{\csname thepoint#1\endcsname}{0}{% Checks if there were more than 1 \point#1 used
\foreach \i in {0,...,\csname thepoint#1\endcsname}{% Loops through all arrows i of kind #1
\ExtractCoordinate{$(#1\i-1)$};% Gets the arrow coordinates
\let\itail\xlast\let\yi\ylast\dimgdef\minvdist{100cm}%
\foreach \j in {0,...,\csname thepoint#1\endcsname}{\findcloserj{#1}};% Loops through all other arrows besides i and retrives the closer one
\ifdefempty{\closerj}{}{\draw[#1] (#1\closerj-1) -| (#1\i-1) -- (#1\i-2);}% Draws the connection if it exists
\gdef\closerj{}% clear the variable for next iteration
};\setcounter{point#1}{-1}\renewcommand*{\closerj}{}% Resets the counter and clear the variable for the next loop
}{}}%
\expandafter\def\csname show#1s\endcsname{%
\foreach \i in {0,...,\csname thepoint#1\endcsname}{\node[above left, font=\tiny, red] at (#1\i-2) {#1\i};};
}%
}

\newpoint{X}{-latex, black}
\newpoint{K}{-latex, gray}

\begin{document}
\begin{tikzpicture}
\blank
\pointX 0,2|5|4|1.
\pointX 3,1|5|4|1.
\pointX 1,4|6|4|3.
\pointX 1.5,3.5|4|3|5.
\pointX 4,2.5|4|4|0.
\pointXo 3|2|3|0.
\pointXo 2|2|3|1.
\pointK 2,7|3|-2|1.
\pointK 4,6|3|1|2.
\pointKo 7|2|1|0.
\pointKo 6.5|2|-1|2.
\pointK 6,5|4|2|5.
\pointK 10.5,4|2|-2|1.
\pointXo 2.5|2|3|1.
\showKs
\showXs
\autoconnectK
\autoconnectX
\end{tikzpicture}
\end{document}


• The [x=5cm] is just to make the vectores naturally larger (they were too small with the values I had put). I'll Change the syntax, not too complicated. – Guilherme Zanotelli Nov 16 '16 at 13:29
• @GuilhermeZ.Santos I see. But Guido stated that a step should connect to the nearest one only. Although the way he reformulated it does not make this point so clear anymore. – Eric Domenjoud Nov 16 '16 at 15:31
• Thank's, I forgot about the \pointXo thing. That's easily done adding a move to operation on the \pointX path and placing a coordinate there, namely Ki-node, then extract that coordinatae before drawing \pointXo, I've added that change to the code. And for tinkering with the code you have to be careful, use an editor with good highlighting to keep track of all {}. :) – Guilherme Zanotelli Nov 18 '16 at 3:36
• @GuidoJorg, I did the last edit in a hurry, sorry. It's missing the code 'coordinate (#1\csname thepoint#1\endcsname-node)' in the definition of \point#1o... I'm traveling and will be back only Sunday. Feel free to edit the answer if you fix it, otherwise I'll do so upon returning. – Guilherme Zanotelli Nov 18 '16 at 13:23
• @GuidoJorg, that's no strange behavior at all, I meant for this to happen as I thought only overlapping arrows should be connected. Now that I know I was wrong I added a test to see if the arrows ends are exactly above/below one another. I've also fixed everything added a foolproof (I think) test case where all things you mention happen. Check it out. – Guilherme Zanotelli Nov 21 '16 at 9:47

Your main problem is the automatic connection to the nearest horizontal step. This is mainly an algorithmic problem.

1) Create a list L of all start and end points of steps of one kind (X or K). 2) Sort this list first by increasing x, and for identical x, put an end point before a start point. 3) Create an empty list S. 3) For each point in L, if it is en end point, remove the corresponding start point from S. If it is a start point, a) going through S, find the nearest step b) connect the start point to this step c) add the start point to S

At each time, the list S contains the starting points of step which are not yet finished.

Here is a code which does essentially this. I didn't address the question of manual connections. It should be the easy part.

\documentclass[11pt]{article}

\usepackage{geometry}
\geometry{paperwidth=16in,paperheight=10in,left=1in,right=1in,top=1in,bottom=1in}

\usepackage{xcolor}
\usepackage{tikz}
\usepackage{etoolbox}

\newcount\blankcount \blankcount=0

\newcount\Xsteps
\newcount\Ksteps

\def\clearsteps#1{%
\csdef{#1points}{}%
\csuse{#1steps}=0
}

\colorlet{Xcolor}{black}
\colorlet{Kcolor}{black!50}

%\def\pointX{\step X{\LARGE.}}
\def\pointX{\step X{$\bullet$}}
\def\pointK{\step K{o}}

% compare two points X = (n,se,x,y) and X' = (n',se',x',y')
% where:
%   n is the step number
%   se is 1 for a start point, 0 for an end point
%   x and y are the coordinates of the points
% X < X' iff x < x' or (x = x' and (se < se' or (se = se' and y < y')))

\newif\iflessthan
\def\compare(#1,#2,#3,#4)(#5,#6,#7,#8){%
\ifdim #3pt<#7pt\relax \lessthantrue  \else
\ifdim #3pt>#7pt\relax \lessthanfalse \else
% both points have the same x coordinate
% an end point is smaller than a start point with the same x coordinate
\ifnum #2<#6\relax \lessthantrue  \else
\ifnum #2>#6\relax \lessthanfalse \else
% both point are either start points or end points
% the smaller one is the one with the smaller y coordinate
\ifdim #4pt<#8pt\relax \lessthantrue
\else \lessthanfalse
\fi\fi\fi\fi\fi
}

% insert the point #2=(n,se,x,y) in the list #1points: #1 = X or K
\newtoks\lsttoks
\def\insertpoint#1#2{%
\def\lst{#1points}%
\let\do\relax
\lsttoks{}%
\edef\next{\xinsertpoint{#2}\csuse\lst\do.\relax}%
\next
}
\protected\def\xinsertpoint#1\do#2{%
\ifx.#2%
% we reached the end of the list: insert #1
% the rest is '\relax'.
\lsttoks\expandafter{\the\lsttoks\do{#1}}%
\let\next\finishinsert
\else
% the rest of the list is '\do{p}...\do{p}\do.\relax'
\compare #1#2%
\iflessthan
% (x,se,y) <_lex (x',se',y')
% insert #1, then #2 and finish
\lsttoks\expandafter{\the\lsttoks\do{#1}\do{#2}}%
\let\next\xfinishinsert
\else
% (x,se,y) >=_lex (x',se',y')
% insert #2 then continue
\lsttoks\expandafter{\the\lsttoks\do{#2}}%
\let\next\xinsertpoint
\fi
\fi
\next{#1}%
}
% finish the insertion by inserting at once the rest of the list
\def\finishinsert#1\relax{\csedef\lst{\the\lsttoks}}
\def\xfinishinsert#1#2\do.\relax{\csedef\lst{\the\lsttoks #2}}

% connect steps
\def\connectlogically#1{%
\begingroup
\def\splist{}% list of start points of unfinished steps
\colorlet{stepcolor}{#1color}%
\let\do\connectpoint
\csuse{#1points}%
\endgroup
}

% If #1 is a start point, connect it to the nearest overlapping step, if any,
% and add #1 to \splist.
% Otherwise, #1 is an end point: remove the start point from \splist
\def\connectpoint#1{\xconnectpoint#1} % remove the braces around the point
\newif\iffound
\def\xconnectpoint(#1,#2,#3,#4){%
\ifnum#2=1 % start step: connect to other steps and add the starting point to \splist
\def\xdo{\yconnect(#1,#3,#4)}%
\foundfalse
\splist\relax
\iffound \draw[line width=2pt, stepcolor] (#3,#4) -- (#3,\ystep); \fi
\let\xdo\relax
\edef\splist{\splist\xdo(#1,#3,#4)}%
\else % end step: remove the starting point from \splist
\def\removesp##1\xdo(#1,##2,##3)##4\relax{\def\splist{##1##4}}%
\expandafter\removesp\splist\relax
\fi
}

\newdimen\yabsdiff
\newdimen\newyabsdiff
\newif\ifnewy

\iftrue

% Search for a step to connect to

% version for connecting only steps with different starting points
\def\yconnect(#1,#2,#3)(#4,#5,#6){%
\newyfalse
\ifdim#5pt<#2pt % the step #4 starts strictly before the step #1 and ends after #2
\newyabsdiff=\dimexpr#3pt-#6pt\relax
\ifdim\newyabsdiff<0pt \newyabsdiff=-\newyabsdiff\fi
\newytrue
\iffound
\ifdim\newyabsdiff<\yabsdiff \else
\newyfalse
\fi
\fi
\foundtrue
\fi
\ifnewy
\yabsdiff=\newyabsdiff
\def\ystep{#6}%
\else
% If there are some start points left in \splist, they all have the current x coordinate.
% The corresponding steps cannot overlap the current point.
% We can discard all the remaining points.
\expandafter\endconnect
\fi
}

\else

% version for connecting steps with identical starting points
\def\yconnect(#1,#2,#3)(#4,#5,#6){%
%  \ifdim#5pt<#2pt % the step #4 starts strictly before the step #1 and ends after #2
\newyabsdiff=\dimexpr#3pt-#6pt\relax
\ifdim\newyabsdiff<0pt \newyabsdiff=-\newyabsdiff\fi
\newytrue
\iffound
\ifdim\newyabsdiff<\yabsdiff \else
\newyfalse
\fi
\fi
\foundtrue
%  \fi
\ifnewy
\yabsdiff=\newyabsdiff
\def\ystep{#6}%
\else
% If there are some start points left in \splist, they all have the current x coordinate.
% We already found a step to connect to and the new one is further:
% The other ones are even further because we sorted them in increasing y's.
% We can discard all the remaining points.
\expandafter\endconnect
\fi
}

\fi

% discards all remaining points in \splist
\def\endconnect#1\relax{}

\def\blank{%
\clearsteps X%
\clearsteps K%
\draw[black!64,->](0,0)--node[left]{$x\atop y$} (0,8);
\draw[black!32](0,3)--(16,3)node{};
\draw[black!64,->](0,0)--node[below]{$a\atop b$} (16,0);
% I didn't understand {$\csname num:\the\blankcount\endcsname$}.
% Is it defined somewhere else?
\node (x\the\blankcount) at (16,8) {\the\blankcount};
}

% Draw a step and add the start point and the end point to the list #1points
% Note: an additional parameter should be supplied for the textual form of
% the label above the edge.
\def\step #1#2#3,#4|#5|#6|#7.{%
\edef\stepnum{\the\csuse{#1steps}}
\draw [line width = 2pt, ->, #1color]
(#3,#4) node (#1start\stepnum) {}
-- node[above] {$#5$}
++(#5,0) node[right] (#1end\stepnum) {$#7$};
\pgfmathparse{#3+.5*#5} \let\X\pgfmathresult
\pgfmathparse{#4+#6} \let\Y\pgfmathresult
\node at (\X,\Y) {#2};
\pgfmathparse{#3+#5}
\insertpoint{#1}{(\stepnum,1,#3,#4)}
\insertpoint{#1}{(\stepnum,0,\pgfmathresult,#4)}
}% step

\begin{document}
\begin{tikzpicture}
\blank
\pointX 0,2|5|4|1.
\pointX 3,1|5|4|1.
\pointX 1,4|6|4|3.
\pointX 1.5,3.5|4|3|5.
\pointX 4,2.5|4|4|2.
\pointK 2,7|3|2|1.
\pointK 4,6|3|1|2.
\pointK 6,5|4|2|5.
\connectlogically X
\connectlogically K
\end{tikzpicture}

\end{document}


• Very nice! How should I correct my counter code, so that the label for each tikzpicture environment advances? (When I do a newline and start another tikzpicture ...) – Guido Jorg Nov 16 '16 at 13:39
• I don't follow the logic here @GuidoJorg, in your question you state that "Those and only those horizontal line segments that would overlap, if they had the same y coordinate, because of their x coordinates, are connected automatically by vertical line segments" In this particular case than, the black segment which the right label is "2" should be connected to all other segments, not just the one under it... If this answer is in fact right you should clarify your question. – Guilherme Zanotelli Nov 16 '16 at 14:06
• @GuidoJorg Just put \global in front of \advance\blankcount by 1. Otherwise, the previous value of \blankcount is restored at the end of the tikzpicture environment. – Eric Domenjoud Nov 16 '16 at 14:16