# How to Improve my TikZ Code to construct a Triangle Diagram

I'm inviting interesting, alternative TikZ solutions to improve on the code for a "triangle diagram" that may be found further on.

One of the problems with the code is that I hardcoded some lengths because they were needed in different path/draw commands. I could have defined the lengths with \def (or related TeX/LaTeX commands) but that's not really a TikZ solution. If you have a high-level, more elegant TikZ solution then I'd like to know.

Please note this question is related to this other question but the purpose of this question is different.

\documentclass{minimal}

\usepackage{tikz}
\usetikzlibrary{calc}
\tikzset{my node/.style={shape=circle,draw=black,fill=red!40,minimum size=5mm,inner sep=0pt}}

\begin{document}
\begin{tikzpicture}
\draw[red,thick]
(0,0) coordinate (A) -- ++(120:6)
coordinate (B) -- ++(6,0)
coordinate (C) -- cycle;
\foreach \a/\b/\c in {A/C/B,B/A/C,C/B/A} {
\draw[<->]
($(\a)!5mm!(\b)$)   coordinate (fst)
($(\a)!1!30:(fst)$) coordinate (snd)
($(\a)!5mm!(\c)$)   coordinate (lst)
($(\a)!1.5!(snd)$) node {60}
(fst) .. controls (snd) .. (lst);
}
\filldraw[draw=black,fill=red!40]
let \n{length}={1.5} in
\foreach \r[evaluate=\r as \rr using \r*\n{length},
evaluate=\r as \lst using int(round(\r*(\r+1)/2)),
evaluate=\r as \fst using int(round(\lst-\r+1)] in {1,...,5} {
\foreach \curr in {\fst,...,\lst} {
($(A)+(120:-1.5)+(120:\rr)+(-\n{length}*\fst,0)+(\n{length}*\curr,0)$)
node[my node] {$\curr$}
}
};
\end{tikzpicture}
\end{document}


-
I am interested in seeing a solution with a recursive macro. – kiss my armpit Apr 23 '13 at 6:35
@Bugbusters I am more interested in improving maintenance/development aspects. – Marc van Dongen Apr 23 '13 at 7:45

For regular shaped things, such as triangular meshes I would lean against using a custom coordinate system (cm). Even so, when using this one has to be careful about only using the cm on the specific nodes of interest. I have not completed the full figure, but the idea is clear.

I have decided to use the cm which takes in the x-direction the level of the mesh. And the y to determine the column in the level.
Thus your cm will look something like:

cm={-.5,1,1,0,(0,0)}


Of course, by changing the dimensions in the cm you change the size/separation, etc. of the nodes in the mesh.

To show what it looks like see:

\begin{tikzpicture}[tri/.style={cm={-.5,1,1,0,(0,0)}}]
\path[tri] (0,0) node (1) {1};
\path[tri] (1,0) node (2) {2};
\path[tri] (1,1) node (3) {3};
\path[tri] (2,0) node (4) {4};
\path[tri] (2,1) node (5) {5};
\draw (1) -- (2);
\draw (1) -- (3);
\draw (-.25,.5) arc (120:60:.5) node[midway,above] {$60^\circ$} ;
\end{tikzpicture}


notice that you probably do not want to make cm global. As you want to control everything else.

This can easily be extended to accommodate automatic creation of the mesh via a loop construct:

\foreach \lvl in {0,1,...,5} {
\foreach \myc in {0,...,\lvl} {
\path[tri] ...
}
}


I will expand on the loop tomorrow (at work), as there are some subtleties in getting the expanding counters etc. to work (but I know you know this :) ). But at least this will give you an idea of making the drawing more simple.

So I have added the remaining part, plus the request as you commented. It is quite elaborate as it tracks the edge nodes via the loop construct.

\tikzset{
my node/.style={
shape=circle,
draw=black,
fill=red!40,
minimum size=5mm,
inner sep=0pt},
tri levels/.initial=4,
tri border/.style={red,thick},
}
\makeatletter
\newcommand\drawmesh[1][]{
\bgroup
\tikzset{#1}
\pgfkeysgetvalue{/tikz/tri levels}{\tri@lvls}
% just crash... :)
\ifnum\tri@lvls<1You are not allowed to do this....\fi
% Full draw (relies on the fill of the nodes, so only for example purpose)
%\draw[tri,tri border] (0,0) -- (\tri@lvls,0)  -- (\tri@lvls,\tri@lvls) -- cycle;
\foreach \i [remember=\k (initially 0)] in {0,...,\tri@lvls}
\foreach \j [remember=\k, evaluate={\k=int(\k+1);}] in {0,...,\i}
\path[tri] (\i,\j) node[my node] (\k) {\k};
% Save the name of the last node
\edef\tri@last@node{\k}
% Connect the nodes:
\foreach \i [remember=\k as \lastk (initially 1), % the left side
remember=\k, evaluate={\k=int(\lastk+\i)},
remember=\l, evaluate={\l=int(\k+\i)}, % the right side
remember=\lastl, evaluate={\lastl=int(\lastk+\i-1)},
remember=\lastt as \t (initially \tri@last@node), evaluate={\lastt=int(\tri@last@node-\i)}]
in {1,...,\tri@lvls} {
\draw[tri border] (\lastt) -- (\t);
\draw[tri border] (\lastl) -- (\l);
\draw[tri border] (\lastk) -- (\k);}
\egroup
}
\makeatother

