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:
#! /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):
\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:
#! /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:
\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:
\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:
#! /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
:
\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.