# Python, NumPy and MatPlotLib: Visualising Data in 3D

Python

## Generate Data

Let's create the following matrix z:

Python

z=
[[0 1 1 1 0]
[1 2 3 2 1]
[1 2 3 2 1]
[0 1 1 1 0]]



Now let's view it in variable explorer. Note how the variable explorer automatically colours the values, with the lowest values showing in red and the highest values showing in blue. This allows us to visualise how the 3D data looks on the 2D plane without actually looking at the numeric values.

## Contour Plot 2D (contour)

Visualisation of 3D data in 2D can be enhanced by using a contour plot. The contour plot, essentially bins the z data usually in an equally number increments from a minimum to a maximum value. It then uses a series of coloured lines to represent the magnitude of these different bins and these show variations in the value of z.

Python

Looking at the contour plot, we can see the x axis corresponds to the values of the columns and the y axis correspond to the values of the rows.

While it is possible to plot z using these indexes it may be more insightful to plot z with respect to more meaningful x and y values.

For instance the x and y co-ordinates for each measurement in z may be different from the indexes. For example, assume x and y start at position 1 and 5 respectively on a translation stage, at the bottom of a microscope with a single point detector which measures the intensity stored as z. For convenience say we perform a measurement every 1 mm (the step size in an actual measurement could be a fraction of a mm e.g. 0.25 mm or multiple mm for example 2 mm). It therefore follows that:

$\displaystyle \begin{array}{*{20}{c}} {} & {\begin{array}{*{20}{r}} 5 & 6 & 7 & 8 & 9 \end{array}} \\ {\begin{array}{*{20}{c}} 1 \\ 2 \\ 3 \\ 4 \end{array}} & {\left[ {\begin{array}{*{20}{r}} 0 & 1 & 1 & 1 & 0 \\ 1 & 2 & 3 & 2 & 1 \\ 1 & 2 & 3 & 2 & 1 \\ 0 & 1 & 1 & 1 & 0 \end{array}} \right]} \end{array}$

Python

xrow=
[[5 6 7 8 9]]

ycol=
[[1]
[2]
[3]
[4]]

z=
[[0 1 1 1 0]
[1 2 3 2 1]
[1 2 3 2 1]
[0 1 1 1 0]]



To create a contour plot with these x,y and z values we use:

Python

From the default plot, we cannot see the levels of the plot, however we can add a colorbar by using:

Python

Additional input arguments can be selected for instance the direction of the colorbar can be horizontal or vertical (with no input arguments the default of vertical is used).

Python
Python

The levels can also be specified alongside the contour map to use for each level. For example, we can manually specify levels from 0 to 3.0 in steps of 0.5 and we can also specify the contour map as the default viridis (line 168)

Python

We can change to the contour map jet (line 18)

Python

Or the contour map bone for a black and white figure (line 18)

Python

hot is another commonly used contour map (line 18):

Python

The contourmap used ultimately depends on your own preference however in general it is best to select a contourmap with levels that visually shows the differences between your contour lines such as the contourmaps above:

If for instance the levels were changed (line 18) to be steps of 10 with this dataset, then one would assume that there is no data:

Python

The levels (line 18) can of course be specified using the function arange. For example:

Python

Once again recall that zero order indexing is used, so we go up to the upper limit in this case 3.2 but don't reach it so display a maximum level of 3.0.

It is also possible to fine tune some of the properties of this plot but to do so we need to save the figure, axes, colorbar and colorbar axes to variables as we create them which we can index into. First of all we save the figure to a variable called fig2 (line 16).

Python

This gives a blank canvas

We can now assign axes to a variable ax2 (line 18).

Python

Alternatively we could assign axes as a subplot to fig2 (line 18). In this case using the input argument 111 (subplots consisting of 1 row by 1 column and the 1st and only 1 subplot selected).

Python

Now we can add the contour plot and assign it to a variable contour2.

Python

We can add the axes labels (line 22, 23) and the colour map (line 25):

Python

For a 2D contour plot when the plot command for instance contour is used after plt. The contour plot will be assigned to the current or last selected axes (line 20). It is possible to explicitly state the axes, in our case by indexing into ax2 opposed to plt2 (line 20). The Axes labels can also be assigned to the axes using the set_xlabel, and set_ylabel commands (line 22 and 23).

Python

The colourbar can be assigned to fig2. When this is done, it must be assigned to the plot which is contour2 and the axes must also be set to a figure by indexing into it e.g. fig2, however it must also be assigned to a plot e.g. contour 2 and an axes e.g. ax2 (line 25).

