11

The example code below defines a tikz pic that displays an n-by-m grid of circles. It is called by using code like pic {grid={3}{5}} in a tikz path.

However, when code like this starts to have a large number of arguments this kind of syntax gets unwieldy, especially if some of the arguments might need default values. Because of this I'd prefer to be able to call the same code like this instead:

pic[rows=5, columns=3] {grid}

again inserted into a tikz path. Here the rows and columns should be given a default value such as 1 if the user doesn't set them.

The problem is that I don't know how to access the value of the rows and columns keys from within the pic code, or set their default values.

This seems like something that should be possible, but I haven't yet located the correct section of the 1200-page manual to consult about it. Is there a way that this can be done? (Or, if there are multiple ways, which is the best/most idiomatic?)

Here is the MWE code:

\documentclass{article}

\usepackage{tikz}

\tikzset{
    pics/grid/.style 2 args = {
        code = {
            \foreach \i in {1,...,#1} {
                \foreach \j in {1,...,#2} {
                    \draw (\i,\j) circle [radius=3mm];
                }
            }
        }
    }
}

\begin{document}

\begin{tikzpicture}
    \path (0,0) pic {grid={3}{5}};
\end{tikzpicture}

\end{document}

Its output looks like this (but this is not important for my question)

enter image description here

1
  • There is a related question here, but its answers seem to end up passing the keys as a string that then gets parsed later on, so you's call it like pic {grid={rows=5, columns=3}} instead of pic[rows=5, columns=3] {grid}. This is better than nothing but seems like a weird hack, so I'd really like to know how to do it "properly", if it can be done.
    – Nathaniel
    Jul 28 at 7:08
11

A self-answer by the OP:

I did a bit more reading around, and found some useful information in particular in this answer. The following code works, but I wouldn't say I have any expertise about whether it's a good or correct way to do things, so I appreciate answers from others with more experience.

\documentclass{article}

\usepackage{tikz}

\tikzset{
    % set the initial values ( = default values) - this is mandatory, otherwise it will complain about not knowing the keys
    rows/.initial = 1,
    columns/.initial = 1,
    %
    % the code for the pic
    pics/grid/.style = {
        code = {
            % to get the value we use \tikzset (!) with the .get handler to put it in a macro
            \tikzset{rows/.get=\rows}
            \tikzset{columns/.get=\columns}
            %
            % then we can use the \rows and \columns macros to draw the pic
            \foreach \i in {1,...,\columns} {
                \foreach \j in {1,...,\rows} {
                    \draw (\i,\j) circle [radius=3mm];
                }
            }
        }
    }
}

\begin{document}

\begin{tikzpicture}
    \path (0,0) pic[rows=5, columns=3] {grid};
    \path (5,0) pic[rows=2, columns=2] {grid};
    \path (10,0) pic[rows=4] {grid}; % columns defaults to 1
\end{tikzpicture}

\end{document}

enter image description here

One thing I'm a bit unsure of is whether there's a way to manage namespaces a bit better, since I think the keys are currently just stored in tikz/rows and tikz/columns, which means they could clash if some other code defines the same keys.

Here are some brief notes on how the above code works, though they're mostly already explained in comments:

1. setting the default values

this is done by the lines

    rows/.initial = 1,
    columns/.initial = 1,

within the initial \tikzset command. Without setting these defaults tikz will complain about not knowing the key you're trying to set, so it seems that setting defaults is mandatory. (There might be a way to avoid it, but I'm happy with always providing defaults.)

2. getting the values within the pic code

This is done by the lines

           \tikzset{rows/.get=\rows}
           \tikzset{columns/.get=\columns}

within the code key in the pic definition. Each of these commands gets the value of a key and puts it in a macro. It seems a bit odd to get a value using a command with set in its name, but this avoids having to use the pgf layer explicitly.

3. calling the pic code

This is done using exactly the syntax I proposed in the question, and if you don't specify a key it falls back to its default.

3
  • The .initial handler defines the initial value stored by its key (this value persists as long as it is not changed). The .default handler defines the default value used by its key (this value is used when the user calls the key without providing a specific value. ) Jul 29 at 5:39
  • @PaulGaborit that sounds very logical. However, if I change initial to default in my code, I get the error "I do not know the key '/tikz/rows', to which you passed '5'", whereas when using initial it does seem to behave like a default value rather than an initial value, as my example shows. I'd really appreciate an answer from someone with the experience to explain why this is, as I have no idea. (I really just guessed this code and was surprised to find it worked.)
    – Nathaniel
    Jul 29 at 6:08
  • 1
    This seems to be basically the solution used by other packages, which just take care to name the keys so that they're unlikely to collide with anything else. For instance, the star node shape uses keys star points, star point height and star point ratio. So it's probably good practice to name your keys grid rows, grid columns, etc.
    – Emma
    Aug 8 at 15:40
6

Here is a supplement to your excellent answer.

The three variations below always produce the same result:

enter image description here

How to use a family to define its own keys

