6

In the following MWE, I know that the commented line works. What I want to know is why the every node/.style approach is not working.

\documentclass{article}
\usepackage{tikz}

\begin{document}

\begin{tikzpicture}[x=2in,y=2in]

  \node (A) at (1,0)   {};
  \node (B) at (0,1)   {};
  \node (C) at (-1,0)  {};
  \node (D) at (1,1)   {}; 

%      \draw  (A.center) -- (B.center) -- (D.center) -- (C.center) -- cycle;
  \draw[every node/.style={anchor=center}] (A) -- (B) -- (D) -- (C) -- cycle;
\end{tikzpicture}

\end{document}

Is there some way I can do this without having to write (<node_name>.center) for each node I wish to connect with a line?

UPDATE

I can write something like the following:

  \def\concatenatedpoints{}
  \foreach \x in {A,B,C,D}
      {\xdef\concatenatedpoints{\concatenatedpoints (\x.center) -- } }
  \draw \concatenatedpoints     cycle;

which has the desired effect. And, of course, I could write a macro to accomplish this for whenever I want to be able to connect nodes this way.
But it seems that they's got to be a more tikz-flavored approach to efficiently connect the nodes as though they were coordinates.

FURTHER UPDATE

I've tried something very similar to a suggestion made by @percusse in the comments and @jldiaz in his answer. What I had tried was

\draw \foreach \x in {A,B,D,C}{(\x.center) -- } cycle; 

I'd be very curious for an explanation of why my approach fails but either of theirs works.

A.Ellett
  • 50,533
  • Two notes: 1. anchor=center places nodes with their center anchor at the specified point (at), nothing else. 2. Did you want to use coordinates here? – Qrrbrbirlbel Sep 08 '13 at 00:46
  • If you don't nead all the features of a \node, you could use \coordiante instead. – Gonzalo Medina Sep 08 '13 at 00:46
  • @Qrrbrbirlbel Regarding note 1, I was pretty sure I was wrong. Regarding note 2, in my real document I have nodes, which I'm using as nodes, but for the moment I want to refer to them as though they were coordinates. – A.Ellett Sep 08 '13 at 00:48
  • @GonzaloMedina See my comment I just wrote to Qrrbrbirlbel. – A.Ellett Sep 08 '13 at 00:49
  • @A.Ellett Without actually looking up the process of TikZ’s parsing here I would say this will be very complicated to hack in. Would you be willing to write (A.) -- (B.) -- (D.) -- (C.) instead (or simply a shorter (local declared) alias for center)? – Qrrbrbirlbel Sep 08 '13 at 00:52
  • @Qrrbrbirlbel Yes to either or both. – A.Ellett Sep 08 '13 at 00:54
  • You can use \draw (A.center) \foreach \x in {B,D,C}{-- (\x.center)}-- cycle; – percusse Sep 08 '13 at 01:05
  • @percusse I tried something similar, \draw \foreach \x in {A,B,C,D} { (\x.center)--} cycle; but it failed. Why does your approach work and mine didn't? – A.Ellett Sep 08 '13 at 01:11
  • @A.Ellett (\x.center)-- is not a complete path command, they should be hypothetically complete path construction syntaxes. It's a feature. I would have a look at tkz-euclide for some help if I need this for a lot of paths. It's great. – percusse Sep 08 '13 at 01:16

3 Answers3

7

For the \foreach part, a little clarification.

When \foreach is used within the path, it doesn't simply concatenates strings but parses each spin of the loop and concatenates the paths(!)

So what you have is incomplete path snippets that requires a final coordinate, in other words you tell TikZ

\draw (A.center) --
\draw (A.center) -- (B.center) --
\draw (A.center) -- (B.center) -- (C.center) --
\draw (A.center) -- (B.center) -- (C.center) -- (D.center) --
\draw (A.center) -- (B.center) -- (C.center) -- (D.center) -- cycle;

which is only correct in the last spin but it never reaches to that point due to the error. Instead if you use

\draw (A.center) \foreach \x in {B,D,C}{-- (\x.center)}-- cycle;

Then you have

\draw (A.center)
\draw (A.center) -- (B.center)
\draw (A.center) -- (B.center) -- (C.center)
\draw (A.center) -- (B.center) -- (C.center) -- (D.center)
\draw (A.center) -- (B.center) -- (C.center) -- (D.center) -- cycle;

