29

I like TikZ's capability to traverse multiple variables separated by a slash in the foreach (as in the example below). I am looking for something equivalent in PGFplots? I saw in the documentation that say you can't nest the foreach in PGFplots as you can in TikZ, but that only make this kind of feature more important.

Two other small issues:

I like the \x notation so that is why I am using \pgfplotsforeachungrouped. Is there a reason why I should be using pgfplotsinvokeforeach instead?

Also, what is the correct syntax to be able to use a variable to define the list instead of having the actual list in the code. In the following I'd like to be able to reference \ListWithLabels in the foreach.

\documentclass{minimal}
\usepackage{pgfplots}

\newcommand*{\ListWithLabels}{-2/a, -1/b, 1/c, 2/d}
\newcommand*{\ListWithoutLabels}{-2, -1, 1, 2}

\begin{document}
\begin{tikzpicture}
\draw [->][gray, thin](-3,0) -- (3,0) node[blue, right] {$x$}; 
\draw [->][gray, thin](0,-3) -- (0,3) node[blue, above] {$y$};

%\foreach \x/\l in \ListWithLabels {  % works
\foreach \x/\l in {-2/a, -1/b, 1/c, 2/d} {
    \draw [thick, red]
        (\x,-2pt) -- (\x,2pt)
        node [red, above] {\l};
}
\end{tikzpicture}

\begin{tikzpicture}
\begin{axis}[
    minor tick num=0, axis y line=center, axis x line=middle, 
    xmin=-3, xmax=3, ymin=-3, ymax=3, xlabel=$x$, ylabel=$y$,
    xtick={0},  xticklabels={}
    ]

%\pgfplotsinvokeforeach{-2, -1, 1, 2}{             % works
%   \addplot [mark=none,color=red, thin, samples=2]
%       coordinates{(#1,-0.05) (#1,0.05) };
%}

%\pgfplotsforeachungrouped \x in {\ListWithoutLabels}{  % does not work
\pgfplotsforeachungrouped \x in {-2, -1, 1, 2}{    % works
    \addplot [mark=none,color=red, thick, samples=2]%
        coordinates{ (\x,-0.08) (\x,0.08) };
} 

%\pgfplotsforeachungrouped \x/\l in {-2/a, -1/b, 1/c, 2/d}{ % does not work
%   \addplot [mark=none,color=red, thin, samples=2]%
%       coordinates{ (\x,-0.05) (\x,0.05) }
%        node [red, below] {\l};
%} 
\end{axis}
\end{tikzpicture}
\end{document}

Update: I saw this post which explains that the macro in TikZ needs to be not be sourrouned by {}. But that does not work for the PGF commands.

Here is another related post on pgfplotsinvokeforeach using a paramter

  • 1
    You got something wrong here. The \foreach macro is provided by pgffor, not by tikz and can be used for pgf code as well. Also keep in mind that pgfplots is not pgf, but a package based on it. However, its author is also a co-author of PGF/TikZ. You might want to change the 'PGF' in the title and text to 'pgfplots' and maybe remove the {pgf-core} tag. – Martin Scharrer May 8 '11 at 20:19
  • @Martin: Thanks, I have made the changes you suggested. So, are you saying that I should just be able to use pgffor? – Peter Grill May 8 '11 at 21:04
  • 1
    No, I don't think so. I just liked to point out that \foreach isn't tikz specific, but comes from the underlying pgf. Apparently pgfplots doesn't like the grouped macros used by \foreach, so you can't use it. – Martin Scharrer May 8 '11 at 21:29
32

There is exactly one reason why there are pgfplots equivalents to the \foreach command of pgf/tikz: scoping. Occasionally, one wants to aggregrate things, i.e. to compute a value which exists outside of the loop's body -- but which is not global either.

If you do not care about scoping and if you are not aggregrating things, the use of \foreach might be the correct choice.

A solution to you approaches might be to tweak the expansion sequence, i.e. to replace the \x and \l macros by their respective value in a "correct way" (see below).

However, there is one particular thing which makes loops in pgfplots a little bit technical: the fact that an axis first just collects its content (evaluating things like axis limits and the-like) following by a visualization phase which actually draws things. Unfortunately, this leads to problems with expansions. What happens if \l is stored back - and is not actually being evaluated until finally \end{axis} starts the survey phase and tries to recover its meaning? Well - it fails because \l has only a well-defined meaning inside of the loop.

Pgfplots offers the \pgfplotsinvokeforeach thing for basic loops: it does not need expansion and directly inserts the value of the loop variable.

The most flexible way, however, is to tweak the expansion sequence -- it allows to use the full flexibility of TikZ's \foreach statement; and it is not so difficult.

It can be used as follows:


\foreach \x/\l in {-2/a, -1/b, 1/c, 2/d}{
   \edef\temp{\noexpand\addplot [mark=none,color=red, thin, samples=2]%
       coordinates{ (\x,-0.05) (\x,0.05) }
        node [red, below] {\l};
   }
   % \show\temp %-- uncomment this to see what the \temp macro does
   \temp
} 

What happens is that inside of the loop body, a temporary macro \temp is defined. The \edef is a TeX instruction (which is not usually used for LaTeX). It means "expanded definition". It defines \temp to be the fully expanded result of the argument in braces. The \noexpand means "insert the next following token literally rather than expanding it". It expands to \addplot (i.e. it simply keeps it). When using \edef, you need to prefix anything which shall be kept as-is with \noexpand. The occurances \x and \l are both replaced by their respective values. Finally, the call to \temp invokes the result -- and it is unaware of the enclosing loop.

Just a further detail: if you use the loop variable inside of coordinates {...} or inside of anything else which is needed to compute axis limits, it is directly expanded (since pgfplots needs to survey the data). Only visualization instructions like the node are postponed until \end{axis}.

3

A different approach to this expansion problem. Using \pgfplotsinvokeforeach and a key defined with .code args (which is basically just a macro with delimited arguments).

If you need the full power of \foreach you will either need to fake it inside the plot as explained in another example

Code

\documentclass[tikz]{standalone}
\usepackage{pgfplots}
\newcommand*{\ListWithLabels}{-2/a, -1/b, 1/c, 2/d}
\newcommand*\pgfplotsinvokeforeachmacro[1]
  {\expandafter\pgfplotsinvokeforeach\expandafter{#1}}
\begin{document}
\begin{tikzpicture}
\begin{axis}
\pgfplotsset{my plot/.code args={#1/#2}{%
  \addplot[mark=none, color=red, thick, samples=2] coordinates {(#1,-.08) (#1,.08)}
    node[red, below] {#2};}}
\pgfplotsinvokeforeachmacro\ListWithLabels{\pgfplotsset{my plot={#1}}}
\end{axis}
\end{tikzpicture}
\end{document}

Code with dependencies

\documentclass[tikz]{standalone}
\usepackage{pgfplots}
\newcommand*{\ListWithLabels}{-2/a, -1/b, 1/c, 2/d}
\newcommand*\pgfplotsinvokeforeachmacro[1]
  {\expandafter\pgfplotsinvokeforeach\expandafter{#1}}
\makeatletter
\pgfplotsset{my color/.code args={#1!#2!#3}{%
  \pgfmathsetmacro\pgfplots@tempa{#2}%
  \pgfkeysalso{color=#1!\pgfplots@tempa!#3}}}
\makeatother
\begin{document}
\begin{tikzpicture}
\begin{axis}
\pgfplotsset{my plot/.code args={#1/#2}{%
  \addplot[mark=none, my color=red!(#1+2)/4*100!green, thick, samples=2]
    coordinates {(#1,-.08) (#1,.08)} node[red, below] {#2};}}
\pgfplotsinvokeforeachmacro\ListWithLabels{\pgfplotsset{my plot={#1}}}
\end{axis}
\end{tikzpicture}
\end{document}
1

You can solve this issue by using a loop-construct which uses no scope limiting group.

\documentclass[tikz]{standalone}
\usepackage{pgfplots}

\newcommand*{\ListWithLabels}{-2/a, -1/b, 1/c, 2/d}
\newcommand*{\ListWithoutLabels}{-2, -1, 1, 2}

\usepackage{xinttools}
\newcommand*{\ListWithLabelsForXintTools}{(-2, a), (-1, b), (1, c), (2, d)}

\begin{document}
\begin{tikzpicture}
\draw [->][gray, thin](-3,0) -- (3,0) node[blue, right] {$x$}; 
\draw [->][gray, thin](0,-3) -- (0,3) node[blue, above] {$y$};

%\foreach \x/\l in \ListWithLabels {  % works
%\foreach \x/\l in {-2/a, -1/b, 1/c, 2/d} {
\xintForpair #1#2 in \ListWithLabelsForXintTools\do{% works too
    \draw [thick, red]
        (#1,-2pt) -- (#1,2pt)
        node [red, above] {#2};
}
\end{tikzpicture}

\begin{tikzpicture}
\begin{axis}[
    minor tick num=0, axis y line=center, axis x line=middle, 
    xmin=-3, xmax=3, ymin=-3, ymax=3, xlabel=$x$, ylabel=$y$,
    xtick={0},  xticklabels={}
    ]

%\pgfplotsforeachungrouped \x in {\ListWithoutLabels}{  % does not work
\xintFor #1 in {\ListWithoutLabels}\do {% does work
    \addplot [mark=none,color=red, thick, samples=2]%
        coordinates{ (#1,-0.08) (#1,0.08) };
} 

%\pgfplotsforeachungrouped \x/\l in {-2/a, -1/b, 1/c, 2/d}{ % does not work
\xintForpair #1#2 in {\ListWithLabelsForXintTools}\do {% does work
  \addplot [mark=none,color=red, thin, samples=2]%
      coordinates{ (#1,-0.05) (#1,0.05) }
       node [red, below] {#2};
} 
\end{axis}
\end{tikzpicture}
\end{document}

Produces:

enter image description here

and

enter image description here

I hope this is expected output (I am no TikZ expert ;-)).

  • Seems to work. Not sure I like the change in syntax from a slash spearated to an ordered pair, but that is just a matter of taste. – Peter Grill Oct 3 '17 at 3:25

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.