5

Let's argue that I want to draw variations of the infinity symbol (Mostly because I can't draw ducks although it makes for a good MWE). I can't find a simple example that uses pgf/tikz keys to manipulate how a tikz pic is drawn in a concise manner. Looking over my own code base it looks like I'm rather confused about the topic myself.

Requirements :

Let's allow the size of the shape to be altered by the keys width and height and enable some wobble in the lobes with the keys inner targetand outer target. Most importantly we want to select between various calligraphic pens, which must be set by code. This is controlled by selecting left and 'right' for the pen.

Code :

For this example you will need the spath and calligraphy style files, available here, available as the spath3 package and tikz calligraphy libraries.

\documentclass[tikz]{standalone}

\usepackage{spath}
\usepackage{calligraphy}
\usetikzlibrary{calc}

\makeatletter
\tikzset{
 infinity/.is family,
 infinity/.pic = {
  % This is the curve we wish to have drawn
  \calligraphy let \p1=(0,0) in 
     (\p1) .. controls (-\infty@upper*\infty@width, \infty@height) and (-\infty@lower*\infty@width,-\infty@height) .. 
     (\p1) .. controls ( \infty@lower*\infty@width, \infty@height) and ( \infty@upper*\infty@width,-\infty@height) .. cycle;;
  % This curve is simply here as a backup
  \draw[line width=0.1, blue] let \p1=(0,0) in 
     (\p1) .. controls (-\infty@upper*\infty@width, \infty@height) and (-\infty@lower*\infty@width,-\infty@height) .. 
     (\p1) .. controls ( \infty@lower*\infty@width, \infty@height) and ( \infty@upper*\infty@width,-\infty@height) .. cycle;;
 },
 infinity,
 pen/.is choice,
 pen/left/.code={\pen (-.01,.01) -- (.01,-.01);},
 pen/right/.code={\pen (-.01,-.01) -- (.01,.01);},
 pen/.default=left,
 pen/.initial=left,
 width/.store in=\infty@width,
 height/.store in=\infty@height,
 inner target/.store in=\infty@upper,
 outer target/.store in=\infty@lower,
}
\tikzset{
 infinity,
 width        =   1 em,
 height       =   1 em,
 inner target = 0.2 em,
 outer target = 0.8 em,
}
%\tikzset{ % Fails to set the properties before the infinity/.code is executed.
% infinity/.style={width=1em, height=1em, inner target =0.2, outer target =0.8},
%}
\makeatother

\begin{document}
\begin{tikzpicture}
\draw pic {infinity};
\end{tikzpicture}
\begin{tikzpicture}
\draw pic[infinity/pen=left] {infinity};
\end{tikzpicture}
\end{document}

Overview

I am trying to structure the code as follows. Group everything under the infinity family. infinity also acts as the tikz pic whose geometry is controlled by the dimensions \infty@DIMENSION, where DIMENSION is one of width and height, controlling the size, and upper and lower, controlling the wobble. The pen is selected as described above.

Question(s) :

  1. How does one set the defaults for the pic setting ? Setting the geometry keys in infinity/.style fails for me. I've therefore defined them the second \tikzset{} call so that the MWE compiles out of the box.

  2. I also have to explicitly set the value pen for the calligraphic curve to display. Shouldn't this also be made part of the infinity/.style ?

  3. This point relates to 1 and 2 above. Setting the dimensions under infinity/.style fails, as it does not set any of the dimensions before infinity/.code is executed.

    \tikzset{ infinity/.style={...}, }

  4. In some other code I have I use the DIMENISON/.get handler instead of 'DIMENSION/.store in' handler. Should I be doing this here ?

  5. In the second tikzpicture environment I explicitly set pen using infinity/pen=... is there a way I could simply write pen=... ? Is it then also necessary to specify infinity/.search also=/tikz/ in the first \tikzset where I define infinity ?

Background :

There was a very good answer by Ryan Reich regarding some of this stuff but I can't find it now. I have read through the pgfkeys section of the pgfmanual, to me it reads more like an API document and I find I need an example to connect the dots, hence this question.

Update :

My original intention with this question has been userped by a package update, which is really nice in a "Oh darn" kind of way.

  • Select the 'Users' pick on the top menu, then search for Reich. You would quickly find this answer. tex.stackexchange.com/questions/34312/… – R. Schumacher Jun 18 '16 at 17:04
  • @R.Schumacher I did go through that route, thanks. I reread a few of his answers again today but did not see the answer I was looking for when I decided to post the question. I think I made a note somewhere of it but then I'd need to find the note ^_^. The main idea here is to get a nice example showing how to set choices, append code that may be used to modify the pic and then provide a few base dimensions so the pic is easily scaled in some way that deviates from how the overall picture is scaled. – Carel Jun 18 '16 at 17:17
  • What was the package update that usurped your original intention? – Loop Space Jun 19 '16 at 20:40
  • They converted the pen commands into options for the calligraphy command. I've posted potential answer that handles the case that this key is not present. It was one of the things I wanted to understand when I posted the question but after reviewing the question this wasn't particularly clear it was sort of spread across points 1 - 3. – Carel Jun 19 '16 at 22:23
