Programming in TikZ

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.)

-
Your minimal example is missing the package and node definitions. Is this sort of coordinate specification documented somewhere? How should it look without macros? – Caramdir Jun 2 '11 at 17:31
@Caramdir Ah that was me being clever and cutting up one file into two different MWEs... – Seamus Jun 2 '11 at 17:35
It's not enough but you need to define the nodes a, b and c and to add \usepackage{tikz} \usetikzlibrary{intersections} . Then I think the parser is not very happy with \apath because after intersection of a node is necessary and with name intersections={of= ....} you need to use names for the paths. I'm not a great expert of tikz'syntax so I think other user can help you to find how to do this only with Tikz. An idea is to use \pgfextra and to modify your macro to name the paths. – Alain Matthes Jun 2 '11 at 17:55

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!).

-

The arguments in the intersection coordinate system are not parsed as paths. So you have to return the correct syntax from your macro (including the right amount of spaces, i.e. none):

\documentclass[border=2em]{standalone}
\usepackage{tikz}
\usetikzlibrary{calc}
\newcommand\apath[2]{
#1--#2% no additional space at end
}
\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}


Note that this syntax seems to be depreciated in TikZ v2.10 in favor of the more flexible (though more verbose) name path/name intersections syntax.

-
So maybe the intersections thing was a bad example because of its weird (and now deprecated) syntax... What about getting a macro to return a coordinate, for example? – Seamus Jun 2 '11 at 18:02
@Seamus: Again, depends on the way you want to use it. If you want to stay on the level of TikZ, you should just add a named node or return it in (x,y) syntax. If you want to go to the level of PGF you either return a \pgfpoint or set \pgf@x and \pgf@y to your point. – Caramdir Jun 2 '11 at 18:05

Well, something else is screwy with your MWE, that syntax for intersections doesn't seem to work in 2.10 and I can't find any documentation for it. (There is a much more complex syntax for arbitrary intersections.)

But what you want to do is actually very simple (I think; on preview Andrew Stacey's answer has confused me a bit). The thing to remember is that tikz is embedded in a macro language, so its not a matter of what the function returns, but rather what text it expands to. It is easy to create macros that expand to parts of tikz path expressions. Here is a simple example along the lines of what you wanted without the intersection stuff.

\documentclass{article}
\usepackage{tikz}

\begin{document}

\newcommand\apath[2]{(#1) -- (#2)}

\begin{tikzpicture}
\node at (0,0) (a) {a};
\node at (0,1) (b) {b};
\node at (1,1) (c) {c};
\draw \apath{a}{b} \apath{b}{c} \apath{a}{c};
\end{tikzpicture}

\end{document}


This version of \apath can appear anywhere that a path description can appear.

One thing to keep in mind is that there are many things you can't do in just subpaths, so sometimes with sufficiently complex macros it becomes tricky to get a macro of this type that doesn't have terminated paths as parts, and then any options you pass in the containing draw don't apply to the whole result of the macro, and it becomes a mess. For instance, IIRC filling and decorations apply across an entire path and any subpaths if you have them anywhere in the path expression. It is still possible in these cases though to have the macro start without a \draw command and end without a ;, so you can superficially appear to embed it in a path, it is just the path options that are an issue.

-
Yes I realise now that I'm using the now deprecated 2.00 intersections syntax, but that's not what the question is supposed to be about. And I think that what I wanted to happen is actually what happens in most cases. It's just that than intersections syntax is super-weird... – Seamus Jun 2 '11 at 18:04
@seamus right, that's why I tried to construct an example of what you had in mind that ignores the intersection stuff -- but is this what you had in mind? – kgr Jun 2 '11 at 18:09

A possibility but I don't like this code is

\documentclass[border=2em]{standalone}
\usepackage{tikz}
\usetikzlibrary{intersections}
\newcommand\apath[3]{
\path[name path=#3] (#1) -- (#2);
}
\begin{document}
\begin{tikzpicture}
\node (a) at (0,3){};
\node (b) at (4,0){};
\node (c) at (1,-2){};
\node (d) at (4,3){};
\draw (a)--(b);
\draw (d)--(c);
\draw
\pgfextra{\apath{a}{b}{pathone}
\apath{d}{c}{pathtwo}}
[ name intersections={of=pathone and pathtwo},red] (intersection-1) circle (2pt) ;
\end{tikzpicture}
\end{document}

-
Except for the use of \pgfextra, this code is not interesting. Better is to name the path before \draw[name path= ...] (a)--(b);. – Alain Matthes Jun 2 '11 at 18:17
this is what I was trying to do, but I still have pgf 2.00 and the name path syntax is new to 2.10. I will have to upgrade soon! – Seamus Jun 2 '11 at 18:37
Yes there are a lot of interesting things in pgf 2.10. I created tkz before this version and I need to update all my packages, and for example name path is very useful but I prefer to keep my methods to calculate intersections like circle and circle. – Alain Matthes Jun 2 '11 at 20:01