Tikz Templates


A collection of simple figures

Load TikZ in either one way.

  • \documentclass[tikz, border=2pt]{standalone}
  • \usepackage{tikz}

Graph

With pgfplots

PGF Plots automizes the axes configurations, for example:

  • major and minor ticks and labels on the axes
  • major and minor grid lines
  • legend styles and positioning

Simplest example for Discord:

\begin{tikzpicture}[declare function={f(\x)=(\x)^2/2 - 5;}]
\begin{axis}[
  axis equal,
  grid=both,
  grid style={line width=.2pt, draw=gray},
  minor tick num=1,
  xtick={-4,-2,...,4},
  ytick={-4,-2,...,4},
  axis lines=center,
  title={Graph of $y = \frac12 x^2 - 5$},
]
\addplot[yellow,domain=-4:4] {f(\x)};
\pgfplotsset{
  grid style={gray},
  major grid style={gray,line width=0.7}
}
\end{axis}
\end{tikzpicture}

TeXit’s output:

texit graph template

Explanation for the code:

  • \usepackage{pgfplots}: load package
  • \pgfplotsset{compat=1.18}: set dependency
  • \pgfplotsset{grid style={gray}}: draw grid lines
  • \pgfplotsset{major grid style={gray,line width=1}}: thicker major grid lines
  • axis equal: x and y-distances are in 1:1 ratio
  • axis lines=center: axes passes through the origin
  • grid=both: enable major grid lines
  • minor tick num=9: draw 9 minor grid lines between each pair of major grid lines
  • xlabel={$x$}, ylabel={$y$}: labels for the axes
  • title={}: title of a graph
  • legend style={at={(0,-0.05)},anchor=north west,fill=none}: legend styles
    • at={(0,-0.05)} coordinates relative to the entire PGF plot. (0,0) and (1,1) represent the bottom left and top right corners respectively.
    • anchor=north west: relative to the top left corner of the entire legend box
    • fill=none: (for TeXit) transparent background. TeXit renders a black background by default.
  • \begin{scope}[thick, samples=201, smooth]: avoid repetition of code
  • blue!20 is a shorthand for blue!20!white, meaning 20% blue and 80% white.
\documentclass[tikz,border=2pt]{standalone}
\usepackage{pgfplots}
\pgfplotsset{compat=1.18}
\begin{document}
\pgfplotsset{
    grid style={gray},
    major grid style={gray,line width=0.7}
}
\begin{tikzpicture}
\begin{axis}[
    axis equal,
    axis lines=center,
    grid=both,
    minor tick num=9,
    xlabel={$x$},
    ylabel={$y$},
    title={Another \LaTeX{} graph},
    legend style={at={(0,-0.05)},anchor=north west,fill=none}
    ]
    \begin{scope}[thick, samples=201, smooth]
    \addplot[domain=-2.3:2.3, yellow] (x,{0.5 * x^2 - 1.5 * x - 4});
    \addlegendentry{$y = 0.5x^2-1.5x-4$};
    \addplot[domain=0:360,variable=\t, blue!20] ({sqrt(5) * cos(t)}, {sqrt(5) * sin(t)});
    \addlegendentry{$x^2+y^2=5$};
    \end{scope}
\end{axis}
\end{tikzpicture}
\end{document}
sample pgfplots SVG

sample TikZ + PGF plots

SVG generated with dvisvgm from PDF

\pgfplotsset{
    grid style={gray,opacity=0.3},
    major grid style={gray,line width=1,opacity=0.3}
}
\begin{tikzpicture}
\begin{axis}[
    axis equal,
    axis lines=center,
    grid=both,
    xmin=-9,
    xmax=3,
    ymin=-5,
    ymax=7,
    minor tick num=9,
    xlabel={$\mathrm{Re}(z)$},
    ylabel={$\mathrm{Im}(z)$},
    title={Sample \LaTeX{} complex plot},
    enlargelimits={abs=0.5},
    disabledatascaling,
]
\draw[red!40,fill=red!40,fill opacity=0.3] (-5,0) circle [radius=4];
\node at (-6.8,2.2) [red!30,rotate=45]{\scriptsize $|z+5| \le 4$};
\begin{scope}
    \clip (current axis.south west) rectangle (current axis.north east);
    \draw[fill=yellow,fill opacity=0.3] (-15,-10) -- (5,10) -- (current axis.south east) -- cycle;
    \node at (current axis.south east) [anchor=south east, yellow]{\scriptsize $\mathrm{Re}(ze^{i \pi/4}) \ge -5/\sqrt{2}$};
\end{scope}
\end{axis}
\end{tikzpicture}
sample TikZ complex plot

sample LaTeX complex plot

SVG generated with dvisvgm from PDF

