21

I'm struggling to get my head around the pgfkeys package. I currently use the keycommand package, which I find very easy to understand: it's just ordinary LaTeX commands with named rather than numbered arguments. However, I would like to use pgfkeys because my project is heavily based on TikZ, and I believe it is a more powerful solution. I think understanding how to translate the following little example from keycommand to pgfkeys would help me a lot.

Example

I make a command to draw a doublecircle gadget. It has two arguments: diameter and color, both optional, with default values 1 and red respectively.

\documentclass[border=5mm]{standalone}

\usepackage{tikz}
\usepackage{keycommand}

\newcounter{currentheight}
\setcounter{currentheight}{0}

\newkeycommand{\doublecircle}[diameter=1,color=red]{%
  \fill[\commandkey{color}] (0,\thecurrentheight) circle (\commandkey{diameter}/2);
  \fill[\commandkey{color}] (\commandkey{diameter}/2,\thecurrentheight) circle (\commandkey{diameter}/2);
}

\begin{document}

\begin{tikzpicture}[y=-1cm]
\doublecircle[color=blue]
\addtocounter{currentheight}{5}
\doublecircle[diameter=4]
\addtocounter{currentheight}{6}
\doublecircle[diameter=3,color=green]
\end{tikzpicture}

\end{document}

Result

three doublecircle gadgets

4
  • 1
    If you work it out, you become a PGF Master. May 13, 2013 at 10:46
  • Is there a specific reason you used a counter (only integers) instead of a simple macro or a "value"? Does the pgfkeys implementation needs an equivalent add to height key? May 13, 2013 at 12:40
  • No good reason. It's just that I know how to add values to counters, and I don't know how to do that off the top of my head if I had just used a simple macro like \newcommand{\currentheight}{0}. May 13, 2013 at 12:46
  • 2
    Have you seen my answer to a similar question? It's very popular and seems like exactly what you're asking.
    – Ryan Reich
    May 13, 2013 at 14:20

2 Answers 2

21

A possible solution:

\documentclass[border=5mm]{standalone}

\usepackage{tikz}

\pgfkeys{/tikz/.cd,% to set the path
  circle height/.initial=0, % initial value
  circle height/.get=\circleheight, % to get the value from a macro
  circle height/.store in=\circleheight, % to store the value into a macro
  diameter/.initial=1,
  diameter/.get=\diameter,
  diameter/.store in=\diameter,
  circle color/.initial=red,
  circle color/.get=\circlecolor,
  circle color/.store in=\circlecolor,
}

\newcommand{\pgfdoublecircle}[1][]{%
  \tikzset{circle color=red,diameter=1,#1}% the default options will be overwritten by the one introduced
  \fill[\circlecolor] (0,\circleheight) circle (\diameter/2);
  \fill[\circlecolor] (\diameter/2,\circleheight) circle (\diameter/2);
}

\begin{document}
\begin{tikzpicture}[y=-1cm]
\pgfdoublecircle[circle color=blue]
\pgfdoublecircle[circle height=5,diameter=4]
\pgfdoublecircle[circle height=11,diameter=3,circle color=green]
\end{tikzpicture}

\end{document}

According to Qrrbrbirlbel comment and other Q/A like What do the pgfkeys key handlers .get and .store in do? and Arguments for TikZ style I was convinced to abandon the inefficient method to set up keys. Hence, it should be preferred:

\pgfkeys{/tikz/.cd,% to set the path
  circle height/.store in=\circleheight, % to store the value into a macro
  circle height=0, % initial value
  diameter/.store in=\diameter,
  diameter=1,
  circle color/.store in=\circlecolor,
  circle color=red,
}

The final result remains obviously consistent with the latter approach.

6
  • I preferred to put clearly the height coordinate rather than with an increasing counter, but that approach is also doable. May 13, 2013 at 10:51
  • 1
    This is splendid, thank you. So much more helpful than poring over the manual! May 13, 2013 at 10:56
  • 1
    I notice that you set red as the default color twice in the code: you have both circle color/.initial=red and \tikzset{circle color=red,...}. Is this necessary? May 13, 2013 at 12:42
  • 1
    @JohnWickerson Yes, because the \pgfdoublecircle macro isn’t scoped or grouped. The circle color=blue will survive the macro expansion and the value will be still blue for the next \pgfdoublecircles. May 13, 2013 at 12:59
  • 1
    @JohnWickerson I don’t know why Claudio uses .initial and .get. I’d use circle height/.store in=\circleheight, circle height=0 here. May 13, 2013 at 13:21
5

There are a few ways to get, set and store values, see also the first part of another answer of mine.
Depending on the key and how its value is needed one must use the one or the other way.

Oftentimes we can also see

/my namespace/key/.code=\pgfmathsetmacro\namespace@key{#1}

which can be used here too and makes the value available in an already calculated form, though here it isn’t really needed as TikZ throws almost anything in PGF Math itself. See (yet) another answer of mine on how to use this more effectively (the part about the +).

Code

\documentclass[tikz,border=5mm]{standalone}
\newcommand*{\doublecircleset}{\pgfqkeys{/double circle}}
\makeatletter
\doublecircleset{
  /double circle/.code={\pgfqkeys{/double circle}{#1}},
  color/.initial=red, % initialize key 'color' with value 'red'
  height/.initial=0, % see color
  diameter/.store in=\doublecircle@diameter, % the key 'diameter' will be used more than once and also in the 'scope', let's store it right away
  diameter=1,                                % the default value is '1'
  add to height/.style={height/.expanded=\pgfkeysvalueof{/double circle/height}+#1} % let's take the value of the 'height' key and add '#1'.
                                                     % expansion is needed otherwise we have a selv-referencing macro (and an infinite loop)
}
\newcommand{\doublecircle}[1][]{%
  % let's group the whole macro so that the changes to the keys and whatnot are local, this is how TikZ works.
  % we could have used '\begingroup \doublecircleset{#1} … … … \endgroup' here, but the scope allows us to set the radius too.
  \scope[/double circle={#1}, radius=(\doublecircle@diameter)/2] % alright, execute the key=values and set the radius
    \pgfkeysgetvalue{/double circle/height}\doublecircle@height  % we need the height more than once, a single macro would be nice to use
    \fill[color=\pgfkeysvalueof{/double circle/color}]           %
      (0,{\doublecircle@height}) circle []                       % if we use () or , in the key (which is allowed: PGF math) we need to enclose it in braces
      (xyz cs: x={(\doublecircle@diameter)/2},                   % either way
               y={\doublecircle@height}) circle []
      ;
  \endscope
}
\makeatother
\begin{document}
\begin{tikzpicture}[y=-1cm]
\doublecircle[color=blue]
\doublecircleset{add to height=5}
\doublecircle[diameter=4]
\doublecircleset{add to height=6}
\doublecircle[diameter=3, height=2*(2+3), color=green]
\end{tikzpicture}
\end{document}
4
  • Thanks so much @Qrrbrbirlbel, I appreciate all the explanations. Is \scope[X] ... \endscope the same as \begin{scope}[X]...\end{scope}? May 13, 2013 at 13:34
  • 1
    @JohnWickerson Yes, it is (more or less). TikZ includes \begingroup and \endgroup in its environments on its own; using LaTeX’s \begin and \end would just add another group (and some testing). May 13, 2013 at 13:38
  • Ok, thanks. Also, what does xyz cs mean? May 13, 2013 at 13:40
  • 1
    @JohnWickerson Check the PGF manual for the xyz coordinate system. It is simply the explicit version of (x, y, z) (where all are initially 0). May 13, 2013 at 13:48

You must log in to answer this question.

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