Macro to draw a parabola with pgf/TikZ

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

-
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
Please accept an answer. Thanks. –  Ingo Mar 3 '14 at 13:44

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

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}


-

Here is a solution inspired by this answer.

\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}

-
\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

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!

-

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