3

The basic rule with keys (there are exceptions) is that you need to do the following things:

  1. Initialise a key,
  2. Define a key,
  3. Use a key.

On the whole, I find that once I'm in the pgfkeys ecosystem then it is best to stay there. So the values height, width, upper, and lower can be stored in keys rather than needing auxiliary macros.

With the pen, when you use a .code key then that code is executed when it is invoked so it is a little different to storing values for later use. What you wanted was to decide which pen to use and then inside the pic invoke the code necessary to install it. This means separating the setting and getting of that key since you want to set the key in the options to the pic but use it in the actual code. This means it would be easier to use pen to set an option which says which pen to use later on. In fact, you could define the pens at the start of the document and use them in your code.

  1. Using infinity/.style means that you are using the infinity key in three different ways: as a pgfkeys family, as a pic, and as a style. This is possibly a little too confusing for the user. Using keys, it's not necessary to also use styles. You can set initial values for the keys with the .initial key handler.

  2. By making the pen a value rather than explicit code, we can use the .initial key handler for this as well.

  3. The reason infinity/.style doesn't do anything is that you never use it.

  4. Using the keys themselves avoids needing the .store in or .get handlers.

  5. You can set pen to be an alias to infinity/pen as in the code below.

Here's working code, feel free to ask follow-up questions in the comments:

