# Easy curves in TikZ

I find myself needing to draw lots of elegantly curved paths in TikZ. Ideally, I'd just specify a series of points, and TikZ would calculate the extra data itself to draw a nice series of curves passing smoothly through these points, perhaps with an optional "looseness" parameter that I could specify. But the only way I can find to draw nice curves is by explicitly giving control points, or by manually specifying in and out angles.

I can think up a simple algorithm to do this, which would certainly be within TikZ's power to perform: just choose the in and out angles in a simple fashion based on the relative angles between each adjacent pair of line segments.

Is something like this already built-in? Or can someone cook something up that does the job?

Edit: Jake has provided an answer using the plot [smooth] functionality. This is almost perfect! But it can't do what I need, because it doesn't let me specify tangent angles manually where needed, which is especially important at the beginning and end of the curve. I would have thought this would be a natural and straightforward addition to the existing plot [smooth] algorithm: for every coordinate, an optional angle should be able to be specified as an argument, which if supplied is treated as the tangent angle for the curve at that point. And while we're at it, it wouldn't hurt to also allow the tension to be modified along the path.

A minimal extension to the algorithm would just accept two optional parameters, for the curve tangent at the beginning and end.

-
Excellent question. But, it would be good to show a complete example via a MWE that illustrates your current solution. This would give those trying to help something to start with. –  Peter Grill Nov 3 '11 at 23:27
Hi Peter. I have no current solution - I can imagine how a simple algorithm would work that could solve this problem, as I suggest in the second paragraph, but it would be beyond my skills to implement it. –  Jamie Vicary Nov 3 '11 at 23:36
I meant your current solution of specifying the control points manually, with in and out angles. Plus perhaps you could show an example syntax of what you would like to see. –  Peter Grill Nov 3 '11 at 23:39
To me it seems as if you would like to have B-spline interpolation combined with special boundary conditions. With B-splines, you get "natural" smoothness along the path, and the missing degrees of freedom are specified as boundary conditions. If a B-spline does strange things somewhere, you can increase the number of samples in your experiment to get a correct solution. Note that Jake's answer and the current implementation of plot[smooth] actually are a kind of cubic spline interpolation (but with low-quality, hard-coded conditions). What I wanted to say is: pose a feature request. –  Christian Feuersänger Nov 4 '11 at 8:47
I implemented Jonh Hobby's algorithm (used in MetaPost) in pure python. It works for cyclic and non-cyclic paths, but only for "default" paths which do not include any boundary condition (angles at start or end, tension at each point, etc.) However it is not difficult to expand to conver these cases too, since the "infrastructure" is set up. More information at tex.stackexchange.com/questions/54771/… –  JLDiaz May 10 '12 at 10:48

You can use the \draw plot [smooth] coordinates {<coordinate1> <coordinate2> <coordinate3> ...}; syntax, which uses an algorithm similar to the one you described.

The looseness is controlled by the tension parameter. If you want to close the line, you can use [smooth cycle] instead of smooth:

\documentclass{article}

\usepackage{tikz}

\begin{document}
\begin{tikzpicture}
\draw [gray!50]  (0,0) -- (1,1) -- (3,1) -- (1,0)  -- (2,-1) -- cycle;
\draw [red] plot [smooth cycle] coordinates {(0,0) (1,1) (3,1) (1,0) (2,-1)};

\draw [gray!50, xshift=4cm]  (0,0) -- (1,1) -- (2,-2) -- (3,0);
\draw [cyan, xshift=4cm] plot [smooth, tension=2] coordinates { (0,0) (1,1) (2,-2) (3,0)};
\end{tikzpicture}
\end{document}


The smooth algorithm is quite simple: it sets the support points so that the tangent at each corner is parallel to the line from the previous to the next corner. The distance of the support points to the corner is the same in either direction, and proportional to the distance from the previous corner to the next corner. The tension is used as a multiplier for the support point distance. It can not be changed along the curve, and neither can the starting and finishing angles of the line be specified. The algorithm can be found in pgflibraryplothandlers.code.tex as \pgfplothandlercurveto.

