tikz \foreach (ミス?) の動作

tikz \foreach (ミス?) の動作

以下のコードでは、3 つのケース (コメント アウト) が完全に同等であると予想していました。残念ながら (私にとっては) 3 番目のケースでは、座標 (X) から座標 (Y) への直線が生成されます。私は常に、\foreach中括弧で囲まれたコードを入力ストリームに「そのまま」挿入するだけのステートメントだと思っていましたが、そうではないようです。

何が足りないのでしょうか? それともこれは (既知の) バグでしょうか?

\documentclass{article}
\usepackage{tikz}

\begin{document}

\begin{tikzpicture}
  \path 
    (0,0) coordinate(X) 
    (1,1) coordinate(A) node[minimum size=4.3pt,inner sep=0pt](nA){}
    (2,2) coordinate(B) node[minimum size=4.3pt,inner sep=0pt](nB){}
    (3,3) coordinate(Y)
    ;
  \draw[rotate=45] 
    (A) +(-7pt/2,0) arc[start angle=180,end angle=0,radius=7pt/2]
    (B) +(-7pt/2,0) arc[start angle=180,end angle=0,radius=7pt/2]
    ;
    
    % Case (1)
  %\draw (X) -- (nA) -- (nB) -- (Y) ;
  
    % Case (2)
  %\foreach \x/\y in {X/nA,nA/nB,nB/Y} {\draw (\x) -- (\y);}

    % Case (3)
  %\draw (X) \foreach \x in {nA,nB,Y} { -- (\x)};
  
\end{tikzpicture}

\end{document}

編集: ノードの使用は、アークのサイズを処理するために意図的に行われます。また、私の実際の使用例は、tikz 交差パッケージの範囲内です。その場合、多くのパス交差を処理す​​るために foreach を使用する必要があります。その場合、3 番目のケースが最も自然な構成になります。

編集 2: コメントの議論と (今のところ) 唯一の答えから。私はこれを実現するための別の方法は必要ありません。私はすでに実際のケースを解決しました (ケース (2) に基づく戦略を展開することによって)。私の唯一の質問は、3 番目のケースで \foreach に何が起こっているのかということです。なぜそのように動作するのですか? それが唯一の質問であり、その落とし穴を回避するための別の代替方法ではありません。

編集 3: Andrew Stacey さんの回答は、私の最初の質問以上のものでした。つまり、私の唯一の質問は、何が起こっているのか、それが予想されていたのか、ということでした。foreach の実装方法とパス サブシステムとのやり取りを考えると、これはバグであることは明らかです。foreach は、括弧内のコードを展開していくつかの変数 (foreach の変数) を置き換えるだけではありません。

答え1

Skillmon が回答のコメントで述べているように、これはグループ化の問題です。このサイトのさまざまな質問で文書化され、検討されているように、\foreachコードはグループに入れられます (記憶が正しければ、実際には 2 つのグループがあります)。そのため、1 つのループから次のループに記憶する必要があるものはすべて、そのグループ化から取り出すために少し作業が必要です。ユーザー レベルでは、これは通常、rememberQrrbrbirlbel が質問に対するコメントの 1 つで言及しているキーを使用して実現されます。

しかし、パス内では、前の座標を記憶することは非常に便利で一般的なことなので、TikZ は、それが正しく記憶されるようにするために、かなりの努力をします (文字通り)。そのため、TikZ は、ループの各反復の開始と終了に、グループ化が存在しないかのように最後のポイントを記憶するコードをインストールします。これを行うコードは比較的長くなります。

\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%
    \setbox\tikz@figbox@bg=\box\tikz@tempbox@bg%
    \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=\box\tikz@figbox%
      \global\setbox\tikz@tempbox@bg=\box\tikz@figbox@bg%
      \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\pgffor@beginhook\relax%
    \let\pgffor@endhook\relax%
    \let\pgffor@afterhook\relax%
    \setbox\tikz@figbox=\box\tikz@tempbox%
    \setbox\tikz@figbox@bg=\box\tikz@tempbox@bg%
    \tikz@scan@next@command}%
  \global\setbox\tikz@tempbox=\box\tikz@figbox%
  \global\setbox\tikz@tempbox@bg=\box\tikz@figbox@bg%
  \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}%

参考までに、TikZ の現在のバージョンでは、これは の 2582 行目から始まりますtikz.code.tex

ここで、TikZ はパス上にアンカーのないノードに遭遇すると、movetoパスに を追加してパスにブレークを挿入します。ただし、次の座標に最も適したノードの境界上の終了ポイントを選択するため、 のターゲットmovetoは遭遇した時点ではわかりません。したがって、 がmoveto完了していないこと、および TikZ が次の座標を認識したときにmovetoパスの次の部分の残りを計算する前にそれを完了することを記録する必要があり、これを実行する必要があることをフラグ付けするために、TikZ はマクロを定義し\tikz@moveto@waiting、このマクロは TikZ が計算を完了するために必要なノード名を保持します。

私が上に投稿したコードをよく見ると\tikz@foreach、明らかに参照が欠けていることがわかります。\tikz@moveto@waitingつまり、しない各反復の最後に更新されます。つまり、TikZ が反復を開始するたびに、\tikz@moveto@waitingループの外側から元の値が保持されます。あなたの場合、それが最初のノードであり、すべての行が前のノードからXではなく、そこから始まるのはそのためです。X

幸いなことに、必要な行を追加するのは難しくありません。以下は のパッチバージョンです\tikz@foreach

\def\tikz@foreach{%
  \def\pgffor@beginhook{%
    \global\let\tikz@moveto@waiting\tikz@foreach@moveto@waiting%
    \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%
    \setbox\tikz@figbox@bg=\box\tikz@tempbox@bg%
    \expandafter\tikz@scan@next@command\pgfutil@firstofone}%
  \def\pgffor@endhook{\pgfextra{%
      \global\let\tikz@foreach@moveto@waiting\tikz@moveto@waiting%
      \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=\box\tikz@figbox%
      \global\setbox\tikz@tempbox@bg=\box\tikz@figbox@bg%
      \pgfutil@gobble}}%
  \def\pgffor@afterhook{%
    \let\tikz@moveto@waiting\tikz@foreach@moveto@waiting%
    \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\pgffor@beginhook\relax%
    \let\pgffor@endhook\relax%
    \let\pgffor@afterhook\relax%
    \setbox\tikz@figbox=\box\tikz@tempbox%
    \setbox\tikz@figbox@bg=\box\tikz@tempbox@bg%
    \tikz@scan@next@command}%
  \global\setbox\tikz@tempbox=\box\tikz@figbox%
  \global\setbox\tikz@tempbox@bg=\box\tikz@figbox@bg%
  \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}%

テスト可能なコード:

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

\makeatletter
\def\tikz@patched@foreach{%
  \def\pgffor@beginhook{%
    \global\let\tikz@moveto@waiting\tikz@foreach@moveto@waiting%
    \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%
    \setbox\tikz@figbox@bg=\box\tikz@tempbox@bg%
    \expandafter\tikz@scan@next@command\pgfutil@firstofone}%
  \def\pgffor@endhook{\pgfextra{%
      \global\let\tikz@foreach@moveto@waiting\tikz@moveto@waiting%
      \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=\box\tikz@figbox%
      \global\setbox\tikz@tempbox@bg=\box\tikz@figbox@bg%
      \pgfutil@gobble}}%
  \def\pgffor@afterhook{%
    \let\tikz@moveto@waiting\tikz@foreach@moveto@waiting%
    \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\pgffor@beginhook\relax%
    \let\pgffor@endhook\relax%
    \let\pgffor@afterhook\relax%
    \setbox\tikz@figbox=\box\tikz@tempbox%
    \setbox\tikz@figbox@bg=\box\tikz@tempbox@bg%
    \tikz@scan@next@command}%
  \global\setbox\tikz@tempbox=\box\tikz@figbox%
  \global\setbox\tikz@tempbox@bg=\box\tikz@figbox@bg%
  \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}%

\DeclareDocumentCommand \FixForeach {}
{
  \let\tikz@foreach=\tikz@patched@foreach
}
\makeatother

\begin{document}

\begin{tikzpicture}
\node[draw] (A) at (0,0) {};
\node[draw] (B) at (1,1) {};
\node[draw] (C) at (2,0) {};
\draw (A) -- (B) -- (C);
\end{tikzpicture}

\begin{tikzpicture}
\node[draw] (A) at (0,0) {};
\node[draw] (B) at (1,1) {};
\node[draw] (C) at (2,0) {};
\draw (A) \foreach \nd in {B,C} { -- (\nd) };
\end{tikzpicture}

\FixForeach

\begin{tikzpicture}
\node[draw] (A) at (0,0) {};
\node[draw] (B) at (1,1) {};
\node[draw] (C) at (2,0) {};
\draw (A) \foreach \nd in {B,C} { -- (\nd) };
\end{tikzpicture}


\end{document}

上記のコードの出力は、3 つのノードを接続する 3 つのバージョンの図を示しています。最初の図では、3 つのノードが手動で正しい順序で接続されています。2 番目では、元の foreach コードによって 2 番目と 3 番目のノードが最初のノードに接続されています。3 番目では、パッチを適用した foreach コードによって、再び正しく接続されています。


更新:Qrrbrbirlbelは(現在は削除されたコメントで) の使用にはまだ問題があると指摘しto、githubの問題はforeach の座標 [turn] オプションでも同じ問題が発生しますturn。これらをすべて修正し、保存する必要のある項目の追加を少し簡単にするコードを以下に示します。

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

\makeatletter

\def\pgffor@smugglers@cove{}
\let\tikztostart=\relax

\def\pgffor@smuggle@macro#1{%
  \pgfutil@ifx#1\relax{}{%
    \expandafter
    \pgfutil@g@addto@macro
    \expandafter
    \pgffor@smugglers@cove
    \expandafter
    {%
      \expandafter\def\expandafter#1\expandafter{#1}%
    }%
  }%
}

\def\pgffor@smuggle@dimen#1{%
  \pgfutil@ifx#1\relax{}{%
    \expandafter
    \pgfutil@g@addto@macro
    \expandafter
    \pgffor@smugglers@cove
    \expandafter
    {%
      \expandafter#1\expandafter=\the#1\relax%
    }%
  }%
}

\def\pgffor@smugglers@loot{%
  %
  \pgffor@smuggle@macro\tikz@moveto@waiting%
  \pgffor@smuggle@macro\tikztostart%
  \pgffor@smuggle@macro\tikz@tangent%
  %
  \pgffor@smuggle@dimen\tikz@lastx%
  \pgffor@smuggle@dimen\tikz@lasty%
  \pgffor@smuggle@dimen\tikz@lastxsaved%
  \pgffor@smuggle@dimen\tikz@lastysaved%
  %
}


\def\tikz@patched@foreach{%
  \def\pgffor@beginhook{%
    \pgffor@smugglers@cove
    \gdef\pgffor@smugglers@cove{}%
    \setbox\tikz@figbox=\box\tikz@tempbox%
    \setbox\tikz@figbox@bg=\box\tikz@tempbox@bg%
    \expandafter\tikz@scan@next@command\pgfutil@firstofone}%
  \def\pgffor@endhook{\pgfextra{%
      \pgffor@smugglers@loot%
      \global\setbox\tikz@tempbox=\box\tikz@figbox%
      \global\setbox\tikz@tempbox@bg=\box\tikz@figbox@bg%
      \pgfutil@gobble}}%
  \def\pgffor@afterhook{%
    \pgffor@smugglers@cove
    \gdef\pgffor@smugglers@cove{}%
    \let\pgffor@beginhook\relax%
    \let\pgffor@endhook\relax%
    \let\pgffor@afterhook\relax%
    \setbox\tikz@figbox=\box\tikz@tempbox%
    \setbox\tikz@figbox@bg=\box\tikz@tempbox@bg%
    \tikz@scan@next@command}%
  \global\setbox\tikz@tempbox=\box\tikz@figbox%
  \global\setbox\tikz@tempbox@bg=\box\tikz@figbox@bg%
  \pgffor@smugglers@loot%
  \foreach}%

\DeclareDocumentCommand \FixForeach {}
{
  \let\tikz@foreach=\tikz@patched@foreach
}
\makeatother

\begin{document}

\begin{tabular}{rl}
Paths: &
\begin{tikzpicture}
\node[draw] (A) at (0,0) {};
\node[draw] (B) at (1,1) {};
\node[draw] (C) at (2,0) {};
\draw (A) -- (B) -- (C);
\end{tikzpicture} \\
%
Foreach Paths: &
\begin{tikzpicture}
\node[draw] (A) at (0,0) {};
\node[draw] (B) at (1,1) {};
\node[draw] (C) at (2,0) {};
\draw (A) \foreach \nd in {B,C} { -- (\nd) };
\end{tikzpicture} \\
%
Tos: &
\begin{tikzpicture}
\node[draw] (A) at (0,0) {};
\node[draw] (B) at (1,1) {};
\node[draw] (C) at (2,0) {};
\draw (A) to (B) to (C);
\end{tikzpicture} \\
%
Foreach Tos: &
\begin{tikzpicture}
\node[draw] (A) at (0,0) {};
\node[draw] (B) at (1,1) {};
\node[draw] (C) at (2,0) {};
\draw (A) \foreach \nd in {B,C} { to (\nd) };
\end{tikzpicture} \\
%
Turns: &
\begin{tikzpicture}
  \draw[red] (0,0) -- (1,.2) -- ([turn]90:1cm) -- ([turn]90:1cm) -- ([turn]90:1cm) -- ([turn]90:1cm);
\end{tikzpicture} \\
%
Foreach Turns: &
\begin{tikzpicture}
  \draw (0,0) -- (1,.2) foreach \i in {1,2,3,4} { -- ([turn]90:1cm) };
\end{tikzpicture} \\
%
Fixed Foreach Paths: &
\begin{tikzpicture}
\FixForeach
\node[draw] (A) at (0,0) {};
\node[draw] (B) at (1,1) {};
\node[draw] (C) at (2,0) {};
\draw (A) \foreach \nd in {B,C} { -- (\nd) };
\end{tikzpicture} \\
%
Fixed Foreach Tos: &
\begin{tikzpicture}
\FixForeach
\node[draw] (A) at (0,0) {};
\node[draw] (B) at (1,1) {};
\node[draw] (C) at (2,0) {};
\draw (A) \foreach \nd in {B,C} { to (\nd) };
\end{tikzpicture} \\
%
Fixed Foreach Turns: &
\begin{tikzpicture}
\FixForeach
  \draw (0,0) -- (1,.2) foreach \i in {1,2,3,4} { -- ([turn]90:1cm) };
\end{tikzpicture}


\end{tabular}

\end{document}

答え2

免責事項:これは、何らかの問題を解決するという意味での本当の答えではなく、なぜこの問題を質問よりも正確に説明します (コード分析なし)。

あなたの使用法では\foreach、次の線分の参照点は変更されないので、「(Y)への線のみ」を描画するのではなく、代わりに、、、の3つの線を描画し(X) -- (nA)ます(nA) -- (nB)(nB) -- (Y)は、ノードの衝突回避のためのポイントです(Tiの正しい用語がわかりません)。Z/ はpgf再計算されないので、 の左下端から(nA)の左下端まで(nB)半円を横切って描画することになります。

それでも、\foreach描画の内側には奇妙な副作用がある場合があります。開始点と終了点がcoordinates ではなくnodes でもある場合、次の行の開始の参照点はまったく移動されず、開始点が同じ 3 本の線が残ります。

全体的に、3 回目の使用は避けたほうがよいでしょう。結果がどうなるかは予測できないようです。ただし、これがバグであるかどうかはわかりません。

\documentclass{article}
\usepackage{tikz}

\begin{document}

\begin{tikzpicture}[mynode/.style={minimum size=4.3pt, inner sep=0pt, draw=red}]
  \path
    (0,0) node[mynode](X){}
    (1,0) node[mynode](nA){}
    (1,1) node[mynode](nB){}
    (1,2) node[mynode](Y){}
    ;
    % Case (3)
  \draw (X) \foreach \x in {nA,nB,Y} { -- (\x)};

  \path
    (2,0) coordinate(X)
    (3,0) node[mynode](nA){}
    (3,1) node[mynode](nB){}
    (3,2) coordinate(Y)
    ;
    % Case (3)
  \draw (X) \foreach \x in {nA,nB,Y} { -- (\x)};

  \path
    (4,0) coordinate(X)
    (5,0) coordinate(nA)
    (5,1) coordinate(nB)
    (5,2) coordinate(Y)
    ;
  \draw (X) \foreach \x in {nA,nB,Y} { -- (\x)};
\end{tikzpicture}

\end{document}

ここに画像の説明を入力してください


あなたにはこれに対する強引な解決策があるので、私はもっときれいな解決策(Tiを使わない)を提供できるかもしれません。Z 以外の手段もあります。以下では、座標ペアのリストを指定しなくても正しい結果が得られます。

\documentclass[border=3.14,tikz]{standalone}

\ExplSyntaxOn
\cs_new_protected:Npn \tocoordinatelist { \exp:w \__tocoordinatelist:w }
\NewExpandableDocumentCommand \__tocoordinatelist:w { O{--} m }
  { \exp_end: #1 ( \clist_use:nn {#2} { ) #1 ( }) }
\ExplSyntaxOff

\begin{document}

\begin{tikzpicture}[mynode/.style={minimum size=4.3pt, inner sep=0pt, draw=red}]
  \path
    (0,0) coordinate(X)
    (1,0) node[mynode](nA){}
    (1,1) node[mynode](nB){}
    (1,2) coordinate(Y)
    ;
  \draw (X) \tocoordinatelist{nA,nB,Y};
\end{tikzpicture}

\end{document}

ここに画像の説明を入力してください

関連情報