31

Say I have a triangle with vertices defined using

\coordinate (a) at (0,0);
\coordinate (b) at (1,0);
\coordinate (c) at (0,1);

Let us denote by T(x,y) the triangle defined by (a),(b) and (c) which is translated by a vector (x,y). I can now easily draw T(0,0) as follows:

\draw (a) -- (b) -- (c) -- cycle;

So far so good. Now, lets say that I want to plot the triangle T(2,3). Ideally, I would like to do something like:

\begin{scope}[shift={(2,3)}]
    \draw (a) -- (b) -- (c) -- cycle;
\end{scope}

However, as the coordinates were defined outside of the scope the transformation (translation in this case) won't effect them. See this answer. What would be the smart way to do it? Or more generally:

What is the best way to reuse coordinates that are defined once under varying transformations?

Possible solution: Basing on the comment of @Qrrbrbirlbel I posted an answer. This answer however, is very limited and local - is the something more general?

1
  • 2
    Depending on your use-case, shifting every coordinate (\draw ([shift={(2,3)}] a) -- ([shift={(2,3)}] b) -- ([shift={(2,3)}] c) -- cycle;) or using a canvas transformation can be the solution. See the top part of another answer. Commented Feb 19, 2013 at 13:18

8 Answers 8

16

In TikZ/pgf, transformations are applied only to numeric coordinates. To retrieve original numeric coordinates from nodes or coordinates, you may use a let operation:

enter image description here

\documentclass[tikz]{standalone}
\usetikzlibrary{calc,scopes}
\begin{document}
\begin{tikzpicture}
  \coordinate (a) at (0,0);
  \coordinate (b) at (1,0);
  \coordinate (c) at (0,1);
  \draw (a) -- (b) -- (c) -- cycle;

  \draw[blue] let \p{a}=(a),\p{b}=(b),\p{c}=(c) in
  {[rotate=30] (\p{a}) -- (\p{b}) -- (\p{c}) -- cycle}
  {[shift={(2,3)}] (\p{a}) -- (\p{b}) -- (\p{c}) -- cycle};
\end{tikzpicture}
\end{document}
16

As the other questions linked have explained the issue, I'll not go over old ground. Looking at the code there does not seem to be an easy way to say "Apply the following style (say, shift={(2,3)}) to all of the following points.". There is no every point style (contrast the many every path or every node or ...). Moreover, adding one in would not be simple as the coordinate parser needs to see the [ to invoke the bit that does the transformation (the code here has the illuminating comment uhoh... options! at this point).

So there's not a really, really simple solution. But there is a way to make the proper way to do this a little simpler using styles.

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

\tikzset{
  c/.style={every coordinate/.try}
}

\begin{document}
\begin{tikzpicture}
\coordinate (A) at (0,0);
\coordinate (B) at (1,0);
\coordinate (C) at (0,1);
\draw (A) -- (B) -- (C) -- cycle;
\begin{scope}[every coordinate/.style={shift={(2,3)}}]
    \draw ([c]A) -- ([c]B) -- ([c]C) -- cycle;
\end{scope}
\end{tikzpicture}
\end{document}

shifted triangle

Obviously, the style name c can be replaced by something else to avoid confusion with other things, such as coordinate names. The rationale for choosing a single letter was to make it relatively easy to type each time, and the letter c is short for "coordinate".

1
  • 2
    Nice! However I realise that cs may arise some confusing. Should we change ec/.style={every coordinate/.try} and then \draw ([ec]a) -- ([ec]b) -- ([ec]c) -- cycle; ?
    – Black Mild
    Commented Feb 22, 2021 at 17:28
15

It isn't straightforward at all to add a general every coordinate style. But, a \iftikztransformnodecoordinates is fairly simply to hack.

\documentclass{standalone}
\usepackage{tikz}

\makeatletter
\newif\iftikztransformnodecoordinates
\tikzset{transform node coordinates/.is if=tikztransformnodecoordinates}


