[Tex/LaTex] How to use plot in a foreach loop into a single path

tikz-pgf

To answer Creating gears in TikZ, I would use plot in a foreach loop used into a single path. But it seems that it is impossible…

Here, an example to show my problem:

\documentclass{standalone}
\usepackage{tikz}
\begin{document}
\begin{tikzpicture}
  \draw
  (0,0)
  \foreach \myvar in {1,2,3}{
    --
    plot[domain=0:1] (\myvar-\x,\myvar)
  };
\end{tikzpicture}
\end{document}

This document should produce this result:

enter image description here

In fact, it produces following error:

! Package tikz Error: Giving up on this path. Did you forget a semicolon?.

Edit: I add a more complex example to show the problem.

Here, a code without \foreach:

\documentclass{standalone}
\usepackage{tikz}
\begin{document}
\begin{tikzpicture}
  \draw
  (0,0)
  --
  plot[domain=0:120] ({1-cos(3*\x)},{1-sin(\x)})
  --
  plot[domain=0:120] ({2-cos(3*\x)},{2-sin(2*\x)})
  --
  plot[domain=0:120] ({3-cos(3*\x)},{3-sin(3*\x)})
  ;
\end{tikzpicture}
\end{document}

And the result:

enter image description here

And the factorized code with \foreach (but this code produces the error):

\documentclass{standalone}
\usepackage{tikz}
\begin{document}
\begin{tikzpicture}
  \draw[dahsed]
  (0,0)
  \foreach \myvar in {1,2,3}{
    --
    plot[domain=0:120] ({\myvar-cos(3*\x)},{\myvar-sin(\myvar*\x)})
  };
\end{tikzpicture}
\end{document}

Best Answer

It's a bug.

The \foreach that occurs when you do \draw ... \foreach ... is not the same as the \foreach that occurs when you do \foreach ... \draw .... It's a wrapper around the real \foreach that sets up some hooks first before calling the real command. The wrapper command is:

\def\tikz@foreach{%
  \def\pgffor@beginhook{%
    \tikz@lastx=\tikz@foreach@save@lastx%
    \tikz@lasty=\tikz@foreach@save@lasty%
    \tikz@lastxsaved=\tikz@foreach@save@lastxsaved%
    \tikz@lastysaved=\tikz@foreach@save@lastysaved%
    \setbox\tikz@figbox=\box\tikz@tempbox\expandafter\tikz@scan@next@command\pgfutil@firstofone}%
  \def\pgffor@endhook{\pgfextra{%
      \xdef\tikz@foreach@save@lastx{\the\tikz@lastx}%
      \xdef\tikz@foreach@save@lasty{\the\tikz@lasty}%
      \xdef\tikz@foreach@save@lastxsaved{\the\tikz@lastxsaved}%
      \xdef\tikz@foreach@save@lastysaved{\the\tikz@lastysaved}%
      \global\setbox\tikz@tempbox=\copy\tikz@figbox\pgfutil@gobble}}%
  \def\pgffor@afterhook{%
    \tikz@lastx=\tikz@foreach@save@lastx%
    \tikz@lasty=\tikz@foreach@save@lasty%
    \tikz@lastxsaved=\tikz@foreach@save@lastxsaved%
    \tikz@lastysaved=\tikz@foreach@save@lastysaved%
    \setbox\tikz@figbox=\box\tikz@tempbox\tikz@scan@next@command}%
  \global\setbox\tikz@tempbox=\copy\tikz@figbox%
  \xdef\tikz@foreach@save@lastx{\the\tikz@lastx}%
  \xdef\tikz@foreach@save@lasty{\the\tikz@lasty}%
  \xdef\tikz@foreach@save@lastxsaved{\the\tikz@lastxsaved}%
  \xdef\tikz@foreach@save@lastysaved{\the\tikz@lastysaved}%
  \foreach}

The hooks, \pgffor@beginhook and so on, get called at specific places in the \foreach code. The important one is what happens at the end of the beginhook: \expandafter\tikz@scan@next@command\pgfutil@firstofone. The practical upshot of this is that it puts the TikZ parser into the right mode for scanning the body of the foreach loop: it is expecting a path construction command. This is what makes \draw (0,0) \foreach \i in {1} { -- (1,0) }; work: when TikZ finally gets to see the -- (1,0) it is in the right "mode".

The plot function works by calling \foreach to generate all of the points it will draw a path through. But when the plot function is working, it doesn't want the TikZ parser to be in effect, it wants to do its own stuff. So the \pgffor@beginhook is completely in the wrong place here because TikZ will now be expecting a path construction command when it starts generating the points for the path, but the plot code isn't ready to hand control back to the parser at that point. What ends up happening is that the parser goes into an infinite loop, but TikZ has a builtin check for these loops and bails out ... eventually.

One fix is to blank the hooks before the plot function calls its loops. I don't know how reliable this is - I've not tested it extensively. Here's some code that does that:

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

\makeatletter

\def\tikz@clear@foreach{%
\let\pgffor@beginhook=\pgfutil@empty
\let\pgffor@endhook=\pgfutil@empty
\let\pgffor@afterhook=\pgfutil@empty
}

\def\tikz@plot@expression(#1){%
  \edef\tikz@plot@data{\noexpand\tikz@clear@foreach\noexpand\pgfplotfunction{\expandafter\noexpand\tikz@plot@var}{\tikz@plot@samplesat}}%
  \expandafter\def\expandafter\tikz@plot@data\expandafter{\tikz@plot@data{\tikz@scan@one@point\pgfutil@firstofone(#1)}}%
  \tikz@@@plot%
}
\makeatother
\begin{document}
\begin{tikzpicture}
%\traceon
  \draw[dashed]
  (0,0) \foreach \myvar in {1,2,3} {%
   -- plot[domain=0:120] ({\myvar-cos(3*\x)},{\myvar-sin(\myvar*\x)})
%-- ++(\myvar,1)
  }%
;
%\traceoff
\end{tikzpicture}
\end{document}

This produces the desired result:

TikZ plot fix

As I said, there are probably better fixes but think of this as a plaster until the real fix is implemented.