Special technical features of the above graph:

  • axis equal: ensure that the circle isn’t shown as an ellipse.
  • disabledatascaling: ensure that the circle is shown.
  • grid lines: I found that opacity=0.3 with line width=1 suits gray.
  • graph limits: I first tried xmin=-9 and xmax=3, but without ymin=-5 and ymax=7, I only got the graph of about [-1.5,1.5] × [-1,2].
  • enlargelimits={abs=0.5}: to ensure that the tips of the axes don’t overlap the labels for xmax and ymax.
  • the default cs (coordinate system) is axis cs inside the axis environment. This allows a simple syntax for a TikZ circle with [radius=<r>].
  • node at (P) [rotate=<deg>]{text}: rotate node content by <deg> anticlockwise.
  • fill opacity=0.3: shade the region with a light color, so that the math expression of the region can be written inside the filling.
  • current axis: contains points related to the two ends of each axis.

Code sample for a sample data plot

\documentclass[tikz,border=2pt]{standalone}
\usepackage{pgfplots}
\pgfplotsset{compat=1.18}
\begin{document}
\begin{tikzpicture}
\begin{axis} [
title={sample \LaTeX{} graph},
axis lines=middle,
xlabel near ticks,
ylabel near ticks,
xlabel={$t$ time (s)},
ylabel={$h$ distance (cm)},
xmin=0,
xmax=0.5,
ymin=0,
ymax=80,
xtick={0,0.1,...,0.5},
ytick={0,20,...,80},
grid=both,
minor tick num=9,
grid style={line width=.1pt, draw=black!50},
major grid style={line width=.5pt,draw=black!50},
legend style={at={(1.05,1)},anchor=north west,fill=none},
enlargelimits=0.05,
]
\addplot[mark=*,thick] coordinates {
(0,0)
(0.1,20)
(0.2,40)
(0.3,60)
(0.4,80)
};
\addlegendentry{line 1}
\addplot[mark=*,thick,blue!50] coordinates {
(0.05,3.4)
(0.16,21.7)
(0.24,37.6)
(0.33,64.4)
(0.49,78.2)
};
\addlegendentry{line 2}
\end{axis}
\end{tikzpicture}
\end{document}
sample TikZ data plot

simple PGF data plot

SVG generated with dvisvgm from PDF

The $\LaTeX$ in the graph’s title has to be replaced by \LaTeX{} to avoid the error message

You can't use `\spacefactor' in math mode.

A large part of the code is copied from TeX.SE exchange.

\pgfplotsset{
    grid style={gray},
    major grid style={gray,line width=1},
    trig format plots=rad,
    legend columns=2,
}
\pgfmathsetmacro{\PI}{3.141592654}
\begin{tikzpicture}
\begin{axis}[axis lines=center,grid=both,
    xmin=-0.5*\PI,
    xmax=2.5*\PI,
    ymin=-1.5,
    ymax=1.5,
    restrict y to domain=-1.5:1.5,
    minor tick num=9,
    xlabel={$x$},
    ylabel={$y$},
    scaled x ticks={real:\PI},
    xtick scale label code/.code={},
    xtick distance=\PI/2,
    xticklabel={
        \ifdim \tick pt = 1 pt
            \strut$\pi$
        \else\ifdim \tick pt = -1 pt
            \strut$-\pi$
        \else
            \pgfmathparse{round(10*\tick)/10}
            \pgfmathifisint{\pgfmathresult}{%
                \strut$\pgfmathprintnumber[int detect]{\pgfmathresult}\pi$
            }{%
                \strut$\pgfmathprintnumber{\pgfmathresult}\pi$
            }
        \fi\fi
    },
    xticklabel style={
        /pgf/number format/frac,
        /pgf/number format/frac whole=false,
    },
    title={Sample \LaTeX{} trigonometric graph},
    legend style={at={(0,-0.05)},anchor=north west,fill=none},
    enlargelimits={abs=0.2},
]
\begin{scope}[thick,domain=-pi/2:2.5*pi,samples=201,smooth,mark=none]
\addplot+ {sin(x)};
\addlegendentry{$y=\sin(x)$};
\addplot+ {cos(x)};
\addlegendentry{$y = \cos(x)$};
\addplot+[dashed] {tan(x)};
\addlegendentry{$y = \tan(x)$};
\end{scope}
\foreach \k in {0,0.5,...,2} {\addplot[dashed] coordinates{({\k * \PI},-1.5) ({\k * \PI},1.5)};}
\end{axis}
\end{tikzpicture}
  • \pgfmathsetmacro{\PI}{3.141592654}: without this, the calculated \tick would become a strange fraction rather than π/2 when xtick distance=\PI.
  • scaled x ticks={real: \PI}: tick number \tick is divided by \PI. Outside xticklabel, the x-coordinate can be used as usual.
  • xticklabel and xticklabel style: set number plotting to fractions. In case that the numbers are ±1, the \ifdim would isolate the case, and omit the ‘1’ in the result.
  • restrict y to domain=-1.5:1.5: avoid tangent curves from bumping up the frame.
sample PGF trigonometric plot

simple PGF trigonometric plot

SVG generated with dvisvgm from PDF

Without pgfplot

An example copied from Discord.