\documentclass[tikz]{standalone}
%\url{http://tex.stackexchange.com/q/315412/86}

\usetikzlibrary{calc,calligraphy}

\makeatletter
%
% This is a shorthand for getting the value of a relevant key, purely
% to save typing
%
\def\inf@key#1{%
  \pgfkeysvalueof{/tikz/infinity/#1}%
}

\AtBeginDocument{
  %
  % Put these in a box to throw away so that it takes up no space
  %
  \setbox0=\vbox{%
    \definepen[pen name=left] (-.01,.01) -- (.01,-.01);
    \definepen[pen name=right] (-.01,-.01) -- (.01,.01);
  }%
}

\tikzset{
  %
  % So that we can use the pen outside the infinity tree
  %
  pen/.style={
    infinity/pen=#1,
  },
  infinity/.is family,
  infinity/.pic = {
    %
    % The calligraphy/spath3 library currently has some issues with the
    % cycle coordinate.  Oops.
    %
    \calligraphy[pen name=\inf@key{pen}] let \p1=(0,0) in 
     (\p1) .. controls (-\inf@key{inner target}*\inf@key{width}, \inf@key{height}) and (-\inf@key{outer target}*\inf@key{width},-\inf@key{height}) .. 
     (\p1) .. controls ( \inf@key{outer target}*\inf@key{width}, \inf@key{height}) and ( \inf@key{inner target}*\inf@key{width},-\inf@key{height}) .. (\p1);
    % This curve is simply here as a backup
    \draw[line width=0.1, blue] let \p1=(0,0) in 
     (\p1) .. controls (-\inf@key{inner target}*\inf@key{width}, \inf@key{height}) and (-\inf@key{outer target}*\inf@key{width},-\inf@key{height}) .. 
     (\p1) .. controls ( \inf@key{outer target}*\inf@key{width}, \inf@key{height}) and ( \inf@key{inner target}*\inf@key{width},-\inf@key{height}) .. (\p1);
  },
  infinity,
  pen/.initial=left,
  width/.initial=1em,
  height/.initial=1em,
  inner target/.initial=0.2,
  outer target/.initial=0.8
}
\makeatother

\begin{document}
\begin{tikzpicture}
\draw pic[infinity/width=2em] {infinity};
\end{tikzpicture}
\begin{tikzpicture}
\draw pic[infinity/pen=right] {infinity};
\end{tikzpicture}
\begin{tikzpicture}
\draw[pen=right] pic {infinity};
\end{tikzpicture}
\end{document}

Incidentally, the spath library was superseded by the spath3 library which is available from CTAN and available in all good TeX distributions.

  • If one wanted to allow the specification of width/height/upper/lower as pic options i.e. pic[height=2,pen=left] {infinity} does one have to map each dimension to it's own style as is done with pen/.style={...} ? That was why I made infinity it's own style and asked about the .search also handler. Perhaps if pic provided it's own scope one could then do infinity/.pic={[infinity, pic actions] ...} or infinity/.pic={\begin{scope}[infinity, pic actions] ... \end{scope}}. As you point out I was not invoking infinity/.style anywhere. Perhaps this is a bad idea. – Carel Jun 19 '16 at 9:02
  • @Carel You could do pic[infinity,height=2,pen=left] since the first infinity shifts the path to the infinity sub-tree. If you want to mix infinity and normal styles then you need the search also stuff. NB the pic stuff is relatively new and I for one am finding that not everything works as I expect it to so I end up with lots and lots of experiments as I try to figure it out! So carry on trying. – Loop Space Jun 19 '16 at 20:40
  • From the docs the optional argument in pic gets wrapped into pic actions so having something like \calligraphy[infinity, pic actions] within the pic code should allow this to pass through into the infinity scope/branch. Trying this still turns out a few errors for me either related to expansion of pic actions or it just exceeds TeX's capacity. – Carel Jun 19 '16 at 22:34
  • I've since formalized my last question. – Carel Jun 20 '16 at 11:05
1

Sticking with the older packages I was able to resolve something I was curious about. Namely how does one optionally change the pen when specifying the pic.

Following the advice @Loop Space provided, namely don't mush all of ones keys together under one path, actually use the styles you define, store ones values in the pgf tree and provide a wrapper macro to retrieve them. Results in the following, albeit slightly verbose version of his answer.

\documentclass[tikz]{standalone}

\usepackage{spath}
\usepackage{calligraphy}
\usetikzlibrary{calc}

\makeatletter
\def\inf@key#1{%
  \pgfkeysvalueof{/tikz/infinity dimensions/#1}%
}    
\tikzset{
 % Dimensions
 infinity dimensions/.is family,
 infinity dimensions/width/.initial =1 em,
 infinity dimensions/height/.initial=1 em,
 infinity dimensions/upper target/.initial=0.2,
 infinity dimensions/lower target/.initial=0.8,
 % Options
 infinity options/.is family,
 infinity options/pen/.is choice,
 infinity options/pen/left/.code  = {\pen (-.01, .01) -- ( .01,-.01);},
 infinity options/pen/right/.code = {\pen (-.01,-.01) -- ( .01, .01);},
 infinity options/pen/.default=left,
 % Style
 infinity style/.style={/tikz/infinity options/.cd, pen=#1},
 % Pic/Symbol
 infinity symbol/.pic = {
  % This is the curve we wish to have drawn
  \calligraphy[infinity style=#1] let \p1=(0,0) in 
     (\p1) .. controls (-\inf@key{upper target}*\inf@key{width}, \inf@key{height}) and (-\inf@key{lower target}*\inf@key{width},-\inf@key{height}) .. 
     (\p1) .. controls ( \inf@key{lower target}*\inf@key{width}, \inf@key{height}) and ( \inf@key{upper target}*\inf@key{width},-\inf@key{height}) .. cycle;
  % This curve is simply here as a backup
  \draw[line width=0.1, blue] let \p1=(0,0) in 
     (\p1) .. controls (-\inf@key{upper target}*\inf@key{width}, \inf@key{height}) and (-\inf@key{lower target}*\inf@key{width},-\inf@key{height}) .. 
     (\p1) .. controls ( \inf@key{lower target}*\inf@key{width}, \inf@key{height}) and ( \inf@key{upper target}*\inf@key{width},-\inf@key{height}) .. cycle;
 },
}
\makeatother

\begin{document}
\begin{tikzpicture}
\draw pic {infinity symbol};
\end{tikzpicture}
\begin{tikzpicture}
\draw pic {infinity symbol=right};
\end{tikzpicture}
\end{document}

The difference being that I deliberately kept the .is choice handler, set up the .style and importantly used it. (EZKeys provides an alternate approach). The magic happens with the line(s) infinity style/.style={...} and \calligraphy[infinity style=#1] ... which enable the optional argument specified in \draw pic {infinity symbol= ...}. These act together to passing the argument through to the correct branch/name space within pgfkeys. The \pen ...; code is then called before the calligraphy path is traced, which was not happening in the original question.

I have not managed to resolve the passing of pic actions to infinity dimensions just yet.

It would be beneficial to have a version with all the keys "mushed" together for comparison which is possible as the handlers do not in general clash. It also saves one from naming branches like infinity symbol/options/dimensions, which should really belong in an infinity sub-tree/branch.

This was more of an exercise but it could be useful as it allows 'classes' of pic. As an example if I specified a series of car related pics I could provide a default, say a volla (Beetle), for \draw pic {car}; but allow one to select an alternate \draw pic {car=ALTERNATE}; such as a hotrod, saloon or hatchback.

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.