# How can I create a time chart with a non-uniform row height within TikZ loop?

The code below produces the given chart. (Thanks Sandy G! - see How do I create a timeline charts similar to this style? Suggestions )

I have made many modifications to this, even with additional variables (i.e. a horizontal and vertical stretch factor). But, I am now getting stuck. I am wanting to make modifications to handle cases where the left labels span multiple lines. I'm not new to programming in general, but I am new to Tikz programming. I'd like to create a variable to store Y offset, (which is accumulated), initially set to 0, used in the foreach loop.

Simplifying the question:
How can I code the following without an error inside a foreach loop, where yoffset is initially set to zero:

\yoffset = \yoffset + 1

\documentclass{article}

\usepackage{tikz}
\definecolor{linecolor}{rgb}{0, 0.5, 0.5}

\newcommand{\timeline}[6][2]{\begin{tikzpicture}
\node[anchor=base] at (0,.15){\textsf{\textbf{\scriptsize#2}}};
\foreach [var=\n, evaluate=\n as \l using int(\n*#4+#3-#4)] in {1,...,#5}{
\node[anchor=base] at (\n,.15){\textsf{\textbf\l}};}
\foreach [var=\timea, var=\timeb, var=\event, count=\n] in {#6}{
\pgfmathsetmacro{\col}{\ifodd\n"linecolor"\else"white"\fi}
\fill[color=\col!15](-#1,-\n+1) rectangle (#5+1,-\n);
\foreach \m in {0,...,#5}{\draw[linecolor](\m,-\n+1)--(\m,-\n);}
\fill[linecolor,rounded corners=1.5mm]({(\timea-#3+#4)/#4},-\n+.65) rectangle ({(\timeb-#3+#4)/#4},-\n+.35);
\node[anchor=base, right] at (-#1,-\n+.5) {\textsf{\textbf\event}};}
\draw[linecolor](-#1,0)--(#5+1,0);
\foreach [var=\timea, var=\timeb, count=\n] in {#6}{\draw[linecolor](-#1,-\n)--(#5+1,-\n);}
\end{tikzpicture}}
%\timeline{label}{start}{step}{number}{intervals}

\begin{document}

\timeline[2.5]{A.D.}{30}{5}{11}{29/33/event 1,32/34.5/event 2,35.5/38/something,44/48/??,45.5/48/X,47/49.5/Y,47.5/52/Z,52/57.5/,61.5/63/,64/67/A}

\end{document}


• I'm not sure I understand your desired output. Do you want the label to appear on the separator line (half on green and half on white)? Where should it go if it stretches across three lines? Jan 8 at 22:02
• Is it sufficient just to put a table for your label? For example, change A in your example (last label) to \begin{tabular}{l} A1\\A2 \end{tabular}. Jan 8 at 22:29

I think the \yoffset idea complicates things. Instead, I recommend setting a maximum width for your labels using \node[anchor=base, right, text width=#1cm] (#1 is the the optional label width parameter). Then let TikZ make its own line breaks.

\timeline[2.5]{A.D.}{30}{5}{11}{29/33/event 1,32/34.5/event 2,35.5/38/something,44/48/??,45.5/48/X,47/49.5/Y,47.5/52/Z,52/57.5/,61.5/63/,64/67/here is a very long label}


will produce the output

\documentclass{article}

\usepackage{tikz}
\definecolor{linecolor}{rgb}{0, 0.5, 0.5}

\newcommand{\timeline}[6][2]{\begin{tikzpicture}
\node[anchor=base] at (0,.15){\textsf{\textbf{\scriptsize#2}}};
\foreach [var=\n, evaluate=\n as \l using int(\n*#4+#3-#4)] in {1,...,#5}{
\node[anchor=base] at (\n,.15){\textsf{\textbf\l}};}
\foreach [var=\timea, var=\timeb, var=\event, count=\n] in {#6}{
\pgfmathsetmacro{\col}{\ifodd\n"linecolor"\else"white"\fi}
\fill[color=\col!15](-#1,-\n+1) rectangle (#5+1,-\n);
\foreach \m in {0,...,#5}{\draw[linecolor](\m,-\n+1)--(\m,-\n);}
\fill[linecolor,rounded corners=1.5mm]({(\timea-#3+#4)/#4},-\n+.65) rectangle ({(\timeb-#3+#4)/#4},-\n+.35);
\node[anchor=base, right, text width=#1cm] at (-#1,-\n+.5) {\textsf{\textbf\event}};}
\draw[linecolor](-#1,0)--(#5+1,0);
\foreach [var=\timea, var=\timeb, count=\n] in {#6}{\draw[linecolor](-#1,-\n)--(#5+1,-\n);}
\end{tikzpicture}}
%\timeline{label}{start}{step}{number}{intervals}

\begin{document}

\timeline[2.5]{A.D.}{30}{5}{11}{29/33/event 1,32/34.5/event 2,35.5/38/something,44/48/??,45.5/48/X,47/49.5/Y,47.5/52/Z,52/57.5/,61.5/63/,64/67/here is a very long label}

\end{document}

• But, this will not adjust a row's height, will it ? I'd like to keep the amount of blank space above the text (and matching blank space below the text) the same ... AND also not having huge amounts of wasted blank space for those lines that are only one line of text. I do not mind doing the complicated computation of anchors and everything ... if someone could just demo how to initialize the \yoffset var to be zero before loop, and then to change that inside the loop - I can do the rest. It doesnt even have to do anything. I can program the rest myself. Still I'll give your solution a try Jan 8 at 23:13

The code may look a mess ... but, I'm pleased with the final results.

This still does not really answer the original stated question (how can a change a variable's value within a Tikz foreach loop?) - but it accomplishes the goal.

I still have no clue how to change a variable's value within a foreach loop for Tikz. Seems that already computed constants must be fed to the loop. Math can be done with those constants for computing X and Y for rectangle, line, etc.

But ... within the code below, I do have control for a vertical grid factor, a horizontal grid factor, and row height for text that spans past a single line; I also have accomplished centering the time labels with a uniform height. I can even cross the B.C. to A.D. boundary ... (by simply using negative numbers in the input for any B.C. dates).

Thanks you Sandy G for your suggestion. That suggestion was key to this final solution here as the text is anchored to the center, and performs line breaks automatically at the given threshold - which did simplify a lot!

My solution is not a definition macro - more like copy and paste; but I do have a top section to control variables.

Here's the code for anyone interested:

\documentclass{article}

\usepackage{tikz}
\usetikzlibrary{math} % to store vars

\begin{document}

\noindent\scalebox{0.70}{
\begin{tikzpicture}
% define some variable for readability below
\tikzmath{
\eventwidth = 13;
\hfact = 1.2;
\vfact = 0.9;
\numTopLabels = 2;
\capsulOffset = 1;
\nlabels = 9;
\labelrise = 0.25;
\datestart = 10;
\dateincr = 10;
\lastrow = 19;
}. % \lastrow needs to be manually set to last record's \rowend below. Might be better named "ychartheight".

% I think a few of the vars above ended up not being used.  I left them in just in case, and have not tested deleting them, numTopLabels is one that I think is not used; capsuleOffset is handy for situations where the math is correct, but off by a grid-unit due to timeline start between BC and AD.  set it to zero for more standard cases

% Top Labels
% yeah, this could probably be placed into a loop - but it was easy enough
\node[anchor=base] at (0*\hfact,\labelrise){\textsf{\textbf{\scriptsize 10 \textsubscript{B.C.}}}};
\node[anchor=base] at (1*\hfact,\labelrise){\textsf{\textbf{\scriptsize 1  \textsubscript{A.D.}}}};
\node[anchor=base] at (2*\hfact,\labelrise){\textsf{\textbf{\scriptsize 10 }}};
\node[anchor=base] at (3*\hfact,\labelrise){\textsf{\textbf{\scriptsize 20 }}};
\node[anchor=base] at (4*\hfact,\labelrise){\textsf{\textbf{\scriptsize 30 }}};
\node[anchor=base] at (5*\hfact,\labelrise){\textsf{\textbf{\scriptsize 40 }}};
\node[anchor=base] at (6*\hfact,\labelrise){\textsf{\textbf{\scriptsize 50 }}};
\node[anchor=base] at (7*\hfact,\labelrise){\textsf{\textbf{\scriptsize 60 }}};
\node[anchor=base] at (8*\hfact,\labelrise){\textsf{\textbf{\scriptsize 70 }}};

% Rows

% data:  timea / timeb / rowstart / rowend / text describing event / (the next record's rowstart needs to be last record's rowend ... which are used to control a row's height . )

\foreach [var=\timea, var=\timeb, var=\rowstart, var=\rowend, var=\event, count=\n] in {
-6/-6/0/1/John the Baptist is born (6 \textsubscript{B.C.}),
-5/-5/1/2/Jesus is born (5 \textsubscript{B.C.}),
-4/-4/2/3.5/Jesus' family flees to Egypt; Herod the Great dies; Jesus' family goes to Nazareth (4 \textsubscript{B.C.}),
8/8/3.5/4.5/Jesus (age 12) talks with teachers in the temple (\textsubscript{A.D.} 8\textsuperscript{*}),
8/30/4.5/5.5/Jesus works as a carpenter in Nazareth (8\textsuperscript{*}--28\slash 30),
28/29/5.5/6.5/John the Baptist begins his ministry (28\slash 29),
28/30/6.5/7.5/Jesus begins his ministry; Peter becomes a disciple of Jesus (28\slash 30),
30/33/7.5/8.5/Jesus is crucified and resurrected (33 [or 30])\textsuperscript{\textdagger},
44/47/8.5/10.5/{John, whose other name was Mark'', joins Paul and Barnabus's party for their famine-relief visit to Jerusalem; Mark accompanies Barnabus and Paul on part of the first missionary journey (44--47\textsuperscript{*})},
48/51/10.5/11.5/Barnabus takes John Mark to Cyprus (48\slash 49--51\textsuperscript{*}),
50/54/11.5/12.5/John Mark meets with Peter in Rome (50\slash 54),
53/54/12.5/13.5/Gospel according to Mark written (53--55\textsuperscript{*}),
60/62/13.5/15/{Paul, under house arrest in Rome, summons Mark, who serves as his delegate to Asia Minor (60--62\textsuperscript{*})},
62/63/15/16/{Peter, in Rome, writes 1 Peter; Mark is with him (62--63\textsuperscript{*})},
64/67/16/17/Peter is martyred in Rome (64--67\textsuperscript{*}),
67/67/17/18/The church flees to Pella (67\textsuperscript{*}),
70/70/18/19/Destruction of Jerusalem temple (70)
} {

% compute alternating \rowcolor variable

% row stripes
\fill[color=\rowcolor] (-\eventwidth, -\rowstart*\vfact)
rectangle
( {(\nlabels)*\hfact},{(-\rowend)*\vfact});

% the horizontal grid lines

% the time marks (capsules; the rounded rectangles)
{ ( (\timea-\datestart+\dateincr) * \hfact/\dateincr ) + \capsulOffset*\hfact - 1.1/\dateincr*\hfact},
-{ \rowstart*\vfact + (\rowstart-\rowend)*0.5*\vfact - .15 }
)
rectangle
(
{ ( (\timeb-\datestart+\dateincr) * \hfact/\dateincr ) + \capsulOffset*\hfact + 1.1/\dateincr*\hfact},
-{\rowstart*\vfact + (\rowstart-\rowend)*0.5*\vfact + .15 }
);
% the time-event text
\node[anchor=east, right, text width=12cm] at
(-\eventwidth + 0.3,  -{\rowstart*\vfact + (\rowstart-\rowend)*0.5*\vfact  }
)
{\textsf{\event}};

}

% manually draw last horizontal line

% vertical grid lines
\draw[ultra thin, gray](-\eventwidth,-0)--(-\eventwidth,-\lastrow*\vfact);          % left-most vertical line

\foreach \m in {0,...,\nlabels}{                                           % remaining vertical grid lines
\draw[gray]( (\m*\hfact, 0) -- (\m*\hfact,-\lastrow*\vfact);
}

\end{tikzpicture}
}

\end{document}