212

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.

6
  • 1
    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. Nov 3, 2011 at 23:27
  • 2
    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. Nov 3, 2011 at 23:39
  • 4
    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. Nov 4, 2011 at 8:47
  • 3
    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, 2012 at 10:48
  • 3
    For the record: Andrew Stacey's hobby package is much much better and is pure TikZ. Thanks to @JLDiaz for pointing to the right question. May 21, 2015 at 12:03

5 Answers 5

243

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
    \advance\pgf@xa by\pgf@plot@firstsupportrelx%
    \advance\pgf@ya by\pgf@plot@firstsupportrely%
  \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
      \advance\pgf@xa by\pgf@plot@lastsupportrelx%
      \advance\pgf@ya by\pgf@plot@lastsupportrely%
    \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}
9
  • 2
    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? Nov 3, 2011 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. Nov 3, 2011 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. Nov 4, 2011 at 0:16
  • Thanks very much your updated answer, Jake. I've edited my question in response. Nov 4, 2011 at 0:38
  • 1
    Is it possible to specify points on the resulting curve at arbitrary positions? Say the midpoint or something like that?
    – Dror
    Apr 11, 2014 at 7:59
46

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}

result

3
  • 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, 2011 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. Nov 6, 2011 at 22:27
  • @JamieVicary: Sorry, I read over that … O:-)
    – Tobi
    Nov 6, 2011 at 22:46
24

I was surprised that no one used the "bend" option. Here is the code:

\documentclass[12pt]{article}
\usepackage{amsmath}
\usepackage{tikz}


\begin{document}

\begin{tikzpicture}
  \coordinate (O) at (0,0,0);
  \coordinate (A) at (3,0,0);

  \draw[] (O)--(A);
  \draw[color=red] (O) to [bend left=10] (A);
  \draw[color=red] (O) to [bend right=10] (A);
  \draw[color=blue] (O) to [bend left=30] (A);
  \draw[color=blue] (O) to [bend right=30] (A);
  \draw[color=green] (O) to [bend left=50] (A);
  \draw[color=green] (O) to [bend right=50] (A);
  \draw[color=yellow] (O) to [bend left=70] (A);
  \draw[color=yellow] (O) to [bend right=70] (A);
  \draw[color=orange] (O) to [bend left=90] (A);
\end{tikzpicture}


 \end{document}

Here the figure: Using bend to joint points

I ran this with "lualatex"

This is an extremely useful function. There are many occasions when we need to connect two points and there is not a simple equation or arc function to use between points. This comes quite handy in this context. For example check the following post

curve triangles on a sphere

The red and blue triangles in the bottom picture are faked with the "bend" function. It is hard to find an analytic equation that represent them. Without the "bend" function you would obtain something like the equivalent figure in this post: flat edged triangles on a sphere

While it is true that the connection of two curves using "bend" is not as smooth as a spline (unless the curvature is preserved) this is precisely a point of favor when we want to do certain type of curves which do not require smoothness. The best example I can think of are lunes in a sphere or triangles in a sphere as shown in the link above.

H.

5
  • 2
    bend is fine for two points, but it isn't all that helpful for when you have a list of more points that you want the curve to go through. Nov 20, 2015 at 17:50
  • 1
    @LoopSpace : From the TiKz/PGF Manual: bend left= angle This option sets out= angle ,in=180 − angle ,relative. If no angle is given, the last given bend left or bend right angle is used. That is, this is not very different from (A) to [out=alpha, in=beta] paths above. The default is that between two consecutive points the path will bend with the same curvature. This is as smooth as it can be. Nov 20, 2015 at 18:14
  • @LoopSpace: I believe I see your point. The connection between two points is quite smooth, but when you go for the third point smoothness will not be preserved as well as with splines, and there the "plot" "smooth" "tension" elements are the right call. Thanks. Nov 20, 2015 at 18:30
  • 1
    That's right. It's a bit buried in the comments on the original question, but there was a later question on Hobby's algorithm which gives a better solution. See tex.stackexchange.com/questions/54771/… Nov 20, 2015 at 18:44
  • I was not aware of the Hobby's algorithm. This is a great pointer and I appreciate your advice. Nov 21, 2015 at 17:38
5

Solution with Rounded Corners

It may have occured that you wanted to draw a smooth line in tikz, by using coordinates and by repeatedly improving the outcome. This can be achieved with \draw[rounded corners].

At some point I wanted to draw a smooth looking line, see the picture below. I first tried using tikz's \draw[smooth] option which was mentioned in earlier answers here. But it often created loops and it wasn't obvious to me how I could improve the intermediate results.

I came across the \draw[rounded corners] option here.

Defining a path

The following code produces a path:

\documentclass[border=5]{standalone}
\usepackage{tikz}
\begin{document}
\begin{tikzpicture}[scale=0.4]
\draw[thick,densely dotted, blue,rounded corners=0.4mm] (0,1)--(-0.1,1)--(-0.4,0.87)--(-0.6,0.83)--(-0.8,0.87)--(-1,1.04)--(-1.4,0.9)--(-1.6,1.1)--(-1.8,1.1)--(-1.8,0.8)--(-1.66,0.7)--(-1.68,0.4)--(-1.84,0.24)--(-1.8,0)--(-1.6,0)--(-1.4,0.11)--(-1.2,0.15)--(-1,0.11)--(-0.8,0)--(-0.6,-0.05)--(-0.4,0.02)--(-0.2,0.07)--(0,0);
\fill [black] (0,1) circle (0.1);
\fill [black] (0,0) circle (0.1);
\end{tikzpicture} 
\end{document}

enter image description here

Scaling

After making a path this way, there are two parameters that can be changed, scale and the rounded corner's width. To both scale the image and keep it smooth, it turns out neccesary to change both the scaling factor and the corner width. In this case after increasing the scale more dots are created.

For instance, in the images below I set scale=1 and rounded corners=0.4mm, alongside scale=1 and rounded corners=1mm.

enter image description here enter image description here

Conclusion

Making curves this way could take substantial time. With a picture in mind (maybe drawn on paper) and having chosen a suitable corner width, you will have to define and adjust the coordinates so as to obtain a smooth, good looking curve. For those who have to draw a lot of these curves this method might not be very practical.

If you have to draw just a single curved line this method will work just fine.

4

This is a link-only answer because, frankly, this link does not deserve to be buried in comments as it is now. Since this question was asked, a similar question at a later date stimulated the creation of a new package. You specify the points, optionally with angles, tension changes etc., and the library calculates a smooth curve using the Hobby algorithm. It does a better job than TikZ's smooth, is much easier than figuring out control points etc. by hand and generally just makes something quite tricky easily tractable.

Examples and introduction to the hobby library are covered in responses to Curve through a sequence of points with Metapost and TikZ.

You must log in to answer this question.

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