4

I have a hard time understanding the response to the GitHub issue pgf-tikz/pgf#356.

original picture

The original question asker has posted the following MWE. I've wrapped it with \begin{document} for completeness.

\documentclass[tikz,border=2mm]{standalone}
\begin{document}
\begin{tikzpicture}
  \coordinate (v1)  at (0,0);
  \coordinate (c21) at (1,0);
  \coordinate (c22) at (2,0);
  \coordinate (v2)  at (2,1);
  \coordinate (c31) at (2,2);
  \coordinate (c32) at (3,2);
  \coordinate (v3)  at (4,2);
  \node[draw,inner sep=0pt,minimum size=6pt,circle] (a) at (v1) {};
  \draw (a) \foreach \i in {2,3} {.. controls (c\i1) and (c\i2) .. (v\i)};
\end{tikzpicture}
\end{document}

The reply to the linked question suggests that the \foreach is expanded to

\draw (a) .. controls (c21) and (c22) .. (v2) 
      (a) .. controls (c31) and (c32) .. (v3);

However, if the node (a) in the line containing \foreach in his MWE is replaced by the coordinate (v1), then the \foreach loop isn't expanded to

\draw (v1) .. controls (c21) and (c22) .. (v2) 
      (v1) .. controls (c31) and (c32) .. (v3);

Why is it expanded like the following?

\draw (v1) .. controls (c21) and (c22) .. (v2) 
           .. controls (c31) and (c32) .. (v3);

enter image description here

The reply doesn't explain that.

There's no mention of \iftikz@shapeborder in PGF & TikZ manual, whose chapter 88 says

Note that the node and pic path commands also support the foreach statement in special ways.

Unluckily, there's no explanation about the "special ways" in the manual.

3

2 Answers 2

5

The difference between coordinates and nodes is that a node (normally) has size. That is, at least one of a node's height, depth, and width is greater than zero. TikZ/pgf takes care of this size and will always draw from/to node border when a node without anchor is used as coordinate, no matter that border is drawn or not. See pgfmanual, sec. 17.11 "Connecting Nodes: Using Nodes as Coordinates" (html) for more info.

Then comes the scoping property of (\)foreach. To support on-path foreach, quite some info should be backed-up and restored when each foreach loop is leaved and entered. TikZ has already handled some of such info (see tikz.code.tex, definition of \tikz@foreach), but it's far from comprehensive. Issue #356 is one case for curve-to path operation, and issue #1047 is another case for turn option.

For OP's current question, one can think that the coordinate version has been handled (by backing-up and restoring \tikz@last[xy] and \tikz@last[xy]saved), and the node version has not yet.

Anyway, for OP's question (and for issue #356, it seems the only missing piece of info is \tikz@moveto@waiting, which is either let to \relax or \edefed to hold \tikz@shapeborder@name (a node name).

\documentclass[tikz,border=2mm]{standalone}
\usepackage{regexpatch}

\tikzset{
  expected/.style={blue!10, line width=5pt},
  simulated/.style={blue!50, line width=3pt, opacity=.5},
  actual/.style={line width=.6pt}
}

