using CalculusWithJulia
using Plots
plotly()
import Contour: contours, levels, level, lines, coordinates
using LinearAlgebra
using ForwardDiff
59 2D and 3D plots in Julia with Plots
This section uses these add-on packages:
This covers plotting the typical 2D and 3D plots in Julia with the Plots
package.
We will make use of some helper functions that will simplify plotting provided by the CalculusWithJulia
package. As well, we will need to manipulate contours directly, so pull in the Contours
package, using import
to avoid name collisions and explicitly listing the methods we will use.
59.1 Parametrically described curves in space
Let \(r(t)\) be a vector-valued function with values in \(R^d\), \(d\) being \(2\) or \(3\). A familiar example is the equation for a line that travels in the direction of \(\vec{v}\) and goes through the point \(P\): \(r(t) = P + t \cdot \vec{v}\). A parametric plot over \([a,b]\) is the collection of all points \(r(t)\) for \(a \leq t \leq b\).
In Plots
, parameterized curves can be plotted through two interfaces, here illustrated for \(d=2\): plot(f1, f2, a, b)
or plot(xs, ys)
. The former is convenient for some cases, but typically we will have a function r(t)
which is vector-valued, as opposed to a vector of functions. As such, we only discuss the latter.
An example helps illustrate. Suppose \(r(t) = \langle \sin(t), 2\cos(t) \rangle\) and the goal is to plot the full ellipse by plotting over \(0 \leq t \leq 2\pi\). As with plotting of curves, the goal would be to take many points between a
and b
and from there generate the \(x\) values and \(y\) values.
Let’s see this with 5 points, the first and last being identical due to the curve:
r₂(t) = [sin(t), 2cos(t)]
= range(0, stop=2pi, length=5) ts
0.0:1.5707963267948966:6.283185307179586
Then we can create the \(5\) points easily through broadcasting:
= r₂.(ts) vs
5-element Vector{Vector{Float64}}:
[0.0, 2.0]
[1.0, 1.2246467991473532e-16]
[1.2246467991473532e-16, -2.0]
[-1.0, -3.6739403974420594e-16]
[-2.4492935982947064e-16, 2.0]
This returns a vector of points (stored as vectors). The plotting function wants two collections: the set of \(x\) values for the points and the set of \(y\) values. The data needs to be generated differently or reshaped. The function unzip
above takes data in this style and returns the desired format, returning a tuple with the \(x\) values and \(y\) values pulled out:
unzip(vs)
([0.0, 1.0, 1.2246467991473532e-16, -1.0, -2.4492935982947064e-16], [2.0, 1.2246467991473532e-16, -2.0, -3.6739403974420594e-16, 2.0])
To plot this, we “splat” the tuple so that plot
gets the arguments separately:
plot(unzip(vs)...)
This basic plot is lacking, of course, as there are not enough points. Using more initially is a remedy.
= range(0, 2pi, length=100)
ts plot(unzip(r₂.(ts))...)
As a convenience, CalculusWithJulia
provides plot_parametric
to produce this plot. The interval is specified with the a..b
notation of IntervalSets
(which is available when the CalculusWithJulia
package is loaded), the points to plot are adaptively chosen:
plot_parametric(0..2pi, r₂) # interval first
59.1.1 Plotting a space curve in 3 dimensions
A parametrically described curve in 3D is similarly created. For example, a helix is described mathematically by \(r(t) = \langle \sin(t), \cos(t), t \rangle\). Here we graph two turns:
r₃(t) = [sin(t), cos(t), t]
plot_parametric(0..4pi, r₃)
59.1.2 Adding a vector
The tangent vector indicates the instantaneous direction one would travel were they walking along the space curve. We can add a tangent vector to the graph. The quiver!
function would be used to add a 2D vector, but Plots
does not currently have a 3D
analog. In addition, quiver!
has a somewhat cumbersome calling pattern when adding just one vector. The CalculusWithJulia
package defines an arrow!
function that uses quiver
for 2D arrows and a simple line for 3D arrows. As a vector incorporates magnitude and direction, but not a position, arrow!
needs both a point for the position and a vector.
Here is how we can visualize the tangent vector at a few points on the helix:
plot_parametric(0..4pi, r₃, legend=false)
= range(0, 4pi, length=5)
ts for t in ts
arrow!(r₃(t), r₃'(t))
end
Adding many arrows this way would be inefficient.
59.1.3 Setting a viewing angle for 3D plots
For 3D plots, the viewing angle can make the difference in visualizing the key features. In Plots
, some backends allow the viewing angle to be set with the mouse by clicking and dragging. Not all do. For such, the camera
argument is used, as in camera(azimuthal, elevation)
where the angles are given in degrees. If the \(x\)-\(y\)-\(z\) coordinates are given, then elevation
or inclination, is the angle between the \(z\) axis and the \(x-y\) plane (so 90
is a top view) and azimuthal
is the angle in the \(x-y\) plane from the \(x\) axes.
59.2 Visualizing functions from \(R^2 \rightarrow R\)
If a function \(f: R^2 \rightarrow R\) then a graph of \((x,y,f(x,y))\) can be represented in 3D. It will form a surface. Such graphs can be most simply made by specifying a set of \(x\) values, a set of \(y\) values and a function \(f\), as with:
= range(-2, stop=2, length=100)
xs = range(-pi, stop=pi, length=100)
ys f(x,y) = x*sin(y)
surface(xs, ys, f)
Rather than pass in a function, values can be passed in. Here they are generated with a list comprehension. The y
values are innermost to match the graphic when passing in a function object:
= [f(x,y) for y in ys, x in xs]
zs surface(xs, ys, zs)
Remembering if the ys
or xs
go first in the above can be hard. Alternatively, broadcasting can be used. The command f.(xs,ys)
would return a vector, as the xs
and ys
match in shape–they are both column vectors. But the transpose of xs
looks like a row vector and ys
looks like a column vector, so broadcasting will create a matrix of values, as desired here:
surface(xs, ys, f.(xs', ys))