Take the 2-minute tour ×
TeX - LaTeX Stack Exchange is a question and answer site for users of TeX, LaTeX, ConTeXt, and related typesetting systems. It's 100% free, no registration required.

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:

Instead of:

share|improve this question
4  
\foreach \x [count=\xi from 2] in {1,...,10} { \draw (p\x)-- (p\xi);} –  Alain Matthes Oct 3 '12 at 11:33
add comment

3 Answers

up vote 10 down vote accepted

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

share|improve this answer
    
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. –  Paul Accisano Oct 3 '12 at 12:03
add comment

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

share|improve this answer
    
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? –  Paul Accisano Oct 3 '12 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)}. –  macmadness86 Nov 13 '12 at 9:09
add comment

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

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.