Take the 2-minute tour ×
TeX - LaTeX Stack Exchange is a question and answer site for users of TeX, LaTeX, ConTeXt, and related typesetting systems. It's 100% free, no registration required.

Motivating Example:

Suppose that I want to draw the following tree:

    root
  /      \
 / \     / \
A   B   C   D

This is easy enough to do by explicitly drawing the two subtrees as children of the root node and using a child foreach in each subtree to draw the leaves. For instance:

\documentclass{standalone}
\usepackage{tikz}

\begin{document}
\begin{tikzpicture}
  \node {root} [level/.append style={sibling distance=2cm/#1}]
    child {
      coordinate
      child foreach \x in {A,B} {
        node {\x}}}
    child {
      coordinate
      child foreach \x in {C,D} {
        node {\x}}};
\end{tikzpicture}
\end{document}

enter image description here

However, even in this simple example, a fair amount of code is duplicated across the two subtrees. If the leaves were more complex, code duplication would be an even bigger disadvantage.

An alternate idea for drawing this tree attempts to use nested child foreach constructs to reduce the code duplication:

\documentclass{standalone}
\usepackage{tikz}

\begin{document}
\begin{tikzpicture}
  \node at (5cm,0) {root} [level/.append style={sibling distance=2cm/#1}]
    child foreach \xs in {{A,B}, {C,D}} {
      coordinate
      child foreach \x in \xs {
        node {\x}}};
\end{tikzpicture}
\end{document}

Unfortunately, this code produces the wrong tree structure:

enter image description here

Interestingly, Martin Scharrer's answer to TikZ \foreach loop with macro-defined list suggests that the code should work as is. The only difference that I can see is that that question uses \foreach rather than child foreach.

Problem:

The underlying problem seems to be that \xs is not expanded before the inner loop is executed. As a result, TikZ is treating the inner loop as having a range that is a singleton set, namely \xs (unexpanded). If \xs could be expanded before the inner loop is executed, then TikZ should treat the inner loop as having a range that is {A,B} in one case and {C,D} in the other case.

Question:

How can I expand a macro, such as \xs above, used inside the range of a foreach loop?

share|improve this question

1 Answer 1

up vote 11 down vote accepted

Deep in the bowels of TikZ, the child creation code calls \foreach to create the children. However, by the time it has done so then the various parameters to the \foreach loop have passed through several macros. Each time, there is the potentiality to strip off an outer pair of braces. Rather than try to keep track of this, TikZ gets round it by ensuring that the inner call to \foreach is of the form \foreach#2in{#3} which means that in your situation it sees \foreach \x in {\xs}. This is what is preventing the \foreach from expanding the macro to find the list: if it saw \foreach \x in \xs there would be no problem.

So we need to hack the code. When the foreach is first noticed we need to peek ahead to see if the range is surrounded by braces or not and remember that for when we get to the bottom. There are probably far more elegant ways of doing this than the following which involves a reasonable amount of repeated code (I copied the places where TikZ has the \foreach and removed the braces and then put in a decision as to which set of macros to use at the outset).

\documentclass{article}
%\url{http://tex.stackexchange.com/q/67187/86}
\usepackage{tikz}

\makeatletter
\def\tikz@collect@children@foreach[#1]foreach#2in{%
  \pgfutil@ifnextchar\bgroup{\tikz@collect@children@foreachbr{#1}{#2}}%
{\tikz@collect@children@foreachnbr{#1}{#2}}}

\def\tikz@collect@children@foreachbr#1#2#3{%
  \pgfutil@ifnextchar\bgroup{\tikz@collect@children@foreachA{#1}{#2}{#3}}{\tikz@collect@children@foreachA{#1}{#2}{#3}{}}}

\def\tikz@collect@children@foreachnbr#1#2#3{%
  \pgfutil@ifnextchar\bgroup{\tikz@collect@children@foreachAnbr{#1}{#2}{#3}}{\tikz@collect@children@foreachAnbr{#1}{#2}{#3}{}}}

\def\tikz@collect@children@foreachAnbr#1#2#3#4{%
  \expandafter\def\expandafter\tikz@children@list\expandafter
    {\tikz@children@list\tikz@childrennodesnbr[#1]{#2}{#3}{#4}}%
  \c@pgf@counta=\tikznumberofchildren%
  \foreach#2in#3%
  {%
    \global\advance\c@pgf@counta by1\relax%
  }%
  \tikznumberofchildren=\c@pgf@counta%
  \tikz@collect@children%
}
\def\tikz@childrennodesnbr[#1]#2#3#4{%
  \c@pgf@counta=\tikznumberofcurrentchild\relax%
  \setbox\tikz@tempbox=\box\tikz@figbox%
  \foreach#2in#3{%
    \tikznumberofcurrentchild=\c@pgf@counta\relax%
    \setbox\tikz@figbox=\box\tikz@tempbox%
    \tikz@childnode[#1]{#4}%
    % we must now make the current child number and the figbox survive
    % the group
    \global\c@pgf@counta=\tikznumberofcurrentchild\relax%
    \global\setbox\tikz@tempbox=\box\tikz@figbox%
  }%
  \tikznumberofcurrentchild=\c@pgf@counta\relax%
  \setbox\tikz@figbox=\box\tikz@tempbox%
}

\makeatother

\begin{document}
\begin{tikzpicture}
  \node at (5cm,0) {root} [level/.append style={sibling distance=2cm/#1}]
    child foreach \xs in {{A,B}, {C,D}} {
      coordinate
      child foreach \x in \xs {
        node {\x}}};
\end{tikzpicture}
\end{document}

Produces:

TikZ foreach tree children code

share|improve this answer
    
Thanks! This works like a charm. I guess the obvious follow-up question is "Why isn't this 'hack' a part of TikZ already?" You should make this into a package or see if the TikZ authors would be willing to add your code! –  Henry DeYoung Aug 15 '12 at 15:21
    
It's possible that there are other cases where this breaks something, or that this was a use-case not considered (or not considered to be likely to come up). There's a TikZ dev active here but I forget who it is ... –  Loop Space Aug 15 '12 at 15:33

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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