and each spin is a valid statement. OK, mr. know it all how do you explain this?

\draw \foreach \x in {A,B,D,C}{-- (\x.center)}-- cycle;

No error and no path! Well not really. This is due to the missing internal \pgfpathmoveto{} such that the pen is put on the canvas. For example, a nice riddle is

\pgfpathmoveto{}
\draw  -- (A.center)-- (B.center)--(C.center)--(D.center); 

:) Where does that (a) to (d) line come from?

percusse
  • 157,807
  • So are you saying that when the \foreach loop is being called, I'm actually excuting 5 different \draw commands retracing what I've previously already drawn? – A.Ellett Sep 08 '13 at 01:59
  • @A.Ellett It is just parsed and stored. Drawing happens when you hit the ;. Hmm, on a second thought, I'm not super sure about that though. But I'm pretty sure if I understand the low-level thingy correctly. That's why \draw (0,0) -- (1,1) (1,0) -- (0,1); works – percusse Sep 08 '13 at 02:01
  • Ah ha! The line from (A) to (D) comes from the fact that \pgpathmoveto{} is including the last placed node. So, if you add a node such as \node (E) at (-2,-2) {E}; right after the declaration for node D, you'll get node A connected to node E. So, its really just an artifact of what the last named node was. Right? – A.Ellett Sep 08 '13 at 02:15
  • @A.Ellett Well yes and no eheh, a hint : add some text to the node (D) :) – percusse Sep 08 '13 at 02:16
  • Not sure what else to say about your riddle. It looks like the west anchor for node D. – A.Ellett Sep 08 '13 at 02:58
  • 1
    @A.Ellett You actually got it, the rest is gory details but it's actually the text anchor of (D) as the last known coordinate. – percusse Sep 08 '13 at 03:01
  • 1
    Thanks for the tid-bits. The riddle was both fun and interesting. – A.Ellett Sep 08 '13 at 03:07
  • @percusse I have the impression that the problem is not the path parsing, but that \foreach runs in a group and we can't have something like this \draw {(0,0)--} (1,1);. Am I wrong ? – Kpym Jan 18 '15 at 21:49
  • @Kpym Path declarations are global. You can have such things for example to change rounded corners temporarily etc. – percusse Jan 19 '15 at 00:09
  • @percusse sorry I don't understand your answer. We can have \draw (0,0){-- (1,1)}; but we can't have \draw {(0,0)--} (1,1); for local styling. And as we can see here we can use \foreach to construct a path that is not complete. – Kpym Jan 19 '15 at 05:58
  • @Kpym A TikZ scope is not exactly a TeX group. In your example your first one is a complete path because you have the implicit first point. Your second one is not complete. So you might choose to say this is because of the scoping behavior. I consider this as a frontend property that is not necessarily valid for PGF level syntax where grouping is not the problem at all. If it was like as you described, why would scoping matter for path to be complete or not? – percusse Jan 19 '15 at 07:37
  • @percusse I haven't checked this with \tikzextra but my guess is that it will work on PGF level. If the problem was as you say "foreach doesn't simply concatenates strings but parses each spin of the loop and concatenate the paths", why we can construct incomplete path using \foreach and xdef and use it after? I think that the incomplete path is not a problem for \foreach, except if it behaves differently inside and outside a path. – Kpym Jan 19 '15 at 07:57
  • @percusse checking with this path \draw (0:2) foreach \i in {1,...,4}{[rounded corners=\i^3 pt]--(90*\i:2)};, we can see that there is a grouping produced by foreach, and we can understand why {\x.center --} is not correct. For me this is a grouping problem, not a foreach parse problem. – Kpym Jan 19 '15 at 08:16
  • @percusse I think that the path produced in the OP is \draw {(A.center) --} {(B.center) --} {(C.center) --} {(D.center) --} cycle; or \draw {(A.center) -- {(B.center) -- {(C.center) -- {(D.center) --}}}} cycle; but not what you say (without grouping). – Kpym Jan 19 '15 at 08:25
  • @Kpym It is about the paths not the syntax. Read my first sentence. I'm not making any claims about the syntax. That snippet I wrote down is the resulting path that accumulates. You are also not answering why {(A) --} is an invalid syntax. Why would brace pair matter if it is merely a group? That's what I'm trying to draw your attention to. It's not just a mere TeX group. – percusse Jan 19 '15 at 08:35
