12

I have been trying to build a macro \parabola{...} to draw a parabola passing through 3 coordinates with TikZ but without success.

For example

\parabola{A}{B}{C}

would draw the parabola interpolating the (x,y) coordinates (A), (B) and (C). I would like also to specify the style of the curve, the plot domain, etc.

The main problem I found is that I cannot figure out how to obtain the value of a given coordinate in dimensionless form (given a certain unit).

3
  • 1
    Did you try the parabola path option already? There are some examples on page 146 in the manual. What is the obstacle that prevents you to achieve what you want with parabola?
    – percusse
    Jan 13, 2012 at 22:05
  • Welcome to TeX.SE. It would be helpful to post a compilable MWE that illustrates what you done so far so those trying to help can have something to start with. Jan 13, 2012 at 22:10
  • As far as I know, the parabola path operation allows to draw the parabola passing through 3 given points only if one of them is the bend. I want a way to draw a parabola using three arbitrary TikZ coordinates.
    – maeshtro
    Jan 13, 2012 at 22:47

6 Answers 6

26

Introduction

This is an old question, but all previous answers have limitations: the main one is that all use plot. And plot command produce multiple cubic curves. But to draw a parabola a single quadratic (cubic) curve is enough.

Some explanations

Any parabola can be drawn by a quadratic Bézier curve, and so by a cubic Bézier curve.
(A cubic curve with control points A,B,C,D draws a quadratic one iff AD=3BC.)

enter image description here

The "standard" parabola t(1-t) over [0,1] can be drawn by \draw (0,0) .. controls (1/3,1/3) and (2/3,1/3) .. (1,0);.

enter image description here

Every parabola between two points can be obtained by an affine transform from this "standard one". Using this we can define a style parabola through that use a single Bézier curve to draw the desired parabola. This style can be used with to or edge in the following way (A) to[parabola through={(B)}] (C).

The code

The definition of the parabola through is:

