17

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}
Paul Gaborit
  • 70,770
  • 10
  • 176
  • 283
  • A complete example is in my answer http://tex.stackexchange.com/a/58828/14500 (a gear). – Paul Gaborit Jun 06 '12 at 23:05
  • I'm going for bug. The TikZ command parser goes into an infinite loop (it bails out because TikZ has an inbuilt limit on infinity) where it keeps trying to expand the same thing over and over again. – Andrew Stacey Jun 07 '12 at 10:29

3 Answers3

11

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.

Andrew Stacey
  • 153,724
  • 43
  • 389
  • 751
  • Thank you for this perfect analysis. Christophe Jorssen has completed my bug report with a link to your answer. Now, I'm waiting for the real fix (even if I can compile pgfmanual with your fix). – Paul Gaborit Jun 07 '12 at 17:34
  • 1
    With the latest CVS version of TikZ/pgf (since 2013-07-17), this bug is fixed. – Paul Gaborit Jul 29 '13 at 14:32
4

I assumed the problem to be that it is illegal to cut the \draw command with a \foreach loop as you did. Seems I was wrong about that one. Nevertheless, a solution to circumvent this bug (as suggested by other posters), I propose to use a lower level command, namely \pgfpathlineto:

\documentclass{standalone}
\usepackage{tikz}
\begin{document}
\begin{tikzpicture}
  \pgfpathmoveto{\pgfpointorigin}
  \foreach \myvar/\x in {1cm/0cm,1cm/1cm,2cm/0cm,2cm/1cm,3cm/0cm,3cm/1cm}{
    \pgfpathlineto{\pgfpoint{\myvar-\x}{\myvar}}
  };
  \pgfusepathqstroke
\end{tikzpicture}
\end{document}

First the start of the path is set to the origin of the coordinate system of the canvas. Then in the \foreach loop the successive segments are added to the path and finally the path is actually drawn. If you omit \pgfusepathqstroke, you will have a path, but it won't be visible.

Count Zero
  • 17,424
  • 1
    It's possible to cut the \draw command with a \foreach loop, for example : \draw (0,0) \foreach \myvar in {1,2,3}{ -- (\myvar,\myvar*\myvar-\myvar)} ;. I think but I'm not sure that the problem comes from a conflict between plot and \foreach – Alain Matthes Jun 06 '12 at 21:33
  • Using \foreach loop into \draw is legal and useful. My real problem is in the question : using plot into \foreach loop into \draw... – Paul Gaborit Jun 06 '12 at 22:30
  • I see this garnered a couple of down-votes. I think it was a reasonable attempt to answer the question. In such a situation, it is better to leave a comment explaining why the answer doesn't answer the question and give the answerer a chance to respond. – Andrew Stacey Jun 07 '12 at 13:11
  • @AndrewStacey Ok. I delete my down-vote. – Paul Gaborit Jun 08 '12 at 12:11
  • @PolGab Thank you. (Note that I had no way to know who had voted down this answer.) I notice you're fairly new to this site so I hope you don't mind my comments. We tend to be fairly sparing in our down-votes - I've only voted down 16 times in nearly 2 years (compared to over 4000 votes up). Note that Count Zero has edited the answer to make it clear that the original assumption was wrong so there's no misinformation that might confuse later viewers of this question. – Andrew Stacey Jun 08 '12 at 12:32
3

It's possible to cut the \draw command with a \foreach loop, for example : \draw (0,0) \foreach \myvar in {1,2,3}{ -- (\myvar,\myvar*\myvar-\myvar)} ;.

I think but I'm not sure that the problem comes from a conflict between plot and \foreach

Here a solution to get the result

\documentclass{article}
\usepackage{tikz}
\begin{document}
\begin{tikzpicture} 
 \draw (0,0)
  \foreach \myvar in {1,2,3}{%
   -- (\myvar,\myvar) -- (\myvar-1,1+\myvar)} ;
\end{tikzpicture}
\end{document} 

enter image description here

Alain Matthes
  • 95,075