11

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).

  • 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 '12 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. – Peter Grill Jan 13 '12 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 '12 at 22:47
16

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).

  • @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 '18 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.) – Circumscribe Jul 3 '18 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 '18 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}
  • \coordinate (1) at (-3,0); \coordinate (2) at (-2,2); \coordinate (3) at (0,0); gives dimension too large – Alain Matthes Jan 14 '12 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 '12 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
    \draw (-3,0) to[parabola through={(-2,2)}] (0,0); is incorrect – Alain Matthes Jan 14 '12 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 '12 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}
  • 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. – Alain Matthes Jan 15 '12 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? – Pureferret Feb 11 '12 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. – Alain Matthes Feb 11 '12 at 18:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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