chrisphan.com

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

An analog clock reading 3:41

An analog clock reading 3:41
An analog clock reading 3:41
2019-05-13 / 2019-W20-1T15:41:00-05:00 / 0x5cd9d65c

Categories: TikZ ist kein Zeichenprogramm, indeed, LaTeX, math

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

A plot of a real-valued function on the real numbers inclusively between -5 and 5, with the exception of -2. The function is continuous except that there is a removable discontinuity, a vertical asymptote, and a jump discontinuity

A plot of a real-valued function on the real numbers inclusively between -5 and 5, with the exception of -2. The function is continuous except that there is a removable discontinuity, a vertical asymptote, and a jump discontinuity
A plot of a real-valued function on the real numbers inclusively between -5 and 5, with the exception of -2. The function is continuous except that there is a removable discontinuity, a vertical asymptote, and a jump discontinuity

In this post, I will describe one method for using TikZ to to plot a function defined by a formula, such as y=(2x2x1)ex. 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:

Python
#! /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:

LaTeX

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

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

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

plot of y = (2 x^2 - x - 1) e^(-x) from -1 to 10

plot of y = (2 x^2 - x - 1) e^(-x) from -1 to 10
plot of y = (2 x^2 - x - 1) e^(-x) from -1 to 10

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,kZ 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)=6x3+21x221x363x3+3x212x12, with 15.5x15.5. Let's additionally suppose I want to only see y-values with 15.5y15.5. (In other words, the "viewing rectangle" will be [15.5,15.5]×[15.5,15.5].)

Factoring the numerator and denominator, we have:

r(x)=6x3+21x221x363x3+3x212x12 r(x)=(x+1)(2x3)(3x+12)(x+1)(x+2)(3x6) r(x)=(2x3)(3x+12)(x+2)(3x6),x1.

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 r^(x)=(2x3)(3x+12)(x+2)(3x6) along three intervals:

(Actually, for each of the above intervals I, we plot something approximating {(x,r^(x)):xI and |r^(x)|16}.)

Here is the Python script:

Python
#! /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:

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

a plot of the function. There is a horizontal asymptote at both ends at y = 2, and vertical asymptotes at x = -2 and x = 2

a plot of the function. There is a horizontal asymptote at both ends at y = 2, and vertical asymptotes at x = -2 and x = 2
a plot of the function. There is a horizontal asymptote at both ends at y = 2, and vertical asymptotes at x = -2 and x = 2

Plotting functions that go through certain points: two ways

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

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:

The syntax is: \draw (x0,y0) .. controls (x1,y1) and (x2,y2) .. (x3,y3);

bezier curve
with start, control, and end points as described

bezier curve
with start, control, and end points as described
bezier curve
with start, control, and end points as described

Now, we can combine multiple Bézier curves into a single \draw. For example, \draw (x0,y0) .. controls (x1,y1) and (x2,y2) .. (x3,y3) .. controls (x4,y4) and (x5,y5) .. (x6,y6); will produce:

two bezier curves attached at a sharp corner

two bezier curves attached at a sharp corner
two bezier curves attached at a sharp corner

In general, this curve will not necessarily be differentiable. We can get differentiability by ensuring that (x2,y2), (x3,y3) and (x4,y4) are colinear (i.e., the end of the first Bézier curve has the same derivative as the beginning of the second) with x2x3:

two bezier curves attached so that the two curves have the same tangents at
their connecting ends

two bezier curves attached so that the two curves have the same tangents at
their connecting ends
two bezier curves attached so that the two curves have the same tangents at
their connecting ends

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

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

a plot of a differentiable function passing through the desired

a plot of a differentiable function passing through the desired
a plot of a differentiable function passing through the desired

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:

Python
#! /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:

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

a plot of a polynomial function whose graph passes through the desired points

a plot of a polynomial function whose graph passes through the desired points
a plot of a polynomial function whose graph passes through the desired points

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.