Plotting with TikZ, Part III: Plotting a function defined by a formula, and plotting functions that go through certain points

In the last post, I described how I use TikZ to create a graph that might otherwise be created freehand:

In this post, I will describe one method for using TikZ to to plot a function defined by a formula, such as $$y = \left(2x^2 - x - 1\right)e^{-x}.$$ Then I will show two ways to make a function go through a certain point.

Plotting from a formula

Simple example

There are a number of ways to achieve this, and PGF actually includes the functionality to perform calculations in TeX. (For example, I have used the PGF pseudorandom number generator in graphics. Someone even made TikZ code to generate a random city skyline.) However, for efficiency sake, I prefer to have the calculations done outside TeX, in a more efficient computer language.

The trick is to use the gnuplot table import format. You simply have to generate a text file with a list of coordinates in the following form:

-1.250000 11.779907
-1.240000 11.456050
-1.230000 11.138839

This file contains the coordinates $(-1.25, 11.779907)$, $(-1.24, 11.456050)$, $(-1.23, 11.138839)$.

There are a number of ways to generate such a file. Since I like Python, I used the following (simple) Python script:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#! /usr/bin/env python3

import numpy as np

f = lambda x: (2*x**2 - x - 1)*np.exp(-x)

xvals = np.arange(-1.25, 10.25, 0.01)

with open("plot1.table", "wt") as outfile:
    for x in xvals:
        outfile.write("{:f} {:f}\n".format(x, f(x)))

Now, this curve can be imported into your TikZ illustration using \draw […] plot; in this case:

\draw[thick, blue, <->] plot[smooth] file {plot1.table};

Here is a complete example (with coordinate axes and everything):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
\documentclass[border=0.25cm]{standalone}

\usepackage{tikz}
\usepackage{fouriernc}

\begin{document}

  \begin{tikzpicture}[yscale=0.5]

    \draw[gray] (-1.25, -1.25) grid (10.25, 12.25);

    % x-axis
    \draw[thick, black, ->] (-1.25, 0) -- (10.25, 0)
      node[anchor=south west] {$x$};

    % x-axis tick marks
    \foreach \x in {-1, 10}
      \draw[thick] (\x, 0.2) -- (\x, -0.2)
        node[anchor=north] {\(\x\)};

    % y-axis
    \draw[thick, black, ->] (0, -1.25) -- (0, 12.25)
      node[anchor=south west] {$y$};

    % y-axis tick marks
    \foreach \y in {-1, 12}
      \draw[thick] (-0.1, \y) -- (0.1, \y)
        node[anchor=west] {\(\y\)};

    % graph of function
    \draw[thick, blue, <->] plot[smooth] file {plot1.table};

  \end{tikzpicture}
\end{document}

And here is the output:

In my Python script above, I carefully chose the set of inputs $x$ (np.arange(-1.25, 10.25, 0.01), i.e., every real number of the form $-1.25 + 0.01k, \; k \in \mathbb{Z}$ in the interval $[-1.25, 10.25)$) to keep $f(x) < 12$. This can also be handled computationally.

Example involving asymptotes

For example, suppose I want to plot $y = r(x)$, where $$r(x) = \frac{6x^3 + 21x^2 - 21x - 36}{3x^3 + 3x^2 - 12x - 12},$$ with $-15.5 \leq x \leq 15.5$. Let’s additionally suppose I want to only see $y$-values with $-15.5 \leq y \leq 15.5$. (In other words, the “viewing rectangle” will be $[-15.5, 15.5] \times [-15.5, 15.5]$.)

Factoring the numerator and denominator, we have:

$$r(x) = \frac{6x^3 + 21x^2 - 21x - 36}{3x^3 + 3x^2 - 12x - 12} $$ $$\phantom{r(x)}= \frac{\left(x + 1\right) \left(2 x - 3\right) \left(3 x + 12\right)}{\left(x + 1\right) \left(x + 2\right) \left(3 x - 6\right)}$$ $$\phantom{r(x)}= \frac{\left(2 x - 3\right) \left(3 x + 12\right)}{\left(x + 2\right) \left(3 x - 6\right)}, \; x \not = -1.$$

