Im folgenden Code habe ich erwartet, dass die 3 Fälle (auskommentiert) völlig gleichwertig sind. Leider (für mich) erzeugt der 3. Fall eine gerade Linie von Koordinate (X) zu Koordinate (Y). Ich dachte immer, die \foreach
Anweisung würde den Code einfach „so wie er ist“ zwischen Klammern in den Eingabestrom einfügen, aber das scheint nicht der Fall zu sein.
Was übersehe ich? Oder ist das ein (bekannter) Fehler?
\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}
Bearbeiten: Die Verwendung von Knoten ist beabsichtigt, um mit der Bogengröße umzugehen. Außerdem liegt mein tatsächlicher Anwendungsfall im Rahmen des Tikz-Schnittpunktpakets. In diesem Fall muss ich ein Foreach verwenden, um mit den vielen Pfadschnittpunkten umzugehen, und dann wäre der 3. Fall die natürlichste Konstruktion.
Bearbeitung 2: Aus dem Argument in den Kommentaren und der (vorerst) einzigen Antwort. Ich brauche keine alternativen Wege, um dies zu erreichen. Ich habe meinen realen Fall bereits gelöst (durch den Einsatz einer Strategie basierend auf Fall (2)). Meine einzige Frage ist: Was passiert mit \foreach im 3. Fall? Warum verhält es sich so? Das ist die einzige Frage, nicht nach anderen, alternativen Wegen, um diese Falle zu vermeiden.
Bearbeitung 3: Vielen Dank für Andrew Staceys Antwort, die über meine ursprüngliche Frage hinausgeht. Ich meine, meine einzige Frage war, ob das, was passierte, auch zu erwarten war. Jetzt ist es klar, dass es sich um einen Fehler handelt, wenn man bedenkt, wie foreach implementiert ist und mit dem Pfadsubsystem interagiert. Foreach tut mehr, als nur den Code in Klammern zu erweitern und einige Variablen (die von foreach) zu ersetzen.
Antwort1
Wie Skillmon in seinem Kommentar zu seiner Antwort sagt, handelt es sich hier um ein Gruppierungsproblem. Wie in verschiedenen Fragen auf dieser Site dokumentiert und untersucht, \foreach
wird der Code in eine Gruppe eingeordnet (wenn ich mich recht erinnere, sind es eigentlich zwei Gruppen), sodass alles, was von einer Schleife zur nächsten gespeichert werden muss, ein wenig Arbeit erfordert, um es aus dieser Gruppierung herauszubekommen. Auf Benutzerebene wird dies normalerweise durch die Verwendung des remember
Schlüssels erreicht, den Qrrbrbirlbel in einem seiner Kommentare zu Ihrer Frage erwähnt.
Innerhalb eines Pfads ist es jedoch so nützlich und üblich, sich die vorherige Koordinate zu merken, dass TikZ große Anstrengungen unternimmt (im wahrsten Sinne des Wortes), um sicherzustellen, dass sie richtig gespeichert wird. Daher installiert TikZ am Anfang und Ende jeder Iteration der Schleife Code, um sich die letzten Punkte zu merken, als ob die Gruppierungen nicht vorhanden wären. Der Code, der dies tut, ist relativ lang:
\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}%
Als Referenz: In der aktuellen Version von TikZ beginnt dies in Zeile 2582 von tikz.code.tex
.
Wenn TikZ nun auf einen Knoten ohne Anker auf einem Pfad stößt, fügt es eine Unterbrechung in den Pfad ein, indem es moveto
dem Pfad ein hinzufügt. Das Ziel davon moveto
ist jedoch zum Zeitpunkt der Begegnung nicht bekannt, da es den Ausstiegspunkt an der Grenze des Knotens auswählt, der für die nächste Koordinate am besten geeignet ist. Es muss also aufzeichnen, dass das moveto
nicht abgeschlossen wurde, und dass TikZ, wenn es die nächste Koordinate kennt, diese abschließen wird, moveto
bevor der Rest des nächsten Teils des Pfads berechnet wird. Um zu kennzeichnen, dass dies geschehen muss, definiert TikZ ein Makro \tikz@moveto@waiting
und dieses Makro enthält den Knotennamen, den TikZ zum Abschließen der Berechnung benötigt.
Wenn Sie sich den Code, den ich oben gepostet habe, genau ansehen \tikz@foreach
, werden Sie einen deutlichen Mangel an Verweisen auf bemerken, \tikz@moveto@waiting
was bedeutet, dass diesnichtwird am Ende jeder Iteration aktualisiert. Das bedeutet, dass jedes Mal, wenn TikZ eine Iteration startet, \tikz@moveto@waiting
der ursprüngliche Wert von außerhalb der Schleife beibehalten wird. In Ihrem Fall ist das der erste Knoten X
und deshalb beginnen alle Ihre Zeilen hier X
und nicht beim vorherigen Knoten.
Glücklicherweise ist das Hinzufügen der erforderlichen Zeilen nicht schwierig. Hier ist eine gepatchte Version von \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}%
Testbarer Code:
\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}
Update: Qrrbrbirlbel bemerkte (in einem inzwischen gelöschten Kommentar), dass es immer noch ein Problem mit der Verwendung gab to
, und das GitHub-Problem war verknüpft mitKoordinatenoption [Turn] in foreachwas das gleiche Problem mit aufwirft turn
. Hier ist ein Code, der angeblich alle diese Probleme behebt und es möglicherweise etwas einfacher macht, weitere Dinge hinzuzufügen, die gespeichert werden müssen.
\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}
Antwort2
Haftungsausschluss:Dies ist keine wirkliche Antwort in dem Sinne, dass sie irgendein Problem löst, es veranschaulicht nur dieWarumdieses Problems genauer als die Frage (ohne Codeanalyse).
In Ihrer Verwendung \foreach
wird der Referenzpunkt für das nächste Liniensegment nicht geändert, Sie zeichnen also nicht „nur die Linie zu (Y)“, sondern drei Linien, (X) -- (nA)
, (nA) -- (nB)
, und (nB) -- (Y)
. Das Problem ist, dass die Punkte zur Kollisionsvermeidung von Knoten (keine Ahnung, was der korrekte Begriff in TikZ/ pgf
ist und zu faul, es nachzuschlagen) werden nicht neu berechnet, Sie zeichnen also vom unteren linken Ende von (nA)
zum unteren linken Ende von (nB)
und kreuzen dabei Ihren Halbkreis.
Dennoch \foreach
kann das Zeichnen innerhalb der Linie seltsame Nebeneffekte haben. Wenn Ihre Start- und Endpunkte nicht coordinate
s, sondern auch node
s sind, wird der Referenzpunkt für den Beginn der nächsten Linie überhaupt nicht verschoben und Sie erhalten drei Linien mit identischem Startpunkt.
Insgesamt würde ich Ihre dritte Verwendung ganz vermeiden, das Ergebnis scheint ziemlich unvorhersehbar. Ich habe allerdings keine Ahnung, ob das ein Fehler ist!
\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}
Da Sie eine Brute-Force-Lösung dafür haben, kann ich vielleicht noch eine schönere Lösung anbieten (die Ti nicht verwendetkZ, aber andere Mittel). Das Folgende liefert korrekte Ergebnisse, ohne dass Sie eine Liste von Koordinatenpaaren angeben müssen.
\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}