Python

The colorbar itself has it's own axes and we can index into its variable name colorbar2 using dot indexing followed by ax (line 5). Note we don't use add_subplot here as the colorbar is not a subplot, we just want to grab the ax from the existing colourbar

Python

Before proceeding it is worthwhile using getp on all the assigned properties and having a look through the results.

Python

fig2 properties
agg_filter = None
alpha = None
animated = False
axes = [&lt;matplotlib.axes._subplots.AxesSubplot object at ...
children = [&lt;matplotlib.patches.Rectangle object at 0x000001C...
clip_box = None
clip_on = True
clip_path = None
constrained_layout = False
constrained_layout_pads = (0.04167, 0.04167, 0.02, 0.02)
contains = None
default_bbox_extra_artists = [&lt;matplotlib.axes._subplots.AxesSubplot object at ...
dpi = 100.0
edgecolor = (1.0, 1.0, 1.0, 1.0)
facecolor = (1.0, 1.0, 1.0, 1.0)
figheight = 4.8
figure = None
figwidth = 6.4
frameon = True
gid = None
in_layout = True
label =
path_effects = []
picker = None
rasterized = None
size_inches = [6.4 4.8]
sketch_params = None
snap = None
tight_layout = False
transform = IdentityTransform()
transformed_clip_path_and_affine = (None, None)
url = None
visible = True
window_extent = TransformedBbox(     Bbox(x0=0.0, y0=0.0, x1=6.4, ...
zorder = 0
contour2 properties
alpha = None
array = [0.  0.2 0.4 0.6 0.8 1. ]...
clim = (0.0, 3.0)
cmap = &lt;matplotlib.colors.LinearSegmentedColormap object ...
transform = CompositeGenericTransform(     TransformWrapper(  ...
ax2 properties
agg_filter = None
alpha = None
anchor = (0.5, 0.0)
animated = False
aspect = auto
autoscale_on = True
autoscalex_on = True
autoscaley_on = True
axes_locator = None
axisbelow = line
children = [&lt;matplotlib.collections.LineCollection object at ...
clip_box = None
clip_on = True
clip_path = None
contains = None
data_ratio = 0.75
data_ratio_log = 2.3584991696787525
default_bbox_extra_artists = [&lt;matplotlib.collections.LineCollection object at ...
facecolor = (1.0, 1.0, 1.0, 1.0)
fc = (1.0, 1.0, 1.0, 1.0)
figure = Figure(640x480)
frame_on = True
geometry = (2, 1, 1)
gid = None
gridspec = GridSpecFromSubplotSpec(2, 1, height_ratios=[0.7, ...
images = &lt;a list of 0 AxesImage objects>
in_layout = True
label =
legend = None
legend_handles_labels = ([], [])
lines = &lt;a list of 0 Line2D objects>
navigate = True
navigate_mode = None
path_effects = []
picker = None
position = Bbox(x0=0.125, y0=0.3410000000000001, x1=0.9, y1=0...
rasterization_zorder = None
rasterized = None
renderer_cache = None
shared_x_axes = &lt;matplotlib.cbook.Grouper object at 0x000001727CC8...
shared_y_axes = &lt;matplotlib.cbook.Grouper object at 0x000001727CCC...
sketch_params = None
snap = None
subplotspec = &lt;matplotlib.gridspec.SubplotSpec object at 0x00000...
title =
transform = IdentityTransform()
transformed_clip_path_and_affine = (None, None)
url = None
visible = True
window_extent = Bbox(x0=76.5, y0=160.18000000000004, x1=579.5, y1=...
xaxis = XAxis(80.000000,163.680000)
xaxis_transform = BlendedGenericTransform(     CompositeGenericTrans...
xbound = (5.0, 9.0)
xgridlines = &lt;a list of 9 Line2D xgridline objects>
xlabel = x position (Âµm)
xlim = (5.0, 9.0)
xmajorticklabels = &lt;a list of 9 Text xticklabel objects>
xminorticklabels = &lt;a list of 0 Text xticklabel objects>
xscale = linear
xticklabels = &lt;a list of 9 Text xticklabel objects>
xticklines = &lt;a list of 18 Line2D xtickline objects>
xticks = [5.  5.5 6.  6.5 7.  7.5]...
yaxis = YAxis(80.000000,163.680000)
yaxis_transform = BlendedGenericTransform(     BboxTransformTo(     ...
ybound = (1.0, 4.0)
ygridlines = &lt;a list of 7 Line2D ygridline objects>
ylabel = y position (Âµm)
ylim = (1.0, 4.0)
ymajorticklabels = &lt;a list of 7 Text yticklabel objects>
yminorticklabels = &lt;a list of 0 Text yticklabel objects>
yscale = linear
yticklabels = &lt;a list of 7 Text yticklabel objects>
yticklines = &lt;a list of 14 Line2D ytickline objects>
yticks = [1.  1.5 2.  2.5 3.  3.5]...
zorder = 0
colorbar2 properties
array = None
clim = (0.0, 3.0)
cmap = &lt;matplotlib.colors.LinearSegmentedColormap object ...
ticks = [0.  0.4 0.8 1.2 1.6 2. ]...
colorbar2ax2 properties
agg_filter = None
alpha = None
anchor = (0.5, 1.0)
animated = False
aspect = 0.05
autoscale_on = False
autoscalex_on = False
autoscaley_on = False
axes_locator = None
axisbelow = line
children = [&lt;matplotlib.collections.LineCollection object at ...
clip_box = None
clip_on = True
clip_path = None
contains = None
data_ratio = 1.0
default_bbox_extra_artists = [&lt;matplotlib.collections.LineCollection object at ...
facecolor = (1.0, 1.0, 1.0, 1.0)
fc = (1.0, 1.0, 1.0, 1.0)
figure = Figure(640x480)
frame_on = False
geometry = (1, 3, 2)
gid = None
gridspec = GridSpecFromSubplotSpec(1, 3, width_ratios=[0.0, 1...
images = &lt;a list of 0 AxesImage objects>
in_layout = True
label =
legend = None
legend_handles_labels = ([], [])
lines = &lt;a list of 0 Line2D objects>
navigate = False
navigate_mode = None
path_effects = []
picker = None
position = Bbox(x0=0.125, y0=0.17383333333333334, x1=0.900000...
rasterization_zorder = None
rasterized = None
renderer_cache = None
shared_x_axes = &lt;matplotlib.cbook.Grouper object at 0x000001727CC8...
shared_y_axes = &lt;matplotlib.cbook.Grouper object at 0x000001727CCC...
sketch_params = None
snap = None
subplotspec = &lt;matplotlib.gridspec.SubplotSpec object at 0x00000...
title =
transform = IdentityTransform()
transformed_clip_path_and_affine = (None, None)
url = None
visible = True
window_extent = Bbox(x0=76.5, y0=79.94, x1=579.5000000000001, y1=1...
xaxis = XAxis(80.000000,83.440000)
xaxis_transform = BlendedGenericTransform(     CompositeGenericTrans...
xbound = (0.0, 1.0)
xgridlines = &lt;a list of 8 Line2D xgridline objects>
xlabel =
xlim = (0.0, 1.0)
xmajorticklabels = &lt;a list of 8 Text xticklabel objects>
xminorticklabels = &lt;a list of 0 Text xticklabel objects>
xscale = linear
xticklabels = &lt;a list of 8 Text xticklabel objects>
xticklines = &lt;a list of 16 Line2D xtickline objects>
xticks = [0.         0.13333333 0.26666667 0.4        0.533...
yaxis = YAxis(80.000000,83.440000)
yaxis_transform = BlendedGenericTransform(     BboxTransformTo(     ...
ybound = (0.0, 1.0)
ygridlines = &lt;a list of 0 Line2D ygridline objects>
ylabel =
ylim = (0.0, 1.0)
ymajorticklabels = &lt;a list of 0 Text yticklabel objects>
yminorticklabels = &lt;a list of 0 Text yticklabel objects>
yscale = linear
yticklabels = &lt;a list of 0 Text yticklabel objects>
yticklines = &lt;a list of 0 Line2D ytickline objects>
yticks = []
zorder = 0



Let's have a look at the contour2 properties first.


contour2 properties
alpha = None
array = [0.  0.2 0.4 0.6 0.8 1. ]...
clim = (0.0, 3.0)
cmap = &lt;matplotlib.colors.LinearSegmentedColormap object ...
transform = CompositeGenericTransform(     TransformWrapper(  ...



Let's type in contour2 into the command window followed by a dot and then tab, this gives a list of commands we can access.

There are a number of get and set commands. One which you may want to look at is clim which sets the lower and upper bound of the contour lines. This can be typed in the command window:

Python

(0.0, 3.0)



(0.0, 3.0)



In some datasets which have both high and low values, the features of the lower values are often not observable because they get drowned out by the larger value. It is quite common to amend the upper limit of clim in order to view these smaller features (line 31).

Python

Any value out of bounds of clim will be shown up as white. We can type the following in the command window to look up the properties of contour2:

Python

contour2 properties
alpha = None
array = [0.  0.2 0.4 0.6 0.8 1. ]...
clim = (0.0, 2.0)
cmap = &lt;matplotlib.colors.LinearSegmentedColormap object ...
transform = CompositeGenericTransform(     TransformWrapper(  ...



If instead the lower bound is increased, anything below it will turn black (line 27):

Python

We can once again look at the contour properties via the command window:

Python

contour2 properties
alpha = None
array = [0.  0.2 0.4 0.6 0.8 1. ]...
clim = (2.0, 3.0)
cmap = &lt;matplotlib.colors.LinearSegmentedColormap object ...
transform = CompositeGenericTransform(     TransformWrapper(  ...



Returning clim back to the full range:

Python

We may wish to alter the numbers shown on the colourbar by indexing into colorbar2. We can view the ticks of the colourbar by indexing into it using get_ticks() in the command window.

Python

array([0. , 0.4, 0.8, 1.2, 1.6, 2. , 2.4, 2.8])



We can set our desired ticks using set_ticks (line 31).

Python

If we didn't want numeric values on the colorbar but rather the text labels, low, medium, high, we can index into the axes of the colorbar, already assigned to colorbar2ax2 and index into this to get the xticklabels using get_ticklabels in the command window:

Python

&lt;a list of 3 Text xticklabel objects>



We can now modify these using set_xticklabels (line 33).

Python

## Contour Plot 2D Fill (contourf)

To get a filled contour plot, the function contourf is used opposed to contour. These two functions respond completely identically so we won't go through it step by step again but instead modify the code to get the chart above as a filled contour plot. First we will modify line 16, 18, 20, 22, 23, 25, 27, 29, 31 and 33 to correspond to figure 3 with variable names ending in 3 fig3, ax3, contour3, colorbar3, colorbar3ax3. Then in line 20 we change contour to contourf.

Python

As you can see instead of an outline contour, the chart is filled in. It is worth seeing this plot done in other commonly used colourmaps; viridis, jet and bone. This is done by changing cmap to these on line 20.

## Perquisites for 3D Plots

The library matplotlib is designed for 2D plots by default. It does however have a toolkit for 3D plotting. This has to be imported to enable 3D plotting:

Python

Note these guides have so far been based on the IDE Spyder but all the steps above were also applicable to Jupyter notebooks. Jupyter and Spyder by default plot figures inline meaning they shown embedded into the Command Window. For 2D and 3D plotting, 3D plotting especially it is far better to use Spyder and configure it so the plots are interactive.

Before creating any figures, you should adjust your preferences for how you wish to display figures. The default option is inline which means all figures will be printed to the Console as shown:

If instead you want the Figures to be shown as a separate Window, you can change the setting to Automatic. To do this go to Tools â†’ Preferences:

To do this go to Tools â†’ Preferences:

Next on the left hand menu select iPython console:

Select Graphics:

Change the setting from Inline to Automatic:

Select Apply:

Now go to Consoles and Restart the Kernal:

When rerunning your code, your figure will be in a separate window opposed to being inline within the Console:

Note Spyder Version 3.3 may give a stream of errors instead of making a plot. If you have this version (installed by default with the Anaconda March 2019 installer) you should close down Spyder and then update both Anaconda and Spyder. To do this open the Anaconda PowerShell Prompt and type in:

Python

Note it is also possible to toggle between the two settings without restarting the Kernal using the following commands:

Python

In these guides however the setting automatic will be applied and the figures will all be shown as separate windows.

## Meshgrid

Assume we've got some data z which has row indexes [0,1,2,3] and column indexes [0,1,2,3,4].

While it is possible to plot z using these indexes it may be more insightful to plot z with respect to more meaningful x and y values.

For instance the x and y co-ordinates for each measurement in z may be different from the indexes. For example, assume x and y start at position 1 and 5 respectively on a translation stage and for convenience say we perform a measurement every 1 mm (the step size in an actual measurement could be a fraction of a mm e.g. 0.25 mm or multiple mm for example 2 mm). It therefore follows that:

$\displaystyle \begin{array}{*{20}{c}} {} & {\begin{array}{*{20}{r}} 5 & 6 & 7 & 8 & 9 \end{array}} \\ {\begin{array}{*{20}{c}} 1 \\ 2 \\ 3 \\ 4 \end{array}} & {\left[ {\begin{array}{*{20}{r}} 0 & 1 & 1 & 1 & 0 \\ 1 & 2 & 3 & 2 & 1 \\ 1 & 2 & 3 & 2 & 1 \\ 0 & 1 & 1 & 1 & 0 \end{array}} \right]} \end{array}$

In Python we can write this as:

Python

xrow=
[[5 6 7 8 9]]
ycol=
[[1]
[2]
[3]
[4]]
z=
[[0 1 1 1 0]
[1 2 3 2 1]
[1 2 3 2 1]
[0 1 1 1 0]]



While the 2D contour plotting functions can understand x,y,z data in this form given above, the 3D functions often are more restrictive. They instead look for the x, y and z data to have the same dimensions. In our case our x data is a 5 element vector, our y data is a 4 element vector and our z is a 4 by 5 matrix.


ValueError: all the input array dimensions except for the concatenation axis must match exactly



We need to instead have the data as an individual x, y and z co-ordinates. This is where the function meshgrid comes into play (line 25).

Python

z=
[[0 1 1 1 0]
[1 2 3 2 1]
[1 2 3 2 1]
[0 1 1 1 0]]

xx=
[[5 6 7 8 9]
[5 6 7 8 9]
[5 6 7 8 9]
[5 6 7 8 9]]

yy=
[[1 1 1 1 1]
[2 2 2 2 2]
[3 3 3 3 3]
[4 4 4 4 4]]



the new x values xx are essentially the original row of x repeated 4 times (which is the length of the column vector y) and the new y values yy are essentially the original column of y repeated 5 times (which is the length of the row vector x).

$\displaystyle \text{xx}=\left[ {\begin{array}{*{20}{r}} 5 & 6 & 7 & 8 & 9 \\ 5 & 6 & 7 & 8 & 9 \\ 5 & 6 & 7 & 8 & 9 \\ 5 & 6 & 7 & 7 & 9 \end{array}} \right]$

$\displaystyle \text{yy}=\left[ {\begin{array}{*{20}{r}} 1 & 1 & 1 & 1 & 1 \\ 2 & 2 & 2 & 2 & 2 \\ 3 & 3 & 3 & 3 & 3 \\ 4 & 4 & 4 & 4 & 4 \end{array}} \right]$

$\displaystyle \text{z}=\left[ {\begin{array}{*{20}{r}} 0 & 1 & 1 & 1 & 0 \\ 1 & 2 & 3 & 2 & 1 \\ 1 & 2 & 3 & 2 & 1 \\ 0 & 1 & 1 & 1 & 0 \end{array}} \right]$

These map directly to x,y co-ordinates and the measurement z at these co-ordinates (for example assuming a microscope with a single point-detector this could be intensity):

$\displaystyle \text{xx}=\left[ {\begin{array}{*{20}{r}} 5 & 6 & \mathbf{7} & 8 & 9 \\ 5 & 6 & 7 & 8 & 9 \\ 5 & 6 & 7 & 8 & 9 \\ 5 & 6 & 7 & 7 & 9 \end{array}} \right]$

$\displaystyle \text{yy}=\left[ {\begin{array}{*{20}{r}} 1 & 1 & \mathbf{1} & 1 & 1 \\ 2 & 2 & 2 & 2 & 2 \\ 3 & 3 & 3 & 3 & 3 \\ 4 & 4 & 4 & 4 & 4 \end{array}} \right]$

$\displaystyle \text{z}=\left[ {\begin{array}{*{20}{r}} 0 & 1 & \mathbf{1} & 1 & 0 \\ 1 & 2 & 3 & 2 & 1 \\ 1 & 2 & 3 & 2 & 1 \\ 0 & 1 & 1 & 1 & 0 \end{array}} \right]$

For instance the co-ordinate highlighted in bold maps to xx=7 Î¼m,yy=1 Î¼m and z=1 unit of intensity.

Now that we have xx, yy and z we can go ahead and flatten them and list them side by side to create a matrix with x,y co-ordinates and z (intensity at the single-point detector).

Python
Python

xyzdata=
[[5. 1. 0.]
[6. 1. 1.]
[7. 1. 1.]
[8. 1. 1.]
[9. 1. 0.]
[5. 2. 1.]
[6. 2. 2.]
[7. 2. 3.]
[8. 2. 2.]
[9. 2. 1.]
[5. 3. 1.]
[6. 3. 2.]
[7. 3. 3.]
[8. 3. 2.]
[9. 3. 1.]
[5. 4. 0.]
[6. 4. 1.]
[7. 4. 1.]
[8. 4. 1.]
[9. 4. 0.]]



This is how some of the 3D plotting routines will read in the data, when x,y and z values are input for instance the wiregrid and surface functions.

## Contour Plot 3D (contour3D)

MatPlotLib is designed to use 2D axes by default, we need to assign the axes to a 3D projection in order to carry out a 3D plot (line 17), this is an input argument in fig4.add_subplot or plt.axes. We can then create a contour3D plot by assigning it to these 3D axes (line 19).

Note there is an inconsistency, line 6 which calls the perquisite uses upper case 3D, however the input argument in line 20 uses lower case 3d and the name of the plot type line 22 once again uses upper case 3D. These are case sensitive.

Python

Here we see a 3D contour plot. If we hover above using the mouse, we get an x,y,z 3D co-ordinate corresponding to the mouse position shown on the bottom left.

The figure is displayed with a default azimuth of -60 degrees and elevation of 30 degrees. If however left click on the figure (and hold down the left click), then we can rotate the figure using the mouse shown, the azimuth and elevation are shown on the bottom left while we are holding down the left click.

These can be set to a desired value (line 24-25) using:

Python

Note in testing, this only seems to work when run in a script and not when typed into the command window. Setting both the elevation and azimuth angle to 0 degrees and zero degrees gives the following view. This is like what a 2 graph would show with just x and y and all the z numbers are on top of each other.

We can change only the elevation to 45 degrees (line 24)

Python

Note how the elevation appears to lift it up and out the page (as if the top of the graph is rotating out of the page towards you). Returning back to an elevation and azimuth values of 0 degrees and 0 degrees (line 24).

Python

Now we can look at what happens when only the aximuth is set to 45 degrees (line 25).

Python

Notice how the figure rotates as if the right hand side of the screen is coming towards you.

The x and y axes can be labelled using xlabel and ylabel however as the matplotlib commands are designed for 2D graphing, the command zlabel is not recognised. One has to index into the 3D axes ax4 and use the set_zlabel command:

Python

For the colourbar, we need to assign the figure, the axis, and the plot (line 31). Then we must call the colorbar from fig4, assign it to our plot, in our case contour4, the axis of the plot, in our case ax4

Python

Once again, the colourbar can be assigned to a variable and it's axes can be assigned to a variable. One can then change the ticks of the colourbar and assign the xticklabels to the colourbars axes:

Python

## Contour Plot 3D Fill (contourf3D)

One can carry out a 3D contour filled plot by using the function contourf3D which acts otherwise in an identical manner to contour3D. Here we will reference fig5 opposed to fig4 in line 17,19,21,25,27,29,31,33 and 35. We will change contour3D to contourf3D in line 21.

Python

Note the filled contour plot doesn't look different at this angle. If we change the azimeth back to the default of 60 and the elevation to 30, we will see the difference between fig4 (the contour3D plot) and figure 5 (the contourf3D plot) in more detail:

In the 3D contour filled plot, although the contour is filled, one can clearly see the slices through z.

## Surface (plot_surface)

For a 3D plot that doesn't look like a series of slices, but rather a solid object one would use a slightly different plot type called a surface plot. Let's copy the code from earlier to line 25 and modify lines 17, 19, 21 and 25 to refer to figure 6. let's also modify line 19 to refer to the surface plot:

Python

By default the surface is a monochrome blue. It can be assigned another colour using the additional input argument color (line 21)

Python

A colourmap can also be specified (line 21) but there is no input argument "levels" like the contour plots had, so it cannot be as fine-tuned as in the contour3D plot.

Python

The colourbar can also be added (line 27)

Python

One can also select the ticks for the colourbar using:

Python

However note the the lowest non-zero value is automatically selected as the lower bound (0 and 0.5 are omitted) and the highest value it shows on the surface plot in this case 2.5 (which is less than 3.0 in the actual data) is shown.

## Wiregrid (plot_wireframe)

In the surface plot, all the surfaces were filled in giving the data plotted a solid appearance. The wireframe plot has the same form as the surface plot but none of the surfaces are filled, making it transparent. Let's copy the code from earlier to line 25 and modify lines 17, 19, 21 and 25 to refer to figure 7. let's also modify line 21 to refer to the wiregrid plot:

Python

Because none of the surfaces are filled in, the colourmap doesn't work with type of plot however the colour of the frame can be changed by using the input argument color (line 21):

Python