61

[Note: the system didn't like the word 'you' in my question. It prefers 'one'. Go figure.]

When I first used TikZ, I struggled to draw curves using control points.

\documentclass[tikz,border=10pt,multi]{standalone}

\begin{document}

\begin{tikzpicture}

  \draw (0,0) .. controls (1,1) and (2,-1) .. (3,0);

\end{tikzpicture}

\end{document}

This works great if I happen to want a curve like this

curve

But if I want a curve like this

curve

I have no idea how to figure out how to substitute for the question marks

  \draw (0,0) .. controls (??,??) and (??,??) .. (3,0);

Note that I know the two images look identical. That is because they are the same image. That's my point.

When I discovered TikZ offered other ways to draw curves, I essentially gave up on Bézier curves altogether.

But in many cases, Bézier curves look nicer and Wikipedia assures me that they can be controlled 'intuitively' by control points which is why they are so popular in drawing computer graphics.

I understand that what Wikipedia probably has in mind is a GUI, where the curves can be controlled more-or-less intuitively by adjusting control points. (I take it this is what I do in GIMP, for example.)

But how can I figure out which control points to specify in order to draw a particular curve without doing it by mere trial-and-error?

Indeed, how can I decide when to draw a curve using this method rather than one of the other curve-drawing methods TikZ provides?

I'm not asking how to calculate precise values. I want to know how to get an intuitive idea of roughly what might be about right. I want to have a sense of when doing it this way rather than some other way might make sense.

13
  • 1
    I've always consider the control point as points that "pull the path towards them". See this examples: \documentclass{article} \usepackage{tikz} Commented Jul 14, 2015 at 1:24
  • 1
    \begin{document} \begin{tikzpicture} \draw[help lines] (0,0) grid (8,3); \draw (1,0) .. controls (4,2) .. (7,0); \end{tikzpicture} \begin{tikzpicture} \draw[help lines] (0,0) grid (8,3); \draw (1,0) .. controls (3,2) and (5,2) .. (7,0); \end{tikzpicture} \begin{tikzpicture} \draw[help lines] (0,0) grid (8,3); \draw[overlay] (1,0) .. controls (10,2) and (-2,2) .. (7,0); \end{tikzpicture} \begin{tikzpicture} \draw[help lines] (0,-1) grid (8,1); \draw (1,0) .. controls (3,2) and (5,-2) .. (7,0); \end{tikzpicture} \end{document} Commented Jul 14, 2015 at 1:24
  • 1
    The third one is particularly good to explain why I meant with "pull toward them" you start in (1,0) then the path is pull towards (10,2) and then it goes backwards when it is pulled towards (-2,2) and finally it heads to (7,0). I immediately see the loop forming in my head :) Commented Jul 14, 2015 at 1:27
  • 1
    Make the same mental exercise for the other examples and you'll get an intuition on control points. Think of a control point as "pulling the path towards it". Sorry about these lonh chain of comments. Commented Jul 14, 2015 at 1:28
  • 1
    Then, if I understand you, yes, a point which is further away pulls the curve more or "stronger". Commented Jul 14, 2015 at 1:52

6 Answers 6

66

The analogy that I've always used is to think of control points as points that "pull the path towards them". In the examples below, control points are drawn using the same color than the corresponding path in which they were used; when there are two control points, the number below the point indicates which one goes first in the code.

enter image description here

The example with the loop is particularly good to explain why I meant with "pull the curve towards them": the path starts in (1,0) then the path is pulled towards (10,2) and then it goes backwards when it is pulled towards (-2,2) and finally it heads towards (7,0). Immediately you can see the loop forming in your head.

The code for the image:

\documentclass[parskip=full]{scrartcl}
\usepackage[margin=2cm,paperheight=40cm]{geometry}
\usepackage{tikz}