\def\tikz@parse@node#1(#2){%
    \pgfutil@in@.{#2}%
    \ifpgfutil@in@
        \tikz@calc@anchor#2\tikz@stop%
    \else%
        \tikz@calc@anchor#2.center\tikz@stop%
        \expandafter\ifx\csname pgf@sh@ns@#2\endcsname\tikz@coordinate@text%
        \else
            \tikz@shapebordertrue%
            \def\tikz@shapeborder@name{#2}%
        \fi%
    \fi%
    \iftikztransformnodecoordinates%
        \pgf@pos@transform{\pgf@x}{\pgf@y}%
    \fi
    \edef\tikz@marshal{\noexpand#1{\noexpand\pgfqpoint{\the\pgf@x}{\the\pgf@y}}}%
    \tikz@marshal%
}

\begin{document}

\begin{tikzpicture}

\coordinate (a) at (0,0);
\coordinate (b) at (1,0);
\coordinate (c) at (0,1);

\draw (a) -- (b) -- (c) -- cycle;

\draw [red, transform node coordinates, rotate=10] 
    (a) -- (b) -- (c) -- cycle;

\draw [green, transform node coordinates, shift={(1,1)}] 
    (a) -- (b) -- (c) -- cycle;

\draw [blue, transform node coordinates, rotate=10, shift={(1,1)}] 
    (a) -- (b) -- (c) -- cycle;

\end{tikzpicture}

\end{document}

node_coordinate_transforms

Not sure if it is 100% robust though.

10

Not as pgf-hacky as the other answers, but how about the following. Very straight forward, simple and easy to understand workaround.

\documentclass{article}
\usepackage{tikz}

\begin{document}
\begin{tikzpicture}
  \newcommand*{\defcoords}{%
    \coordinate (a) at (0,0);
    \coordinate (b) at (1,0);
    \coordinate (c) at (0,1);
  }
  \begin{scope}[shift={(0,0)}]
      \defcoords
      \draw (a) -- (b) -- (c) -- cycle;
  \end{scope}
  \begin{scope}[shift={(2,3)}]
      \defcoords
      \draw (a) -- (b) -- (c) -- cycle;
  \end{scope}
\end{tikzpicture}
\end{document}
8

What about defining a generic anchor (r for relative) which applies the transformation? EDIT: Now a version that really works!

Two problems:

  • the code that calls the generic anchor expects the coordinates in the node's cs.
  • that same code applies the inverse of the outer transformation --- this is what makes the named coordinates absolute!

Out job is to do the inverses of all these operations (in the reverse order).

\documentclass{article}
\usepackage{tikz}

\makeatletter
\pgfdeclaregenericanchor{r}{%
  % 1. get (x,y) in the node's cs
  \pgf@sh@reanchor{\csname pgf@sh@ns@\pgfreferencednodename\endcsname}{center}%
  {% 2. apply the node transformation
    \pgfsettransform{\csname pgf@sh@nt@\pgfreferencednodename\endcsname}%
    \pgf@pos@transform{\pgf@x}{\pgf@y}%
    \global\pgf@x=\pgf@x%
    \global\pgf@y=\pgf@y%
  }%
  % 3. *don't* apply the inverse of the outer transformation! We want
  %    relative coordinates!
  {% 4. apply the inverse of the inverse of the outer transform
   %    (cancels the inverse of the outer transformation done by \pgfpointanchor)
    \pgf@pos@transform{\pgf@x}{\pgf@y}%
    \global\pgf@x=\pgf@x%
    \global\pgf@y=\pgf@y%
  }%
  {% 5. apply the inverse of the nodes transformation (cancels the node
   %    transformation done by \pgfpointanchor)
    \pgfsettransform{\csname pgf@sh@nt@\pgfreferencednodename\endcsname}%
    \pgftransforminvert
    \pgf@pos@transform{\pgf@x}{\pgf@y}%
    \global\pgf@x=\pgf@x%
    \global\pgf@y=\pgf@y%
  }%
}
\makeatother

\begin{document}

\begin{tikzpicture}
  \coordinate (a) at (0,0);
  \coordinate (b) at (1,0);
  \coordinate (c) at (0,1);
  \draw[blue,ultra thick] (a) -- (b) -- (c) -- cycle;
  \begin{scope}[shift={(2,0)},scale=2]
    \draw[red] (a.r) -- (b.r) -- (c.r) -- cycle;
  \end{scope}
\end{tikzpicture}

\end{document}

scaled and translated triangle

2

Here is a solution that is mixture of a macro seen in another answer and the pic command from TiKz 3.0.

% macro taken from https://tex.stackexchange.com/a/33765
% -----------------------------------------------------
\makeatletter
\newcommand{\gettikzxy}[3]{%
  \tikz@scan@one@point\pgfutil@firstofone#1\relax
  \edef#2{\the\pgf@x}%
  \edef#3{\the\pgf@y}%
}
\makeatother
% -----------------------------------------------------

\begin{tikzpicture}

  \coordinate (a) at (0,0);
  \coordinate (b) at (1,0);
  \coordinate (c) at (0,1);

  \gettikzxy{(a)}{\ax}{\ay}
  \gettikzxy{(b)}{\bx}{\by}
  \gettikzxy{(c)}{\cx}{\cy}
  \tikzset{
    triangle/.pic={
        \path[pic actions] (\ax,\ay) -- (\bx,\by) -- (\cx,\cy) -- cycle;
    }
  }

   \path (1,1) pic[draw]{triangle};
   \path (0,0) pic[fill=red]{triangle};
\end{tikzpicture}

the result

EDIT : Here is another solution that produce the same image using 'let/shift' trick (adapted from Paul Gaborit's answer).

\begin{tikzpicture}

  \coordinate (a) at (0,0);
  \coordinate (b) at (1,0);
  \coordinate (c) at (0,1);

\coordinate (origin) at (0,0);
\tikzset{
  triangle/.pic={
    \path let \p{a}=(a), \p{b}=(b), \p{c}=(c) in 
       [pic actions, shift={([scale=-1]origin)}]
        (\p{a}) -- (\p{b}) -- (\p{c}) -- cycle;
  }
}

  \path (1,1) pic[draw]{triangle};
  \path (0,0) pic[fill=red]{triangle};
\end{tikzpicture}

NOTE: But with this second method rotate and scale don't work. For example

\path (0,0) pic[fill=red, rotate=30]{triangle};

do not rotate the triangle (with the first method it is ok). Like if rotate=30 is not passed to pic actions but directly transforming the coordinates (as directly passed to path command).

1

Based on the comment of @Qrrbrbirlbel: One can define the coordinates globally as in the OP. Then, the following function can be defined:

\newcommand*{\drawT}[1]{%
   \draw ([shift={#1}] a) -- ([shift={#1}] b) -- ([shift={#1}] c) -- cycle;%
}

Now, calling \drawT{(2,3)} will draw T(2,3) as per the OP definitions.

1
  • I accepted my own answer because this is the one I actually used once I posted it and it solved my problem. I did not tested the other solutions.
    – Dror
    Commented Feb 24, 2013 at 16:09
0

Sometimes, it can be done easier.

% Draw two identical heptagons, reusing coordinates.
\begin{tikzpicture}
\coordinate (v1) at (1.5,0);
\coordinate (v2) at (1,1);
\coordinate (v3) at (0.3,1.3);
\coordinate (v4) at (-1,0.5);
\coordinate (v5) at (-1,-0.8);
\coordinate (v6) at (0,-1);
\coordinate (v7) at (1.1,-0.7);
\foreach \s in {-6,0} {
  \draw[fill=lightgray]
  (\s,0) + (v1) -- + (v2) -- + (v3) -- + (v4) -- + (v5) -- + (v6) -- + (v7) -- cycle;
  \foreach \v in {v1,v2,v3,v4,v5,v6,v7} {
    \draw[fill=black] (\s,0) + (\v) circle (0.5mm);
  }
}
\end{tikzpicture}
1
  • 1
    The question is about reusing the coordinates with transformations.
    – percusse
    Commented Jun 16, 2016 at 17:56

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .