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:
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 linesaxis equal
: x and y-distances are in 1:1 ratioaxis lines=center
: axes passes through the origingrid=both
: enable major grid linesminor tick num=9
: draw 9 minor grid lines between each pair of major grid linesxlabel={$x$}
,ylabel={$y$}
: labels for the axestitle={}
: title of a graphlegend style={at={(0,-0.05)},anchor=north west,fill=none}
: legend stylesat={(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 boxfill=none
: (for TeXit) transparent background. TeXit renders a black background by default.
\begin{scope}[thick, samples=201, smooth]
: avoid repetition of codeblue!20
is a shorthand forblue!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}
\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}
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
withline width=1
suitsgray
. - graph limits: I first tried
xmin=-9
andxmax=3
, but withoutymin=-5
andymax=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 forxmax
andymax
.- the default
cs
(coordinate system) isaxis cs
inside theaxis
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}
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 whenxtick distance=\PI
.scaled x ticks={real: \PI}
: tick number\tick
is divided by\PI
. Outsidexticklabel
, the x-coordinate can be used as usual.xticklabel
andxticklabel 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.
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}
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}
\begin{tikzpicture}[scale=0.5]
\draw (0,0) rectangle (3,7);
\end{tikzpicture}
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}
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}
\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 sidesdTheta
: size of angle of a sector\the\numexpr\n-1
: evaluten − 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.
- I’m not sure if
\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 bydraw
).
- 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 alabel=<angle>:<text>
instead of a\coordinate
with apin=<angle>:<text>
because\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 actualshape=circle
.- I don’t want any pin edges. It would be more sensible to use
label
s instead ofpin
s withevery pin edge/.style={opacity=0}
.
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}
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}
\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.
\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}
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.
-
(Optional step) Search “applet” in the page’s source code so as to find the applet’s ID.
In this example, the desired ID is
37303159
. -
Type the first few letters of
ggbApplet
in the web developer tools’ console, followed by.
-
Choose the function
exportPGF()
with the tab key. -
Inside the bracket, type
function(a){console.log(a}
.
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>
- with an example for
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-
omission of final
;
-
omission of surrounding
{};
in the TikZ command{\draw …;};
inside a\for
loop -
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
andevaluate
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.
\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
coordinate
s $R_nP_{3(k-1)+j}$, where $j \in \lbrace 1, 2, 3 \rbrace$, so that
these new coordinate
s 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.
\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
: longerint
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
- first line has
\parindent
- other lines have
.0
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
insidefor
loop insidefunction
won’t stopfor
loop from running.- I can’t do declaration and assignment at the same time.
- I can’t
break;
afor
loop. - Each
for
,if
,function
, etc, statement ends with a semicolon;
. - A TeX.SE user suggested replacing
if [cond] then { "break;" };
withif ![cond] then { %TODO }
.
An approximation to Cantor set using closed intervals
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
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.
\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}