23

Playing around with pgffor, I'm trying to slightly modify an example from the TikZ/pgf manual:

\path \foreach \x/\content/\style in {%
  0/a/draw,
  1/b/{draw,red},
  2/c/{circle,blue},
  3/d/draw%
} {
  node[\style] at (\x,0) {\content}
} ;

Unfortunately, this does not work for nodes with multiple \style options set. TikZ complains with

! Package pgfkeys Error: I do not know the key '/tikz/draw,red' and I am going
to ignore it. Perhaps you misspelled it.

Now, this works perfectly, when I use a comma separated list for some other variable (e.g. \content). So now, my question is: how can I split \style while executing the loop? Obviosly, pgf tries to find one key named draw,red, when it should be looking for two keys instead, namely draw and red.

Is there some clever LaTeX macro that correctly expands the loop variable or tells pgf to expect multiple keys?

5
  • 1
    Not an answer to your question, but you can define a new name for your comma-separated styles. For example red drawing/.style={red, draw} and use this style in your loop.
    – JLDiaz
    Commented Jul 20, 2012 at 21:22
  • 2
    Is there a certain reason why you don't like to set up a style an use this? You may say \tikzset{somekey/.style={draw, red}} and insert somekey instead of the comma-list.
    – bloodworks
    Commented Jul 20, 2012 at 21:25
  • 1
    @JLDiaz, bloodworks: Yep, I used that as a workaround after writing this question, but it is just too unflexible, if you have a lot of different combination of styles (some of which you will only use once)
    – knittl
    Commented Jul 20, 2012 at 21:29
  • Related: tex.stackexchange.com/q/29048/86 as this is about expansion of macros in keys. Commented Jul 20, 2012 at 21:46
  • And I'm sure we've had similar questions but my searching skills are severely depleted. Commented Jul 20, 2012 at 21:50

5 Answers 5

25

Here's another version, not all that different from the previously supplied but combines them in a slightly different way. In effect, it is a bit like defining a dynamic style alias that expands to the given list of options. When a style like red drawing/.style={draw,red} is defined, then calling red drawing executes \tikzset{draw,red} (sort of, actually it's pgfkeys). So we simulate this by defining a key that executes \tikzset on whatever it is passed. So apply style={do,something,and,something,else} will execute \tikzset{do,something,and,something,else}. On its own, this isn't all that useful. But when combined with the /.expand once key handler we can pass it a macro, get that macro expanded once, and then execute the style it contains.

