6

I'm new to Tikz so sorry if this is trivial. I'd like to draw a Cayley tree in Tikz that looks something like [this image I found on Google.

The catch is that I want alternating layers to be alternate node shapes -- circle and square nodes. Secondly, I would like the circular nodes to have degree 3 and the square nodes to have degree 5. Is there some way to generate this without having to place each node manually where I want?

  • 2
    Welcome to TeX.SX! You can have a look at our starter guide to familiarize yourself further with our format. In particular, please add a minimal working example (MWE) of what you have tried so far, and don't give the link to the desired image but upload it using the official Stack Exchange interface, i.e. the image icon on top of the text field (shortcut: Ctrl+G). This ensures that it will always be accessible and won't expire. – CarLaTeX Aug 6 '17 at 5:09
  • 1
    You could use \usetikzlibrary{trees} see Example: Title graphics. If you need further help add a mwe. – Bobyandbob Aug 6 '17 at 7:13
  • This would be easy with LuaLaTeX. With plain tikz it's possible, but too painful for me. – JPi Aug 6 '17 at 13:10
  • 1
    If 'degree' means what I suspect, I'm with @JPi on this. – cfr Aug 6 '17 at 13:13
  • 3
    See if this helps : Cayley Graph of Free Group in TikZ – marsupilam Aug 6 '17 at 13:26
11

Second proposition (using two nested loops as suggested by marsupilam)

(Note: My previous proposition did not use the correct definition of degrees. Thanks to cfr and marsupilam for having noticed.)

Here is a pdflatex solution. The first node (the center) is c-0-1. The first level nodes are c-1-1, c-1-2 and c-1-3... The fourth level nodes are c-4-1, c-4-2, ... and c-4-96.

\documentclass[tikz]{standalone}
\tikzset{
  common/.style={draw,name=#1,node contents={},inner sep=0,minimum size=3},
  disc/.style={circle,common=#1},
  square/.style={rectangle,common={#1}},
}
\begin{document}
\begin{tikzpicture}
  \draw (0,0) node[disc=c-0-1];
  \xdef\radius{0cm}
  \xdef\level{0}
  \xdef\nbnodes{1}
  \xdef\degree{(3+1)} % special degree just for the root node
  \foreach \ndegree/\form in {5/square,3/disc,5/square,3/disc}{
    \pgfmathsetmacro\nlevel{int(\level+1)}
    \pgfmathsetmacro\nnbnodes{int(\nbnodes*(\degree-1))}
    \pgfmathsetmacro\nradius{\radius+1cm}
    \draw[red] (c-0-1) circle(\nradius pt);
    \foreach \div in {1,...,\nnbnodes} {
      \pgfmathtruncatemacro\src{((\div+\degree-2)/(\degree-1))}
      \path (c-0-1) ++({\div*(360/\nnbnodes)-180/\nnbnodes}:\nradius pt) node[\form=c-\nlevel-\div];
      \draw (c-\level-\src) -- (c-\nlevel-\div);
    }
    \xdef\radius{\nradius}
    \xdef\level{\nlevel}
    \xdef\nbnodes{\nnbnodes}
    \xdef\degree{\ndegree}
  }
\end{tikzpicture}
\end{document}

enter image description here

  • So what does the degree stuff mean? I guess my guess was wrong. – cfr Aug 6 '17 at 22:22
  • Nice ! More of an art-deco look. Can't we wrap all these in an outer foreach loop, by having those 3,12,36,144 computed by an \xdef at each turn and with an `\xdef\parity{(1-\parity)}, though ? – marsupilam Aug 6 '17 at 22:23
  • @cfr Paul chose alternating degrees of 3 and 4 instead of 3 and 5. It gets really cramped ! – marsupilam Aug 6 '17 at 22:23
  • @marsupilam Isn't this 4 and 5 rather than 3 and 4 or 3 and 5? – cfr Aug 7 '17 at 2:11
  • 1
    @cfr You are right. I corrected my answer. – Paul Gaborit Aug 7 '17 at 7:21
11

I followed the advice by JPi and cfr, and went for lua.

(edit : I think this style gives a nice blooming sakura vibe.)

The output

enter image description here

The tikz

Compile with lualatex

\RequirePackage{luatex85}
\documentclass[12pt,tikz]{standalone}
\usepackage{luacode}
\begin{luacode*}
  print=tex.print
  makeGrow=require("makeGrow.lua")
\end{luacode*}
\begin{document}
\tikzset
{
  odd/.style = {draw=blue!50!pink,circle,ultra thick},
  even/.style = {draw=red,line width=3pt,minimum size=4mm},
}
\begin{tikzpicture}[scale=20]
  \directlua{makeGrow.make(4)}
\end{tikzpicture}
\end{document}

The lua

-- makeGrow.lua
-- mode = {totalAngle = totalAngle, number = numberOfChildren, radius = lengthOrArm}
local function makeChildren(parent,mode,f)
  local f = f or function() return end
  parent.children = {}
  for k=1,mode.number do
    parent.children[k] = {
      generation = parent.generation + 1,
      angle = parent.angle + (k-.5*(mode.number+1)) * mode.totalAngle / mode.number,
      parent = parent,
      label = parent.label .. tostring(k),
    }
    f(parent.children[k]) -- we smuggled f in there to recurse.
  end
end
local function tikzNode(node)
  return {
    name = "(myNode-" .. node.label .. ")",
    style = "[" .. ({"even","odd"})[node.generation%2+1] .. "]",
  }
end
local function draw(node,mode)
  if node.parent then
    print([[\path]] .. tikzNode(node.parent).name .. " -- ++(" .. node.angle .. ":" ..  mode.radius .. ") node" .. tikzNode(node).style .. tikzNode(node).name .. "{} ;")
    print([[\draw]] .. tikzNode(node.parent).name .. " -- " .. tikzNode(node).name .. ";")
  else
    print([=[\node[odd]]=] .. tikzNode(node).name .. "at (0,0) {};")
  end
end
local function drawMakeChild(n,modes)
  local function recurse(node)
      draw(node,modes[node.generation-1])
      if node.generation<=n then
        makeChildren(node,modes[node.generation],recurse)
      end
  end
  return recurse
end
local function computeMode(generation,hash)
  local number = ({5,3})[generation%2+1] -- alternating number of children
  local radius = hash.radRatio^generation
  local totalAngle = 360 * hash.angRatio^(generation-1)
  return {totalAngle = totalAngle , number = number, radius = radius}
end
local function make(n)
  local origin = {
    generation = 1,
    angle = 0,
    label = ""
  }
  local modes = {}
  for k=1,n do modes[k] = computeMode(k,{angRatio=.98,radRatio=.35}) end
  drawMakeChild(n,modes)(origin)
end
return {make=make}

Edit : Using Paul's style

The output

enter image description here

or with \def\N{4}

enter image description here

The tikz

\RequirePackage{luatex85}
\documentclass[12pt,tikz]{standalone}
\usepackage{luacode}
\begin{luacode*}
  print=tex.print
  makeGrow=require("paulStyle.lua")
\end{luacode*}
\begin{document}
\tikzset
{
  commons/.style={fill=white},
  odd/.style = {draw=red,circle,ultra thick,commons},
  even/.style = {draw=blue,ultra thick,minimum size=3mm,commons},
}
\def\N{3}
\begin{tikzpicture}[scale=4]
  \foreach \k in {1,...,\N} \draw[help lines,purple] (0,0) circle (\k);
  \directlua{makeGrow.make(\N)}
\end{tikzpicture}
\end{document}

The lua

-- paulStyle.lua
local function makeChildren(parent,multiplicity,f)
  parent.children = {}
  for k=1,multiplicity do
    parent.children[k] = {
      generation = parent.generation + 1,
      cumProd = parent.cumProd * multiplicity,
      angle = parent.angle + ((k-.5)/multiplicity-.5 ) * 360 / parent.cumProd,
      parent = parent,
      label = parent.label .. tostring(k),
    }
    f(parent.children[k]) -- we smuggled f in there to recurse.
  end
end
local function tikzNode(node)
  return {
    name = "(myNode-" .. node.label .. ")",
    style = "[" .. ({"odd","even"})[node.generation%2+1] .. "]",
  }
end
local function draw(node)
  if node.parent then
    print([[\node]].. tikzNode(node).style .. tikzNode(node).name .. " at " .. "(" .. node.angle .. ":" ..  node.generation .. ") {};")
    print([[\draw]] .. tikzNode(node.parent).name .. " -- " .. tikzNode(node).name .. ";")
  else
    print([=[\node]=] .. tikzNode(node).style .. tikzNode(node).name .. "at (0,0) {};")
  end
end
local function drawMakeChild(n,multiplicities)
  local function recurse(node)
      draw(node,multiplicities[node.generation-1])
      if node.generation<n then
        makeChildren(node,multiplicities[node.generation],recurse)
      end
  end
  return recurse
end
local function make(n)
  local origin = {
    cumProd = 1,
    generation = 0,
    angle = 0,
    label = ""
  }
  local multiplicities = {}
  for k=1,n do multiplicities[k-1] = ({5,3})[k%2+1] end
  drawMakeChild(n,multiplicities)(origin)
end
return {make=make}
  • Nice. This isn't actually what I had in mind. (Was more thinking of the graphs stuff.) But very neat! – cfr Aug 6 '17 at 22:21
  • Thanks @cfr I find lua absolutely fun ! I just learnt about closures during this summer vacation (had only read about them in the vim documentation...!) I'm so grateful to luaLaTeX for offering me this new universe. – marsupilam Aug 6 '17 at 22:45
  • I am very impressed as I know nothing at all about Lua. – cfr Aug 7 '17 at 3:12
  • That's pretty!! – JPi Aug 7 '17 at 13:07
  • Thanks so much! This looks fabulous! I had to choose a right answer to resolve this but all the answers are really good. – WiFO215 Aug 7 '17 at 15:47
9

Here's mine. Despite my comments, this is, in fact, pure TikZ without any of the graphs stuff. That is, you can compile it with LaTeX, pdfLaTeX or whatever and it should work. (You can presumably convert it into a TeX version, should you so wish.) I planned to use graphs to draw the connections, which doesn't need LuaTeX anyway. But, somehow, I never got around to it as, once I'd placed the nodes, this seemed easier.

Change the maximum value for the outer loop to increase the number of levels. I practised with values of 3 and 4, but use 5 for the 'display' version here. The complication is that the first node gets 3 children, in order to have degree 3, while later nodes of degree 3 only need 2, because they have a parent. This requires special treatment of the first 3 levels to get things to line up correctly. After that, subsequent levels are just a question of TeX capacity and your patience.

Use \showtrue to draw a labelled version, for debugging purposes.

\documentclass[border=10pt]{standalone}
\usepackage{tikz}
\newif\ifshow\showfalse % set true to debug
\begin{document}
\begin{tikzpicture}
  % \i: level \s: shape \a: angle \t: turn \d: degree -1 \j: number \m: rotation \g: group \k: number \c: colour
  \def\j{1}\def\dlast{1}\def\tlast{0}
  \foreach \i [remember=\i as \ilast] in {0,...,5}
  {
    \draw [darkgray] (0,0) circle (\i cm);
    \pgfmathsetmacro\a{360/\j}
    \ifodd\i\def\s{}\def\c{magenta}\def\d{4}\pgfmathsetmacro\t{\tlast-1.5*\a}\else\def\c{blue!50!cyan}\def\s{circle}\def\d{2}\pgfmathsetmacro\t{\tlast-2.5*\a}\fi
    \ifnum\i=1\pgfmathsetmacro\t{-\a}\fi
    \ifnum\i=0\def\d{3}\def\t{0}\fi
    \foreach \k [evaluate=\k as \m using {(\k*\a)+\t}, evaluate=\k as \g using {int((floor((\k-1)/\dlast)))}, count=\n from 0 ] in {1,...,\j}
    {
      \ifshow\def\tempa{n-\i-\g-\n:\k}\else\let\tempa\relax\fi
      \node (n-\i-\n) [draw, fill, \c, \s, minimum size=2.5pt, inner sep=0pt, label={[font=\tiny]{\tempa}} ] at (\m:\i cm) {};
      \ifnum\i>0 \draw [darkgray] (n-\i-\n) -- (n-\ilast-\g); \fi
    }
    \pgfmathsetmacro\j { \j*\d }
    \global\let\j\j
    \global\let\dlast\d
    \pgfmathsetmacro\tlast{(\i==1) ? 0 : (\t+\a) }
    \global\let\tlast\tlast
  }
\end{tikzpicture}
\end{document}

Cayley tree

Here's a more parametized version, with (optional) special effects.

  • circle colour=<colour> will give a uniform colour for the circles;
  • circle colours=<colour>:<colour> will give shades varying between the two colours for the circles;
  • square colour=<colour> will give a uniform colour for the circles;
  • square colours=<colour>:<colour> will give shades varying between the two colours for the squares;
  • circle degree=<integer> sets the degree for the circular nodes;
  • square degree=<integer> sets the degree for the square nodes;
  • connection colour<colour> sets the colour for the circles and the background;
  • levels=<integer> sets the number of levels.

If you don't want the background, just delete the line

\scoped[on background layer]{\shade [inner color=lcol!5, outer color=lcol!35] circle (\l cm);}

In that case, you don't need the backgrounds library and can delete

\usetikzlibrary{backgrounds}

if you don't need it for anything else.

\documentclass[border=10pt]{standalone}
\usepackage{tikz}
\usetikzlibrary{backgrounds}
\newif\ifshow\showfalse % set true to debug
\begin{document}
\begin{tikzpicture}
  % \i: level \s: shape \a: angle \t: turn \d: degree -1 \j: number \m: rotation \g: group \k: number ncol: colour
  \tikzset{
    circle degree/.code={\def\dr{#1}\pgfmathsetmacro\dc{int(#1-1)}\pgfmathsetmacro\tc{((\dc-1)/2)+1}},
    square degree/.code={\pgfmathsetmacro\ds{int(#1-1)}\pgfmathsetmacro\ts{((\ds-1)/2)+1}},
    levels/.code={\pgfmathsetmacro\l{int(#1-1)}},
    connection colour/.code={\colorlet{lcol}{#1}},
    circle colours/.code args={#1:#2}{\colorlet{ccol1}{#1}\colorlet{ccol2}{#2}},
    square colours/.code args={#1:#2}{\colorlet{scol1}{#1}\colorlet{scol2}{#2}},
    circle colour/.style={circle colours=#1:#1},
    square colour/.style={square colours=#1:#1},
    circle degree=3,
    square degree=5,
    levels=6,
    connection colour=darkgray,
    square colours=green:blue,
    circle colours=blue:magenta,
  }
  \def\j{1}\def\dlast{1}\def\tlast{0}
  \foreach \i [remember=\i as \ilast] in {0,...,\l}
  {
    \draw [lcol] (0,0) circle (\i cm);
    \pgfmathsetmacro\a{360/\j}
    \pgfmathsetmacro\z{6.5-\i*.8}
    \ifodd\i\def\s{}\colorlet{ncol1}{scol1}\colorlet{ncol2}{scol2}\let\d\dc\pgfmathsetmacro\t{\tlast-\ts*\a}\else\colorlet{ncol1}{ccol1}\colorlet{ncol2}{ccol2}\def\s{circle}\let\d\ds\pgfmathsetmacro\t{\tlast-\tc*\a}\fi
    \ifnum\i=1\pgfmathsetmacro\t{-\a}\fi
    \ifnum\i=0\let\d\dr\def\t{0}\fi
    \foreach \k [evaluate=\k as \m using {(\k*\a)+\t}, evaluate=\k as \g using {int((floor((\k-1)/\dlast)))}, count=\n from 0, evaluate=\k as \p using { (\k > \j/2) ? ((1-\k/\j)*200) : ((200/\j)*\k)} ] in {1,...,\j}
    {
      \ifshow\def\tempa{n-\i-\g-\n:\k}\else\let\tempa\relax\fi
      \node (n-\i-\n) [draw, fill, ncol1!\p!ncol2, \s, minimum size=\z pt, inner sep=0pt, label={[font=\tiny]{\tempa}} ] at (\m:\i cm) {};
      \ifnum\i>0 \draw [ncol1!\p!ncol2] (n-\i-\n) -- (n-\ilast-\g); \fi
    }
    \pgfmathsetmacro\j { \j*\d }
    \global\let\j\j
    \global\let\dlast\d
    \pgfmathsetmacro\tlast{(\i==1) ? 0 : (\t+\a) }
    \global\let\tlast\tlast
  }
  \scoped[on background layer]{\shade [inner color=lcol!5, outer color=lcol!35] circle (\l cm);}
\end{tikzpicture}
\end{document}

fancier version

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.