In the previous section,
we introduced plotfunc2d
and plotfunc3d
that serve
for plotting functions in 2D and 3D with simple calls in easy syntax.
Although all plot attributes accepted by function graphs (of type plot::Function2d
or plot::Function3d
, respectively)
are also accepted by plotfunc2d/3d
, there remains
a serious restriction: the attributes are used for all functions simultaneously.
If attributes are to be applied to functions individually, one needs to invoke a (slightly) more elaborate calling syntax to generate the plot:
plot( plot::Function2d(f1, x1 = a1..b1, attrib11, attrib12, ...), plot::Function2d(f2, x2 = a2..b2, attrib21, attrib22, ...), ... ):
In this call, each call of plot::Function2d
creates
a separate object that represents the graph of the function passed
as the first argument over the plotting range passed as the second
argument. An arbitrary number of plot attributes can be associated
with each function graph. The objects themselves are not displayed
directly. The plot
command triggers the evaluation
of the functions on some suitable numerical mesh and calls the renderer
to display these numerical data in the form specified by the given
attributes.
In fact, plotfunc2d
and plotfunc3d
do precisely
the same: Internally, they generate plot objects of type plot::Function2d
or plot::Function3d
,
respectively, and call the renderer via plot
.
In general, graphical scenes are collections of "graphical
primitives." There are simple primitives such as points, line
segments, polygons, rectangles and boxes, circles and spheres, histogram
plots, pie charts etc. An example of a more advanced primitive is plot::VectorField2d
that
represents a collection of arrows attached to a regular mesh visualizing
a vector field over a rectangular region in ℝ^{2}.
Yet more advanced primitives are function graphs and parametrized
curves that come equipped with some internal intelligence, e.g., knowing
how to evaluate themselves numerically on adaptive meshes and how
to clip themselves in case of singularities. The most advanced primitives
in the present plot
library are plot::Ode2d
, plot::Ode3d
and plot::Implicit2d
, plot::Implicit3d
. The
first automatically solve systems of ordinary differential equations
numerically and display the solutions graphically. The latter display
the solution curves or surfaces of algebraic equations f(x, y)
= 0 or f(x, y, z)
= 0, respectively, by solving these equation
numerically.
All these primitives are just "objects" representing some graphical entities. They are not rendered directly when they are created, but just serve as data structures encoding the graphical meaning, collecting attributes defining the presentation style and providing numerical routines to convert the input parameters to some numerical data that are to be sent to the renderer.
An arbitrary number of such primitives can be collected to form
a graphical scene. Finally, a call to plot
passing
a sequence of all primitives in the scene invokes the renderer to
draw the plot. The following example shows the graph of the function f(x)
= x sin(x).
At the point (x_{0}, f(x_{0})),
a graphical point is inserted and the tangent to the graph through
this point is added:
f := x -> x*sin(x): x0 := 1.2: dx := 1: g := plot::Function2d(f(x), x = 0..2*PI): p := plot::Point2d(x0, f(x0)): t := plot::Line2d([x0 - dx, f(x0) - f'(x0)*dx], [x0 + dx, f(x0) + f'(x0)*dx]):
The picture is drawn by calling plot
:
plot(g, p, t):
Each primitive accepts a variety of plot attributes that may
be passed as a sequence of equations AttributeName = AttributeValue
to
the generating call. Most prominently, each primitive allows to set
the color explicitly:
g := plot::Function2d(f(x), x = 0..2*PI, Color = RGB::Blue):
Alternatively, the generated objects allow to set attributes
via slot assignments of the form primitive::AttributeName
:= AttributeValue
as in
p::Color := RGB::Black: p::PointSize := 3.0*unit::mm: t::Color := RGB::Red: t::LineWidth := 1.0*unit::mm:
The help page of each primitive provides a list of all attributes the primitive is reacting to.
Certain attributes such as axes style, the visibility of grid
lines in the background etc. are associated with the whole scene rather
than with the individual primitives. These attributes may be included
in the plot
call:
plot(g, p, t, GridVisible = TRUE):
As explained in detail in section The Full Picture: Graphical Trees,
the plot
command automatically embeds the graphical
primitives in a coordinate system, which in turn is embedded in a
graphical scene, which is drawn inside a canvas. The various attributes
associated with the general appearance of the whole picture are associated
with these "grouping structures": a concise list of
all such attributes is provided on the help pages of plot::Canvas
, plot::Scene2d
, plot::Scene3d
, plot::CoordinateSystem2d
,
and plot::CoordinateSystem3d
,
respectively.
The object browser provided by the MuPAD^{®} graphics tool allows to select each primitive in the plot. After selection, the attributes of the primitive can be changed interactively in the property inspector (see section Viewer, Browser, and Inspector: Interactive Manipulation).
Next, we wish to demonstrate a animation. It is remarkably simple
to generate an animated picture. We want to let the point x_{0} at
which the tangent is added move along the graph of the function. In MuPAD,
you do not need to create an animation frame by frame. Instead, each
primitive can be told to animate itself by simply defining it with
a symbolic animation parameter and adding an animation range for this
parameter. Static and animated objects can be mixed and rendered together.
The static function graph of f(x) used
in the previous plot is recycled for the animation. The graphical
point at (x_{0}, f(x_{0})) and
the tangent through this point shall be animated using the coordinate x0
as
the animation parameter. Deleting its value set above, we can use
the same definitions as before, now with a symbolic x0
.
We just have to add the range specification x0 = 0..2*PI
for
this parameter:
delete x0: dx := 2/sqrt(1 + f'(x0)^2): p := plot::Point2d(x0, f(x0), x0 = 0..2*PI, Color = RGB::Black, PointSize = 2.0*unit::mm): t := plot::Line2d([x0 - dx, f(x0) - f'(x0)*dx], [x0 + dx, f(x0) + f'(x0)*dx], x0 = 0..2*PI, Color = RGB::Red, LineWidth = 1.0*unit::mm): plot(g, p, t, GridVisible = TRUE):
Details on animations and further examples are provided in section Graphics and Animations.
We summarize the construction principles for graphics with the MuPAD plot
library:
Note:
Graphical scenes are built from graphical primitives. Section Graphics and Animations provides
a survey of the primitives that are available in the |
Note:
Primitives generated by the |
Note:
Graphical attributes are specified as equations |
Note:
Attributes determining the general appearance of the picture
may be passed in the |
Note: All attributes can be changed interactively in the viewer. |
Note: Presently, 2D and 3D plots are strictly separated. Objects of different dimension cannot be rendered in the same plot. |
Note: Animations are not created frame by frame but objectwise (also see section Frame by Frame Animations). An object is animated by generating it with a symbolic animation parameter and providing a range for this parameter in the generating call. Section Graphics and Animations provides for further details on animations. |
Presently, it is not possible to add objects to an existing plot. However, using animations, it is possible to let primitives appear one after another in the animated picture. See Graphics and Animations.
We wish to visualize the interpolation of a discrete data sample by cubic splines. First, we define the data sample, consisting of a list of points [[x_{1}, y_{1}], [x_{2}, y_{2}], …]. Suppose they are equidistant sample points from the graph of the function :
f := x -> x*exp(-x)*sin(5*x): data := [[i/3, f(i/3)] $ i = 0..9]:
We use numeric::cubicSpline
to
define the cubic spline interpolant through these data:
S := numeric::cubicSpline(op(data)):
The plot shall consist of the function f(x) that
provides the data of the sample points and of the spline interpolant S(x).
The graphs of f(x) and S(x) are
generated via plot::Function2d
.
The data points are plotted as a plot::PointList2d
:
plot( plot::Function2d(f(x), x = 0..3, Color = RGB::Red, LegendText = expr2text(f(x))), plot::PointList2d(data, Color = RGB::Black), plot::Function2d(S(x), x = 0..3, Color = RGB::Blue, LegendText = "spline interpolant"), GridVisible = TRUE, SubgridVisible = TRUE, LegendVisible = TRUE ):
A cycloid is the curve that you get when following a point fixed to a wheel rolling along a straight line. We visualize this construction by an animation in which we use the x coordinate of the hub as the animation parameter. The wheel is realized as a circle. There are 3 points fixed to the wheel: a green point on the rim, a red point inside the wheel and a blue point outside the wheel:
WheelRadius := 1: WheelCenter := [x, WheelRadius]: WheelRim := plot::Circle2d(WheelRadius, WheelCenter, x = 0..4*PI, LineColor = RGB::Black): WheelHub := plot::Point2d(WheelCenter, x = 0..4*PI, PointColor = RGB::Black): WheelSpoke := plot::Line2d(WheelCenter, [WheelCenter[1] + 1.5*WheelRadius*sin(x), WheelCenter[2] + 1.5*WheelRadius*cos(x)], x = 0..4*PI, LineColor = RGB::Black): color:= [RGB::Red, RGB::Green, RGB::Blue]: r := [1.5*WheelRadius, 1.0*WheelRadius, 0.5*WheelRadius]: for i from 1 to 3 do Point[i] := plot::Point2d([WheelCenter[1] + r[i]*sin(x), WheelCenter[2] + r[i]*cos(x)], x = 0..4*PI, PointColor = color[i], PointSize = 2.0*unit::mm): Cycloid[i] := plot::Curve2d([y + r[i]*sin(y), WheelRadius + r[i]*cos(y)], y = 0..x, x = 0..4*PI, LineColor = color[i]): end_for: plot(WheelRim, WheelHub, WheelSpoke, Point[i] $ i = 1..3, Cycloid[i] $ i = 1..3, Scaling = Constrained, Width = 120*unit::mm, Height = 60*unit::mm):
We wish to visualize the solution of the ordinary differential
equation (ODE)
with
the initial condition y(0)
= 0. The solution shall be drawn together with
the vector field
associated
with this ODE (along the solution curve, the vectors of this field
are tangents of the curve). We use numeric::odesolve2
to generate the solution
as a function plot. Since the numerical integrator returns the result
as a list of one floating-point value, one has to pass the single
list entry as Y(x)[1]
to plot::Function2d
. The vector field is
generated via plot::VectorField2d
:
f := (x, y) -> -y^3 + cos(x): Y := numeric::odesolve2( numeric::ode2vectorfield({y'(x) = f(x, y), y(0) = 0}, [y(x)])): plot( plot::Function2d(Y(x)[1], x = 0..6, Color = RGB::Red, LineWidth = 0.7*unit::mm), plot::VectorField2d([1, f(x, y)], x = 0..6, y = -1..1, Color = RGB::Blue, Mesh = [25, 25]), GridVisible = TRUE, SubgridVisible = TRUE, Axes = Frame ):
The radius r of an object with rotational symmetry around the x-axis is measured at various x positions:
x | 0.00 | 0.10 | 0.20 | 0.30 | 0.40 | 0.50 | 0.60 | 0.70 | 0.80 | 0.90 | 0.95 | 1.00 |
r(x) | 0.60 | 0.58 | 0.55 | 0.51 | 0.46 | 0.40 | 0.30 | 0.15 | 0.23 | 0.24 | 0.20 | 0.00 |
A spline interpolation is used to define a smooth function r(x) from the measurements:
samplepoints := [0.00, 0.60], [0.10, 0.58], [0.20, 0.55], [0.30, 0.51], [0.40, 0.46], [0.50, 0.40], [0.60, 0.30], [0.70, 0.15], [0.80, 0.23], [0.90, 0.24], [0.95, 0.20], [1.00, 0.00]: r := numeric::cubicSpline(samplepoints):
We reconstruct the object as a surface of revolution via plot::XRotate
. The
rotation angle is restricted to a range that leaves a gap in the surface.
The spline curve and the sample points are added as a plot::Curve3d
and a plot::PointList3d
,
respectively, and displayed in this gap:
plot( plot::XRotate(r(x), x = 0..1, AngleRange = 0.6*PI..2.4*PI, Color = RGB::MuPADGold), plot::Curve3d([x, 0, r(x)], x = 0..1, LineWidth = 0.5*unit::mm, Color = RGB::Black), plot::PointList3d([[p[1], 0, p[2]] $ p in samplepoints], PointSize = 2.0*unit::mm, Color = RGB::Red), CameraDirection = [70, -70, 40] ):
The following sum is a Fourier representation of a periodic step function:
We wish to show the convergence of the partial sums
for some small values of n.
To this end, we assign the value of f_{n}(x) to
the MuPAD identifier
f_n
. For
our second and third example, we will need to accept fractional values
of n,
so the code uses floor
to
get a "proper" integer value for the sum:
f_n := sum(sin((2*k-1)*x)/(2*k-1), k = 1..floor(n))
First, we use plotfunc2d
and
the sequence operator $
to plot the first 5 partial
sums into the same coordinate system (and switch off the legend, which
is not useful for this application).
plotfunc2d(f_n $ n = 1..5, LegendVisible = FALSE)
This plot clearly shows what is known as Gibbs' phenomenon:
At the discontinuities of the step function, the approximation "overshoots."
Plotting more approximations simultaneously is going to create a too
crowded plot to be of use, so to show that using more terms in the
sum does not help against Gibbs' phenomenon, we revert to animations
to show the first 30 partial
sums – this is one of the reasons we used floor(n)
above:
plotfunc2d(f_n, x = -5..5, n = 1..30, Frames = 15)
Another possibility of showing the convergence behavior is to create a 3D plot with the second ordinate being the number of terms used in the approximation:
plotfunc3d(f_n, x = -5..5, n = 1..30, Submesh = [5,1], FillColorType = Rainbow)