Implementing a syntax: Smooth curves with specified points and tangents

Note: the following question is similar to the final version of this question, but none of the answers to that question appear to answer my question.

Often, I find it desirable to specify a curve by specifying that it passes through certain points with certain tangents. For instance, I might want to specify that a curve starts at (0,0) with at ten-degree angle and passes through (1,1) at a 70 degree incline, (2,2) at a 0-degree incline, and (3,0) at a -50 degree incline (in that order). The following code accomplishes this:

\documentclass[tikz]{standalone}
\begin{document}
\begin{tikzpicture}
\draw (0,0) to[out=10,in=70-180] (1,1)%
to[out=70,in=0-180] (2,2)%
to[out=0,in=-50-180] (3,0);
\end{tikzpicture}
\end{document} However, I find this awkward, for several reasons, one of which is most significant: if I want to fiddle with the tangent at a point, I have to change two numbers rather than just one.

What I'd like to be able to write is something more like the following:

\begin{tikzpicture}
\draw (0,0) to[start angle=10,next angle=70] (1,1)%
to[next angle=0] (2,2)%
to[next angle=-50] (3,0);
\end{tikzpicture}


This syntax avoids redundancies and is more intuitive, at least to me. Unfortunately, my attempts to implement it using \pgfkeys type commands have so far been unsuccessful.

How would one go about implementing something like the above? (Bonus points, figuratively speaking, for avoiding the use of low-level TeX commands.)

• Very good question! Once again an answer makes tikz closer to Metapost in terms of features! – mbork Nov 6 '12 at 6:32

Solution A

This solution provides two TikZ styles:

• next angle that does what you want:
• using the provided angle advanced by 180 degrees as the in angle (at the next coordinate)
• using the angle saved the last time next angle was used as the out angle
• saving the provided next angle angle in the \nextAngle macro, but so that it gets set after the current to path is calculated and the macro is available for the next to path.
• start angle is simply a clone of out. Additionally it sets \nextAngle so that it can be used before next angle.
The order next anglestart angle/out does work, too, but not outnext angle.

Code

