[Tex/LaTex] Programming in TikZ

tikz-pgf

How do I write a macro that will return a path or a coordinate?

Here's an example:

\documentclass[border=2em]{standalone}
\usepackage{tikz}
\usetikzlibrary{calc}
% \circleByPoint{centre}{point} draws a circle with centre at centre through point
\newcommand\circleByPoint[2]{
  \draw let \p1 = (#1),\p2 = (#2) in
  \pgfextra{\pgfmathveclen{\x1-\x2}{\y1-\y2}}
  (#1) circle (\pgfmathresult pt);
}
\begin{document}
\begin{tikzpicture}
\node at (0,0) (a) {};
\node at (1,0) (b) {};
\node at (0,3) (c) {};
\circleByPoint{a}{b}
\circleByPoint{b}{c}
\circleByPoint{c}{a}
\end{tikzpicture}
\end{document}

Here I define a macro that draws a circle. Likewise I can define a macro that draws the perpendicular bisector of a line segment (given two points). The question is, what if I want the outcome of the macro to be the path rather than having it function to just draw the path? Many program languages have something like a return which allows you to specify what the function returns. I can't work out how to get this to work in TikZ…

For example, the following MWE fails to compile properly (in fact, it hangs):

\documentclass[border=2em]{standalone}
\usepackage{tikz}
\usetikzlibrary{calc}
\newcommand\apath[2]{
  \path (#1) -- (#2);
}
\begin{document}
\begin{tikzpicture}
\node at (0,0) (a) {};
\node at (1,0) (b) {};
\node at (0,3) (c) {};
\node at (intersection of \apath{a}{b} and \apath{a}{c}) {x};
\end{tikzpicture}
\end{document}

So my question is, how do I write helper functions for TikZ like this?

(I know that Altermundus' tkz bundle actually defines most of the functions I could possibly want for plane geometry, but the point of this question is to learn more about how to write TikZ code.)

Best Answer

That way madness lies!

I'm tempted to say that we should have a special TikZ chatroom for this sort of thing as it's often hard to frame a precise question. Also, the best place to learn this is in the manual for PGF2.10 as that contains some details on the process from TikZ to PGF to actual output.

But anyway here goes for your specific question. In brief, TikZ code invokes PGF code that draws what is called a softpath. The softpath is a macro containing very basic instructions about how the path is to be drawn. By the time the softpath is constructed, all the coordinates have been translated in to canvas coordinates, all the complicated paths have been reduced to lines, bezier curves, and rectangles, and all fancy stuff like rounded corners have been done. Arrowheads haven't, and nor have other trickery. The key thing about softpaths is that they are 1) in a standard format, and 2) manipulable. So when you want a macro to return a path, the best thing to do is to have it return a softpath.

There is a TikZ option to do this: save path=\pathname. If you put this on your path, it will save it in the macro \pathname. You can then mess with it as you please, until you are ready to "bake" it into a proper path. To do that, you need to \pgfsyssoftpath@setcurrentpath{\pathname}\pgfsyssoftpath@flushcurrentpath and then \pgfusepath{stroke} (or whatever). If you want to add stuff first, then issue the \pgfsyssoftpath@setcurrentpath{\pathname} command, do your new commands, and they will be added to the current path (note: not to \pathname, you'll need to save it again if you want to store it). If you get to the level of messing around with softpaths, you'll want to use the lower-level method of saving a path: \pgfsyssoftpath@getcurrentpath{\pathname} as the tikz option only works when a path is constructed by TikZ.

For an example of this sort of thing, you could look at the calligraphy package in the TeX-SX meta-package. A "pen" is a softpath and so I have some code that allows you to define a pen and save the resulting softpath (and do this without affecting the bounding box). Then later there is some quite messy softpath manipulation that takes the stroke path and "thickens" it by the pen path.

However, this still isn't going to work in your MWE because (as Caramdir has just pointed out) the input to the intersection syntax is not actual paths but the name of a path. To do that, you'd have to look at the code for intersections to figure out exactly what input it will take (which Caramdir has done for you!).