\documentclass{article}

\usepackage{tikz}
\usetikzlibrary{decorations.pathreplacing,shapes.misc}

\begin{document}
\begin{tikzpicture}
\tikzset{
show curve controls/.style={
decoration={
show path construction,
curveto code={
\draw [blue, dashed]
(\tikzinputsegmentfirst) -- (\tikzinputsegmentsupporta)
node [at end, cross out, draw, solid, red, inner sep=2pt]{};
\draw [blue, dashed]
(\tikzinputsegmentsupportb) -- (\tikzinputsegmentlast)
node [at start, cross out, draw, solid, red, inner sep=2pt]{};
}
}, decorate
}
}

\draw [gray!50]  (0,0) -- (1,1) -- (3,1) -- (1,0)  -- (2,-1) -- cycle;
\draw [show curve controls] plot [smooth cycle] coordinates {(0,0) (1,1) (3,1) (1,0) (2,-1)};
\draw [red] plot [smooth cycle] coordinates {(0,0) (1,1) (3,1) (1,0) (2,-1)};

\draw [gray!50, xshift=4cm]  (0,0) -- (1,1) -- (3,-1) -- (5,1) -- (7,-2);
\draw [cyan, xshift=4cm] plot [smooth, tension=2] coordinates { (0,0) (1,1) (3,-1) (5,1) (7,-2)};
\draw [show curve controls,cyan, xshift=4cm] plot [smooth, tension=2] coordinates { (0,0) (1,1) (3,-1) (5,1) (7,-2)};
\end{tikzpicture}
\end{document}