6

A silly idea with an ugly syntax, but perhaps you do like it.

\documentclass{article}
\usepackage{tikz}

\def\drawPathThrough#1#2{
  \def\firstnode{#1}
  \draw \foreach \n in {#1,#2} {\ifx\n\firstnode\else -- \fi (\n.center) } --cycle;
}

\begin{document}
\begin{tikzpicture}[x=2in,y=2in]
  \node (A) at (1,0)   {A};
  \node (B) at (0,1)   {B};
  \node (C) at (-1,0)  {C};
  \node (D) at (1,1)   {D};
  \drawPathThrough{A}{B,D,C};
\end{tikzpicture}
\end{document}

Result

JLDiaz
  • 55,732
5

This answer provides three solutions:

  1. The first solution uses your exact syntax (i.e. node name without a specified anchor or the indicating .). Unfortunately, this needs a fix of an existing macro that is used to parse the implicit version of the node cs.

    This macro checks for an . in the specified coordinate: If one is found it is used to split up node name and referenced anchor. If no .<anchor> is given it automatically adds .center which it only does in case the tikz@shapeborder if is ignored. This part is the cause why nodes connect so intelligently when you do not specify an anchor.

    If we intercept this, we can make your syntax possible.

  2. Solution two simply allows to define a generic anchor locally.

    The macro \pgfdeclaregenericanchor{<name>}{<code>} creates a macro \pgf@anchor@generic@<name> that excepts one argument which we can refer to in <code> as ##1. This is used to simply refer to another anchor.

    If you leave out the name of that generic anchor, you can use (A.) to refer to short-cut.

  3. Seeing the other solutions to avoid the anchor problem I’d define a few styles that build a path via the insert path style and the .list handler to connect coordinates/nodes:

    • open polygon={<first coordinate>, <list of other coordinates>} and
    • closed polygon with the same syntax that includes an additional -- cycle.

    The use of insert path instead of a rather fixed macro makes it possible to use that path as part of another path (the possibilites aren’t that great for a closed polygon but still).

Solution 1

The patching builds a \tikz@parse@node macro defined as (comments from the original source):

\def\tikz@parse@node#1(#2){%
  \pgfutil@in@.{#2}% Ok, flag this
  \ifpgfutil@in@
    \tikz@calc@anchor#2\tikz@stop%
  \else%
    \pgfkeysgetvalue{/tikz/use anchor}\tikz@temp
    \ifx\tikz@temp\pgfutil@empty
      \tikz@calc@anchor#2.center\tikz@stop% to be on the save side, in
                                  % case iftikz@shapeborder is ignored...
      \expandafter\ifx\csname pgf@sh@ns@#2\endcsname\tikz@coordinate@text%
      \else
        \tikz@shapebordertrue%
        \def\tikz@shapeborder@name{#2}%
      \fi%
    \else
      \expandafter\tikz@calc@anchor#2\tikz@temp\tikz@stop
    \fi
  \fi%
  \edef\tikz@marshal{\noexpand#1{\noexpand\pgfqpoint{\the\pgf@x}{\the\pgf@y}}}%
  \tikz@marshal}

The \pgfkeysgetvalue and the \ifx as well as its \else part is new.

What happens here? First of all, our code is only used if none . is in #2 that means, if an anchor is specified it is used. But if no . is inside #2 it will check whether the value of /tikz/use anchor is empty, if it is, the usual behavior is applied: The .center anchor is used and the boolean tikz@shapeborder is switched on. This Boolean along with the name \tikz@shapeborder@name is later used in many path operators to actually find the start of a line. If you usually say

\draw (<node1>) -- (<some other coordinate>);

The line actually starts at the angular anchor in <some other coordinate>’s direction. Those path operators check \iftikz@shapeborder if a node has been specified without an explicit anchor and an additional move-to is included.

In our case we disable this intelligence (as long as /tikz/use anchor is not empty) and simply add the anchor anyway without setting tikz@shapeborder.

Code 1

\documentclass[tikz]{standalone}
\tikzset{use anchor/.initial=}
\usepackage{etoolbox}
\makeatletter
\patchcmd\tikz@parse@node{\else\tikz@calc@anchor}{%
  \else\pgfkeysgetvalue{/tikz/use anchor}\tikz@temp
  \ifx\tikz@temp\pgfutil@empty\tikz@calc@anchor}{}{}
\patchcmd\tikz@parse@node{\fi\fi}{\fi\else\tikz@calc@anchor#2.\tikz@temp\tikz@stop\fi}{}{}
\makeatother
\begin{document}
\begin{tikzpicture}[x=2in,y=2in, nodes={draw, help lines}]
  \node (A) at (1,0)   {A}; \node (B) at (0,1)   {B};
  \node (C) at (-1,0)  {C}; \node (D) at (1,1)   {D}; 
  \draw[use anchor=center]           (A) -- (B) -- (D) -- (C) -- cycle;
  \draw[use anchor=south west, blue] (A) -- (B) -- (D) -- (C) -- cycle;
\end{tikzpicture}
\end{document}

Solution 2

Not much is to be added to the initial notes of this solution.

This may be the most flexible solution as you actually can (globally) define a few short-cuts like

\tikzset{
  anchor shortcut/.list={
    {nw}{north west},
    {n}{north},
    {ne}{north east}}}

Code 2

\documentclass[tikz]{standalone}
\makeatletter
\tikzset{
  use anchor/.style={anchor shortcut={}{#1}},
  anchor shortcut/.code 2 args=\pgfdeclaregenericanchor{#1}{\pgf@sh@reanchor{##1}{#2}}}
\makeatother
\begin{document}
\begin{tikzpicture}[x=2in, y=2in, nodes={draw, help lines}]
  \node (A) at (1,0)   {A}; \node (B) at (0,1)   {B};
  \node (C) at (-1,0)  {C}; \node (D) at (1,1)   {D}; 
  \draw[use anchor=center]                    (A.)   -- (B.)   -- (D.)   -- (C.)   -- cycle;
  \draw[anchor shortcut={sw}{south west},blue](A.sw) -- (B.sw) -- (D.sw) -- (C.sw) -- cycle;
\end{tikzpicture}
\end{document}

Solution 3

I also can’t say much additional to this solution.

It may be noted that due to the fact how the argument to open polygon (and thus closed polygon) is parsed one cannot write

open polygon={A , …}

but only

open polygon={A, …}

Otherwise, spaces do no harm as all other coordinates are parsed by the \foreach parser.

Please note that the . has to be given in the argument of open polygon anchor. (This can be changed with another text and an \edef without a problem though.)


If you only draw tetragons, you could actually just define

\tikzset{
  tetragon/.style args={#1:#2,#3,#4,#5}{
    insert path={(#2.#1) -- (#3.#1) -- (#4.#1) -- (#5.#1) -- cycle}}
}

which you can use as

\draw[red, thick, tetragon={east:A,B,C,D}];

and be done with it.

Code 3

\documentclass[tikz]{standalone}
\makeatletter
\tikzset{
  open polygon anchor/.initial=,
  open polygon/.code args={#1,#2}{%
    \edef\tikz@temp{\pgfkeysvalueof{/tikz/open polygon anchor}}%
    \tikzset{@open polygon/.expanded={\tikz@temp}{#1}{#2}}},
  @open polygon/.style n args={3}{%
    insert path={(#2#1)},
    @@open polygon/.style={insert path={-- (##1#1)}},
    @@open polygon/.list={#3}},
  closed polygon/.style={open polygon={#1},insert path={-- cycle}}}
\makeatother
\begin{document}
\begin{tikzpicture}[x=2in,y=2in, nodes={draw, help lines}]
  \node (A) at (1,0)   {A};  \node (B) at (0,1)   {B};
  \node (C) at (-1,0)  {C};  \node (D) at (1,1)   {D}; 
  \draw[open polygon anchor=.center,     closed polygon={A,B,D,C}]      ;
  \draw[open polygon anchor=.south west, closed polygon={A,B,D,C}, blue];
\end{tikzpicture}
\end{document}

Outputs

enter image description here

Qrrbrbirlbel
  • 119,821
  • I like your code 2 approach. code 1 left me in a fog for a bit. But I'm left still a bit confused between your comment about how anchors work and the code you have here where you're declaring anchors. Could you explain the difference? – A.Ellett Sep 08 '13 at 01:24
  • @A.Ellett Does my update resolve some of the confusion? I also included the insert path solution I posted on the other answer as a comment. I usually try to avoid macros that use \path directly. – Qrrbrbirlbel Sep 08 '13 at 10:44