As you can see, we will have vertical asymptotes at $x = -2$ and $x = 2$, and a hole at $x = -1$. There will also be a horizontal asymptote at $y = 2$.

To handle the asymptotes, we plot the function $$\hat r(x) = \frac{\left(2 x - 3\right) \left(3 x + 12\right)}{\left(x + 2\right) \left(3 x - 6\right)}$$ along three intervals:

  • $[-15.5, -2)$,
  • $(-2, 2)$, and
  • $(2, 15.5]$

(Actually, for each of the above intervals $I$, we plot something approximating $\left\{(x, \hat r(x)): x \in I \text{ and } |\hat r(x)|\leq 16\right\}$.)

Here is the Python script:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#! /usr/bin/env python3

import numpy as np

r = lambda x: (2*x - 3)*(3*x + 12)/((x + 2)*(3*x - 6))

xvals = [
        np.arange(-15.5, -2, 0.01),
        np.arange(-2.01, 2, 0.01),
        np.arange(2.01, 15.6, 0.01)
        ]

for idx, interval in enumerate(xvals):
    with open("plot2_{}.table".format(idx), "wt") as outfile:
        for x in interval:
            if abs(r(x)) <= 16:
                outfile.write("{:f} {:f}\n".format(x, r(x)))

Here is the corresponding LaTeX code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
\documentclass[border=0.25cm]{standalone}

\usepackage{tikz}
\usepackage{fouriernc}

\begin{document}

  \begin{tikzpicture}[scale=0.5] % Scale keeps the graphic small

    \draw[gray] (-15.5, -15.5) grid (15.5, 15.5);

    % x-axis
    \draw[thick, black, ->] (-15.5, 0) -- (15.5, 0)
      node[anchor=south west] {$x$};

    % x-axis tick marks
    \foreach \x in {-15, -10, -5, 5, 10, 15}
      \draw[thick] (\x, 0.5) -- (\x, -0.5)
        node[anchor=north] {\(\x\)};

    % y-axis
    \draw[thick, black, ->] (0, -15.5) -- (0, 15.5)
      node[anchor=south west] {$y$};

    % y-axis tick marks
    \foreach \y in {-15, -10, -5, 5, 10, 15}
      \draw[thick] (-0.5, \y) -- (0.5, \y)
        node[anchor=west] {\(\y\)};

    % graph of function
    \draw[thick, blue, <->] plot[smooth] file {plot2_0.table};
    \draw[thick, blue, <->] plot[smooth] file {plot2_1.table};
    \draw[thick, blue, <->] plot[smooth] file {plot2_2.table};

    % hole at (-1, 5)
    \draw[thick, blue, fill=white] (-1, 5) circle (2.5mm);

    % vertical asymptotes
    \draw[thick, dashed, red] (-2, -15.5) -- (-2, 15.5);
    \draw[thick, dashed, red] (2, -15.5) -- (2, 15.5);

    % horizontal asymptote
    \draw[thick, dashed, red] (-15.5, 2) -- (15.5, 2);

  \end{tikzpicture}
\end{document}

And here is the result:

Plotting functions that go through certain points: two ways

Suppose I want to plot a function that go through certain points, say:

  • $(-4, 2)$,
  • $(-1, 6)$,
  • $(2, 3)$, and
  • $(4, 5)$.

I want the function to be differentiable.

Method one: Bézier curves

Recall from the last post that a (cubic) Bézier curve is specified by four points:

  • A start point ($(x_0, y_0)$ in the diagram below)
  • Two control points ($(x_1, y_1)$ and $(x_2, y_2)$ in the diagram below)
  • An end point ($(x_3, y_3)$ in the diagram below)

The syntax is: \draw ($x_0, y_0$) .. controls ($x_1, y_1$) and ($x_2, y_2$) .. ($x_3, y_3$);

Now, we can combine multiple Bézier curves into a single \draw. For example, \draw ($x_0, y_0$) .. controls ($x_1, y_1$) and ($x_2, y_2$) .. ($x_3, y_3$) .. controls ($x_4, y_4$) and ($x_5, y_5$) .. ($x_6, y_6$); will produce:

In general, this curve will not necessarily be differentiable. We can get differentiability by ensuring that $(x_2, y_2)$, $(x_3, y_3)$ and $(x_4, y_4)$ are colinear (i.e., the end of the first Bézier curve has the same derivative as the beginning of the second) with $x_2 \not = x_3$:

Keeping these principles in mind, we can plot a function going through the desired points with:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
\documentclass[border=0.25cm]{standalone}

\usepackage{tikz}
\usepackage{fouriernc}

\begin{document}

  \begin{tikzpicture}[scale=0.5] % Scale keeps the graphic small

    \draw[gray] (-5.25, 0) grid (5.25, 8.25);

    % x-axis
    \draw[thick, black, ->] (-5.25, 0) -- (5.25, 0)
      node[anchor=south west] {$x$};

    % x-axis tick marks
    \foreach \x in {-5, 5}
      \draw[thick] (\x, 0.25) -- (\x, -0.25)
        node[anchor=north] {\(\x\)};

    % y-axis
    \draw[thick, black, ->] (0, 0) -- (0, 8.25)
      node[anchor=south west] {$y$};

    % y-axis tick marks
    \foreach \y in {4, 8}
      \draw[thick] (-0.25, \y) -- (0.25, \y)
        node[anchor=west] {\(\y\)};

    % graph of function
    \draw[thick, blue, <->] (-5.25, -0.5)
      .. controls (-4, 0) and (-5, 0) .. (-4, 2)
      .. controls (-3.5, 3) and (-3, 6) .. (-1, 6)
      .. controls (1, 6) and (1.75, 4) .. (2, 3)
      .. controls (2.25, 2) and (3, 2) .. (4, 5)
      .. controls (4.5, 6.5) and (4.5, 7) .. (5.25, 8);

  \end{tikzpicture}
\end{document}

Here is the output:

Method two: Polynomial interpolation

SciPy’s interpolation functionality can find a smooth function that passes through an arbitrary list of points. Here is an example of a Python script to produce such a function and create a table in the necessary format:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#! /usr/bin/env python3

import numpy as np
import scipy.interpolate

coords = [(-10, 8), (-4, 2), (-1, 6), (2, 3), (4, 5), (10, 1)]
# Since our goal is to plot from x = -5.25 to x = 5.25,
# we need to specify some points beyond that interval on both sides.

xcoords, ycoords = zip(*coords)
# This produces a seperate list of x-coordinates and y-coordinates

f = scipy.interpolate.interp1d(xcoords, ycoords, kind="cubic")

xvals = np.arange(-5.25, 5.25, 0.01)
yvals = f(xvals)

outcoords = zip(xvals, yvals)
# This produces a list of coordinate pairs

with open("plot3.table", "wt") as outfile:
    for pair in outcoords:
        outfile.write("{:f} {:f}\n".format(pair[0], pair[1]))

Note that if we had set coords = [(-4, 2), (-1, 6), (2, 3), (4, 5)], then the resulting function would not have had a defined value outside of the interval $[-4, 4]$.

To plot, we use the following tikzpicture:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
\documentclass[border=0.25cm]{standalone}

\usepackage{tikz}
\usepackage{fouriernc}

\begin{document}

  \begin{tikzpicture}[scale=0.5] % Scale keeps the graphic small

    \draw[gray] (-5.25, 0) grid (5.25, 8.25);

    % x-axis
    \draw[thick, black, ->] (-5.25, 0) -- (5.25, 0)
      node[anchor=south west] {$x$};

    % x-axis tick marks
    \foreach \x in {-5, 5}
      \draw[thick] (\x, 0.25) -- (\x, -0.25)
        node[anchor=north] {\(\x\)};

    % y-axis
    \draw[thick, black, ->] (0, 0) -- (0, 8.25)
      node[anchor=south west] {$y$};

    % y-axis tick marks
    \foreach \y in {4, 8}
      \draw[thick] (-0.25, \y) -- (0.25, \y)
        node[anchor=west] {\(\y\)};

    % graph of function
    \draw[thick, blue, <->] plot[smooth] file {plot3.table};

  \end{tikzpicture}
\end{document}

Here is the result:

As we have seen, creating plots with TikZ allows you a deep amount of control over the final result, and can be especially powerful when combined with a programming language such as Python.