Here, I define the new family /tikz/my grid with two new keys /tikz/my grid/rows and /tikz/my grid/columns. But the user and the developer must know and use the name of this family to use these keys.

\documentclass{standalone}

\usepackage{tikz}

\tikzset{
  % define a family
  my grid/.is family,
  % use it to define the other keys,
  my grid,
  % set the initial values (if the key is not called)
  rows/.initial=1,
  columns/.initial=1,
  % set the default values (if the key is called without value)
  rows/.default=2,
  columns/.default=2,
}

\tikzset{
  % the code for the pic
  pics/grid/.style={
    code={
      % to get the value we use \tikzset (!) with the .get handler to put it in a macro
      \tikzset{my grid/rows/.get=\rows}
      \tikzset{my grid/columns/.get=\columns}
      
      % then we can use the \rows and \columns macros to draw the pic
      \foreach \i in {1,...,\columns} {
        \foreach \j in {1,...,\rows} {
          \draw (\i,\j) circle [radius=3mm];
        }
      }
    }
  }
}

\begin{document}
\begin{tikzpicture}
  \path (0,0) pic[my grid/rows=5,my grid/columns=3] {grid};
  \path (5,0) pic[my grid/rows=2,my grid/columns=2] {grid};
  \path (10,0) pic[my grid/rows] {grid}; % rows defaults to 2, columns keeps its initial value (1)
\end{tikzpicture}
\end{document}

How to use its own keys in the parameter of the grid pic

Here, I use the parameter of the grid pic to get the keys provided by the user. I can freely choose my family name since the user no longer needs it. But the pic developer must know and use the name of this family many times to use these keys.

\documentclass{standalone}

\usepackage{tikz}

\tikzset{
  % define a family
  my grid parameters/.is family,
  % use it to define the other keys,
  my grid parameters,
  % set the initial values (if a key is not called)
  rows/.initial=1,
  columns/.initial=1,
  % set the default values (if a key is called without value)
  rows/.default=2,
  columns/.default=2,
}

\tikzset{
  % the code for the pic
  pics/grid/.style={% a style (or a key) can use one parameter by default
    code={
      \tikzset{my grid parameters,#1}
      % to get the value we use \tikzset (!) with the .get handler to put it in a macro
      \tikzset{my grid parameters/rows/.get=\rows}
      \tikzset{my grid parameters/columns/.get=\columns}
      
      % then we can use the \rows and \columns macros to draw the pic
      \foreach \i in {1,...,\columns} {
        \foreach \j in {1,...,\rows} {
          \draw (\i,\j) circle [radius=3mm];
        }
      }
    }
  }
}

\begin{document}
\begin{tikzpicture}
  \path (0,0) pic {grid={rows=5,columns=3}};
  \path (5,0) pic {grid={rows=2,columns=2}};
  \path (10,0) pic {grid={rows}}; % rows defaults to 2, columns keeps its initial value (1)
\end{tikzpicture}
\end{document}

How to use its own macros to store the value of its own keys

Here, I use the parameter of the grid pic to get the keys provided by the user and I use the .store in handler to store the value of key in my own macros. I can freely choose my family name since the user no longer needs it. In the grid pic, the developer uses the family name one time but must know and use the name of my own macros.

\documentclass{standalone}

\usepackage{tikz}

\tikzset{
  % define a family
  my grid parameters/.is family,
  % use it to define the other keys,
  my grid parameters,
  % define keys to store its value in a specific macro
  rows/.store in=\mygridrows,
  columns/.store in=\mygridcolumns,
  % store the global initial values (if a key is not called)
  rows=1,
  columns=1,
  % set the default values (if a key is called without value)
  rows/.default=2,
  columns/.default=2,
}

\tikzset{
  % the code for the pic
  pics/grid/.style={% a style (or a key) can use one parameter by default
    code={
      \tikzset{my grid parameters,#1}
      % the values are already stored in specific macros then we can use
      % the \mygridrows and \mygridcolumns macros to draw the pic
      \foreach \i in {1,...,\mygridcolumns} {
        \foreach \j in {1,...,\mygridrows} {
          \draw (\i,\j) circle [radius=3mm];
        }
      }
    }
  }
}

\begin{document}
\begin{tikzpicture}
  \path (0,0) pic {grid={rows=5,columns=3}};
  \path (5,0) pic {grid={rows=2,columns=2}};
  \path (10,0) pic {grid={rows}}; % rows defaults to 2, columns keeps its initial value (1)
\end{tikzpicture}
\end{document}
3

This shows how to set default values:

\documentclass{article}

\usepackage{tikz}

\tikzset{
    pics/grid/.style 2 args = {
        code = {
            \foreach \i in {1,...,#1} {
                \foreach \j in {1,...,#2} {
                    \draw (\i,\j) circle [radius=3mm];
                }
            }
        }
    },
    pics/grid/.default={1}{1}
}

\begin{document}

\begin{tikzpicture}
    \pic {grid={2}{3}};
    \path (3,0) pic {grid};
\end{tikzpicture}

\end{document}

enter image description here

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.