8

Possible Duplicate:
How to extract the value from a pgfkeys style element

Let us assume I have defined the following TikZ style:

\tikzset{
  mystyle/.style={draw, fill=green, node distance=3mm}
}

and I want to draw the following picture:

enter image description here

To draw this, I use the following code:

\documentclass{article}

\usepackage{tikz}
\usetikzlibrary{calc, positioning}

\begin{document}
\begin{tikzpicture}[%
    mystyle/.style={draw, node distance=3mm}
  ]
  \node [mystyle] (n1) {1};
  \node [mystyle, right=of n1] (n2) {2};
  \node at ([yshift=-3mm] $(n1.south) !0.5! (n2.south)$) [mystyle, below] (n3) {3};
\end{tikzpicture}
\end{document}

Although it works, it has the problem that the distance (3mm) is defined twice -- once in the style and once in the figure itself.

Before, I've solved this by creating a new dimension register which defines the distance, but I would like to be able to extract the value from the style directly. As an example, I would like something like this:

\documentclass{article}

\usepackage{tikz}
\usetikzlibrary{calc, positioning}

\begin{document}
\begin{tikzpicture}[%
    mystyle/.style={draw, node distance=3mm}
  ]
  \node [mystyle] (n1) {1};
  \node [mystyle, right=of n1] (n2) {2};
  \node at ([yshift=-\styleproperty{mystyle/node distance}] $(n1.south) !0.5! (n2.south)$) [mystyle, below] (n3) {3};
\end{tikzpicture}
\end{document}

Is this possible to achieve?

gablin
  • 17,006
  • 1
    Please ensure that your code snippets are fully compilable. It didn't take me long to realise you were using calc and positioning, but with more obscure libraries it can take longer to figure out and that makes it harder for people to know that they are answering the question well. – Andrew Stacey Mar 29 '12 at 11:52
  • 1
    @AndrewStacey: Ah, didn't think about the required TikZ libraries. I assumed a fully working example wasn't necessary here, but indeed obscure library usage will cause much annoyance. I've updated the question accordingly. – gablin Mar 29 '12 at 13:23
  • 2
    Full working examples are always nice: I cut-and-paste directly into a clean Emacs buffer and so it's nice to be able to just do C-y C-x C-f without having to think. Then I can see the problem straight away and make a quick assessment on whether or not I'll be able to help. – Andrew Stacey Mar 29 '12 at 13:32
  • @Daniel: Oh yeah, it is! How come I didn't find that one during search?? XD – gablin Mar 30 '12 at 07:01

2 Answers2

9

Thanks to Ryan Reich's Most Excellent PGFKeys tracing package (at How do I debug pgfkeys?), I found that the key node distance stores its value in the macro \tikz@node@distance. This means that it cannot be accessed via the "standard channels" for pgfkeys (namely \pgfkeyvalueof). Moreover, as the macro name has @s in it then it cannot be used as-is. So we have to define a wrapper to get the value of this macro.

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{calc,positioning}

% This bit was how I found out what was going on
%\usepackage{trace-pgfkeys}

%\pgfkeystracelevel{verbose}
%\tikzset{node distance=3mm}
%\pgfkeystracelevel{silent}

\makeatletter
\def\getnodedistance{\tikz@node@distance}
\makeatother

\begin{document}

\begin{tikzpicture}[%
    mystyle/.style={draw, node distance=3mm}
  ]
  \node [mystyle] (n1) {1};
  \node [mystyle, right=of n1] (n2) {2};
  \node [mystyle, below] at ([yshift=-\getnodedistance]$(n1.south) !0.5! (n2.south)$) (n3)
        {3};
\end{tikzpicture}
\end{document}

This method doesn't easily generalise, but that's because pgfkeys can do whatsoever they like with the values that they are passed, including storing them in arbitrary macros. Sometimes you are lucky and they store them in keys in which case \pgfkeysvalueof{full key name} is what you want here. If a general macro is used, then you have to use that macro to access it.