\makeatletter
\def\pt@get#1#2{
  \tikz@scan@one@point\pgfutil@firstofone#2\relax%
  \csname pgf@x#1\endcsname=\pgf@x%
  \csname pgf@y#1\endcsname=\pgf@y%
}
\tikzset{
  parabola through/.style={
    to path={{[x={(\pgf@xc,\pgf@yc)}, y=\parabola@y, shift=(\tikztostart)]
      -- (0,0) .. controls (1/3,1/3) and (2/3,1/3) .. (1,0) \tikztonodes}--(\tikztotarget)}
  },
  parabola through/.prefix code={
    \pt@get{a}{(\tikztostart)}\pt@get{b}{#1}\pt@get{c}{(\tikztotarget)}%
    \advance\pgf@xb by-\pgf@xa\advance\pgf@yb by-\pgf@ya%
    \advance\pgf@xc by-\pgf@xa\advance\pgf@yc by-\pgf@ya%
    \pgfmathsetmacro\parabola@y{(\pgf@yc-\pgf@xc/\pgf@xb*\pgf@yb)%
      /(\pgf@xb-\pgf@xc)*\pgf@xc}%
  }
}
\makeatother

Note: We can avoid \makeatletter/\makeatother and all @s by using let from the calc library.

We can use (A) to[parabola through={(B)}] (C):

  • in every case where the parabola exists, so when the three x-coordinates are different,
  • the point B can be outside the drawn are,
  • this can be part of a general path with nodes positioned on it.

Example 1:

\tikz\draw[help lines] (0,0) grid (4,3)
  (0,0) edge[parabola through={(3,2)},
    red,thick,fill=blue,fill opacity=.21] (4,1);

enter image description here

Example 2 (Full MWE):

\documentclass[tikz,border=7pt]{standalone}
\makeatletter
\def\pt@get#1#2{
  \tikz@scan@one@point\pgfutil@firstofone#2\relax%
  \csname pgf@x#1\endcsname=\pgf@x%
  \csname pgf@y#1\endcsname=\pgf@y%
}
\tikzset{
  parabola through/.style={
    to path={{[x={(\pgf@xc,\pgf@yc)}, y=\parabola@y, shift=(\tikztostart)]
      -- (0,0) .. controls (1/3,1/3) and (2/3,1/3) .. (1,0) \tikztonodes}--(\tikztotarget)}
  },
  parabola through/.prefix code={
    \pt@get{a}{(\tikztostart)}\pt@get{b}{#1}\pt@get{c}{(\tikztotarget)}%
    \advance\pgf@xb by-\pgf@xa\advance\pgf@yb by-\pgf@ya%
    \advance\pgf@xc by-\pgf@xa\advance\pgf@yc by-\pgf@ya%
    \pgfmathsetmacro\parabola@y{(\pgf@yc-\pgf@xc/\pgf@xb*\pgf@yb)%
      /(\pgf@xb-\pgf@xc)*\pgf@xc}%
  }
}
\makeatother
\begin{document}
  \begin{tikzpicture}
    \draw[help lines] (-1,-1) grid (3,3);
    % variations of the point "through"
    \foreach \y in {-1,-.9,...,1}
      \draw[green] (-1,1) node[black]{.}
        to[parabola through={(0,\y)}] node[black]{.}
          node[black,at end]{.} (1,.5);
    % variations of a boundary point
    \foreach \y in {1.5,1.7,...,3}
      \draw[purple] (-1,2) node[black]{.}
        to[parabola through={(0,2)}] node[black]{.}
          node[black,at end]{.} (1,\y);
    % variations of a point "trough" outside the drawn part
    \foreach \y in {-1,-0.5,...,3}{
      \draw[red,thick] (.5,1) node[black]{.}
        to[parabola through={(3,\y)}] node[black]{.}
          node[black,at end]{.} (2,1);
      \draw[dashed,blue] (.5,1) node[black]{.}
        to[parabola through={(2,1)}] node[black]{.}
          node[black,at end]{.} (3,\y);
    }
  \end{tikzpicture}
\end{document}

enter image description here

Compared to the built in parabola operation

TikZ provide a parabola path operation. But it is not very well designed :

  • the (0,0) parabola (1,1) is supposed to draw the parabola t^2 between 0 and 1. It draws a cubic curve that is close to this parabola but it is not exactly the same, actually it draws (0,0) .. controls (.5,0) and (0.8875,0.775) .. (1,1), but the exact curve is (0,0) .. controls (1/3,0) and (2/3,1/3) .. (1,1) (not clear why this curve is not used),
  • when used with bend option, it use two cubic curves to approximate the parabola, but only one is enough to draw the exact one,
  • when used with bend=<point> option, if you do not choose well the point the curve is not a parabola.

There is a situation where the original parabola is simpler to use (even if not exactly a parabola is drawn), when the bend (the extremal point) is at the start or at the end : (0,0) parabola (2,4) is simpler than (0,0) to[parabola through={(1,1)}] (2,4).

3
  • @PaulGaborit Thanks ! I changed the code following your advise putting the order "division, multiplication,division, multiplication" in the formula of\parabola@y.
    – Kpym
    May 5, 2018 at 14:42
  • Nice. Any idea why these can't be concatenated like other types of edges though? (E.g.,\tikz\draw (0,2) to[parabola through={(1,0)}] (2,4) to[parabola through={(3,3)}] (4,3); has a gap.) Jul 3, 2018 at 17:16
  • @Circumscribe I have no idea why. But it is easy to correct : I have added --(\tikztotarget) to the end of to path and now it works as expected. Thank you for reporting this inconsistency.
    – Kpym
    Jul 3, 2018 at 21:02
7

1) A variant with fp :

\documentclass{article}
\usepackage{tikz,fp}
\FPmessagesfalse
\FPdebugfalse

\makeatletter  
\tikzset{%
  parabola through/.style={
    to path={%
      \pgfextra{%
        \tikz@scan@one@point\pgfutil@firstofone(\tikztostart)\relax 
        \FPeval\xa{\pgf@sys@tonumber{\pgf@x}/28.45274}   
        \FPeval\ya{\pgf@sys@tonumber{\pgf@y}/28.45274}   
        \tikz@scan@one@point\pgfutil@firstofone#1\relax
        \FPeval\xb{\pgf@sys@tonumber{\pgf@x}/28.45274}   
        \FPeval\yb{\pgf@sys@tonumber{\pgf@y}/28.45274}   
        \tikz@scan@one@point\pgfutil@firstofone(\tikztotarget)\relax
        \FPeval\xc{\pgf@sys@tonumber{\pgf@x}/28.45274}   
        \FPeval\yc{\pgf@sys@tonumber{\pgf@y}/28.45274}   
        \FPeval\pb@a{(\ya*(\xb-\xc)+\yb*(\xc-\xa)+\yc*(\xa-\xb))/%
        ((\xa-\xb)*(\xa-\xc)*(\xb-\xc))}
        \FPeval\pb@b{(\ya*(\xc+\xb)*(\xc-\xb)+\yb*(\xa+\xc)*(\xa-\xc)+\yc*(\xb+\xa)*(\xb-\xa))/((\xa-\xb)*(\xa-\xc)*(\xb-\xc))} 
        \FPeval\pb@c{(\ya*\xb*\xc*(\xb-\xc)+\yb*\xa*\xc*(\xc-\xa)+\yc*\xa*\xb*(\xa-\xb))/((\xa-\xb)*(\xa-\xc)*(\xb-\xc))} 

    \draw plot[domain=\xa:\xc]  (\x,{\pb@a*(\x*\x)+\pb@b*\x+\pb@c}) ;
    }(\tikztotarget)
    }
  }
}
\makeatother
\begin{document}

\begin{tikzpicture}
\draw [help lines] (-3,-1) grid (7,4);  
\draw (-3,0) to[parabola through={(-2,2)}]%
  (0,-1) to[parabola through={(2,4)}] (4,0) to[parabola through={(5,3)}] (7,0);     
\end{tikzpicture}

\end{document} 

2) From maeshtro's answer with gnuplot

\documentclass{article}
\usepackage{tikz}
\makeatletter
\tikzset{%
  parabola through/.style={
    to path={%
      \pgfextra{%
        \tikz@scan@one@point\pgfutil@firstofone(\tikztostart)\relax 
        \edef\xa{\pgf@sys@tonumber{\pgf@x}}   
        \edef\ya{\pgf@sys@tonumber{\pgf@y}}   
        \tikz@scan@one@point\pgfutil@firstofone#1\relax
        \edef\xb{\pgf@sys@tonumber{\pgf@x}}   
        \edef\yb{\pgf@sys@tonumber{\pgf@y}}   
        \tikz@scan@one@point\pgfutil@firstofone(\tikztotarget)\relax
        \edef\xc{\pgf@sys@tonumber{\pgf@x}}   
        \edef\yc{\pgf@sys@tonumber{\pgf@y}}   

\draw plot[domain=\xa/28.45274:\xc/28.45274] function{
  \ya/28.45274*((x*28.45274-\xb)*(x*28.45274-\xc))/((\xa-\xb)*(\xa-\xc))+
  \yb/28.45274*((x*28.45274-\xa)*(x*28.45274-\xc))/((\xb-\xa)*(\xb-\xc))+
  \yc/28.45274*((x*28.45274-\xa)*(x*28.45274-\xb))/((\xc-\xa)*(\xc-\xb))
};
    }(\tikztotarget)
    }
  }
}
\makeatother
\begin{document}


\begin{tikzpicture}
    \draw [help lines] (-3,-1) grid (7,4);  
  \draw (-3,0) to[parabola through={(-2,2)}] (0,-1) to[parabola through={(2,4)}] (4,0) to[parabola through={(5,3)}] (7,0);     
\end{tikzpicture}

\end{document} 

enter image description here

4

Here is one solution. It essentially solves the linear equation obtained from quadratic interpolation. It is important to note however that it is far from perfect. In particular there are strong constraints on possible points because of tikz computations limitations: the numbers in the computations must be small enough. This is clearly not what tikz is made for. Using other ways to obtain the coefficients would be better (sagetex or asymptote or others).

At least it is a nice example of the use of letin a tikz path.

I hope the computations are clear enough. The coefficients A, B and C are the coefficients of the quadratic polynomial Ax^2 + Bx + C.

The points must be entered in increasing order of the x coordinates for the code to work correctly.

The code is

\documentclass{article}

\usepackage{tikz}
\usetikzlibrary{calc}

\begin{document}

\begin{tikzpicture}
\coordinate (1) at (0.1,0.2);
\coordinate (2) at (0.2,0.7);
\coordinate (3) at (0.4,-0.3);

\draw let \p1 = (1),
          \p2 = (2),
          \p3 = (3),
          \n{denom} = {(\x1 - \x2)*(\x1 - \x3)*(\x2-\x3)},
          \n{A} = {(\x3*(\y2-\y1) + \x2*(\y1-\y3) + \x1*(\y3-\y2))/\n{denom}},
          \n{B} = {(\x3*\x3*(\y1-\y2) + \x2*\x2*(\y3-\y1)+\x1*\x1*(\y2-\y3))/\n{denom}},
          \n{C} = {(\x2*\x3*(\x2-\x3)*\y1 + \x3*\x1*(\x3-\x1)*\y2 + \x1*\x2*(\x1-\x2)*\y3)/\n{denom}} in
          plot[domain=\x1:\x3] (\x,{\n{A}*\x*\x+\n{B}*\x + \n{C}});

\end{tikzpicture}

\end{document}
2
  • \coordinate (1) at (-3,0); \coordinate (2) at (-2,2); \coordinate (3) at (0,0); gives dimension too large Jan 14, 2012 at 22:40
  • @Altermundus: As I mentioned in my answer, there are (major) limitations on what my answer can do. That is why I used points that are close together.
    – Frédéric
    Jan 15, 2012 at 4:24
2

Here is a solution inspired by this answer.

enter image description here

\documentclass{standalone}
\usepackage{tikz}
\makeatletter
\def\parabola@save@target#1{%
  \def\parabola@target{#1}}
\def\parabola@save@start#1{%
  \def\parabola@start{#1}}
\def\parabola@save@midpoint#1{%
  \def\parabola@midpoint{#1}}
\tikzset{
  parabola through/.style={
    to path={%
      \pgfextra{%
        \edef\parabola@@target{(\tikztotarget)}%
        \tikz@scan@one@point\parabola@save@target\parabola@@target\relax
        \edef\parabola@@start{(\tikztostart)}%
        \tikz@scan@one@point\parabola@save@start\parabola@@start\relax
        \edef\parabola@@midpoint{(#1)}%
        \tikz@scan@one@point\parabola@save@midpoint\parabola@@midpoint\relax
        \parabola@start
        \pgfmathsetmacro{\parabola@xa}{\the\pgf@x/1cm}
        \pgfmathsetmacro{\parabola@ya}{\the\pgf@y/1cm}
        \parabola@midpoint
        \pgfmathsetmacro{\parabola@xb}{\the\pgf@x/1cm}
        \pgfmathsetmacro{\parabola@yb}{\the\pgf@y/1cm}
        \parabola@target
        \pgfmathsetmacro{\parabola@xc}{\the\pgf@x/1cm}
        \pgfmathsetmacro{\parabola@yc}{\the\pgf@y/1cm}
        % f(x) = ax^2 + bx + c
        % a=-(-x1*y3+x3*y1+x2*y3+x1*y2-x2*y1-x3*y2)/(x1*x3^2-x2*x3^2+x2*x1^2-x3*x1^2+x3*x2^2-x1*x2^2)
        % b=(-x1^2*y3+x1^2*y2+y1*x3^2-y2*x3^2+x2^2*y3-y1*x2^2)/((x1-x2)*(-x1*x3+x1*x2+x3^2-x2*x3))
        % c=(x1^2*x2*y3-x1^2*x3*y2-x2^2*x1*y3+y2*x1*x3^2+x2^2*x3*y1-y1*x2*x3^2)/((x1-x2)*(-x1*x3+x1*x2+x3^2-x2*x3))
        \pgfmathsetmacro{\parabola@a}{-(-\parabola@xa*\parabola@yc+\parabola@xc*\parabola@ya+\parabola@xb*\parabola@yc+\parabola@xa*\parabola@yb-\parabola@xb*\parabola@ya-\parabola@xc*\parabola@yb)/(\parabola@xa*\parabola@xc^2-\parabola@xb*\parabola@xc^2+\parabola@xb*\parabola@xa^2-\parabola@xc*\parabola@xa^2+\parabola@xc*\parabola@xb^2-\parabola@xa*\parabola@xb^2)}
        \pgfmathsetmacro{\parabola@b}{(-\parabola@xa^2*\parabola@yc+\parabola@xa^2*\parabola@yb+\parabola@ya*\parabola@xc^2-\parabola@yb*\parabola@xc^2+\parabola@xb^2*\parabola@yc-\parabola@ya*\parabola@xb^2)/((\parabola@xa-\parabola@xb)*(-\parabola@xa*\parabola@xc+\parabola@xa*\parabola@xb+\parabola@xc^2-\parabola@xb*\parabola@xc))}
        \pgfmathsetmacro{\parabola@c}{(\parabola@xa^2*\parabola@xb*\parabola@yc-\parabola@xa^2*\parabola@xc*\parabola@yb-\parabola@xb^2*\parabola@xa*\parabola@yc+\parabola@yb*\parabola@xa*\parabola@xc^2+\parabola@xb^2*\parabola@xc*\parabola@ya-\parabola@ya*\parabola@xb*\parabola@xc^2)/((\parabola@xa-\parabola@xb)*(-\parabola@xa*\parabola@xc+\parabola@xa*\parabola@xb+\parabola@xc^2-\parabola@xb*\parabola@xc))}
        \draw plot[samples=100,domain=\parabola@xa:\parabola@xc] function {\parabola@a*(x**2)+\parabola@b*x+\parabola@c};
      }
    }
  }
}
\makeatother
\begin{document}
\begin{tikzpicture}
  \node[circle,fill=red] at (0,0) {};
  \node[circle,fill=blue] at (2,2) {};
  \node[circle,fill=green] at (4,0) {};
  \draw (0,0) to[parabola through={(2,2)}] (4,0);
\end{tikzpicture}
\end{document}
2
  • 2
    \draw (-3,0) to[parabola through={(-2,2)}] (0,0); is incorrect Jan 14, 2012 at 22:43
  • @Altermundus You're right. maple gave me this solution. Maybe some operator precedence issue? The use of Lagrange polynomials is much better here.
    – cjorssen
    Jan 15, 2012 at 21:38
1

You can rely on \pgfmathparse to always return any given length in pt. Take a look at the output of:

\pgfmathparse{12cm+1pt}
\pgfmathresult

\pgfmathparse{1pt}
\pgfmathresult

which will output as:

342.43306
1.0

With this you can pretty much do all your calculation by transferring to the (now dimensionless) unit pt. However you should be weary of very large numbers.

To circumvent this you could rely on the fpu unit of pgf.

% Preamble
\usepgflibrary{fpu}

\begin{document}

\pgfkeys{/pgf/fpu,/pgf/fpu/output format=fixed}
\pgfmathparse{12cm+1pt}
\pgfmathresult

\pgfmathparse{1pt}
\pgfmathresult

342.43306000000000
1.0000000000

Then you have access to numbers which are always in one specific unit!

0

Thank you all for your answers. They helped me a lot. Finally I chose the following approach which uses gnuplot. It is not very elegant but it meets my needs.

\documentclass{standalone}
\usepackage{tikz}
\usetikzlibrary{calc}

\newcommand\parabola[3]{%
\path[draw]
let
  \p1=#1,\p2=#2,\p3=#3
in
\pgfextra
  \pgfmathsetmacro{\xa}{\x1/1cm}
  \pgfmathsetmacro{\ya}{\y1/1cm}
  \pgfmathsetmacro{\xb}{\x2/1cm}
  \pgfmathsetmacro{\yb}{\y2/1cm}
  \pgfmathsetmacro{\xc}{\x3/1cm}
  \pgfmathsetmacro{\yc}{\y3/1cm}
\endpgfextra
plot[domain=\xa:\xc] function{
  \ya*((x-\xb)*(x-\xc))/((\xa-\xb)*(\xa-\xc))+
  \yb*((x-\xa)*(x-\xc))/((\xb-\xa)*(\xb-\xc))+
  \yc*((x-\xa)*(x-\xb))/((\xc-\xa)*(\xc-\xb))
};}

\begin{document}
\begin{tikzpicture}
\coordinate (A) at (1,0);
\coordinate (B) at (3,8);
\coordinate (C) at (4,-1);
\fill[red] (A) circle (2pt) (B) circle (2pt) (C) circle (2pt); 
\parabola{(A)}{(B)}{(C)}  
\end{tikzpicture}
\end{document}
3
  • Fine idea to use the Lagrange polynomial without develop it but you can develop the idea and gnuplot can divide for you. I update my answer. Jan 15, 2012 at 7:05
  • @Altermundus and Maeshtro I've tried this but my system spits this at me: Package pgf Warning: Plot data file <file name> no foundo n input line any idea why? Feb 11, 2012 at 17:02
  • It's difficult to answer with so few elements. First you need to test gnuplot, then you need to authorize TeX to launch gnuplot and to verify your path. Feb 11, 2012 at 18:17

You must log in to answer this question.

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