\begin{tikzpicture}
    \draw[help lines,step=1 cm](-4.9,-3.9) grid (4.9,3.9);

    % shaded region
    \fill [gray, domain=-2:2, variable=\x]
    (-2, 0)
    -- plot ({\x}, {\x-1})
    -- (2, 0)
    -- cycle;

    % x-axis
    \draw [thick] [->] (-5,0)--(5,0) node[right, below] {$x$};
    \foreach \x in {-4,...,4}
    \draw[xshift=\x cm, thick] (0pt,-1pt)--(0pt,1pt) node[below] {$\x$};

    % y-axis
    \draw [thick] [->] (0,-4)--(0,4) node[above, left] {$y$};
    \foreach \y in {-3,...,3}
    \draw[yshift=\y cm, thick] (-1pt,0pt)--(1pt,0pt) node[left] {$\y$};

    % function plot(s)
    \draw [domain=-2:2, variable=\x]
    plot ({\x}, {\x*\x}) node[right] at (1.5,2) {$f(x)=x^2$};

    \draw [domain=-2:3.5, variable=\x] plot ({\x}, {\x-1});
\end{tikzpicture}
sample TikZ graph with grid without PGF plots

Example of TikZ plot without PGF plots

code copied from Discord

Geometry

2D

\begin{tikzpicture}
\draw[<->, >=latex, thick] (-3,0) -- (3,0) node [below, pos=0.5] {6 cm};
\draw[thick] circle (3);
\draw (0,0) node {$\bullet$};
\end{tikzpicture}
sample TikZ circle

simple TikZ circle with given diameter

SVG generated with dvisvgm from PDF

\begin{tikzpicture}[scale=0.5]
  \draw (0,0) rectangle (3,7);
\end{tikzpicture}
sample TikZ rectangle

sample TikZ rectangle with two given points

two endpoints of a diagonal

Right-angled triangle with the right angle mark.

\begin{tikzpicture}[scale=0.5]
\coordinate (O) at (0,0);
\coordinate (B) at (12,0);
\coordinate (H) at (0,16);
\draw[thick] (O) node [below left] {$O$}
-- (B) node [below, pos=0.5]{12} node [right] {$B$}
-- (H) node [above] {$H$}
-- cycle node [left, pos=0.5] {16};
\draw[thick] (0,1) -- (1,1) -- (1,0);
\end{tikzpicture}

sample TikZ right-angled triangle with given side-lengths

Angle with mark.

\draw (x,y) arc (start:stop:radius); draws an arc.

\begin{tikzpicture}[scale=0.5]
\draw (0,0) node[below] {$O$};
\draw (2,0) node[right] {$A$};
\draw (2,2) node[right] {$B$};
\draw (0,0) -- (2,0);
\draw (0,0) -- (2,2);
\draw (1,0) arc (0:45:1);
\end{tikzpicture}

sample TikZ angle

  • \newcommand\myvar{value} before \begin{document}
  • radius: radius length (i.e. distance of a vertex to the center), \r is used by the system.
  • n: number of sides
  • dTheta: size of angle of a sector
  • \the\numexpr\n-1: evalute n − 1.
    • I’m not sure if \n - 1 triggers an error because \n might be interpreted as text.
    • \numexpr\n-1 means \n-1 is a numerical expression.
    • \the preceeding \numexpr evaluates this expression.
  • \foreach \x in {1,...,m} {[cmd in terms of \x]}: use for loop to avoid repetition.

Improved stand alone example of regular polygon.

\newcommand\myR{2}
\newcommand\n{7}
\newcommand\dTheta{360/\n}
\tikzset{
  every node/.style={draw,fill,circle,minimum size=6pt,inner sep=-2pt,},
  label distance=2.5mm,
}
\begin{tikzpicture}
  \foreach \k in {1,...,\n} {
    \node[label=\k*\dTheta:$V_{\k}$] (V\k) at ({\dTheta*\k}:\myR){};
  }
  \draw (V1.center) foreach \k in {2,...,\n} { -- (V\k.center)} -- cycle;
\end{tikzpicture}
  • every node/.style applies a series of styles inside [...] to every node.
    • node {$\bullet$} can’t represent the actual position described by the character ‘•’.
    • minimum size is the minimum diameter of the circular dot.
    • inner sep is the “signed space” between the perimeter of the content box and the node’s boundary (rendered by draw).
  • I’ve tried \draw (V1) -- foreach ... {} -- cycle but I got a logic error: the node edges were connected to the node boundary instead of the coordinate (V\k.center). This is known as TikZ’s “path shortening” effect, which is deactivated .
  • I’ve used an empty \node with a label=<angle>:<text> instead of a \coordinate with a pin=<angle>:<text> because
    1. \coordinate is a shorthand of \node[shape=coordinate], but I’m using a circular dot \node[draw,fill,shape=circle] to mark the actual position of the vertex. As a result, the actual shape=circle.
    2. I don’t want any pin edges. It would be more sensible to use labels instead of pins with every pin edge/.style={opacity=0}.
sample TikZ regular polygon

regular n-gon drawn with TikZ with n = 7

