1

I am passing an argument to a pic like so pic[n=3]. Am I doing it right? And how about passing several arguments, say two like so pic[n=3, r=1]?

I really like being able to write the argument explicitly inside square bracket, rather than say pic{3}{1} (difficult to remember the order of the arguments). I tried repeating what I did with \pgfkeysgetvalue{/tikz/n}\n for \r and adding .pic 2 args ={ or .pic n args = {2}{, but that failed.

    \documentclass[class=article,border=2mm,tikz]{standalone}
    \usetikzlibrary{calc}
\tikzset{%
  circle pyramid/.pic ={
    \begin{scope}
      \pgfkeysgetvalue{/tikz/n}\n
      \foreach \x in {\n,...,1} {
        \draw[fill=white]
          \ifnum\x=\n
            (0,0)
          \else
            (row)++(60:2)
          \fi
          coordinate (row)%
            \foreach \y in {1,...,\x} {
              circle[radius=1]++(2,0)% circles of radius 1, would like to make it \r
            };
        }
    \end{scope}
  },
  n/.initial=2
}

% radius of the circumscribed circle in terms of the smaller circle r
% R = r (3+2*(n-1)*sqrt(3))/3, and we set r = 1
\newcommand{\radius}[1]{\pgfmathparse{(3+2*(#1-1)*sqrt(3))/3}}%

\begin{document}
\begin{tikzpicture}
  \radius{3}%
  \draw[fill=black] circle[radius=\pgfmathresult cm];
  \path (-2,-1.16) pic[n=3]{circle pyramid}; % inelegant tweak of the center
\end{tikzpicture}

\end{document}

The code for the pyramid of circles was borrowed from skillmon. I tried to reduce it to the minimum elements I needed. But as you can see from the screenshot, there is some unwanted white space on the right-hand side. I'd like to fix that too.

enter image description here

PatrickT
  • 2,923
  • Unrelated, but I have a feeling that radius=\pgfmathresult cm is not the best way to pass a calculation to the argument, is there a better way? – PatrickT Apr 03 '21 at 13:05
  • It's helpful when at all possible to stick to one question per post. Here you ask a question that's quite useful to me at the current time (how to pass arguments as keys) and also one that's quite specific to your own use case (how to get rid of the excess space in your particular image), and the answers mix up both of them, making it difficult to extract the information. – N. Virgo Jul 28 '21 at 06:16

2 Answers2

4

The problem of the excess space arises because the code injects an excess coordinate in

circle[radius=1]++(2,0)

One should not add this ++(2,0) in the last coordinate. One can also compute (-2,-1.16) from first principles, it is the polar coordinate (-150:R-r), where R is the radius of the large background circle and r the radius of the smaller circles. As for the problem of passing arguments to the pic, I use the method from https://tex.stackexchange.com/a/534355, but it is also rather similar to what Andrew Stacey's answer does except that here I use \pgfkeysvalueof, which avoids defining many extra macros (e.g. \n could potentially clash with the syntax of calc, which you load but which is not needed in this very example). As for the radius function, I just use declare function to define cpradius. The background circle has been added to the pic, but it can be turned off with a switch. Here is code with a number of examples:

\documentclass[class=article,border=2mm,tikz]{standalone}
%\usetikzlibrary{calc}
\newif\iftikzcirclepyramiddrawbackgroundcircle
\tikzset{%
  declare function={cpradius(\x)=(3+2*(\x-1)*sqrt(3))/3;},
  pics/circle pyramid/.style={code={%
      \tikzset{circle pyramid/.cd,#1}%
      \def\pv##1{\pgfkeysvalueof{/tikz/circle pyramid/##1}}%
      \iftikzcirclepyramiddrawbackgroundcircle
       \path[circle pyramid/bg circle] circle[radius={\pv{r}*cpradius(\pv{n})}];
      \fi
      \foreach \x in {\pv{n},...,1} {
        \draw[circle pyramid/fg circle]
          \ifnum\x=\pv{n}
            (-150:{\pv{r}*cpradius(\pv{n})-\pv{r}})
          \else
            (row)++(60:2*\pv{r})
          \fi
          coordinate (row)%
            \ifnum\x>1
            foreach \y in {1,...,\the\numexpr\x-1} {
              circle[radius=\pv{r}]++(2*\pv{r},0)% circles of radius 1, would like to make it \r
            }
            \fi
            circle[radius=\pv{r}];
        }
    }  
 },
  circle pyramid/.cd,n/.initial=2,r/.initial=1,
  background circle/.is if=tikzcirclepyramiddrawbackgroundcircle,
  background circle=true,bg circle/.style={draw,fill=black},
  fg circle/.style={draw,fill=white},
}

\begin{document} \begin{tikzpicture} \path pic{circle pyramid={r=1,n=3}} (9,-3) pic{circle pyramid={r=1.5,n=3}} (0,-7) pic{circle pyramid={r=0.8,n=4}} (8,-10) pic{circle pyramid={r=0.6,n=2,background circle=false}} (11,-10) pic{circle pyramid={r=0.6,n=2,bg circle/.style={fill=blue}, fg circle/.style={fill=orange}}}; \end{tikzpicture} \end{document}

enter image description here

  • That is an amazing answer, thanks! I see that defining \pv saves having to repeatedly write the \pgfkeysvalueof line, is there another advantage? Thanks for explaining the problem with the excess coordinate, got it! I didn't remember about the declare function option, neat: so I do not need calc anymore, does tikz do as good a job without it? – PatrickT Apr 04 '21 at 03:44
  • 1
    @PatrickT Yes, the purpose of \pv is that instead of, say, \pgfkeysvalueof{/tikz/circle pyramid/n} you only need \pv{n}, so it is only a short cut. The calc library allows you to do coordinate computations, and this is independent of declare function. –  Apr 04 '21 at 03:51
  • Do you use ##1 because it appears alongside a #1? Thanks! – PatrickT Apr 04 '21 at 05:30
  • 1
    @PatrickT Precisely. If you put a \def inside a \def, you need to double the number of #. Of course, in this case the other \def is a pic, but internally this also uses \defs. See e.g. https://tex.stackexchange.com/q/42463 for a discussion. –  Apr 04 '21 at 05:32
1

One way to do this is to pass a set of key-value pairs as the single argument to the pic which are then reparsed by pgfkeys. I find it helps to put such keys in families to avoid issues with reusing them. The other thing I've changed is as mentioned in the comments then your method of setting the outer circle radius is not robust (in the non-TeX sense) as any intermediate calculation will overwrite the result. The following code produces the same result as in the question.

(I did wonder about the background circle - should that be part of the pic?)


    \documentclass[class=article,border=2mm,tikz]{standalone}
    \usetikzlibrary{calc}
\tikzset{%
  circle pyramid/.pic ={
    \begin{scope}[circle pyramid options/.cd,#1]
      \pgfkeysgetvalue{/tikz/circle pyramid options/n}\n
      \pgfkeysgetvalue{/tikz/circle pyramid options/r}\r
      \foreach \x in {\n,...,1} {
        \draw[fill=white]
          \ifnum\x=\n
            (0,0)
          \else
            (row)++(60:2)
          \fi
          coordinate (row)%
            \foreach \y in {1,...,\x} {
              circle[radius=\r]++(2,0)% circles of radius 1, would like to make it \r
            };
        }
    \end{scope}
  },
  circle pyramid options/.is family,
  circle pyramid options/.cd,
  n/.initial=2,
  r/.initial=1
}

% radius of the circumscribed circle in terms of the smaller circle r
% R = r (3+2*(n-1)*sqrt(3))/3, and we set r = 1
\newcommand{\radius}[1]{((3+2*(#1-1)*sqrt(3))/3)}%

\begin{document}
\begin{tikzpicture}
  \draw[fill=black] circle[radius=\radius{3}];
  \path (-2,-1.16) pic{circle pyramid={n=3,r=1}}; % inelegant tweak of the center
\end{tikzpicture}

\end{document}

Andrew Stacey
  • 153,724
  • 43
  • 389
  • 751
  • Great answer, thank you Andrew! The background circle could be part of the pic, indeed, I took it out to keep the question a little more focused, as it's already asking a lot! – PatrickT Apr 04 '21 at 03:29
  • Andrew, I have received two fantastic answers. I do not know how to choose. The code I ended up using combines ideas from both. Yours came earlier, but user238301 also helped me remove the white space. Thanks! – PatrickT Apr 04 '21 at 05:33