19

I'm running into a weird problem here... I have very many nodes defined, and I would like to draw a path from the first to the last using foreach. But, when I try, it just draws edges between p1 and all of the remaining nodes. What gives?

\begin{tikzpicture}[scale=.5]
    \foreach [count=\x] \pt in {(0,5), (0,2), (2,2), (2,0), (6,0), (6,2), (8, 2), (8,6), (6,6), (6,8), (3,8)}
        \node[black, circle, draw] (p\x) at \pt {};

    \draw (p1)
        \foreach \x in {2,...,11} {
            --(p\x)
        };
\end{tikzpicture}

Produces:

enter image description here

Instead of:

enter image description here

1
  • 6
    \foreach \x [count=\xi from 2] in {1,...,10} { \draw (p\x)-- (p\xi);} Oct 3, 2012 at 11:33

3 Answers 3

19

In my opinion, this is a bug in TikZ. As is well-known on this site (!) the PGF \foreach command executes its innards inside a group (actually, two groups). This means that when something is meant to be remembered from one iteration to the next, there has to be an explicit mechanism for remembering it. When the \foreach construction is used to build a path then there are some obvious things that need to be remembered, such as the last point constructed and the last "saved" point. So when TikZ encounters a \foreach in the path construction then it actually calls a wrapper around the PGF \foreach which includes this "saving" apparatus.

However, it would appear that the implementation forgot to remember something. When TikZ encounters a bare node coordinate, such as (p1), then it can't immediately construct the path at this point. The path will lead up to a point on the node's border, but will then hop to some other point on the node's border and that hop will be determined by the next bit of the path. So TikZ remembers that there is a moveto which is "waiting" and figures it out when it knows what the next bit of the path will be.

Unfortunately, this waiting moveto is not part of what is remembered when it goes around the \foreach iteration. So it reverts to the previously remembered moveto which was the one set up before all the grouping started, namely (p1).

A solution is to make TikZ remember that moveto between groups. Here's an adaptation of \tikz@foreach which adds in this rememberence. I have a suspicion that there are a few other things that ought to be remembered between loops as well, such as \tikzlastnode, but to avoid overcomplicating the code I haven't put them in.

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

\makeatletter
\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%
    \let\tikz@moveto@waiting=\tikz@foreach@moveto@waiting
    \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\let\tikz@foreach@moveto@waiting=\tikz@moveto@waiting
      \global\setbox\tikz@tempbox=\box\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%
    \let\tikz@moveto@waiting=\tikz@foreach@moveto@waiting
    \setbox\tikz@figbox=\box\tikz@tempbox\tikz@scan@next@command}%
  \global\setbox\tikz@tempbox=\box\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}%
    \global\let\tikz@foreach@moveto@waiting=\tikz@moveto@waiting
  \foreach}
\makeatother

\begin{document}

\begin{tikzpicture}[scale=.5]
    \foreach [count=\x] \pt in {(0,5), (0,2), (2,2), (2,0), (6,0), (6,2), (8, 2), (8,6), (6,6), (6,8), (3,8)}
        \node[black, circle, draw] (p\x) at \pt {};

    \draw (p1)
        \foreach \x in {2,...,11} {
           -- (p\x)
        };
\end{tikzpicture}
\end{document}

Result:

Foreach with remembered movetos

1
  • 3
    Thanks very much for your detailed answer. This seems like a very major bug... I hope your fix will be included in a future version. Oct 3, 2012 at 12:03
17

You could make TikZ 'remember' the previous point, from which to continue drawing:

\documentclass{standalone}
\usepackage{tikz}

\begin{document}

\begin{tikzpicture}[scale=.5]
    \foreach [count=\x] \pt in {(0,5), (0,2), (2,2), (2,0), (6,0), (6,2), (8, 2), (8,6), (6,6), (6,8), (3,8)}
        \node[black, circle, draw] (p\x) at \pt {};

    \draw \foreach \x [remember=\x as \lastx (initially 1)] in {2,...,11}{(p\lastx) -- (p\x)};
\end{tikzpicture}

\end{document}

enter image description here

2
  • Well, okay, that's a good workaround I guess. But I'm more interested in why the code I posted doesn't work. Is it a bug? And if so, how could such simple use-case be bugged? Oct 3, 2012 at 11:43
  • This example is particularly nice because I can reference a node's center for both the left and right nodes of a connection. for example, {p\lastx.center -- (p\x.center)}. Nov 13, 2012 at 9:09
7

For basic loops, i.e. without a lot of different counts and evaluates (although this could be implemented), you can use PGFkeys’ .list handler which is also implemented via a \foreach but differently, avoiding the grouping problem.

Code

\documentclass[tikz,convert=false]{standalone}
\begin{document}
\begin{tikzpicture}[scale=.5]
    \foreach [count=\x] \pt in {(0,5), (0,2), (2,2), (2,0), (6,0), (6,2), (8, 2), (8,6), (6,6), (6,8), (3,8)}
        \node[black, circle, draw] (p\x) at \pt {};

    \draw[foreach/.style={insert path=--(p#1)}] (p1) [foreach/.list={2,...,11}];
\end{tikzpicture}
\end{document}

Output

enter image description here

You must log in to answer this question.

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