% Drawing code:

\begin{tikzpicture}[tri/.style={cm={-.5,1,1,0,(0,0)}}]
\drawmesh[tri levels=4]
\draw (1) -- (2);
\draw (1) -- (3);
\draw (-.25,.5) arc (120:60:.5) node[midway,above] {$60^\circ$} ;
\end{tikzpicture}


Here you can simply specify the size via the \drawmesh macro. The tri cm has to be predefined, but can of course be put in a key as well. The border color is easily changed by modifying the key tri border. Lastly by drawing explicitly from the tri border key you can utilize the shorten >=<length> to obtain special features.

This will let you create all meshes by one parameter, the tri levels=<int> in the argument of \drawmesh. It will then produce:

-
Thanks. It would also be nice if you could show a solution where you don't rely on the fact that the circles are drawn on top of the triangles. – Marc van Dongen Apr 22 '13 at 8:21
Do you mean that one should draw the triangle first, and then the nodes? Or are you looking into a generic tikz-solution for automatic detecting of triangular mesh size? – zeroth Apr 22 '13 at 8:24
No (but you may ignore the question/comment). What I meant was that it would be nice if you could first define the nodes and then connect them later (as opposed to first drawing the lines that make up the mesh and then draw the nodes on top of them; relying on fill to hide the lines). – Marc van Dongen Apr 22 '13 at 8:42
Please see the update. More self-contained and easy to expand. – zeroth Apr 23 '13 at 9:26
I accept this solution because the idea to use a dedicated coordinate system is neat. I should have thought about that.... – Marc van Dongen Apr 24 '13 at 12:04

Dunno about "more elegant", but I would have gone for the approach taken below. Note, that the three "60" nodes aren't positioned identically.

\documentclass{standalone}
\usepackage{tikz}

\tikzset{
my node/.style={
shape=circle,
draw=black,fill=red!40,
minimum size=5mm,
inner sep=0pt
}
}

\begin{document}

\begin{tikzpicture}

\begin{scope}[x=(0:1.5cm),y=(120:1.5cm)]

\draw [red, thick] (1,1) -- (1,5) -- (5,5) -- cycle;
\foreach \i [remember=\k (initially 0)] in {1,...,5}
\foreach \j [remember=\k, evaluate={\k=int(\k+1);}] in {1,...,\i}
\node [my node] at (\j,\i) (circle-\k) {\k};

\end{scope}

\draw [<->] (circle-1)  ++(120:0.5) arc (120:60:0.5 and 0.5)
node [midway, above] {60};
\draw [<->] (circle-11) ++(0:0.5)   arc (0:-60:0.5 and 0.5)
node [midway, below right] {60};
\draw [<->] (circle-15) ++(180:0.5) arc (180:240:0.5 and 0.5)
node [midway, below left] {60};

\end{tikzpicture}
\end{document}


One could try to tie the angle measurement part up in a style, perhaps a bit like this:

