Documentation Center

  • Trial Software
  • Product Updates

Advanced Plotting: Principles and First Examples

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.

General Principles

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) = xsin(x). At the point (x0, f(x0)), 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 x0 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 (x0, f(x0)) 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 plot library.

    Note:   Primitives generated by the plot library are symbolic objects that are not rendered directly. The call plot(Primitive1, Primitive2, ...) generates the pictures.

    Note:   Graphical attributes are specified as equations AttributeName = AttributeValue. Attributes for a graphical primitive may be passed in the call that generates the primitive. The help page of each primitive provides a complete list of all attributes the primitive reacts to.

    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.

Some Examples

Example 1

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 [[x1, y1], [x2, y2], …]. 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
):

Example 2

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):

Example 3

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
):

Example 4

The radius r of an object with rotational symmetry around the x-axis is measured at various x positions:

x0.00 0.100.200.300.400.500.600.700.800.900.95 1.00
r(x)0.60 0.580.550.510.460.400.300.150.230.240.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]
):

Example 5

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 fn(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)

Was this topic helpful?