\documentclass{article}
\usepackage{tikz}
\tikzset{%
  apply style/.code={%
    \tikzset{#1}%
  }
}
\begin{document}

\begin{tikzpicture}
\path \foreach \x/\content/\style in {%
  0/a/draw,
  1/b/{draw,red},
  2/c/{circle,draw=blue},
  3/d/draw%
} {
  node[apply style/.expand once=\style] at (\x,0) {\content}
} ;
\end{tikzpicture}
\end{document}
4
  • 2
    I really like this solution. It's the least overhead and leaves the loop almost un-modified :)
    – knittl
    Commented Jul 22, 2012 at 7:52
  • I've tried almost the same apply style=\style option with ecode and I don't understand why \tikzset{apply style/.ecode={\noexpand\tikzset\noexpand{#1}}} refuses to work.
    – percusse
    Commented Jul 22, 2012 at 13:56
  • Ah scratch that. Now I have it with regular /.code and \expandafter's which is still lengthy than your solution.
    – percusse
    Commented Jul 22, 2012 at 13:58
  • @percusse: .ecode is effective at key definition time, not when the key is called. Even .estyle won't help you in that case.
    – Ahmed Musa
    Commented Jul 22, 2012 at 15:00
16

Use indirect styles numbered (perhaps more verbose but flexible):

\documentclass[tikz]{standalone}
\begin{document}
\begin{tikzpicture}
  \tikzset{
    s0/.style={draw},
    s1/.style={draw,red},
    s2/.style={circle,draw=blue},
    s3/.style={draw},
  }
  \foreach \x/\content in {%
    0/a,
    1/b,
    2/c,
    3/d%
  } {
    \node[s\x] at (\x,0) {\content};
  };
\end{tikzpicture}
\end{document}

enter image description here

12

Personally I always use the next method and in a lot of cases I use \protected@edef with LaTeX. With the next method you can use \path node ... node ... ;.

\documentclass{standalone}
\usepackage{tikz}
 % \makeatletter\let\protectededef\protected@edef   not useful here but It' sometimes  
 % interesting
\begin{document}
  \begin{tikzpicture}
    \path 
    \foreach \x/\content/\style in {0/a/draw,
                                    1/b/{draw,red},
                                    2/c/{draw,circle,blue},
                                    3/d/draw}{
  \pgfextra{\edef\tmp{   % or \pgfextra{\protectededef\tmp{
  node[\style] at (\x,0) {\content}}}
  \tmp
  };
  \end{tikzpicture}  
\end{document}

enter image description here

2
  • I think this would also work with only \edef (without protection).
    – percusse
    Commented Jul 21, 2012 at 9:43
  • @percusse Yes you are right! in a lot of cases, I need \protected@edef so it's always my first attempt. Commented Jul 21, 2012 at 11:00
10

You can stick with your method. With a slight modification, it works.

\documentclass{standalone}
\usepackage{tikz}
\begin{document}
\begin{tikzpicture}
  \foreach \x/\content/\style in {%
    0/a/draw,
    1/b/{draw=red},
    2/c/{circle,draw=blue},
    3/d/draw
  }{
    \edef\x{\noexpand\node[\style] at (\x,0) {\content}}\x;
  }
\end{tikzpicture}
\end{document}

enter image description here

And PolGab's solution is:

\documentclass[tikz]{standalone}
\usepackage{tikz}
\begin{document}
\begin{tikzpicture}[0/.style=draw,1/.style={draw=red},
    2/.style={circle,draw=blue},3/.style=draw]
  \foreach \x/\content in {0/a, 1/b, 2/c, 3/d}{
    \node[\x] at (\x,0) {\content};
  }
\end{tikzpicture}
\end{document}

EDIT (2012/07/22)

Expanding on Andrew Stacey's solution, here is another one. Using + to list the keys eliminates the need for braces. The code can be generalized to take any symbol in place of +.

\documentclass{article}
\usepackage{tikz}
\makeatletter
\def\replaceplus#1{\xreplaceplus#1+\@nil}
\def\xreplaceplus#1+#2\@nil{%
  \ifcat$\detokenize{#2}$\expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
  {\unexpanded{#1}}{\xreplaceplus#1,#2\@nil}%
}
\newcommand*\tikzexpanded[2]{%
  \begingroup
  \def\reserved@a##1#1##2##3\@nil{%
    \ifx\@nnil##2\def\x{#2}\else\edef\x{##2{#2}}\fi
  }%
  \reserved@a
    xx{\@iden}x0{\unexpanded}x1{\unexpanded\expandafter}%
    x2{\unexpanded\expandafter\expandafter\expandafter}#1{\@nnil}\@nil
  \edef\x{\expandafter\replaceplus\expandafter{\x}}%
  \edef\x{\endgroup\noexpand\tikzset{\unexpanded\expandafter{\x}}}\x
}
\tikzset{%
  use style/.code={\tikzset{#1}},
  use style expanded/.code={\tikzexpanded{xx}{#1}},
  use style expand once/.code={\tikzexpanded{x1}{#1}},
  use style expand twice/.code={\tikzexpanded{x2}{#1}}
}
\makeatother
\begin{document}
\begin{tikzpicture}
\path \foreach \x/\content/\style in{%
  0/a/draw,
  1/b/draw + red,
  2/c/circle + draw=blue,
  3/d/draw
}{
  node[use style expand once=\style] at (\x,0) {\content}
};
\end{tikzpicture}
\end{document}
2
  • Can I use the same for a \path node ... node ... node command?
    – knittl
    Commented Jul 21, 2012 at 8:34
  • @knittl with my method you can use ` \path node .. node .. ;` Commented Jul 21, 2012 at 11:03
4

I have been using some keys that use the same mechanism as described in the answer by Loop Space. The user syntax is simpler and truthful to the key-value method. It solves more general questions, by expanding a value once, twice or completely before processing it as a key. The example in the question is slightly modified to include an example where expanding twice is necessary.

To answer the question, the following keys set style and expand style once are enough. I include the key expand style twice to solve my modification of the example. I also include the key expand style which is useful when complete expansion is the goal and the number of expansions needed is not known.

You may want to check the minimal examples illustrating these facts in my related answer here.

\documentclass{article}
\usepackage{tikz}
\tikzset{%
  set style/.style={#1},
  expand style/.style={%completely expands the <value> of its argument (using \edef) before processing the result as a <key>
    set style/.expanded={#1}% needs braces around the argument
  },
  expand style once/.style={% expands the <value> of its argument once before processing the result as a <key>
    set style/.expand once={#1}% needs braces around the argument
  },
  expand style twice/.style={% expands the <value> of its argument twice before processing the result as a <key>
    set style/.expand twice={#1}% needs braces around the argument
  }
}
\begin{document}

\begin{tikzpicture}
\def\mystyle{fill=yellow}
\path \foreach \x/\content/\style in {%
  0/a/draw,
  1/b/{draw,red},
  2/c/{circle,draw=blue},
  3/d/\mystyle% \mystyle needs one more expansion after \style has been expanded once.
} {
  node[expand style=\style] at (\x,0) {\content}
} ;
\end{tikzpicture}
\end{document}

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .