tikz \foreach (小姐?)行為

tikz \foreach (小姐?)行為

在下面的程式碼中,我預期這 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 junctions 套件的範圍內。在這種情況下,我確實需要使用 foreach 來處理許多路徑交叉點,然後第三種情況將是最自然的構造。

編輯2:來自評論中的論點和(目前)唯一的答案。我不需要其他方法來實現這一目標。我已經解決了我的現實案例(透過部署基於案例(2)的策略)。我的唯一問題是:在第三種情況下 \foreach 發生了什麼事?為什麼它會這樣?這是唯一的問題,沒有其他不同的方法來避免這個陷阱。

編輯3:非常感謝Andrew Stacey 的回答超出了我原來的問題。我的意思是,我唯一的問題是,所發生的事情是否是預期的?現在很明顯,考慮到 foreach 的實作方式以及與路徑子系統的互動方式,這是一個錯誤。 Foreach 的作用不僅僅是擴展大括號中的程式碼,取代一些變數(foreach 變數)。

答案1

正如 Skillmon 在他們對答案的評論中所說,這是一個分組問題。正如本網站上的各種問題所記錄和探索的那樣,\foreach將其程式碼放在一組中(如果我沒記錯的話,那麼實際上它是兩個群組),因此從一個循環到下一個循環需要記住的任何內容都需要做一些工作將其從該分組中取出。在用戶級別,這通常是透過使用金鑰來完成的remember,Qrrbrbirlbel 在他們對您的問題的評論之一中提到了這一點。

然而,在一條路徑中,記住前一個座標是一件非常有用且常見的事情,因此 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向路徑添加 a 來在路徑中插入中斷。但是,在遇到它的點上,其目標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}

上述程式碼的輸出顯示了連接三個節點的圖的三個版本。首先,按照正確的順序手動連接三個節點。在第二個中,原始的 foreach 程式碼將第二個和第三個連接到第一個。在第三個中,修補後的 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) 的線”,而是繪製三條線(X) -- (nA)(nA) -- (nB)(nB) -- (Y)。問題是節點避免碰撞的點(不知道 Ti 中的正確術語是什麼)kZ/pgf是並且懶得查找它)不會重新計算,因此您從 的左下端繪製(nA)到 的左下端(nB),穿過半圓。

儘管如此,\foreach內部抽籤仍然會產生奇怪的副作用。如果您的起點和終點不是coordinates,而是nodes,則下一行開頭的參考點根本不會移動,最終會得到具有相同起點的三行。

總的來說,我會完全避免你的第三次使用,結果似乎很難預測。我不知道這是否是一個錯誤!

\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}

在此輸入影像描述


既然你有一個強力解決方案,也許我仍然可以提供一個更漂亮的解決方案(不使用 TikZ,但其他方式)。以下給出了正確的結果,而無需指定座標對列表。

\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}

在此輸入影像描述

相關內容