SVG generated with dvisvgm from PDF

3D

\usetikzlibrary{3d} in the preamble.

Simplest example to how coordinates are presented.

\begin{tikzpicture}
  \draw[-latex] (0,0,0)--(1,0,0) node[pos=1.5]{$\vec{i}$};
  \draw[-latex] (0,0,0)--(0,1,0) node[pos=1.5]{$\vec{j}$};
  \draw[-latex] (0,0,0)--(0,0,1) node[pos=1.5]{$\vec{k}$};
  \foreach \k in {0,1} {\draw (0,0,\k)--(1,0,\k)--(1,1,\k)--(0,1,\k)--cycle;}
  \foreach \i in {0,1} \foreach \j in {0,1} \draw (\i,\j,0)--(\i,\j,1);
\end{tikzpicture}
sample TikZ cube

sample cube with three axes

SVG generated with dvisvgm from PDF

You might want the z-axis pointing upwards and the y-axis pointing to the right. TikZ pour l’impatient has provided a handy math3d style for that.

\documentclass[preview,tikz]{standalone}
\usetikzlibrary{3d}
\begin{document}
\begin{tikzpicture}
[
  math3d/.style={x= {(-0.353cm,-0.353cm)},z={(0cm,1cm)},y={(1cm,0cm)}},
]
\draw[-latex,math3d] (0,0,0)--(1,0,0) node[pos=1.5]{$\vec{i}$};
\draw[-latex,math3d] (0,0,0)--(0,1,0) node[pos=1.5]{$\vec{j}$};
\draw[-latex,math3d] (0,0,0)--(0,0,1) node[pos=1.5]{$\vec{k}$};
\foreach \k in {0,1} {\draw (0,0,\k)--(1,0,\k)--(1,1,\k)--(0,1,\k)--cycle;}
\foreach \i in {0,1} \foreach \j in {0,1} \draw (\i,\j,0)--(\i,\j,1);
\end{tikzpicture}
\end{document}
better sample TikZ cube

better sample cube with three axes

SVG generated with dvisvgm from PDF

\documentclass[preview,tikz]{standalone}
\usetikzlibrary{3d}
\begin{document}
\begin{tikzpicture}
[
  every node/.style={draw,fill,circle,minimum size=6pt,inner sep=-2pt,},
  x= {(-0.353cm,-0.353cm)}, z={(0cm,1cm)},y={(1cm,0cm)},
]
\pgfmathsetmacro\myR{2}
\pgfmathsetmacro\n{7}
\pgfmathsetmacro\dTheta{360/\n}
\pgfmathsetmacro\myH{3}
\foreach \lvl in {0,\myH} {
  \foreach \k in {1,...,\n} {
    \node (L\lvl V\k) at (xyz cylindrical cs:radius=\myR, angle={\dTheta * \k}, z=\lvl) {};
  }
}
\foreach \lvl in {0,\myH} {
  \draw[dashed] (L\lvl V1.center) foreach \k in {2,...,\n} { -- (L\lvl V\k.center)} -- cycle;
}
\foreach \k in {1,...,\n} {
  \draw[dashed] (L0V\k.center) -- (L\myH V\k.center);
}
\end{tikzpicture}
\end{document}

The space between L0 and V\k.center is taken away to avoid compilation error.

sample TikZ prism

sample regular n-gon prism with n = 7

SVG generated with dvisvgm from PDF

\documentclass[preview,tikz]{standalone}
\usetikzlibrary{3d}
\begin{document}
\begin{tikzpicture}
[
  every node/.style={draw,fill,circle,minimum size=6pt,inner sep=-2pt,},
  x= {(-0.353cm,-0.353cm)}, z={(0cm,1cm)},y={(1cm,0cm)},
]
\pgfmathsetmacro\myR{2}
\pgfmathsetmacro\n{7}
\pgfmathsetmacro\dTheta{360/\n}
\pgfmathsetmacro\myH{3}
\node (apex) at (0,0,\myH) {};
\foreach \k in {1,...,\n} {
  \node (V\k) at (xyz cylindrical cs:radius=\myR, angle={\dTheta * \k}, z=0) {};
}
\draw[dashed] (V1.center) foreach \k in {2,...,\n} { -- (V\k.center)} -- cycle;
\foreach \k in {1,...,\n} {\draw[dashed] (apex) -- (V\k.center);}
\end{tikzpicture}
\end{document}
sample TikZ pyramid

sample regular n-gon pyramid with n = 7

SVG generated with dvisvgm from PDF

Generate from Geogebra

99% of the guide/docs use Desktop version. Luckily, this Geogebra help thread provides a way to convert Geogebra figures to TikZ code on the web app.

