17

I am thoroughly confused by this answer to How to generate n points on a circumference and connect all of them while having constraints on the image size as I don't understand how the parameter processing works?

The macro is declared as

\def\drawconnectvertices[num vertex=#1, circle radius=#2] at (#3);{%
    \pgfmathtruncatemacro\vertices{#1}
    \pgfmathsetmacro\circleradius{#2}
    ...
}

and is invoked as:

\drawconnectvertices[num vertex=6, circle radius=3] at (0,0);

When I read this, I see #1 as num vertex=6, circle radius=3, so the \pgfmathtruncatemacro\vertices{#1} would be:

\pgfmathtruncatemacro\vertices{num vertex=6, circle radius=3}

and should have failed.

TeX appears to be even more flexible in how you can define macros than I had though. So the question I have are:

  • Is this an acceptable way to define macros? If not, how should they be defined.

  • One problem is that the first parameter is not really optional. But other than that are there other issues that are lurking in this way of defining macros?

Code:

\documentclass{article}% Extracted from https://tex.stackexchange.com/a/88312/4301
\usepackage{tikz}
\usetikzlibrary{shapes.geometric} % required for the polygon shape

\def\drawconnectvertices[num vertex=#1, circle radius=#2] at (#3);{%
\pgfmathtruncatemacro\vertices{#1}
\pgfmathsetmacro\circleradius{#2}
\pgfmathsetmacro\halfcircleradius{\circleradius/2}
\draw[blue] (#3) circle (\halfcircleradius cm) node[regular polygon, regular polygon sides=\vertices, minimum size=\circleradius cm, draw=none, name={vertex set}] {};
\foreach \x in {1,...,\vertices}{
    \node[draw,circle, inner sep=1pt,blue, fill=blue] at (vertex set.corner \x) {};
}

\foreach \x in {1,...,\vertices}{
    \foreach \y in {\x,...,\vertices}{
            \draw[ultra thin, red] (vertex set.corner \x)--(vertex set.corner \y);
    }
}
}

\begin{document}
\begin{tikzpicture}
    \drawconnectvertices[num vertex=6, circle radius=3] at (0,0);
\end{tikzpicture}
\end{document}
3
  • No, I don't think this is a good way to define that macro; if you omit the space between the comma and circle, for instance, you'll get a puzzling error.
    – egreg
    Dec 27, 2012 at 19:10
  • 2
    this is what is called a delimited macro, and it is a "plain" tex, not a latex, way of defining a macro. up to a point, it is more meaningful to a human to read, but it has the down side that one can't omit or add a single space or other character, and can't misspell any of the required context; otherwise, as egreg points out, an error will result, and the message won't be obvious. this technique may be appropriate for programmatically created input, or for very simple definitions, such as \latex/ so you don't have to think about following spaces. Dec 27, 2012 at 19:30
  • 1
    I agree with all that is said, though I want to mention that many things in TikZ are defined that way. From tikzlibrarytrees.code.tex: \def\tikz@parse@three one child at#1(#2)#3and two children at#4(#5)#6and#7(#8){%. #1, #3, #4, #5 and #7 are only there to allow for having spaces between at, and and ( or ) or a tighter input like at(. The actual arguments that get processed are #2, #5 and #8. The same way how TikZ works his way through a path. Dec 27, 2012 at 22:07

2 Answers 2

16

There is more than one problem with such a definition. The \def instruction has the following syntax:

\def<cs><parameter text><left brace><balanced text><right brace>

where <cs> is the control sequence or active character to define; <left brace> and <right brace> stand for explicit braces (character tokens with category code 1 and 2, respectively); <balanced text> is any token list with balanced explicit braces, which can contain any #x (x a digit from 1 to 9) that appear also in <parameter text>.

The important thing here is of course the <parameter text>, which can be quite general since the only limitations are essentially that explicit braces are not allowed in it and the "parameter tokens" #1, #2 and so on must appear in order.

If the parameter text starts with something different from #1, those tokens are required to follow the macro name. If a parameter token is immediately followed by another parameter token it represents an undelimited argument, otherwise it represents a delimited token and those tokens must appear in the same order when the macro is called: control sequence names must be the same and, for characters, both the character code and category code must match. The argument will be all that goes from the preceding delimiter (or argument if it was undelimited) up to the delimiter token, but that also results in a token list that is balanced with respect to braces.

For instance, let's look at

\def\foo\bar#1x\baz#2#3\end{#1--#2--#3}

Here we have

  1. a "macro delimiter" \bar

  2. the first argument is anything that goes from \bar (excluded) to the first instance of x\baz

  3. the second argument is "undelimited", so it will be the next token or, when this is a left brace, all the balanced group

  4. the third argument is anything from the end of the second argument to \end

Examples of calls

  1. \foo\bar XYx\baz ZABC\end

    We have #1=XY, #2=Z, #3=ABC

  2. \foo\bar XYx\baz {SOME}ABC\end

    We have #1=XY, #2=SOME, #3=ABC (the braces are stripped off the undelimited argument)

  3. \foo\bar {XYx\baz}x\baz ZABC\end

    We have #1=XYx\baz, #2=Z, #3=ABC, because the first x\baz pair is at brace level one

  4. \foo\bar XYx\baz Z\end

    We have #1=XY, #2=Z, and #3 is empty.

Many other examples can be concocted, but it's best to refer to the TeXbook (chapter 20) or TeX by Topic (chapter 11)


Let's see what happens with the code you gave

\def\drawconnectvertices[num vertex=#1, circle radius=#2] at (#3);{...}

(the <replacement text> is irrelevant for this discussion).

  1. The parameter text consists of a "macro delimiter":

     [num vertex=
    
  2. The first argument is delimited by

     , circle radius=
    
  3. The second argument is delimited by

     ] at (
    
  4. The third argument is delimited by

     );
    

Thus a call like

\drawconnectvertices[num vertex=6, circle radius=3] at (0,0);

will absorb

#1 = 6
#2 = 3
#3 = 0,0

The delimiter tokens will be discarded in the process.

What can go wrong?

In option lists it's common to omit spaces after a comma; so a user might input

\drawconnectvertices[num vertex=6,circle radius=3] at (0,0);

which would result in TeX not finding the correct delimiter for the first argument and continue reading from the file until the end. Well, not until the end of the file, because some particular spots will tell TeX that something went wrong, in particular a \par token will. The error message would be, in this case,

! Paragraph ended before \drawconnectvertices was complete.
<to be read again> 
                   \par 

which doesn't seem very helpful.

The same would happen with

\drawconnectvertices[num vertex=6, circle radius=3] at (0,0) ;

because of the space in front of the semicolon; this might be even more disastrous, because some other later TikZ instruction could have the form (1,2); and all the text from 0,0 up to 1,2 would be considered the third argument to \drawconnectvertices.

How to do it better?

Use the proper TikZ conventions for defining such a macro: define keys and use the infrastructure provided by PGF/TikZ (I'm no expert in this and surely someone else will help in finding a more robust definition). Surely a safe definition would be:

\def\drawconnectvertices#1#2#3{%
  \pgfmathtruncatemacro\vertices{#1}
  \pgfmathsetmacro\circleradius{#2}
  \pgfmathsetmacro\halfcircleradius{\circleradius/2}
  \draw[blue] (#3) circle (\halfcircleradius cm) node[regular polygon, regular polygon sides=\vertices, minimum size=\circleradius cm, draw=none, name={vertex set}] {};
  \foreach \x in {1,...,\vertices}{
    \node[draw,circle, inner sep=1pt,blue, fill=blue] at (vertex set.corner \x) {};
  }
  \foreach \x in {1,...,\vertices}{
    \foreach \y in {\x,...,\vertices}{
            \draw[ultra thin, red] (vertex set.corner \x)--(vertex set.corner \y);
    }
  }
}

with a call such as

\drawconnectvertices{6}{3}{0,0}

but, of course, a more TikZish version would be more attractive and also clearer.


A more TikZish markup

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{calc,shapes.geometric}

\makeatletter
\tikzset{
  num vertices/.code=\def\dcv@vertices{#1},
  num vertices/.default=3,
  circle radius/.code=\def\dcv@circleradius{#1},
  circle radius/.default=1,
}
\newcommand\drawconnectvertices[1][]{%
  \tikzset{num vertices, circle radius, #1}%
  \dcv@auxi}
\def\dcv@auxi#1at#2;{\dcv@auxii#2;}
\def\dcv@auxii#1(#2)#3;{\def\dcv@origin{#2}\dcv@main}
\def\dcv@main{%
  \pgfmathtruncatemacro\dcv@vertices{\dcv@vertices}
  \pgfmathsetmacro\dcv@circleradius{\dcv@circleradius}
  \pgfmathsetmacro\dcv@halfcircleradius{\dcv@circleradius/2}
  \draw[blue] (\dcv@origin) circle (\dcv@halfcircleradius cm) 
     node[regular polygon, 
          regular polygon sides=\dcv@vertices, 
          minimum size=\dcv@circleradius cm, 
          draw=none, 
          name={vertex set}] {};
  \foreach \x in {1,...,\dcv@vertices}{
    \node[draw,circle, inner sep=1pt,blue, fill=blue] at (vertex set.corner \x) {};
  }
  \foreach \x in {1,...,\dcv@vertices}{
    \foreach \y in {\x,...,\dcv@vertices}{
            \draw[ultra thin, red] (vertex set.corner \x)--(vertex set.corner \y);
    }
  }
}
\makeatother

\begin{document}
\begin{tikzpicture}
\drawconnectvertices[num vertices=6,circle radius=3] at (0,0);
\end{tikzpicture}

\begin{tikzpicture}
\drawconnectvertices[circle radius=3] at (0,0);
\end{tikzpicture}

\begin{tikzpicture}
\drawconnectvertices[num vertices=6] at (0,0);
\end{tikzpicture}
\end{document}

enter image description here

I'm sure that TikZ has features for making this even more robust.

1
  • As always, an excellent answer. I never thought that a delimiter could be more than a single character like [num vertex=. Good to know, but will try not to abuse this. :-) Dec 28, 2012 at 14:37
8

Here is a key-value interface. I use skeyval package instead of pgfkeys only because I wanted to preset keys.

\documentclass{article}
\usepackage{skeyval}
\usepackage{tikz}
\usetikzlibrary{shapes.geometric}
\makeatletter
% Spurious spaces will appear in the following methods in horizontal
% mode, but TikZ doesn't mind them. The star (*) form of \directkeys
% assumes every code line ends with comment sign.
\directkeys*{
  .family=graph,
  .holder prefix=cvt@,
  .initialize keys after define,
  .define keys={
    % Multiple ordinary keys with default '6' and common callback:
    .ord/{num vertices,number of vertices,vertices}/6/
      \skvensureinteger{vertices}{#1}
      \def\cvt@vertices{#1}
    ,
    .ord/{radius,circle radius}/2/\def\cvt@radius{#1},
    % Command keys with default '0' and no callbacks:
    .cmd/{x,y}/0
  },
  % Set preset list, in case some keys are absent when calling \drawgraph:
  .preset keys={vertices,radius,x,y}
}
\newcommand*\drawgraph[1][]{%
  \directkeys{
    .family=graph,.set keys={#1}
  }
  \pgfmathsetmacro\halfcircleradius{\cvt@radius/2}
  \draw[blue] (\cvt@x,\cvt@y) circle (\halfcircleradius cm)
    node [regular polygon, regular polygon sides=\cvt@vertices,
          minimum size=\cvt@radius cm, draw=none, name={vertex set}]{};
  \foreach \x in {1,...,\cvt@vertices}{
    \node[draw,circle,inner sep=1pt,blue,fill=blue] at (vertex set.corner \x){};
  }
  \foreach \x in {1,...,\cvt@vertices}{
    \foreach \y in {\x,...,\cvt@vertices}{
      \draw[ultra thin, red] (vertex set.corner \x)--(vertex set.corner \y);
    }
  }
}
\makeatother

\begin{document}
\begin{tikzpicture}
\drawgraph[number of vertices=6, circle radius=3, x=0, y=0]
\drawgraph[number of vertices=8, circle radius=3, x=4, y=0]
\end{tikzpicture}
\par\bigskip

\begin{tikzpicture}
% Use default values of positions:
\drawgraph[vertices=8, radius=3]
% Use default values of vertices and radius:
\drawgraph[x=4, y=0]
\end{tikzpicture}
\par\bigskip

\begin{tikzpicture}
% Use default values of all keys:
\drawgraph
\end{tikzpicture}
\end{document}

enter image description here

There are graph examples at http://www.texample.net/tikz/examples/tag/graphs/.

You must log in to answer this question.

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