Loops
Repetitive structures — grids, chains, matrices of nodes — can be
created in two ways: with Python loops that call add_node / draw
many times, or with native TikZ \foreach loops via
fig.add_loop(), which embeds the loop directly in the generated LaTeX
instead of unrolling it in Python.
Benefits of TikZ \foreach loops: - the generated .tex file is much
shorter and more readable - loop variable values are available as TikZ
expressions (e.g. \i) at compile time - loops can be nested
This tutorial covers: - Python loop vs TikZ \foreach — side-by-side
comparison - basic add_loop usage - loop variable in colour
expressions - nested loops for a grid - add_variable for reusable TikZ
constants - a practical chain-of-boxes diagram
from tikzfigure import TikzFigurePython loop vs TikZ \foreach — a side-by-side comparison
Section titled “Python loop vs TikZ \foreach — a side-by-side comparison”Both approaches produce the same visual output: a row of five circles.
The key difference is in the generated LaTeX. The Python loop unrolls
every node into a separate \node command, while add_loop emits a
single compact \foreach block.
Python loop version:
fig = TikzFigure()
for i in range(5): fig.add_node( x=i * 2, y=0, shape="circle", fill="blue!40", content=str(i), minimum_size="0.8cm", )
fig.show()
Show Tikz code
print(fig)% --------------------------------------------- %% Tikzfigure generated by tikzfigure v0.2.1 %% https://github.com/max-models/tikzfigure %% --------------------------------------------- %\begin{tikzpicture} \node[shape=circle, fill=blue!40, minimum size=0.8cm] (node0) at ({0}, {0}) {0}; \node[shape=circle, fill=blue!40, minimum size=0.8cm] (node1) at ({2}, {0}) {1}; \node[shape=circle, fill=blue!40, minimum size=0.8cm] (node2) at ({4}, {0}) {2}; \node[shape=circle, fill=blue!40, minimum size=0.8cm] (node3) at ({6}, {0}) {3}; \node[shape=circle, fill=blue!40, minimum size=0.8cm] (node4) at ({8}, {0}) {4};\end{tikzpicture}print(fig.generate_standalone())\documentclass[border=10pt]{standalone}\usepackage{tikz}\usepackage{pgfplots}\pgfplotsset{compat=newest}\usepgfplotslibrary{groupplots}\usetikzlibrary{arrows.meta}\begin{document}% --------------------------------------------- %% Tikzfigure generated by tikzfigure v0.2.1 %% https://github.com/max-models/tikzfigure %% --------------------------------------------- %\begin{tikzpicture} \node[shape=circle, fill=blue!40, minimum size=0.8cm] (node0) at ({0}, {0}) {0}; \node[shape=circle, fill=blue!40, minimum size=0.8cm] (node1) at ({2}, {0}) {1}; \node[shape=circle, fill=blue!40, minimum size=0.8cm] (node2) at ({4}, {0}) {2}; \node[shape=circle, fill=blue!40, minimum size=0.8cm] (node3) at ({6}, {0}) {3}; \node[shape=circle, fill=blue!40, minimum size=0.8cm] (node4) at ({8}, {0}) {4};\end{tikzpicture}
\end{document}TikZ \foreach version — same output, much shorter LaTeX:
fig = TikzFigure()
with fig.add_loop("i", range(5), comment="Row of circles") as loop: loop.add_node( x=r"2*\i", y=0, shape="circle", fill="blue!40", content=r"\i", minimum_size="0.8cm", )
fig.show()
Show Tikz code
print(fig)% --------------------------------------------- %% Tikzfigure generated by tikzfigure v0.2.1 %% https://github.com/max-models/tikzfigure %% --------------------------------------------- %\begin{tikzpicture} % Row of circles \foreach \i in {0,1,2,3,4}{ \node[shape=circle, fill=blue!40, minimum size=0.8cm] () at ({2*\i}, {0}) {\i}; }\end{tikzpicture}print(fig.generate_standalone())\documentclass[border=10pt]{standalone}\usepackage{tikz}\usepackage{pgfplots}\pgfplotsset{compat=newest}\usepgfplotslibrary{groupplots}\usetikzlibrary{arrows.meta}\begin{document}% --------------------------------------------- %% Tikzfigure generated by tikzfigure v0.2.1 %% https://github.com/max-models/tikzfigure %% --------------------------------------------- %\begin{tikzpicture} % Row of circles \foreach \i in {0,1,2,3,4}{ \node[shape=circle, fill=blue!40, minimum size=0.8cm] () at ({2*\i}, {0}) {\i}; }\end{tikzpicture}
\end{document}Notice how \foreach \i in {0,1,2,3,4} replaces five separate \node
commands.
Basic loop: a row of circles
Section titled “Basic loop: a row of circles”fig.add_loop(var, iterable) returns a context manager. Inside the
with block, use loop.add_node() and loop.draw(). The loop variable
\i (or whatever name you choose) is available in any string passed as
a coordinate or option.
fig = TikzFigure()
with fig.add_loop("i", range(6), comment="Row of circles") as loop: loop.add_node( x=r"2*\i", y=0, shape="circle", fill="blue!40", content=r"\i", minimum_size="0.8cm", )
fig.show()
Show Tikz code
print(fig)% --------------------------------------------- %% Tikzfigure generated by tikzfigure v0.2.1 %% https://github.com/max-models/tikzfigure %% --------------------------------------------- %\begin{tikzpicture} % Row of circles \foreach \i in {0,1,2,3,4,5}{ \node[shape=circle, fill=blue!40, minimum size=0.8cm] () at ({2*\i}, {0}) {\i}; }\end{tikzpicture}print(fig.generate_standalone())\documentclass[border=10pt]{standalone}\usepackage{tikz}\usepackage{pgfplots}\pgfplotsset{compat=newest}\usepgfplotslibrary{groupplots}\usetikzlibrary{arrows.meta}\begin{document}% --------------------------------------------- %% Tikzfigure generated by tikzfigure v0.2.1 %% https://github.com/max-models/tikzfigure %% --------------------------------------------- %\begin{tikzpicture} % Row of circles \foreach \i in {0,1,2,3,4,5}{ \node[shape=circle, fill=blue!40, minimum size=0.8cm] () at ({2*\i}, {0}) {\i}; }\end{tikzpicture}
\end{document}Loop variable in colour expressions
Section titled “Loop variable in colour expressions”TikZ colour mixing expressions like blue!\i0!red interpolate between
two colours. Here \i ranges from 1 to 9, so \i0 gives 10, 20, … 90 —
a smooth gradient from blue to red.
fig = TikzFigure()
with fig.add_loop("i", range(1, 10), comment="Colour gradient") as loop: loop.add_node( x=r"2*\i", y=0, shape="circle", fill=r"blue!\i0!red", content=r"\i", minimum_size="1cm", color="white", font=r"\bfseries", )
fig.show()
Show Tikz code
print(fig)% --------------------------------------------- %% Tikzfigure generated by tikzfigure v0.2.1 %% https://github.com/max-models/tikzfigure %% --------------------------------------------- %\begin{tikzpicture} % Colour gradient \foreach \i in {1,2,3,4,5,6,7,8,9}{ \node[shape=circle, color=white, fill=blue!\i0!red, minimum size=1cm, font=\bfseries] () at ({2*\i}, {0}) {\i}; }\end{tikzpicture}print(fig.generate_standalone())\documentclass[border=10pt]{standalone}\usepackage{tikz}\usepackage{pgfplots}\pgfplotsset{compat=newest}\usepgfplotslibrary{groupplots}\usetikzlibrary{arrows.meta}\begin{document}% --------------------------------------------- %% Tikzfigure generated by tikzfigure v0.2.1 %% https://github.com/max-models/tikzfigure %% --------------------------------------------- %\begin{tikzpicture} % Colour gradient \foreach \i in {1,2,3,4,5,6,7,8,9}{ \node[shape=circle, color=white, fill=blue!\i0!red, minimum size=1cm, font=\bfseries] () at ({2*\i}, {0}) {\i}; }\end{tikzpicture}
\end{document}Nested loops: a 5x4 grid of nodes
Section titled “Nested loops: a 5x4 grid of nodes”Use a second loop.add_loop() inside the first to create a 2-D grid.
The inner loop variable \j is independent of the outer \i.
fig = TikzFigure()
with fig.add_loop("i", range(5), comment="Outer loop (columns)") as loop_i: with loop_i.add_loop("j", range(4), comment="Inner loop (rows)") as loop_j: loop_j.add_node( x=r"2*\i", y=r"2*\j", shape="rectangle", fill="orange!30", content=r"(\i,\j)", minimum_size="0.9cm", )
fig.show()
Show Tikz code
print(fig)% --------------------------------------------- %% Tikzfigure generated by tikzfigure v0.2.1 %% https://github.com/max-models/tikzfigure %% --------------------------------------------- %\begin{tikzpicture} % Outer loop (columns) \foreach \i in {0,1,2,3,4}{ % Inner loop (rows) \foreach \j in {0,1,2,3}{ \node[shape=rectangle, fill=orange!30, minimum size=0.9cm] () at ({2*\i}, {2*\j}) {(\i,\j)}; } }\end{tikzpicture}print(fig.generate_standalone())\documentclass[border=10pt]{standalone}\usepackage{tikz}\usepackage{pgfplots}\pgfplotsset{compat=newest}\usepgfplotslibrary{groupplots}\usetikzlibrary{arrows.meta}\begin{document}% --------------------------------------------- %% Tikzfigure generated by tikzfigure v0.2.1 %% https://github.com/max-models/tikzfigure %% --------------------------------------------- %\begin{tikzpicture} % Outer loop (columns) \foreach \i in {0,1,2,3,4}{ % Inner loop (rows) \foreach \j in {0,1,2,3}{ \node[shape=rectangle, fill=orange!30, minimum size=0.9cm] () at ({2*\i}, {2*\j}) {(\i,\j)}; } }\end{tikzpicture}
\end{document}The generated TikZ is a compact nested \foreach — compare that with
what 20 separate \node commands would look like.
add_variable: reusable TikZ constants
Section titled “add_variable: reusable TikZ constants”fig.add_variable(name, value) emits a \pgfmathsetmacro declaration
at the top of the picture. Use it to store a radius, spacing, or any
numeric constant that you want to be able to change in one place.
Here 12 nodes are placed on a circle of radius \radius. Python
computes the trigonometric coefficients; TikZ multiplies them by
\radius at compile time.
import math
fig = TikzFigure()fig.add_variable("radius", 3)
N = 12for i in range(N): frac = round(i / N, 4) angle = frac * 2 * math.pi fig.add_node( x=f"{{\\radius*{math.cos(angle):.4f}}}", y=f"{{\\radius*{math.sin(angle):.4f}}}", shape="circle", fill="teal!50", content=str(i + 1), minimum_size="0.7cm", color="white", font=r"\small", )
fig.show()
Show Tikz code
print(fig)% --------------------------------------------- %% Tikzfigure generated by tikzfigure v0.2.1 %% https://github.com/max-models/tikzfigure %% --------------------------------------------- %\begin{tikzpicture} \pgfmathsetmacro{\radius}{3} \node[shape=circle, color=white, fill=teal!50, minimum size=0.7cm, font=\small] (node0) at ({{\radius*1.0000}}, {{\radius*0.0000}}) {1}; \node[shape=circle, color=white, fill=teal!50, minimum size=0.7cm, font=\small] (node1) at ({{\radius*0.8661}}, {{\radius*0.4998}}) {2}; \node[shape=circle, color=white, fill=teal!50, minimum size=0.7cm, font=\small] (node2) at ({{\radius*0.4998}}, {{\radius*0.8661}}) {3}; \node[shape=circle, color=white, fill=teal!50, minimum size=0.7cm, font=\small] (node3) at ({{\radius*0.0000}}, {{\radius*1.0000}}) {4}; \node[shape=circle, color=white, fill=teal!50, minimum size=0.7cm, font=\small] (node4) at ({{\radius*-0.4998}}, {{\radius*0.8661}}) {5}; \node[shape=circle, color=white, fill=teal!50, minimum size=0.7cm, font=\small] (node5) at ({{\radius*-0.8661}}, {{\radius*0.4998}}) {6}; \node[shape=circle, color=white, fill=teal!50, minimum size=0.7cm, font=\small] (node6) at ({{\radius*-1.0000}}, {{\radius*0.0000}}) {7}; \node[shape=circle, color=white, fill=teal!50, minimum size=0.7cm, font=\small] (node7) at ({{\radius*-0.8661}}, {{\radius*-0.4998}}) {8}; \node[shape=circle, color=white, fill=teal!50, minimum size=0.7cm, font=\small] (node8) at ({{\radius*-0.4998}}, {{\radius*-0.8661}}) {9}; \node[shape=circle, color=white, fill=teal!50, minimum size=0.7cm, font=\small] (node9) at ({{\radius*-0.0000}}, {{\radius*-1.0000}}) {10}; \node[shape=circle, color=white, fill=teal!50, minimum size=0.7cm, font=\small] (node10) at ({{\radius*0.4998}}, {{\radius*-0.8661}}) {11}; \node[shape=circle, color=white, fill=teal!50, minimum size=0.7cm, font=\small] (node11) at ({{\radius*0.8661}}, {{\radius*-0.4998}}) {12};\end{tikzpicture}print(fig.generate_standalone())\documentclass[border=10pt]{standalone}\usepackage{tikz}\usepackage{pgfplots}\pgfplotsset{compat=newest}\usepgfplotslibrary{groupplots}\usetikzlibrary{arrows.meta}\begin{document}% --------------------------------------------- %% Tikzfigure generated by tikzfigure v0.2.1 %% https://github.com/max-models/tikzfigure %% --------------------------------------------- %\begin{tikzpicture} \pgfmathsetmacro{\radius}{3} \node[shape=circle, color=white, fill=teal!50, minimum size=0.7cm, font=\small] (node0) at ({{\radius*1.0000}}, {{\radius*0.0000}}) {1}; \node[shape=circle, color=white, fill=teal!50, minimum size=0.7cm, font=\small] (node1) at ({{\radius*0.8661}}, {{\radius*0.4998}}) {2}; \node[shape=circle, color=white, fill=teal!50, minimum size=0.7cm, font=\small] (node2) at ({{\radius*0.4998}}, {{\radius*0.8661}}) {3}; \node[shape=circle, color=white, fill=teal!50, minimum size=0.7cm, font=\small] (node3) at ({{\radius*0.0000}}, {{\radius*1.0000}}) {4}; \node[shape=circle, color=white, fill=teal!50, minimum size=0.7cm, font=\small] (node4) at ({{\radius*-0.4998}}, {{\radius*0.8661}}) {5}; \node[shape=circle, color=white, fill=teal!50, minimum size=0.7cm, font=\small] (node5) at ({{\radius*-0.8661}}, {{\radius*0.4998}}) {6}; \node[shape=circle, color=white, fill=teal!50, minimum size=0.7cm, font=\small] (node6) at ({{\radius*-1.0000}}, {{\radius*0.0000}}) {7}; \node[shape=circle, color=white, fill=teal!50, minimum size=0.7cm, font=\small] (node7) at ({{\radius*-0.8661}}, {{\radius*-0.4998}}) {8}; \node[shape=circle, color=white, fill=teal!50, minimum size=0.7cm, font=\small] (node8) at ({{\radius*-0.4998}}, {{\radius*-0.8661}}) {9}; \node[shape=circle, color=white, fill=teal!50, minimum size=0.7cm, font=\small] (node9) at ({{\radius*-0.0000}}, {{\radius*-1.0000}}) {10}; \node[shape=circle, color=white, fill=teal!50, minimum size=0.7cm, font=\small] (node10) at ({{\radius*0.4998}}, {{\radius*-0.8661}}) {11}; \node[shape=circle, color=white, fill=teal!50, minimum size=0.7cm, font=\small] (node11) at ({{\radius*0.8661}}, {{\radius*-0.4998}}) {12};\end{tikzpicture}
\end{document}Changing fig.add_variable("radius", 5) would scale the entire circle
without touching any node code.
Heart shape
Section titled “Heart shape”Remember the heart shape from the styling
tutorial? It can be drawn with a
single \draw command
width, height = 1.75, 2.0
fig = TikzFigure()
fig.colorlet("lightred", "red!40!white")fig.add_variable("width", 1.75)fig.add_variable("height", 2.0)
A = fig.add_node(r"-\width", r"\height")B = fig.add_node(0, 0)C = fig.add_node(r"\width", r"\height")D = fig.add_node(0, r"\height")
fig.draw( A.to(B, options=["out=-90, in=135"]) .to(C, options=["out=45, in=-90"]) .to(D, options=["in=80, out=100"]) .to(A, options=["in=80, out=100"]), color="red", line_width=4, cycle=True, center=True, fill="lightred",)
fig.show()
Practical example: a chain of labelled boxes
Section titled “Practical example: a chain of labelled boxes”A common diagram pattern is a horizontal chain of labelled boxes
connected by arrows. Using a \foreach loop keeps the LaTeX concise
even for long chains.
steps = ["Load", "Clean", "Transform", "Model", "Evaluate"]
fig = TikzFigure()
# Draw boxes via a TikZ foreach over a comma-separated list of indiceswith fig.add_loop("i", range(len(steps)), comment="Pipeline steps") as loop: loop.add_node( x=r"3*\i", y=0, shape="rectangle", fill="blue!20", draw="blue!60", minimum_width="2.2cm", minimum_height="0.8cm", rounded_corners="4pt", line_width=1.5, )
# Add step labels in Python (strings can't live inside \foreach easily)for i, step in enumerate(steps): fig.add_node( x=i * 3, y=0, content=step, draw="none", font=r"\small\bfseries", )
# Draw arrows between boxesfor i in range(len(steps) - 1): fig.draw( [(i * 3 + 1.1, 0), (i * 3 + 1.9, 0)], arrows="->", line_width=1.5, color="blue!60", )
fig.show()
Show Tikz code
print(fig)% --------------------------------------------- %% Tikzfigure generated by tikzfigure v0.2.1 %% https://github.com/max-models/tikzfigure %% --------------------------------------------- %\begin{tikzpicture} % Pipeline steps \foreach \i in {0,1,2,3,4}{ \node[shape=rectangle, fill=blue!20, draw=blue!60, minimum width=2.2cm, minimum height=0.8cm, line width=1.5, rounded corners=4pt] () at ({3*\i}, {0}) {}; } \node[draw=none, font=\small\bfseries] (node0) at ({0}, {0}) {Load}; \node[draw=none, font=\small\bfseries] (node1) at ({3}, {0}) {Clean}; \node[draw=none, font=\small\bfseries] (node2) at ({6}, {0}) {Transform}; \node[draw=none, font=\small\bfseries] (node3) at ({9}, {0}) {Model}; \node[draw=none, font=\small\bfseries] (node4) at ({12}, {0}) {Evaluate}; \draw[color=blue!60, line width=1.5, arrows=->] (1.1, 0) to (1.9, 0); \draw[color=blue!60, line width=1.5, arrows=->] (4.1, 0) to (4.9, 0); \draw[color=blue!60, line width=1.5, arrows=->] (7.1, 0) to (7.9, 0); \draw[color=blue!60, line width=1.5, arrows=->] (10.1, 0) to (10.9, 0);\end{tikzpicture}print(fig.generate_standalone())\documentclass[border=10pt]{standalone}\usepackage{tikz}\usepackage{pgfplots}\pgfplotsset{compat=newest}\usepgfplotslibrary{groupplots}\usetikzlibrary{arrows.meta}\begin{document}% --------------------------------------------- %% Tikzfigure generated by tikzfigure v0.2.1 %% https://github.com/max-models/tikzfigure %% --------------------------------------------- %\begin{tikzpicture} % Pipeline steps \foreach \i in {0,1,2,3,4}{ \node[shape=rectangle, fill=blue!20, draw=blue!60, minimum width=2.2cm, minimum height=0.8cm, line width=1.5, rounded corners=4pt] () at ({3*\i}, {0}) {}; } \node[draw=none, font=\small\bfseries] (node0) at ({0}, {0}) {Load}; \node[draw=none, font=\small\bfseries] (node1) at ({3}, {0}) {Clean}; \node[draw=none, font=\small\bfseries] (node2) at ({6}, {0}) {Transform}; \node[draw=none, font=\small\bfseries] (node3) at ({9}, {0}) {Model}; \node[draw=none, font=\small\bfseries] (node4) at ({12}, {0}) {Evaluate}; \draw[color=blue!60, line width=1.5, arrows=->] (1.1, 0) to (1.9, 0); \draw[color=blue!60, line width=1.5, arrows=->] (4.1, 0) to (4.9, 0); \draw[color=blue!60, line width=1.5, arrows=->] (7.1, 0) to (7.9, 0); \draw[color=blue!60, line width=1.5, arrows=->] (10.1, 0) to (10.9, 0);\end{tikzpicture}
\end{document}