\newcommand\DrawControl[3]{
  node[#2,circle,fill=#2,inner sep=2pt,label={above:$#1$},label={[black]below:{\footnotesize#3}}] at #1 {}
}

\pagestyle{empty}

\begin{document}
\centering

One control point:\\
\begin{tikzpicture}[baseline]
\draw[help lines] (0,0) grid (8,5);
\draw[ultra thick] 
  (1,0) 
    .. controls (4,0) .. 
  (7,0) \DrawControl{(4,0)}{black}{};  
\draw[ultra thick,blue] 
  (1,0) 
    .. controls (4,2) .. 
  (7,0) \DrawControl{(4,2)}{blue}{};  
\draw[ultra thick,red] 
  (1,0) 
    .. controls (4,6) .. 
  (7,0) \DrawControl{(4,6)}{red}{};  
\end{tikzpicture}\hfill
\begin{tikzpicture}[baseline]
\draw[help lines] (0,0) grid (8,5);
\draw[ultra thick] 
  (1,0) 
    .. controls (2,0) .. 
  (7,0) \DrawControl{(2,0)}{black}{};  
\draw[ultra thick,blue] 
  (1,0) 
    .. controls (2,2) .. 
  (7,0) \DrawControl{(2,2)}{blue}{};  
\draw[ultra thick,red] 
  (1,0) 
    .. controls (2,6) .. 
  (7,0) \DrawControl{(2,6)}{red}{};  
\end{tikzpicture}

\rule{\textwidth}{2pt}

Two control points:\\
\begin{tikzpicture}[baseline]
\draw[help lines] (0,0) grid (8,3);
\draw[ultra thick,blue] 
  (1,0) 
    .. controls (3,2) and (5,2) .. 
  (7,0) \DrawControl{(3,2)}{blue}{1}\DrawControl{(5,2)}{blue}{2} ;  
\draw[ultra thick,red] 
  (1,0) 
    .. controls (3,4) and (5,4) .. 
  (7,0) \DrawControl{(3,4)}{red}{1}\DrawControl{(5,4)}{red}{2};  
\end{tikzpicture}\hfill
\begin{tikzpicture}[baseline]
\draw[help lines] (0,0) grid (8,3);
\draw[ultra thick,blue] 
  (1,0) 
    .. controls (2,2) and (6,2) .. 
  (7,0) \DrawControl{(2,2)}{blue}{1}\DrawControl{(6,2)}{blue}{2};  
\draw[ultra thick,red] 
  (1,0) 
    .. controls (2,4) and (6,4) .. 
  (7,0) \DrawControl{(2,4)}{red}{1}\DrawControl{(6,4)}{red}{2};  
\end{tikzpicture}

\rule{\textwidth}{2pt}

\vspace{3cm}

\begin{tikzpicture}[baseline]
\draw[help lines] (0,0) grid (8,3);
\draw[overlay,ultra thick,blue] 
  (1,0) 
    .. controls (10,2) and (-2,2) .. 
  (7,0) \DrawControl{(10,2)}{blue}{1}\DrawControl{(-2,2)}{blue}{2};  
\draw[overlay,ultra thick,red] 
  (1,0) 
    .. controls (12,4) and (-4,4) .. 
  (7,0) \DrawControl{(12,4)}{red}{1}\DrawControl{(-4,4)}{red}{2};  
\end{tikzpicture}

\rule{\textwidth}{2pt}

\begin{tikzpicture}[baseline]
\draw[help lines] (0,-2) grid (8,2);
\draw[ultra thick,blue] 
  (1,0) 
    .. controls (3,2) and (5,-2) .. 
  (7,0) \DrawControl{(3,2)}{blue}{1}\DrawControl{(5,-2)}{blue}{2};  
\draw[ultra thick,red] 
  (1,0) 
    .. controls (-1,5) and (8,-5) .. 
  (7,0) \DrawControl{(-1,5)}{red}{1}\DrawControl{(8,-5)}{red}{2};  
\end{tikzpicture}

\rule{\textwidth}{2pt}

\end{document}

With the examples and, hopefully with the help of the "pull towards" analogy, it should become clear that, in order to get a path like the one in your question, you should use two control points with these characteristics:

  • Both control points should have same absolute value for the y-coordinate (given the symmetry with respect to the x-axis). The y-coordinate for the first control point should be positive (to pull the path upwards), whilst the y-coordinate for the second control point should be negative (to pull the path downwards).

  • The distance between the x-coordinates for the initial point and the first control point should be equal to the one between the x-coordinates for the second control point and the final point (given the symmetry with respect to the "middle" point on the path).

So

\draw (0,0) .. controls (??,??) and (??,??) .. (3,0);

will become something like

\draw (0,0) .. controls (1,2) and (2,-2) .. (3,0);
0
59

Just for fun, here is an animation of some Bézier curves and of their control points.

enter image description here

To get this picture, compile the document below then call convert from imagemagick:

convert -density 150 tikz-bezier-animation.pdf tikz-bezier-animation.gif

The document (tikz-bezier-animation.tex):

\documentclass[tikz]{standalone}

\usetikzlibrary{decorations.pathreplacing,backgrounds}

\tikzset{
  show curve controls/.style={
    decoration={
      show path construction,
      curveto code={
        \draw[#1!50]
        (\tikzinputsegmentfirst)
        -- (\tikzinputsegmentsupporta)
        -- (\tikzinputsegmentsupportb)
        -- (\tikzinputsegmentlast)
        ;
        \fill[#1!50] (\tikzinputsegmentsupporta) circle(1pt);
        \fill[#1!50] (\tikzinputsegmentsupportb) circle(1pt);
        \draw[#1,line width=1pt]
        (\tikzinputsegmentfirst)
        .. controls (\tikzinputsegmentsupporta)
                and (\tikzinputsegmentsupportb) ..
        (\tikzinputsegmentlast);
      }
    },decorate
  }
}

\begin{document}
\foreach \p in {0,10,...,360} {
  \begin{tikzpicture}
    \begin{scope}
      \path (-4,-2) rectangle (4,2.1);
      \coordinate (a) at (-2,0);
      \coordinate (b) at (2,0);
      \path (a) ++(1,0) ++(\p:0 and 2) coordinate (a1);
      \path (b) ++(-1,0) ++({180-\p}:0 and 2) coordinate (b1);
      \draw[show curve controls={red}] (a) .. controls (a1) and (b1) .. (b);
    \end{scope}
    \begin{scope}[yshift=-4.5cm]
      \path (-4,-1) rectangle (4,4);
      \coordinate (a) at (-2,0);
      \coordinate (b) at (2,0);
      \path (a) ++(45:3) ++(\p:3 and 0) coordinate (a1);
      \path (b) ++(90+45:3) ++(180-\p:3 and 0) coordinate (b1);
      \draw[show curve controls={blue}] (a) .. controls (a1) and (b1) .. (b);
    \end{scope}
    \begin{scope}[yshift=-6cm]
      \path (-4,-3) rectangle (4,4);
      \coordinate (a) at (-2,0);
      \coordinate (b) at (2,0);
      \path (a) ++(1,0) [rotate=45] ++(\p:0 and 2) coordinate (a1);
      \path (b) ++(-1,0) [rotate=45] ++({180+\p}:0 and 2) coordinate (b1);
      \draw[show curve controls={green!50!black}]
        (a) .. controls (a1) and (b1) .. (b);
    \end{scope}
    \begin{pgfonlayer}{background}
      \fill[white] (current bounding box.south west)
         rectangle (current bounding box.north east);
    \end{pgfonlayer}
  \end{tikzpicture}
}
\end{document}
1
  • Thanks. I can't actually view the animations for some reason, but I'm sure they are good!
    – cfr
    Commented Jul 14, 2015 at 10:33
43

In addition to other answers, it is worth noting that it is possible to specify the control points relative to the ends points. When used in combination with polar coordinates, this approach gives a slightly more intuitive approach (I think) to visualising how a Bézier curve will turn out.

In particular, it makes it a bit easier to make consecutive Bézier curves appear to be smoothly joined: in most cases it is possible to just add 180 degrees to the angle of the second control point of the previous curve or (as pointed out by Paul Gaborit) use the same angle and reverse the sign of the distance:

\documentclass[tikz,border=5]{standalone}
\usetikzlibrary{decorations.pathreplacing}
\tikzset{%
  show curve controls/.style={
    postaction={
      decoration={
        show path construction,
        curveto code={
          \draw [blue] 
            (\tikzinputsegmentfirst) -- (\tikzinputsegmentsupporta)
            (\tikzinputsegmentlast) -- (\tikzinputsegmentsupportb);
          \fill [red, opacity=0.5] 
            (\tikzinputsegmentsupporta) circle [radius=.5ex]
            (\tikzinputsegmentsupportb) circle [radius=.5ex];
        }
      },
      decorate
}}}
\begin{document}
\begin{tikzpicture}
\draw [help lines] (-4, -1) grid (4, 5);
\draw [show curve controls]
  (-3, 4) .. controls ++(135:-1) and ++(135:1) .. (0, 4);
\draw [show curve controls] (0, 0) 
  .. controls ++(165:-1) and ++(240: 1) .. ( 3, 2)
  .. controls ++(240:-1) and ++(165:-1) .. ( 2, 4)
  .. controls ++(165: 1) and ++(175:-2) .. (-1, 2)
  .. controls ++(175: 2) and ++(165: 1) .. ( 0, 0);
\end{tikzpicture}
\end{document}

enter image description here

6
  • 2
    Instead of (60:1) (in fact (240-180:1)), you may use (240:-1) to use exactly the same angle. Commented Jul 14, 2015 at 7:16
  • @PaulGaborit Even better! I've updated my answer. Commented Jul 14, 2015 at 7:32
  • 2
    This example is super helpful. I've always fought painful battles with bezier curves, but using your example, I just drew one with multiple control points that I actually like. Thanks! Commented Jul 24, 2018 at 16:39
  • This is one of those answers I wish I could upvote more than once. Commented Dec 12, 2023 at 4:50
  • Relative coordinates of control points in polar coordinates is a killer hack. Commented May 17 at 22:17
19

In addition to Gonzalo Medina's answer, I would like to point out that another criterion you could base your drawings on is that the Bézier curve is tangent to the lines going from the first control point to the second and from the second last to the last. This could be something that gives you an idea of the initial and final path of the curve (something like the to [out=...,in=...] notation).

In the following examples I borrowed some of GM's points for comparison purposes.

\documentclass[border=5pt,tikz]{standalone}
\tikzset{
  ctrlpoint/.style={%
    draw=gray,
    circle,
    inner sep=0,
    minimum width=1ex,
  }
}
\newcommand\Bezier[4]{% \bezier (lowercase 'b') was already defined elsewhere
  \node (p1) [ctrlpoint,label=90:$P_1$] at (#1) {};
  \node (p2) [ctrlpoint,label=90:$P_2$] at (#2) {};
  \node (p3) [ctrlpoint,label=90:$P_3$] at (#3) {};
  \node (p4) [ctrlpoint,label=90:$P_4$] at (#4) {};
  \draw [gray] (p1) -- (p2) -- (p3) -- (p4);
  \draw [blue] (#1) .. controls (#2) and (#3) .. (#4);
}
\begin{document}
\begin{tikzpicture}
  \Bezier{0,0}{1,1}{2,-1}{3,0}
  \begin{scope}[xshift=4cm]
    \Bezier{0,0}{9,2}{-2,2}{7,0}
  \end{scope}
  \begin{scope}[yshift=-5cm]
    \Bezier{0,0}{1,3}{2,3}{7,0}
  \end{scope}
  \begin{scope}[xshift=8cm,yshift=-5cm]
    \Bezier{0,0}{-2,4}{4,-1}{5,0}
  \end{scope}
\end{tikzpicture}
\end{document}

enter image description here

If you want to see a naive implementation of a 5 points-based Bézier curve, I posted a question a short while ago; it has a link to a Wikipedia page explaining the algorithm used and demonstrating the "tangency" aforementionend.

1
  • 1
    Thanks for this. This is helpful, too, although I don't find it quite so intuitive as the 'pulling' model. But it does help relate this to GIMP, which is definitely useful as I have some idea of how that works. (It helps link what I see in GIMP with the 'pulling' analogy in other answers.)
    – cfr
    Commented Jul 14, 2015 at 10:33
12

I found picking control points to be such a terrible experience that I wrote some code to construct a Bézier spline through some given points.

You load the library in the usual way: \usetikzlibrary{spline}.

Some examples from the manual (the pictures are taken from the manual so the background color is different):

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{spline}
\begin{document}
\begin{tikzpicture}[thick]
  \draw[help lines] (0,0) grid (3,2);
  \node[draw=red, circle] (A) at (1,0) {A};
  \node[draw=blue, rectangle] (B) at (2,2) {B};
  \node[draw=green!60!black, circle] (C) at (3,1) {C};
  \draw (A) to[spline through={(0,1)(B)(2,1)}] (C);
\end{tikzpicture}
\end{document}

enter image description here

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{spline}
\begin{tikzpicture}[thick]
  \draw[help lines] (0,0) grid (3,2);
  \coordinate (A) at (1,0);
  \coordinate (B) at (2,2);
  \coordinate (C) at (3,1);

  \draw (A) to[spline coordinates=S,
               closed spline through={(0,1)(B)(2,1)(C)}] (A);
  \foreach \i [evaluate=\i as \j using \i+1] in {1,2,...,\tikzsplinesegments} {
    \draw[semithick]  (S K-\i) -- (S P-\i)
                      (S K-\j) -- (S Q-\i);
    \path[fill=green!60!black] (S P-\i) circle (2pt);
    \path[fill=red] (S Q-\i) circle (2pt);
  }
\end{tikzpicture}
\end{document}

enter image description here

So my answer is: I don't! I'd much rather let the computer pick the control points.

8
  • 1
    Thanks. You don't use hobby? Could you make your examples complete? Or show how you're loading the code, at least? To be honest, I try hard to stick to stuff on CTAN (except for my own code), because keeping track is just a nightmare otherwise. Are you planning to upload this? I think hobby is more intuitive, but, of course, it has a manual and so on.
    – cfr
    Commented Mar 18, 2018 at 4:34
  • 2
    I actually didn't know about hobby. Probably would have saved me the effort of implementing this. I haven't decided what to do with my code yet, to be honest, but I did make a manual github.com/stevecheckoway/tikzlibraryspline/releases/download/…
    – TH.
    Commented Mar 18, 2018 at 4:42
  • 1
    Sorry, @cfr, I forgot to @ you. I've been gone from this site so long, I've kinda forgotten how it works.
    – TH.
    Commented Mar 18, 2018 at 4:59
  • 1
    @cfr, come to think of it, I think it's good I didn't know about hobby. It doesn't seem to play nice with nodes as coordinates or nodes placed along the path. Maybe I'll add a comparison section to my manual.
    – TH.
    Commented Mar 18, 2018 at 15:46
  • 1
    You don't actually need the @, I don't think, if only one person has commented (other than you). At least, I don't think so, though I might be misremembering the rules. You can always specify a node's anchor to get a coordinate, of course, but Hobby does want coordinates, I think, as you say. I would expect it to work with a node on the path, though - slightly surprised it doesn't.
    – cfr
    Commented Mar 19, 2018 at 0:37
2

Chỉ cho vui thôi. (Just for fun)

Compile with Asymptote.

import graph;
pair Bezier(pair P[], real t)
{
pair Bezi;
for (int k=0; k <= P.length-1; ++k)
{  Bezi=Bezi+choose(P.length-1,k)*(1-t)^(P.length-1-k)*t^k*P[k]; }
return Bezi;
}
pair BezierRecursion(pair P[], real t){
int n = P.length - 1;
pair Q[][]=new pair[P.length][P.length];
for (int i=0; i <= n; ++i){ Q[i][0]=P[i]; }
for (int j=1; j <=n; ++j){
for (int i=j; i<=n; ++i){
Q[i][j]=(1-t)*Q[i-1][j-1]+t*Q[i][j-1];
}
}
return Q[n][n];
}
unitsize(1cm);
pair[] P={(0,0),(4,1),(5,3),(2,5),(3.8,6.5)}; // 5 points
pair F(real t){return Bezier(P,t);}
pair G(real t){return BezierRecursion(P,t);}
write(F(0.76)); write(G(0.76));
draw(graph(F,0,1),red);
dot(P); 
draw(operator --(... P));
draw(shift(4,0)*graph(G,0,1),red);
dot(shift(4,0)*P);  draw(shift(4,0)*operator --(... P));

enter image description here

An animation: https://en.wikipedia.org/wiki/B%C3%A9zier_curve#/media/File:B%C3%A9zier_4_big.gif

import animate;
settings.tex="pdflatex"; 
settings.outformat="pdf"; 

animation Ani;
import graph;
size(400);

pair Bezier(pair P[], real t)
{
pair Bezi;
// real choose(int n, int k); // Mathematical functions (page 69)
for (int k=0; k <= P.length-1; ++k)
{
// https://en.wikipedia.org/wiki/B%C3%A9zier_curve
Bezi=Bezi+choose(P.length-1,k)*(1-t)^(P.length-1-k)*t^k*P[k];
}
return Bezi;
}
pair[] P={(0,0),(-.5,3),(4,3),(6,0),(9,2.6)};
pair F(real t){return Bezier(P,t);}
real t=0.5;
pair A[]=new pair[4], B[]=new pair[3], C[]=new pair[2], D;
for (int k=0; k <= 50; ++k)
{
t=k/50;
save();
// A array has 4 points
for (int i : sequence(A.length)) { A[i] = interp(P[i],P[i+1],t); }
// B array has 3 points
for (int i : sequence(B.length)) { B[i] = interp(A[i],A[i+1],t); }
// C array has 2 points
C[0]=interp(B[0],B[1],t);
C[1]=interp(B[1],B[2],t); 
// D array has 1 points
D=interp(C[0],C[1],t);

draw(operator --(... P),magenta+0.7bp);
draw(operator --(... A),(t != 0) ? green+0.7bp : invisible);
draw(operator --(... B),(t != 0) ? orange+0.7bp : invisible);
draw(operator --(... C),(t != 0) ? blue+0.7bp : invisible);
draw(graph(F,0,t),(t != 0) ? red+1.3bp : invisible);
dot(P,magenta);
dot(A,(t != 0) ? green : invisible);
dot(B,(t != 0) ? orange : invisible);
dot(C,(t != 0) ? blue : invisible);
dot(D,pink);
label(Label(format("t=%.2f",t),Fill(white)),(3,0));
Ani.add();
restore();
}
erase();
Ani.movie(BBox(3mm,Fill(black)));

enter image description here

2
  • Your codes and examples are beautiful. But these are Bézier curves of order 4 whereas basically, TikZ (and PDF) uses cubic Bézier curves. Commented Jul 20, 2020 at 13:21
  • @PaulGaborit I believe that that code above can draw all Bézier curves. :-) An array has 4 points, I will have a cubic Bézier curve.
    – user213378
    Commented Jul 20, 2020 at 13:40

You must log in to answer this question.

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