\tikzset{
angle measurement/.style args={arc #1 for (#2) at (#3:#4)}{
insert path={
[shift={(#2)}] [<->] (#3-#1/2:#4) arc (#3-#1/2:#3+#1/2:#4)
\pgfextra{\pgfinterruptpath
\path [shift={(#2)}] (#3:#4) node [anchor=#3+180] {#1};
\endpgfinterruptpath}
}
}
}

\draw [angle measurement=arc 60 for (circle-1) at (90:0.5)];
\draw [angle measurement=arc 60 for (circle-11) at (-30:0.5)];
\draw [angle measurement=arc 60 for (circle-15) at (210:0.5)];


Which takes the form arc <angle> for (<node>) at (<arc midpoint>). The <arc midpoint> also determines the anchor. It definitely could be more flexible.

The style args handle allows delimited arguments to be passed to a style. Much like

\def\mymacro(#1) and (#2);{...}


allows a macro to be defined so that the arguments can be delimited when used:

\mymacro(first arg) and (second arg);


the style args key allows the style to be delimited in a similar way, for example (and it isn't a great example):

\tikzset{size/.style args={#1 x #2}{minimum width=#1, minimum height=#2}}


which could be used as

\node [size=4pt x 5cm] {text};


Note that the pattern must be followed exactly when called, including spaces. And it is important to note that if a macro is used as an argument before a space, for example, size=\w x 5cm, this will fail. If macros are likely to be used as arguments then any following character delimiter should not be a space.

-
@MarcvanDongen see updated answer for "angle measurement" (hopefully I understood what you wanted). And there isn't an equivalent of let outside of paths (and it isn't straightforward to hack into the mechanism either). – Mark Wibrow Apr 23 '13 at 6:22
Thanks for expanding. – Marc van Dongen Apr 25 '13 at 19:21

Here is another solution: I used intentionally the original code, just adding the possibility to the final user to customize things with:

• specific keys;
• some styles.

Two examples are provided:

• the first one to reproduce the initial diagram;
• the second one to show how to customize the diagram.

The code:

\documentclass[tikz,png,border=10pt]{standalone}

\usepackage{tikz}
\usepackage{pdftexcmds}
\usetikzlibrary{calc}

\makeatletter

% keys to customize the aspect
\pgfkeys{/tikz/.cd,
distance of nodes/.initial={1.5},
distance of nodes/.get=\trig@actualnodedistance,
distance of nodes/.store in=\trig@actualnodedistance,
levels/.initial={5},
levels/.get=\trig@numlevels,
levels/.store in=\trig@numlevels,
direction/.initial={up},
direction/.get=\trig@direction,
direction/.store in=\trig@direction,
mark angle line distance/.initial={5mm},
mark angle line distance/.get=\trig@markangledistance,
mark angle line distance/.store in=\trig@markangledistance,
mark angle label distance factor/.initial={1},
mark angle label distance factor/.get=\trig@markanglelabfactor,
mark angle label distance factor/.store in=\trig@markanglelabfactor,
}

% key to draw the diagram
\pgfkeys{/tikz/.cd,
triangle diagram/.code={
\ifnum\pdf@strcmp{\trig@direction}{up}=\z@%
\def\trig@use@direction{($(0,0)+(120:-\trig@actualnodedistance) +(120:\rr) +(-\trig@actualnodedistance*\fst,0) +(\trig@actualnodedistance*\curr,0)$)}
\def\trig@draw@border{++(120:\trigshift)}
\def\trig@draw@angle{($(\a)!1.1!30:(fst)$)}
\fi
\ifnum\pdf@strcmp{\trig@direction}{down}=\z@%
\def\trig@use@direction{($(0,0)-(120:-\trig@actualnodedistance) -(120:\rr) -(-\trig@actualnodedistance*\fst,0) -(\trig@actualnodedistance*\curr,0)$)}
\def\trig@draw@border{++(-120:\trigshift)}
\def\trig@draw@angle{($(\a)!1.1!-30:(fst)$)}
\fi
\pgfmathtruncatemacro{\actualnumlevels}{\trig@numlevels-1}
\pgfmathsetmacro{\trigshift}{\actualnumlevels*\trig@actualnodedistance}
\draw[connection style,save path=\trigpath]
(0,0) coordinate (A) -- \trig@draw@border
coordinate (B) -- ++(\trigshift,0)
coordinate (C) -- cycle;

\foreach \a/\b/\c in {A/C/B,B/A/C,C/B/A} {
\draw[<->,angle line marker]
($(\a)!\trig@markangledistance!(\b)$)   coordinate (fst)
\trig@draw@angle coordinate (snd)
($(\a)!\trig@markangledistance!(\c)$)   coordinate (lst)
($(\a)!\trig@markanglelabfactor*\trig@actualnodedistance!(snd)$)
node {60} (fst) .. controls (snd) .. (lst);
}
\path
\foreach \r
[evaluate=\r as \rr using \r*\trig@actualnodedistance,
evaluate=\r as \lst using int(round(\r*(\r+1)/2)),
evaluate=\r as \fst using int(round(\lst-\r+1)] in {1,...,\trig@numlevels}{
\foreach \curr in {\fst,...,\lst} {
\trig@use@direction
node[my node] {$\curr$}
}
};

},
}

\tikzset{my node/.style={
circle,
draw=black,
fill=red!40,
minimum size=5mm,
inner sep=0pt
},
connection style/.style={
draw,
red,
},
angle line marker/.style={},
diagram grow down/.style={
direction=down,
xscale=-1
}
}
\makeatother

\begin{document}
\begin{tikzpicture}
\node[triangle diagram]{};
\end{tikzpicture}

\tikzset{my node/.append style={
top color=white,
bottom color=magenta!80!blue!50,
draw=magenta!80!blue,
minimum size=7mm},
connection style/.append style={
draw=green!80!blue,
ultra thick,
double,
},
angle line marker/.append style={
very thick,
gray,
text=black
}
}
\begin{tikzpicture}[distance of nodes=2.5,
levels=7,
diagram grow down,
mark angle line distance=10mm,
mark angle label distance factor=0.5,
]
\node[triangle diagram]{};
\end{tikzpicture}
\end{document}


The first example provides:

while the second example leads to:

-
Thanks. Looks interesting. – Marc van Dongen Apr 22 '13 at 12:28