(An alternative method would be to add some code to the original key call to store the value in another macro which you could use. I'd use this if the key used the argument in some non-trivial way which made it hard to extract the original value.)

Note: since the assignment node distance=3mm is local, you need to ensure that the mystyle key is invoked before the \getnodedistance macro is called. That's why I shifted the [mystyle,below] part to before the coordinate calculation. If you wanted to be able to specify the 3mm earlier in the document, I'd do all this in another way: store the 3mm in a separate key and then have both node distance and the yshift access this key. Something like:

\tikzset{
   my global node distance/.initial=3mm,
   mystyle/.style={
      draw,
      node distance=\pgfkeysvalueof{/tikz/my global node distance}
   }
}

...

\node [mystyle,below] at ([yshift=-\pgfkeysvalueof{/tikz/my global node distance]...

EDIT (by percusse) :

Here are two more ways to include the macro assignment in the style declaration

1) Provide a length macro name option to save the value of node distance directly to that macro name(in this case mynodedist).

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{calc,positioning}
\makeatletter
\tikzset{ mystyle/.style={draw, node distance=3mm, save node distance to=mynodedist},
save node distance to/.code={\expandafter\newdimen\csname#1\endcsname%
\advance\csname#1\endcsname by \tikz@node@distance }
}
\makeatother

\begin{document}
\begin{tikzpicture} 
  \node [mystyle] (n1) {1};
  \node [mystyle, right=of n1] (n2) {2};
  \node [mystyle, below] at ([yshift=-\mynodedist ]$(n1.south) !0.5! (n2.south)$) (n3) {3};
\end{tikzpicture}
\end{document}

2) You can do the regular style= and code= assignment of the same key set, for more disciplined key-value pairing which boils down to modifying only

\makeatletter
\tikzset{ mystyle/.style={draw, node distance=3mm},
mystyle/.add code={}{\def\mynodedist{\tikz@node@distance}}
}
\makeatother
Andrew Stacey
  • 153,724
  • 43
  • 389
  • 751
  • I think using a /.code for mystyle on the spot would make the macro assignment a little more organized. – percusse Mar 29 '12 at 12:18
  • @percusse Care to expand on that? I think I need to see it in full to understand your point (feel free to edit my answer if you don't want to provide an answer of your own). – Andrew Stacey Mar 29 '12 at 12:37
  • Could you provide a full minimal example where you use \pgfkeysvalueof? I have trouble getting mine to compile. – gablin Mar 29 '12 at 14:00
  • Found the problem: I must prepend "/tikz/" to the key I want to access. You should probably fix that in your answer. – gablin Mar 29 '12 at 14:04
  • @gablin Sorry about that. I added the \pgfkeysvalueof bit as an afterthought and didn't actually test it. I've edited the answer as you suggested. – Andrew Stacey Mar 29 '12 at 18:13
  • IMHO http://tex.stackexchange.com/questions/41660/how-to-extract-the-value-from-a-pgfkeys-style-element is an exact duplicate of this question; Ryan Reich there has provided an excellent approach to tackle the problem. – Daniel Mar 29 '12 at 20:48
  • @Daniel I'd forgotten that one. Yes, they certainly do cover the same ground so this one should definitely point to that one. Whether or not to close as dup is perhaps not so clear given that the answer I've given is considerably simpler simply because in this case the value wasn't buried away. – Andrew Stacey Mar 29 '12 at 21:29
  • Well, in that case the value was also not buried away :-), so your approach would probably work there as well. Ideally, the answers would be merged, IMHO. – Daniel Mar 29 '12 at 21:47
6

The question is interesting because Andrew and percusse gave interesting informations about \tikz@node@distance. I think it's not necessary to extract the value from the style directly in this case. How to extract is interesting but it's preferable to avoid this because TikZ gives you all the tools.

I 'm not a big fan of the positioning library but if you use it then I think the better way it's to use it till the end of your code. You want to use the value of the current node distancein this case I think below=of is preferable ! (instead of yshift)

I think the next code gives what you want to achieve

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{calc, positioning}

\begin{document}

\begin{tikzpicture}[%
    every node/.style = {draw}, 
    node distance     = 3mm
    ]
  \node  (n1) {1};
  \node [right=of n1] (n2) {2};
  \path let \p1 = ($(n1.south) !0.5! (n2.south)$) in node[below=of \p1] (n3)  {3};
  % more simpler : \node[below= of $(n1.south) !0.5! (n2.south)$] (n3) {3}; % (percusse)
 \end{tikzpicture} 

\end{document} 

enter image description here

Alain Matthes
  • 95,075
  • 1
    Now that you mention it, even \node[mystyle,below= of $(n1.south) !0.5! (n2.south)$] (n3) {3}; is sufficient. – percusse Mar 31 '12 at 09:52
  • It's the first try that I made with an error (pgf cvs) but perhaps I made a syntax mistake. You are right, it's the good way ! – Alain Matthes Mar 31 '12 at 11:12
  • Actually I didn't know that it is possible. I forgot to put the parentheses. Don't tell anyone :) I think this is better than accessing the keys. – percusse Mar 31 '12 at 11:19
  • @percusse: By coincidence, I just realized the same thing I went here to mention it. But you beat me to it. =) – gablin Mar 31 '12 at 12:53