\newcommand\test[2][]{
  \begin{tikzpicture}
    \coordinate (v1)  at (0,0);
    \coordinate (c21) at (1,0);
    \coordinate (c22) at (2,0);
    \coordinate (v2)  at (2,1);
    \coordinate (c31) at (2,2);
    \coordinate (c32) at (3,2);
    \coordinate (v3)  at (4,2);
    \node[draw,inner sep=0pt,minimum size=6pt,circle] (a) at (v1) {};
    
    \begin{scope}[shift={(3,0)}, nodes={font=\sffamily\scriptsize}, y=.5cm]
      \node[anchor=south west, align=center] at  (0,1.5) {start from (#2)#1};
      \draw[expected]  (0, 1) -- +(1,0) node[right] {expected};
      \draw[simulated] (0,.5) -- +(1,0) node[right] {simulated};
      \draw[actual]    (0, 0) -- +(1,0) node[right] {actual};
    \end{scope}
    
    % expected
    \draw[expected]
      (#2) .. controls (c21) and (c22) .. (v2)
          .. controls (c31) and (c32) .. (v3);
  
    % simulated foreach
    \draw[simulated]
      (#2) .. controls (c21) and (c22) .. (v2)
      (#2) .. controls (c31) and (c32) .. (v3);
    
    % actual
    \draw[actual]
      (#2) \foreach \i in {2,3} {.. controls (c\i1) and (c\i2) .. (v\i)};
  \end{tikzpicture}
}

\begin{document}
\test[\\before patch]{a}
\test[\\before patch]{v1}

\makeatletter
% backup globally
\xpatchcmd*\tikz@foreach
  {\xdef\tikz@foreach@save@lastysaved{\the\tikz@lastysaved}}
  {\xdef\tikz@foreach@save@lastysaved{\the\tikz@lastysaved}%
   \global\let\tikz@foreach@moveto@waiting=\tikz@moveto@waiting
   }
  {}{\PatchFailed}

% restore locally
\xpatchcmd*\tikz@foreach
  {\tikz@lastysaved=\tikz@foreach@save@lastysaved}
  {\tikz@lastysaved=\tikz@foreach@save@lastysaved
   \let\tikz@moveto@waiting=\tikz@foreach@moveto@waiting
   }
  {}{\PatchFailed}
\makeatother

\test[\\after patch]{a}
\test[\\after patch]{v1}
\end{document}

enter image description here

2
  • sor9y i can't understand neither the code in the linked TEX file where the foreach command is defined, nor the code you've given in the example. idk any IDE/debugger for LaTeX. a debugger for a normal programming language allows setting breakpoints and view the changes of variables in each step. i can't see how the value \tikz@last[xy] has changed in the source code. having other more important things to do, i'll let go this problem and rmb the functional explanation. Jun 30, 2022 at 23:16
  • I'm afraid there's no similar debugger for (La)TeX. Jul 1, 2022 at 1:54
3

What I understand is that \foreach creates full-local subpaths --- so it is basically equivalent to

\draw (a) { [current point is local] .. controls (c21) and (c22) .. (v2) }
          { [current point is local] .. controls (c31) and (c32) .. (v3) };

About the border things: the idea is that if you use a node like (a) as a coordinate, TikZ will put the coordinate on the border of the shape --- in this case using (v1) is like using (a.center).

Code to play with:

\documentclass[tikz,border=2mm]{standalone}
\begin{document}
\begin{tikzpicture}
  \coordinate (v1)  at (0,0);
  \coordinate (c21) at (1,0);
  \coordinate (c22) at (2,0);
  \coordinate (v2)  at (2,1);
  \coordinate (c31) at (2,2);
  \coordinate (c32) at (3,2);
  \coordinate (v3)  at (4,2);
  \node[draw,inner sep=0pt,minimum size=6pt,circle] (a) at (v1) {};
  \draw (a) \foreach \i in {2,3} {.. controls (c\i1) and (c\i2) .. (v\i)};
  \draw[blue]  (a) .. controls (c21) and (c22) .. (v2)
                   .. controls (c31) and (c32) .. (v3);
  \draw[red, dashed]  (a) { [current point is local] .. controls (c21) and (c22) .. (v2) }
                  { [current point is local] .. controls (c31) and (c32) .. (v3) };
\end{tikzpicture}
\end{document}

output from the snippet above

2
  • sorry i still don't get your answer after checking the definition of current point is local. i'm more interested about the expansion of \draw (a.center) ... than in \draw (a) ..., but most of your answer covers the later. say I replace (a) in the last \draw command in your answer with (a.center). \draw[red, dashed] (a.center) { [current point is local] .. controls (c21) and (c22) .. (v2) } { [current point is local] .. controls (c31) and (c32) .. (v3) }; after parsing the first }, why the current point is not reset to (a.center)? Jun 29, 2022 at 14:45
  • Ah, you are right. No idea... sounds like a bug or an "unexpected feature", really.
    – Rmano
    Jun 29, 2022 at 14:50

You must log in to answer this question.

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