67

I need a macro to extract the x and y coordinates from an arbitrary point, like (3,4), or like (A), or like ([xshift=-2pt] A.north west), where A is the name of a node.

I've seen the solution

 \newdimen\mydim
 \newcommand\getx[1]{
      \pgfextractx\mydim{\pgfpointanchor{#1}{center}}
 }

elsewhere on StackExchange, but this obviously won't work for all the cases described above. I need to be able to call \getx{(3,4)} and \getx{(A)} and \getx{([xshift=-2pt] A.north west)}, and for them all to work equally well, in this case putting the x-coordinate of the argument into the variable \mydim.

I'm kind of stunned how hard this seems to be! Surely I've missed something...

3
  • 6
    For a lot of these cases the let operation looks like a good fit. You can just go let \p1 (3,4) in (\x1,0) -- (\x1,\y1) then \x1 and \y1 will hold the 3 and 4 respectively. You can even do multiple points at once. All of this is within a \path the TikZ manual discusses it on page 150. Nov 5, 2011 at 0:35
  • Roelof's answer is a good one for my case. FYI, the "let" command is discussed in section 14.15 of the TikZ manual for version 3.0.0, starting on page 161.
    – Derek
    Sep 9, 2014 at 0:40
  • Related Question: Compute the elevation of a coordinate in TikZ. Dec 4, 2014 at 21:56

5 Answers 5

44

You can use \pgfgetlastxy{\XCoord}{\YCoord} to extract the x,y coordinate of the most recently used point into the dimension registers \XCoord and {\YCoord}.

To make the point the most recently used, I use \path macro just before extraction. Here is an example where I define points, extract the x and y coordinates, and then label them via the extracted coordinates. The point C is placed at the x-coordainte of A and the y-coordiante of B.

enter image description here

Notes:

  • This has been updated to work properly when a scale= factor is applied to the tikzpicture. To see what the output is without tweaking for a scale factor uncomment the line

    %\let\ExtractCoordinate\ExtractCoordinateOld
    

Code:

\documentclass[border=2pt]{standalone}

\usepackage{tikz}
\usetikzlibrary{calc}

%% https://tex.stackexchange.com/questions/86897/recover-scaling-factor-in-tikz
\newcommand*\getscale[1]{%
  \begingroup
    \pgfgettransformentries{\scaleA}{\scaleB}{\scaleC}{\scaleD}{\whatevs}{\whatevs}%
    \pgfmathsetmacro{#1}{sqrt(abs(\scaleA*\scaleD-\scaleB*\scaleC))}%
    \expandafter
  \endgroup
  \expandafter\def\expandafter#1\expandafter{#1}%
}

\makeatletter
\newdimen\@XCoord
\newdimen\@YCoord
\newdimen\XCoord
\newdimen\YCoord
\newcommand*{\ExtractCoordinate}[1]{%
    \getscale{\@scalefactor}
    \path [transform canvas] (#1); \pgfgetlastxy{\@XCoord}{\@YCoord};
    \pgfmathsetlength{\XCoord}{\@XCoord/\@scalefactor}
    \pgfmathsetlength{\YCoord}{\@YCoord/\@scalefactor}
}
\newcommand*{\ExtractCoordinateOld}[1]{%
    \path [transform canvas] (#1); \pgfgetlastxy{\XCoord}{\YCoord};%
}%
%\let\ExtractCoordinate\ExtractCoordinateOld
\makeatother

\newcommand*{\LabelCurrentCoordinate}[2]{%
    \fill [#1] ($(\XCoord,\YCoord)$) circle (2pt) node [right] {#2}
}


\newdimen\XCoordA
\newdimen\YCoordA
\newdimen\XCoordB
\newdimen\YCoordB

\begin{document}
\begin{tikzpicture}[scale=1.5]
    \coordinate (A) at (3,2);
    \coordinate (B) at ([xshift=-2cm,yshift=-1cm] A.north west);

    \ExtractCoordinate{$(A)$};
    \LabelCurrentCoordinate{red}{A};
    \setlength\XCoordA{\XCoord}
    \setlength\YCoordA{\YCoord}

    \ExtractCoordinate{$(B)$};
    \LabelCurrentCoordinate{blue}{B};
    \setlength\XCoordB{\XCoord}
    \setlength\YCoordB{\YCoord}

    \ExtractCoordinate{$(\XCoordA,\YCoordB)$};
    \LabelCurrentCoordinate{magenta}{C};
\end{tikzpicture}
\end{document}
5
  • If I use different new dimensions for different points, both still end up the same size. Is it possible to save the value somehow?
    – cfr
    Oct 23, 2015 at 16:10
  • @cfr: Not sure I understand. Can you post a new question with a MWE that reproduces the problem? Oct 24, 2015 at 6:53
  • 1
    Nice! How to use it inside a \foreach?
    – Sigur
    Sep 1, 2016 at 22:48
  • @Sigur: Sorry, not sure how I missed this comment. Can you elaborate as I am not understanding what the issue is in using this within a \foreach. As I just edited this question, now would be a good time to address that issue as well. Apr 27 at 21:09
  • @PeterGrill, hi, Thanks. Ow, don't worry, forget my comment. Now I have a better knowledge and I'm able to use your tool within a loop. Regards.
    – Sigur
    Apr 28 at 11:04
41

The answers from Peter, Jamie, and wh1t3 (the last one in the comments) are all fine. I'm adding this as a "low level" version since this is often something that one wants to do as a part of a bigger thing, and then it can be useful to know how to do it at this lower level.

The TikZ command that scans a coordinate and figures out where it actually corresponds to is called \tikz@scan@one@point. It sort-of takes two arguments. The first is a macro, the second is the point to be scanned. The "sort-of" is because the second argument isn't an argument, it's just the bare coordinate. The coordinate is parsed until TikZ feels that it understands it enough to produce an honest x-y coordinate on the page, whereupon is called the macro with that x-y coordinate as its argument (specifically, it calls \themacro{\pgfpoint{x-coord}{y-coord}}. By specifying the macro to be \@firstofone (or \pgfutil@firstofone if we want to be good PGFers) we can set \pgf@x and \pgf@y to be those coordinates. If we want to save them, we could either pass a fancy saving macro or simply save the values of \pgf@x and \pgf@y afterwards.

Here's a simple example where we define a macro that takes three arguments, being the coordinate and two macros in which to save the x and y coordinates.

\documentclass{article}
%\url{http://tex.stackexchange.com/q/33703/86}
\usepackage{tikz}
\makeatletter
\newcommand{\gettikzxy}[3]{%
  \tikz@scan@one@point\pgfutil@firstofone#1\relax
  \edef#2{\the\pgf@x}%
  \edef#3{\the\pgf@y}%
}
\makeatother
\begin{document}
\begin{tikzpicture}
\draw (0,0) .. controls +(1,0) and +(-1,0) .. node[auto] (A) {A} (3,3);
\gettikzxy{(A)}{\ax}{\ay}
\fill[red,fill opacity=.5] (\ax,\ay) circle[radius=12pt];
\begin{scope}[rotate=45,xshift=3cm]
\draw (0,0) -- node[auto] (B) {B} (3,2);
\end{scope}
\gettikzxy{(B)}{\bx}{\by}
\fill[blue,fill opacity=.5] (\bx,\by) circle[radius=12pt];
\gettikzxy{([xshift=-2cm] A.north west)}{\cx}{\cy}
\fill[green] (\cx,\cy) circle[radius=2pt];
\fill ([xshift=-2cm] A.north west) circle[radius=1pt];
\end{tikzpicture}
\end{document}

(The last one is taken from your question but I changed the shift to 2cm so that it would be more obvious.)

The result is:

get TikZ coordinates

3
  • 1
    I was very keen on using this, but unfortunately it doesn't seem to work with pgfplots: I add this: \begin{axis} ; \addplot[name path global=cfunc,blue,domain=0:3.1] {sin(deg(x))} ; \node[auto, name path global=D] (D) at (2,2) {D} ; \gettikzxy{(D)}{\ax}{\ay} ; \end{axis} right before the \end{tikzpicture} in this snippet, and the failure is: "Package pgf Error: No shape named D is known." (neither are the other nodes from an axis environment known). Any possibility for that kind of use?
    – sdaau
    Feb 16, 2014 at 23:35
  • 1
    @sdaau My guess is that the axis environment saves up its drawing commands until the end, so the D node is not positioned until the axis environment is finished. Within the axis environment then \gettikzxy is executed straight away and so before D is positioned. If you shift the \gettikzxy after the \end{axis} then it seems to work. Can you use it like that? Feb 17, 2014 at 10:18
  • Thanks for the answer @LoopSpace - it makes sense; I'd guess it will work that way (unfortunately, I lost the original example leading to the comment). Cheers!
    – sdaau
    Jun 26, 2014 at 5:27
8

This feels very wrong, but I'll do it anyway...

\newdimen{\tempx}
\newdimen{\tempy}
\newcommand\getxy[1]{
    \coordinate (tmp) at #1;
    \pgfextractx\tempx{\pgfpointanchor{tmp}{center}}
    \pgfextracty\tempy{\pgfpointanchor{tmp}{center}}
}
1
  • 13
    Suggestion: Compile this code snippet in a working MWE and it will be more valuable. That way interested users can see exactly how to utilize your "wrong doings". :)
    – Werner
    Nov 5, 2011 at 1:25
5

For the sake of convenience, I've written these small \def and macro that should help to pretty print point coordinates.

\documentclass{minimal}
\pdfminorversion 7
\pdfobjcompresslevel 3
\usepackage{pgf}
\usepackage{tikz}
\usetikzlibrary{hobby,calc,intersections}
\begin{document}
  \begin{tikzpicture}[scale=0.7]
    \pgfkeys{/pgf/number format/.cd,fixed,precision=1,dec sep={,}}
    \def\pttocm#1{\pgfmathparse{#1 / 19.93333
    }\pgfmathprintnumber{\pgfmathresult}}

    \newcommand{\printcoords}[1]{
      \newdimen\posx
      \pgfextractx{\posx}{\pgfpointanchor{#1}{center}}
      \newdimen\posy
      \pgfextracty{\posy}{\pgfpointanchor{#1}{center}}
      (\pttocm\posx ; \pttocm\posy )
    }

    \draw [thick,->] (-4.3,0) -- (4.3,0) node[below] {$x$} ;
    \draw [thick,->] (0,-2.3) -- (0,7.2) node[left] {$y$};
    \draw [dotted] (-4.3,-2.3) grid (4.3,7.2) ;
    \draw [name path=h,thick] plot [smooth] coordinates {(-4,-2)(-3,2)(-2,6)(0,2)
    (2,-2)(3,4)} ;
    \draw (0,0) node [below left] {0} ;
    \draw (1,0) node [below] {1} ;
    \draw (0,1) node [left] {1} ;
    \foreach \i in {-4,-3,-2,-1,1,2,3,4}
    { \draw[very thick] (\i,0) -- (\i,-0.15) ; }
    \foreach \i in {-2,-1,1,2,3,4,5}
    { \draw[very thick] (0,\i) -- (-0.15,\i) ; }
    \foreach \i/\j in { -4/-2, 3/4 }
    { \draw (\i,\j) node[fill,circle, inner sep=1pt] {} ; }
    \draw (-3,-1) node[xshift=-2mm,yshift=1mm] {${C}_h$} ;

    \draw [name path=g,thick,red] plot[domain=-4.3:4.3]
    (\x,{ -(\x-2)*2/3 }) ;
    \draw [dashed,very thick,red] (2,0) -- (2,-2) -- (0,-2) ;
    \draw [name path=eq,red] (-4.3,2) -- (4.3,2) ;
    \path [name intersections ={of = h and eq }] ;
    \coordinate (A) at (intersection-1) ;
    \draw[red,thick] (A) node[draw,circle] {} ;
    \draw[red] (A) node[below left] {\printcoords{A}} ;
    \coordinate (B) at (intersection-2) ;
    \draw[red,thick] (B) node[draw,circle] {} ;
    \draw[red] (B) node[above right] {\printcoords{B}} ;
    \coordinate (C) at (intersection-3) ;
    \draw[red,thick] (C) node[draw,circle] {} ;
    \draw[red] (C) node[above right] {\printcoords{C}} ;
    \path [name intersections ={of = h and g }] ;
    \coordinate (D) at (intersection-1) ;
    \draw[red,thick] (D) node[draw,circle] {} ;
    \draw[red] (D) node[above right] {\printcoords{D}} ;
    \coordinate (E) at (intersection-2) ;
    \draw[red,thick] (E) node[draw,circle] {} ;
    \draw[red] (E) node [above right] {\printcoords{E}} ;
    \coordinate (F) at (intersection-3) ;
    \draw[red,thick] (F) node[draw,circle] {} ;
    \draw[red] (F) node[above right] {\printcoords{F}} ;
   \end{tikzpicture}
\end{document}

It even handles multiple intersection points, thanks to the intersection library.

1
  • 2
    Welcome to TeX.SX! While this might answer the question, it is always better to post a full example such that users can test immediately
    – user31729
    Dec 7, 2014 at 11:30
4

I had a matrix of points, named say e1, ..., e6 on the y-coordinate and S1, ..., S5 along the x-coordinate. My goal was to draw a small square at the coordinates (ei -| Sj) only for some given points (like an adjacency matrix).

I was trying to use the previous solutions for extracting x and y component of a point from a list, like

\foreach \pt in {(1 ,2), (1, 3), (2, 2), (2, 4)} {
    \parsept{\x}{\y}{\pt}
    \fill (S\x |- e\y) +(-1, -1) rectangle +(1, 1);
}

Note that in this case the previous solutions will not work, because they extract the raw coordinate computed by TikZ, so the TeX engine would look for some point named S28.221344pt (failing).

So I ended up writing a naive macro for parsing ( x , y ) expressions. The solution may be useful for other readers that require the coordinates to be parsed literally:

\makeatletter
\def\parsept#1#2#3{%
    \def\nospace##1{\zap@space##1 \@empty}%
    \def\rawparsept(##1,##2){%
        \edef#1{\nospace{##1}}%
        \edef#2{\nospace{##2}}%
    }%
    \expandafter\rawparsept#3%
}
\makeatother

The macro strips away all spaces in the argument (so \parsept{\x}{\y}{( a b, c)} will yield \x=ab and \y=c), but I couldn't make \ignorespaces and \unskip working in this context. Credits for removing spaces go to David Carlisle's answer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.