Here is a slightly modified version of the plothandler, which allows you to specify the first and last support point using the TikZ key first support={<point>} and last support={<point>}, where <point> can be any TikZ coordinate expression, such as(1,2), (1cm,2pt), (A.south west), ([xshift=1cm] A.south west) (thanks to Andrew Stacey's wonderful answer to Extract x, y coordinate of an arbitrary point in TikZ).

By default, the points are assumed to refer to coordinates relative to the first/last point of the path. You can specify that the support points are given as absolute coordinates by using the keys absolute first support, absolute last support, or absolute supports.

 \documentclass{article}

\usepackage{tikz}
\usetikzlibrary{decorations.pathreplacing,shapes.misc}

\begin{document}
\begin{tikzpicture}
\tikzset{
show curve controls/.style={
decoration={
show path construction,
curveto code={
\draw [blue, dashed]
(\tikzinputsegmentfirst) -- (\tikzinputsegmentsupporta)
node [at end, cross out, draw, solid, red, inner sep=2pt]{};
\draw [blue, dashed]
(\tikzinputsegmentsupportb) -- (\tikzinputsegmentlast)
node [at start, cross out, draw, solid, red, inner sep=2pt]{};
}
}, decorate
}
}

\makeatletter
\newcommand{\gettikzxy}[3]{%
\tikz@scan@one@point\pgfutil@firstofone#1\relax
\edef#2{\the\pgf@x}%
\edef#3{\the\pgf@y}%
}

\newif\iffirstsupportabsolute
\newif\iflastsupportabsolute

\tikzset{
absolute first support/.is if=firstsupportabsolute,
absolute first support=false,
absolute last support/.is if=lastsupportabsolute,
absolute last support=false,
absolute supports/.style={
absolute first support=#1,
absolute last support=#1
},
first support/.code={
\gettikzxy{#1}{\pgf@plot@firstsupportrelx}{\pgf@plot@firstsupportrely}
},
first support={(0pt,0pt)},
last support/.code={
\gettikzxy{#1}{\pgf@plot@lastsupportrelx}{\pgf@plot@lastsupportrely}
},
last support={(0pt,0pt)}
}

\def\pgf@plot@curveto@handler@initial#1{%
\pgf@process{#1}%
\pgf@xa=\pgf@x%
\pgf@ya=\pgf@y%
\pgf@plot@first@action{\pgfqpoint{\pgf@xa}{\pgf@ya}}%
\xdef\pgf@plot@curveto@first{\noexpand\pgfqpoint{\the\pgf@xa}{\the\pgf@ya}}%
\iffirstsupportabsolute
\pgf@xa=\pgf@plot@firstsupportrelx%
\pgf@ya=\pgf@plot@firstsupportrely%
\else
\fi
\xdef\pgf@plot@curveto@firstsupport{\noexpand\pgfqpoint{\the\pgf@xa}{\the\pgf@ya}}%
\global\let\pgf@plot@curveto@first@support=\pgf@plot@curveto@firstsupport%
\global\let\pgf@plotstreampoint=\pgf@plot@curveto@handler@second%
}

\def\pgf@plot@curveto@handler@finish{%
\ifpgf@plot@started%
\pgf@process{\pgf@plot@curveto@second}
\pgf@xa=\pgf@x%
\pgf@ya=\pgf@y%
\iflastsupportabsolute
\pgf@xa=\pgf@plot@lastsupportrelx%
\pgf@ya=\pgf@plot@lastsupportrely%
\else
\fi
\pgfpathcurveto{\pgf@plot@curveto@first@support}{\pgfqpoint{\the\pgf@xa}{\the\pgf@ya}}{\pgf@plot@curveto@second}%
\fi%
}
\makeatother

\coordinate (A) at (2,-1);

\draw [gray!50]  (-1,-0.5) -- (1.5,1) -- (3,0);
\draw [
cyan,
postaction=show curve controls
] plot [
smooth, tension=2,
absolute supports,
first support={(A)},
last support={(A)}] coordinates { (-1,-0.5) (1.5,1) (3,0)};

\draw [
yshift=-3cm,
magenta,
postaction=show curve controls
] plot [
smooth, tension=2,
first support={(-0.5cm,1cm)},
last support={(0.5cm,1cm)}] coordinates { (-1,-0.5) (1.5,1) (3,0)};

\end{tikzpicture}
\end{document}

-
Oh, wonderful... thanks very much for this. Can I set the tension for each curve segment independently? And is there a way to specify the angles at the start and end of the curve? –  Jamie Vicary Nov 3 '11 at 23:56
I wonder what algorithm it's using. The one I had in mind was to choose the tangent of the curve at each vertex such that the incoming straight lines from adjacent vertices subtend the same angles. But you can see by eye it's not doing that. –  Jamie Vicary Nov 3 '11 at 23:59
Having played around with this, it does the job I need very well - except I really need to be able to specify the initial and final tangent angles for the ends of the curve. –  Jamie Vicary Nov 4 '11 at 0:16
Thanks very much your updated answer, Jake. I've edited my question in response. –  Jamie Vicary Nov 4 '11 at 0:38
@JamieVicary: I've edited the answer to use Andrew Stacey's code for specifying TikZ coordinates, and added keys for specifying whether the support coordinates are absolute or relative to the end points. –  Jake Nov 6 '11 at 11:39

Another option could be the to operation. With this you can specify the angles but it’s maybe less automatic than the plot using solution that Jake presented.

\documentclass{minimal}

\usepackage{tikz}

\begin{document}
\begin{tikzpicture}
\draw [ultra thick,red] (-2,2) to[out=45,in=115] (1,1) to[out=-180+115,in=10] (-5,-3);
\end{tikzpicture}
\end{document}


-
I think this is the approach Jamie is referring to with his sentence "...the only way I can find to draw nice curves is by explicitly giving control points, or by manually specifying in and out angles.", which is not automatic enough. –  Jake Nov 6 '11 at 20:54
Yes, that was the approach I was referring to, Jake - but thanks anyway, Tobi! Good to have lots of examples of how to draw curves in the same thread. –  Jamie Vicary Nov 6 '11 at 22:27
@JamieVicary: Sorry, I read over that … O:-) –  Tobi Nov 6 '11 at 22:46