\documentclass[tikz]{standalone}
\def\nextAngle{0}
\tikzset{
next angle/.style={
in=#1+180,
out=\nextAngle,
prefix after command= {\pgfextra{\def\nextAngle{#1}}}
},
start angle/.style={
out=#1,
nangle=#1,
},
nangle/.code={% used only internally
\def\nextAngle{#1}
}
}
\begin{document}
\begin{tikzpicture}
\draw (0,0) to[out=10, in=70-180] (1,1)
to[out=70, in=0-180]  (2,2)
to[out=0, in=-50-180] (3,0);
\end{tikzpicture}
\begin{tikzpicture}
\draw (0,0) to[start angle=10,next angle=70] (1,1)
to[next angle=0]                 (2,2)
to[next angle=-50]               (3,0);
\end{tikzpicture}
\end{document}


Output Solution B (“Area 51”)

This solution provides some TikZ styles:

• start angle is only a clone of out.
• next angle does what you want:
• using the provided angle advanced by 180 degrees as the in angle (at the next coordinate)
• saving the provided next angle angle to be used at the next to path as the out angle (this has great repercussions, because it also sets the curve to style—which is the underlying to path of out and in—in a way that it is used even at tos that are not specified in any way).
• last angle is a clone of in but with the customization that 180 degrees are added. In addition the style line to will be set so that the next to will revert to its normal behaviour (see note above).
• last angle simple is like last angle except that line to will not be set.

Note: The default to is the same as -- (meaning: whatever my code does, -- will always work).

The differences between the last three styles may better be seen in the example given below.

I fear that this approach does mess too much with to path and curve to. Therefore, I think that solution A is better.

Code

\documentclass[tikz]{standalone}
\def\nextAngle{0} % old solution
\tikzset{
next angle/.style={
in=#1+180,
prefix after command={\pgfextra{\tikzset{out=#1}}}
},
start angle/.style={out=#1},
last angle/.style={
in=#1+180,
prefix after command={\pgfextra{\tikzset{line to}}} % revert to default to path
},
last angle simple/.style={in=#1+180}
}
\begin{document}
\begin{tikzpicture}
\fill[black!15] (-.5,-1) rectangle (4.5,4);
\coordinate (a) at (0,0); \coordinate (b) at (1,1);
\coordinate (c) at (2,2); \coordinate (d) at (3,0);
\coordinate (e) at (3,3);

\draw[very thick,black] (a) to[start angle=10,next angle=70] (b)
to[next angle=0]                 (c)
to[next angle=-50]               (d)
to                               (e); % uses out=-50
\draw[green!70!black] (a) to[start angle=10,next angle=70] (b)
to[next angle=0]                 (c)
to[last angle=-50]               (d)
to                               (e);
\draw[blue] (a) to[start angle=10,next angle=70] (b)
to[next angle=0]                 (c)
to[last angle simple=-50]        (d) to (e);
\foreach \c in {a,...,e}{
}
\end{tikzpicture}
\end{document}


Output

Legend:

• thick, black: next angle=-50 at the second to last to
• blue: last angle=-50 at the second to last to
• green: last angle simple=-50 at the second to last to • You have not only realized my notation, but done so with a detailed analysis and using code that I have some hope of understanding. Much thanks! – Charles Staats Nov 6 '12 at 21:05

Here's a solution using a different method of generating the smooth curves: Hobby's algorithm from Curve through a sequence of points with Metapost and TikZ (in this particular case, because the tangencies are specified on every single point, the full algorithm isn't used but only the definition of the path from the control points and angles). However, it needs the latest version of the code which is currently at the TeX-SX Launchpad (download hobby.dtx and run tex hobby.dtx to generate the files).

One thing I like about the syntax of this solution is that the tangencies are specified at the points to which they belong. Specifying the angles relies on the fact that, due to the implementation of the algorithm, the coordinate options end up being read twice: once at the end of the previous segment and once at the start of the current one. So we set the out angle if one hasn't already been set (meaning we're at the start) and set the in angle to 180+<angle> (this is pretty safe to always set). The line hobby action={\def\outangle{}} resets the flag so we know to set the out action next time.

\documentclass{article}
%\url{https://tex.stackexchange.com/q/81604/86}
\usepackage{tikz}
\usetikzlibrary{hobby}

\def\outangle{}
\tikzset{
tangent/.style={%
in angle={(180 + #1)},
maybe out angle=#1,
hobby finish,
hobby action={\def\outangle{}},
},
maybe out angle/.code={%
\ifx\outangle\empty
\pgfkeysalso{out angle=#1}%
\def\outangle{t}%
\fi
}
}

\begin{document}
\begin{tikzpicture}[use Hobby shortcut]
\draw ([tangent=10]0,0) .. ([tangent=70]1,1) .. ([tangent=0]2,2) .. ([tangent=-50]3,0);
\end{tikzpicture}

\end{document}


For comparison, here's the various curves. Here's a fix for the issue with when not all tangents are set:

\newif\ifhobbyatend
\hobbyatendtrue
\tikzset{
tangent/.style={%
maybe in angle={(180 + #1)},
maybe out angle=#1,
hobby finish,
hobby action={\global\hobbyatendfalse}
},
maybe out angle/.code={%
\ifhobbyatend
\else
\pgfkeysalso{out angle=#1}%
\global\hobbyatendtrue
\fi
},
maybe in angle/.code={%
\ifhobbyatend
\pgfkeysalso{in angle=#1}%
\fi
}
}


(NB In the next upload to Launchpad - which hasn't happened at time of writing but will soon - I've changed keys with hobby to Hobby to make it clearer that this is someone's name. So if LaTeX complains about unknown keys with hobby in them, try with Hobby.)

• I am accepting Qrrbrbirlbel's answer instead of this one because his/her answer will work, with no modifications, with any up-to-date full TeXLive installation, and because I have much more chance of learning from his/her code, at least in the short term. Nevertheless, this is a superb answer, with syntax superior to what I suggested in the question. I hope it receives many upvotes and ultimately gets incorporated into a package that is universally available (even to people who avoid the command line like the plague). – Charles Staats Nov 6 '12 at 21:11
• @CharlesStaats Completely agree with your reasoning. One thing I like about this site is the proliferation of alternative solutions and that's what this was intended as. (I do intend uploading these changes to the hobby package fairly soon whereupon they will be available through TeXLive, but "fairly soon" is a bit like a TeX \skip: lots of flexibility.) – Loop Space Nov 6 '12 at 22:28
• I managed to get your solution working, but noticed something else that is probably not an intended feature: if the tangent option is used for at least one point (but not all of them), the first and last points of the path automatically have horizontal tangents imposed. – Charles Staats Nov 17 '12 at 19:39
• @CharlesStaats Thanks for the bug report. I'll investigate. – Loop Space Nov 20 '12 at 10:05
• @CharlesStaats I think I've fixed that. This particular code feels a bit fragile - it depends on something being executed a couple of times (turns out that it is three times) and doing something the first (two) times and something else on the third. Hopefully it's not too fragile, though. – Loop Space Nov 21 '12 at 22:05