TeX - LaTeX Stack Exchange is a question and answer site for users of TeX, LaTeX, ConTeXt, and related typesetting systems. It's 100% free, no registration required.

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

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.

share|improve this question
    
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
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. – Peter Grill Nov 3 '11 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. – Christian Feuersänger Nov 4 '11 at 8:47
2  
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
up vote 116 down vote accepted

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}
share|improve this answer
1  
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

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.

share|improve this answer
    
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. – Loop Space Nov 20 '15 at 17:50
    
@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. – Herman Jaramillo Nov 20 '15 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. – Herman Jaramillo Nov 20 '15 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/… – Loop Space Nov 20 '15 at 18:44
    
I was not aware of the Hobby's algorithm. This is a great pointer and I appreciate your advice. – Herman Jaramillo Nov 21 '15 at 17:38

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

share|improve this answer
    
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

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.