4

Why does my code fail? Without the foreach my code works correctly.

Generally, when and how can I use foreach within a path command? Is this a bad practice? If so what should be the alternative?

\documentclass{standalone}

\usepackage{tikz}

\tikzset{ myVrtxStyle/.style = { circle, minimum size= 4mm, fill = blue } }

\begin{document} \begin{tikzpicture} \node[myVrtxStyle = black] (u) {};

    \newcommand{\midSepAngle}{20};
    \newcommand{\remNi}{6}

    \draw (u.{135 + \midSepAngle}) to [out = {135 + \midSepAngle}, in = {135 - \midSepAngle}, looseness = 100]
    \foreach \i in {1,..., \remNi}{
        node[ myVrtxStyle, pos = {\i / \remNi} ] (C1\i) {} 
    }
    (u.{135 - \midSepAngle});

\end{tikzpicture}

\end{document}

I have seen few related posts such as this one but I still do not understand fully what is going on.

Aria
  • 393
  • I don't recall you can use foreach "on line". The place you can use foreach is when TikZ is not "pending". For example, between (A)-- and (B), TikZ is desperately looking for the target of the segment. You can barely place a node there; but certainly a for loop is too much. What you what is probably achievable with the library decorations.markings. – Symbol 1 Aug 10 '21 at 05:57
  • Foreach in the middle of a draw command is just foreach, no backslash. – Andrew Stacey Aug 10 '21 at 06:08

3 Answers3

8
  1. In a path, you should use foreach. For historical reasons, you can also write \foreach instead of foreach (cf. section "14.14 The Foreach Operation", p.167, pgfmanual v3.1.9a).

  2. There are two ways of specifying a node on a line or a curve: Either explicitly by using the pos option or implicitly by placing the node “inside” a path operation (cf. sections "17.8 Placing Nodes on a Line or Curve Explicitly", p.245 and "17.9 Placing Nodes on a Line or Curve Implicitly", p.249, pgfmanual v3.1.9a).

    • Implicit example: \draw (0,0) -- node[pos=.7]{a} (1,1); Only the line-to (--) and curve-to (..) operations support the implicit way. The foreach keyword is not supported by the implicit way.

    • Explicit example: \draw (0,0) -- (1,1) node[pos=.7] {a}; Only the line-to (--), horizontal and vertical line-to (-| and |-), curve-to (..) and arc (arc) operation support the explicit way.

    • For all other path construction operations (like to on your case), the position placement does not work, currently (cf. p.246, pgfmanual, v3.1.9a).

So, you must use the explicit way and replace your to operation by a curve-to (..) operation as in the Andrew Stacey's solution.

Paul Gaborit
  • 70,770
  • 10
  • 176
  • 283
6

I don't know why it doesn't work with a to command - I'll have to investigate that - but with an explicit bézier curve then it does.

  1. The target coordinate has to go before the foreach
  2. The foreach is a key word, not a command
  3. I modified your node style to take an argument as the fill colour with default blue
\documentclass {standalone}
\usepackage{tikz}

\tikzset{ myVrtxStyle/.style = { circle, minimum size= 4mm, fill = #1 }, myVrtxStyle/.default = blue }

\begin{document}

\begin{tikzpicture} \node[myVrtxStyle = black] (u) {}; \newcommand{\midSepAngle}{20}; \newcommand{\remNi}{6} \draw (u.{135 + \midSepAngle}) .. controls +(135+\midSepAngle:20) and +(135-\midSepAngle: 20) .. (u.{135-\midSepAngle}) foreach \i in {1,..., \remNi}{ node[ myVrtxStyle, pos = \i/\remNi ] (C1\i) {} } ; \end{tikzpicture} \end{document}

Andrew Stacey
  • 153,724
  • 43
  • 389
  • 751
3

The following syntax actually works and is almost identical to your original code.

\documentclass{standalone}
\usepackage{tikz}
\tikzset{
    myVrtxStyle/.style={
        circle,minimum size=4mm,fill=blue,fill=#1
    }
}
\begin{document}
    \begin{tikzpicture}
        \node[myVrtxStyle=black](u){};
        \newcommand{\midSepAngle}{20};
        \newcommand{\remNi}{6}
        \draw
            (u.{135 +\midSepAngle})
            to
            [out={135+\midSepAngle},in={135-\midSepAngle},looseness=100]
            node foreach\i in{1,...,\remNi}[pos=\i/\remNi,myVrtxStyle](C1\i){}
            (u.{135-\midSepAngle})
        ;
        \draw foreach\i in{1,...,\remNi}{
            (C1\i)--+(-90-\i*40:1)node{\i}
        };
    \end{tikzpicture}
\end{document}

Remark

My comment under the question is incorrect in the manner that you actually can use a for-loop between (A) -- and (B). However, this for-loop cannot be an arbitrary for-loop; it has to be a for-loop dedicated to nodes. So
(A) -- foreach\i in{1,2,3}{node{\i}} (B) is bad, but
(A) -- node foreach\i in{1,2,3}{\i} (B) is good.

This is because when TikZ's parser has read (A) -- and is waiting for (B), it also keeps an eye on whether there is a node...{...}. Once it sees n, it will collect the node specification and store it in \tikz@collected@onpath, which is latter transferred to \tikz@tonodes and to \tikztonodes. After TikZ computed the Bézier representation of your to path, it places the node specification at the end of the path like this (tikzlibrarytopaths.code.tex#L134-L140)

\def\tikz@to@curve@path{%
  [every curve to]%                ⬇️ This is the main computation ⬇️
  \pgfextra{\iftikz@to@relative\tikz@to@compute@relative\else\tikz@to@compute\fi}
  \tikz@computed@path% ⬅️ this is the Bézier curve (A)..controls(B)and(C)..(D)
  \pgfextra{\tikz@updatenexttrue\tikz@updatecurrenttrue}%
  \tikztonodes% ⬅️ your nodes here (notice the plural)
}

So all you need to do is to make sure \tikztonodes actually catches your node specifications. In case the natural syntax fails, you can always force it. For example
to[bend left,/utils/exec=\def\tikztonodes{...}].

Symbol 1
  • 36,855
  • 1
    I hadn't come across the node foreach syntax before - thanks for posting this! For reference, it is explained in Section 17.2.1 (of the 3.1.9a version) of the TikZ/PGF manual. – Andrew Stacey Aug 10 '21 at 19:32
  • Thanks for the answer, a separate question: Could someone please tell me why my pos does not create equally distant nodes? – Aria Aug 10 '21 at 21:06
  • 1
    pos in this case is parametrized by the "time" of the Bézier curve. If you need equal distance, use decorations.markings because it is parametrized by arc length. – Symbol 1 Aug 10 '21 at 21:57
  • @Symbol1 Thanks. – Aria Aug 11 '21 at 00:57