68

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...

Jamie Vicary
  • 11,098

5 Answers5

46

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}

user202729
  • 7,143
Peter Grill
  • 223,288
  • 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 '15 at 16:10
  • @cfr: Not sure I understand. Can you post a new question with a MWE that reproduces the problem? – Peter Grill Oct 24 '15 at 06:53
  • 1
    Nice! How to use it inside a \foreach? – Sigur Sep 01 '16 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. – Peter Grill Apr 27 '22 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 '22 at 11:04
44

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

Andrew Stacey
  • 153,724
  • 43
  • 389
  • 751
  • 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 '14 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? – Andrew Stacey Feb 17 '14 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 '14 at 05: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}}
}
Jamie Vicary
  • 11,098
  • 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 05 '11 at 01:25
6

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.

  • 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 –  Dec 07 '14 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.