Drawing a Cayley tree

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}

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


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