const patNum = /[\d]+(\.([\d]+))?/g
const rplNum = a => Math.round((parseFloat(a)+Number.EPSILON)*100)/100
const patColor=/\\definecolor{(?<color>[a-z]+)}/g
const patLw=/line width=(\d+\.?\d*)pt/g
const rplLw = a => `lw${a.replace('.','p')}`
const axes = ['x', 'y']
const bddtypes = ['min', 'max']
const PGF_VERSION = 1.18
let myApp
let appNum = 0
new Function(`myApp = ${$('[id^="ggbApplet"]')[appNum].id}`)()
myApp.exportPGF(function(a){
  const SCALE_FACTOR = rplNum(50/Math.max(...a.match(patNum).map(a=>parseFloat(a))))
  // build the array of colors
  let myColors = []
  while((match = patColor.exec(a)) !== null) {
    let color = match.groups.color
    for (var i = 1; i <= color.length; i++) {
      let colorShort = color.substring(0,i)
      if (myColors.find(c=>c.colorShort==colorShort) == null) {
        myColors.push({color:color,colorShort:colorShort})
        break
      }
    }
  }
  // round to 2dp, remove unnecessary styles, inject custom style holder
  let output = a.replace(patNum,rplNum)
    .replace(/\[10pt\]{article}/,'{standalone}')
    .replace(/(compat=)\d+\.\d+/,`$1${PGF_VERSION}`)
    .replace(/^\\usepackage{mathrsfs}\s*\\usetikzlibrary{arrows}\s*\\pagestyle{empty}\s*\n/m,'')
    .replace(/(\\begin{tikzpicture}\[)(.*)(\])/,`$1scale=${SCALE_FACTOR}$3`)
    .replace(/(?=\\begin{tikzpicture})/,'\\tikzset{\n}\n')
  // replace color=, fill= with short names
  const myIdx = output.indexOf('\\tikzset{') + '\\tikzset{\n'.length
  let myStyles = ''
  for (color of myColors) {
    for (word of ['color', 'fill']) {
        myStyles += `${word[0]}${color.colorShort}/.style={${word}=${color.color}},\n`
        let pat = new RegExp(`${word}=${color.color}`, 'g')
        output = output.replace(pat,`${word[0]}${color.colorShort}`)
    }
  }
  // build the set of line widths
  const myLw = new Set()
  while((match = patLw.exec(output)) !== null) {
    line_width = match[1]
    if(!myLw.has(line_width)) {myLw.add(line_width)}
  }
  // replace line width= with short names
  for (lw of myLw) {
    myStyles += `lw${lw.replace('.','p')}/.style={line width=${lw}pt},\n`
    let pat = new RegExp(`line width=${lw}pt`,'g')
    output = output.replace(pat,`lw${lw.replace('.','p')}`)
  }
  // inject my styles into custom styles holder
  output = [output.slice(0,myIdx),myStyles,output.slice(myIdx)].join('')
  // remove extra spaces
  output = output.replace(/\\draw \[/g,'\\draw[')
    .replace(/\)\s*--\s*\(/g,')--(')
  // restrict x,y domains to [xy](min|max)
  if (output.indexOf("\\begin{axis}") > -1){
    const graphBdd = {}  // {x: {min: x0, max: x1}, y: {min: y0, may: y1}}
    let restrictStr = ''
    let pat, bdd
    for (axis of axes) {
      graphBdd[axis] = {}
      for (bddtype of bddtypes) {
        pat = new RegExp(`${axis}${bddtype}=(-?[\\d]+(\.([\\d]+))?)`)
        bdd = parseFloat(output.match(pat)[1])
        graphBdd[axis][bddtype] = bdd
      }
      restrictStr += `,\nrestrict ${axis} to domain=${graphBdd[axis]["min"]}:${graphBdd[axis]["max"]}`
    }
    let g0 = output.match(pat)[0]
    console.log(`restrictStr = ${restrictStr}`)
    output = output.replace(pat,`${g0}${restrictStr}`)
  }
  // print the output
  console.log(output)
})

I’m using my Geogebra activity about the Co-Side Theorem (共邊定理) as an example.

  1. (Optional step) Search “applet” in the page’s source code so as to find the applet’s ID.

    find Geogebra applet number with web dev tool

    In this example, the desired ID is 37303159.

  2. Type the first few letters of ggbApplet in the web developer tools’ console, followed by .

    enter ggbApplet with ID no. with tab

  3. Choose the function exportPGF() with the tab key.

    choose exportPGF() with tab

  4. Inside the bracket, type function(a){console.log(a}.

    press enter to get result

More advanced features

Foreach loops

Example copied from a TeX.SE answer

\documentclass{article}
\usepackage{tikz}
\begin{document}
\begin{tikzpicture}
\foreach \pointA/\pointB in {{(1,0)}/{(2,2)},{(3,4)}/{(2,1)}}{
  \draw \pointA -- \pointB;
}
\end{tikzpicture}
\end{document}

After some testing, I found that the following code works.

\documentclass{article}
\usepackage{tikz}
\begin{document}
\begin{tikzpicture}
\foreach \pointA/\pointB in {
  {(1,0)}/{(2,2)},
  {(3,4)}/{(2,1)}%
} {
  \draw \pointA -- \pointB;
}
\end{tikzpicture}
\end{document}

That’s great especially when there’re

  • many loop variables
  • many list entries

TikZ’s docs about the auto-loaded PGFFor package provides seperate examples for these keys:

  • evaluate=<variable>
    • with an example for as <macro> to store the evaluation result
    • without an example for using <expression>, which should contain at least one reference to <variable>
  • remember=<variable> as <macro> (initially <value>) seemed too advanced and delicate for me in the past. After struggling with \tikzmath{…} primitive \for{…}; loops while often getting compilation errors due to
    1. omission of final ;

    2. omission of surrounding {}; in the TikZ command {\draw …;}; inside a \for loop

    3. problem with dynamic evaluation of TikZ (node\varA name\varB) inside \for loop. I can’t figure out how \expandafter works inside a \for loop inside \tikzmath{…}.

      With some experience in array’s reduction/aggregation operation in some programming language (C#, Java, JavaScript), I start appreciating

      \foreach \var [
        evaluate=\lastVar as \curVar using <funct(\lastVar)>,
        remember=\curVar as \lastVar initially \valZero
      ] in \list {}
      

      like

      const valZero = ?;
      let list = [?, , ?];
      let curVar = _;  // another variable
      let lastVar = valZero;
      for (let i = 0; i < list.length; i++) {
        curVar = funct(lastVar);
      
        // expressions using lastVar and/or curVar and/or list
      
        lastVar = curVar;
      }
      

      In practice, while writing in usual programming languages, we have to take care of each variable’s type and/or scope, slowing down our development. The above usage of remember and evaluate with every optional statement together, which is NOT present in the official docs, can condense code. It has a ‘X’ pattern between \curVar and \lastVar.

Here’s a simple template for fractals.

triangle fractal

\begin{tikzpicture}
  \path (0,0) coordinate (R0P1);
  \pgfmathsetmacro\myN{7}
  \foreach \ring [
    remember=\ring as \lastRing (initially 0),
    evaluate=\lastNumPoints as \numPoints using int(3*\lastNumPoints),
    remember=\numPoints as \lastNumPoints (initially 1),
    evaluate=\lastBranchLen as \branchLen using \lastBranchLen/2,
    remember=\branchLen as \lastBranchLen (initially 3),
  ] in {1,...,\myN} {
    \foreach \parent in {1,...,\lastNumPoints} {
      \foreach \branch [evaluate=\branch as \sonIdx using int(3*(\parent-1)+\branch+1)] in {0,1,2} {
        \path (R\lastRing P\parent) -- ++({120*\branch+90}:\branchLen) coordinate (R\ring P\sonIdx);
      }
      \draw[yellow] ($(R\lastRing P\parent)+(90:2*\branchLen)$)
        -- ($(R\lastRing P\parent)+(210:2*\branchLen)$)
        -- ($(R\lastRing P\parent)+(330:2*\branchLen)$)
        -- cycle;
    }
  }
\end{tikzpicture}

From each “parent” in the previous “ring” $R_{n-1}P_k$, I declared three new coordinates $R_nP_{3(k-1)+j}$, where $j \in \lbrace 1, 2, 3 \rbrace$, so that these new coordinates form an equilateral traingle △ with the parent as its center, with half of the previous branch length. Using this logic and TikZ’s pic command, it’s easy to draw fractals of any pattern.

heart fractal

\tikzset{
  heart/.pic={
    \fill[red!40] (0,0) .. controls (0,0.75) and (-1.5,1.00) .. (-1.5,2) arc (180:0:0.75) -- cycle;
    \fill[red!40] (0,0) .. controls (0,0.75) and (1.5,1.00) .. (1.5,2) arc (0:180:0.75) -- cycle;
  }
}
\begin{tikzpicture}
  \path (0,0) coordinate (R0P1);
  \pgfmathsetmacro\myN{5}
  \foreach \ring [
    remember=\ring as \lastRing (initially 0),
    evaluate=\lastNumPoints as \numPoints using int(3*\lastNumPoints),
    remember=\numPoints as \lastNumPoints (initially 1),
    evaluate=\lastBranchLen as \branchLen using \lastBranchLen/2,
    remember=\branchLen as \lastBranchLen (initially 1),
  ] in {1,...,\myN} {
    \foreach \parent in {1,...,\lastNumPoints} {
      \foreach \branch [evaluate=\branch as \sonIdx using int(3*(\parent-1)+\branch+1)] in {0,1,2} {
        \path (R\lastRing P\parent) -- ++({120*\branch+90}:\branchLen) coordinate (R\ring P\sonIdx);
      }
      \pic[scale=0.33*\branchLen] at ([shift={(0,-0.45*\branchLen)}] R\lastRing P\parent) {heart};
    }
  }
\end{tikzpicture}

TikZ math library

Variables

\tikzmath {
  int \f;
  \f = 20;
  print {$f = \f$};
  print \newline;
}
  • integer: longer int
  • real
  • complex

for-loops (NO while-loops)

“Double quotes” for “text strings”.

\tikzmath{
  for \s in {"a","b","c"} { print {\s}; };
}

Output (in text mode)

a b c

... for ranges.

% documentclass{standalone} won't give new lines, need "article"
\tikzmath{
  for \i in {1,...,5} { print {$i = \i$}; print \newline; };
}

Output

  1. first line has \parindent
  2. other lines have .0

tikzmath foreach example

Functions

TikZ’s official site has an example. Instead of copy & pasting, I’ve using my own one, drafted by ChatGPT. This following function won’t work.

\tikzmath{
  int \mynum, \mybase, \highestPow;
  \mynum = 10;
  \mybase = 2;
  function highestPow(\n, \b) {
    if \n == 0 then {return 0;};
    int \d, \tempnum;
    \d = 0;
    \tempnum = \n;
    for \i in {1,...,\tempnum} {
      int \r;
      \r = mod(\tempnum,\b);
      if \r > 0 then {return \d;};
      print {$n = \tempnum$, $d = \d$, $r = \r$};
      print \newline;
      \d = \d + 1;
      \tempnum = \tempnum / \b;
    };
    return \d;
  };
  \highestPow = highestPow(\mynum, \mybase);
  print {The highest power of \mybase{} that divides \mynum{} is \highestPow};
}
  • return inside for loop inside function won’t stop for loop from running.
  • I can’t do declaration and assignment at the same time.
  • I can’t break; a for loop.
  • Each for, if, function, etc, statement ends with a semicolon ;.
  • A TeX.SE user suggested replacing if [cond] then { "break;" }; with if ![cond] then { %TODO }.

An approximation to Cantor set using closed intervals

closed interval approximation for Cantor set

Cantor set simulation with n = 8

SVG generated with dvisvgm from DVI

Commands used

latex temp.tex
dvisvgm -R -d 2 -T "S 2" --font-format=woff temp.dvi -o 240903-cantor-set.svg
\documentclass[preview, margin=2pt, tikz]{standalone}
\usepackage{tikz}
\usetikzlibrary{math}
\begin{document}
\begin{tikzpicture}[scale=6]
\pgfmathsetmacro\myN{5}  % number of levels (i.e. Cantor)
\pgfmathsetmacro\myRowSep{0.2}  % display (non-math) parameter for row separation
\pgfmathsetmacro\myLblSep{1pt}  % display (non-math) parameter for label separation
\tikzmath{ % auxiliary function to find out highest dividing power
  function highestPow(\n, \b) {
    if \n == 0 then {return 0;};
    int \d, \tempnum;
    \d = 0;
    \tempnum = \n;
    for \i in {1,...,\tempnum} {
      if mod(\tempnum,\b) == 0 then { \d = \d + 1; \tempnum = \tempnum / \b; };
    };
    return \d;
  };
}
\foreach \row in {0,...,\myN} {
  \pgfmathsetmacro\numInt{2^\row}  % number of intervals
  \pgfmathsetmacro\intLen{pow(1/3,\row)}  % interval length
  \pgfmathsetmacro\vpos{-\myRowSep*\row}  % display (non-math) param for vertical position
  \node[left=\myLblSep] at (0, \vpos) {$C_\row$};
  % use 1/3 instead of 3 as base to avoid too large value
  \foreach \col [evaluate=\col as \curStep using {\displacement+\intLen+pow(1/3,\row-highestPow(\col,2))},
      remember=\curStep as \displacement (initially 0)] in {1,...,\numInt} {
    \draw (\displacement, \vpos) -- ++(\intLen, 0);
  }
}
\node[above] at (current bounding box.north) {Cantor set $C = \bigcap_{n = 1}^{\infty} C_n$};
\end{tikzpicture}
\end{document}

A step function approximation to Cantor function

step function approximation for cantor function

Cantor function's approximation using a step function

SVG generated with dvisvgm from PDF

In original TikZ code (copied from Discord a couple of years ago), the axis labels aren’t opposing the arrow tip like above. That’s good when there’s no grid. Unluckily, axis labels on top of gray grid lines don’t look nice. After some experiments, I found that when the axis arrow extension is equal to the grid step, the visual outcome is the best.

Apart from that, I applied some tick markings, and a few tick labels, so that they depict some important lengths of the main character, without overcrowding the axis with too many labels.

On the Discord version, I used \draw[yellow, thick] so that the curve is more visible. An opacity of 0.5 is applied to let the main character shine.

\documentclass[preview, margin=2pt, tikz]{standalone}
\usepackage{tikz}
\usetikzlibrary{math}
\begin{document}
\begin{tikzpicture}[scale=6]
\pgfmathsetmacro\myN{8}  % number of levels, must be greater than one
\pgfmathsetmacro\myRowSep{0.5^\myN}  % row separation
\pgfmathsetmacro\myLblSep{1pt}  % display (non-math) parameter for label separation
\pgfmathsetmacro\myPadding{.05} % display (non-math) parameter for extra axis length

\draw[help lines, step=0.05cm] (0,0) grid (1,1);
% x-axis
\draw[thick] [->] (-\myPadding,0) -- (1+\myPadding,0) node [right] {$x$};
\foreach \x in {0,0.111,...,1} {
  \draw[xshift=\x cm, opacity=.5] (0pt,-.5pt)--(0pt,.5pt);
};
% y-axis
\draw[thick] [->] (0,-\myPadding) -- (0,1+\myPadding) node [above] {$y$};
\foreach \y in {0,0.0625,...,1} {
  \draw[yshift=\y cm, opacity=.5] (-.5pt,0pt)--(.5pt,0pt);
};
% tick labels
\path (0,0) node[below left] {$O$};
\foreach \x in {1/3, 2/3, 1} {\node[below] at (\x,0) {$\x$};};
\foreach \y in {1/4, 1/2, 3/4, 1} {\node[left] at (0,\y) {$\y$};};

\tikzmath{ % auxiliary function to find out highest dividing power
  function highestPow(\n, \b) {
    if \n == 0 then {return 0;};
    int \d, \tempnum;
    \d = 0;
    \tempnum = \n;
    for \i in {1,...,\tempnum} {
      if mod(\tempnum,\b) == 0 then { \d = \d + 1; \tempnum = \tempnum / \b; };
    };
    return \d;
  };
}
\pgfmathsetmacro\numInt{2^\myN-1}  % number of intervals
\pgfmathsetmacro\intLen{pow(1/3,\myN)}  % interval length
% use 1/3 instead of 3 as base to avoid too large value
\foreach \col [evaluate=\col as \curStep using {\displacement+\intLen+pow(1/3,\myN-highestPow(\col,2))},
    remember=\curStep as \displacement (initially 0)] in {1,...,\numInt} {
  \draw (\displacement+\intLen, \col*\myRowSep) -- ++({pow(1/3,\myN-highestPow(\col,2))}, 0);
};
\node[above] at (current bounding box.north) {Cantor function $c(x)$ with $n = \myN$};
\node[above] at (current bounding box.north) {Step function approximation for};
\end{tikzpicture}
\end{document}

An illustration to a homeomorphism ψ : [0, 1] → [0, 2], ψ := id + c, where c is Cantor function.

This function is used for constructing a Lebesgue measurable set which is NOT Borel.

piecewise linear approximation for homeomorphism

Piecewise linear approximation to 𝜓

SVG generated with dvisvgm from PDF

\documentclass[preview, margin=2pt, tikz]{standalone}
\usepackage{tikz}
\usetikzlibrary{math}
\begin{document}
\begin{tikzpicture}[scale=6]
\pgfmathsetmacro\myN{6}  % number of levels
\pgfmathsetmacro\myRowSep{0.5^\myN}
\pgfmathsetmacro\myLblSep{1pt}  % display parameter for label separation
\pgfmathsetmacro\myPadding{.05} % display parameter for extra axis length

\draw[help lines, step=0.05cm] (0,0) grid (1,2);
% x-axis
\draw[thick,->] (-\myPadding,0) -- (1+\myPadding,0) node [right] {$x$};
\foreach \x in {0,0.111,...,1} {
  \draw[xshift=\x cm] (0pt,-.5pt)--(0pt,.5pt);
};
% y-axis
\draw[thick,->] (0,-\myPadding) -- (0,2+\myPadding) node [above] {$y$};
\foreach \y in {0,0.0625,...,2} {
  \draw[yshift=\y cm] (-.5pt,0pt)--(.5pt,0pt);
};
% tick labels
\path (0,0) node[below left] {$O$};
\foreach \x in {1/3, 2/3, 1} {\node[below] at (\x,0) {$\x$};};
\foreach \y in {1/4, 1/2, 3/4, 1, 5/4, 3/2, 7/4, 2} {\node[left] at (0,\y) {$\y$};};

\tikzmath{ % auxiliary function
  function highestPow(\n, \b) {
    if \n == 0 then {return 0;};
    int \d, \tempnum;
    \d = 0;
    \tempnum = \n;
    for \i in {1,...,\tempnum} {
      if mod(\tempnum,\b) == 0 then { \d = \d + 1; \tempnum = \tempnum / \b; };
    };
    return \d;
  };
}
\pgfmathsetmacro\numInt{2^\myN-1}  % number of intervals
\pgfmathsetmacro\intLen{pow(1/3,\myN)}  % interval length
% avoid too large base
\foreach \col [evaluate=\col as \curStep using {\displacement+\intLen+pow(1/3,\myN-highestPow(\col,2))},
    remember=\curStep as \displacement (initially 0)] in {1,...,\numInt} {
  \draw (\displacement+\intLen, \displacement+\intLen+\col*\myRowSep)
    -- ++({pow(1/3, \myN-highestPow(\col,2))}, {pow(1/3, \myN-highestPow(\col,2))});
};
\node[above, text width=6cm] at (current bounding box.north) {Piecewise linear approximation for homeomorphism $\psi(x) = c(x) + x$ with $n = \myN$};
\end{tikzpicture}
\end{document}

(Last modified on October 9, 2024)