This note attempts to provide a summary of the myriad of the existing methods of 3D data visualization in Python.

## 3D-plotting in matplotlib

Over the past few years matplotlib has significantly grown to include additional plotting capabilities including 3D plotting techniques. At this point in the Python learning process, it is generally more sensible to learn the latest techniques of the advanced Python packages (including matplotlib) directly from their reference manual. The reason for this is that the interfaces for many of these packages are constantly evolving and any code that may work today, may not be functional in a few months or years. With this note in mind, the following is a quick overview of some of these plotting functionalities.

### Creating 3D figure objects

To generate 3D figures, you will have to import mpl_toolkits Python package,

%matplotlib notebook
import matplotlib.pyplot as plt
from mpl_toolkits import mplot3d
fig = plt.figure()
ax = plt.axes(projection="3d")

plt.show()

This should generate the following figure handle for you,

To save the plot in an external file, use the savefig() method.

plt.savefig('emptyFigure3D.png')

### Creating 3D line plots

Now suppose we want to visualize a 3D line which is described by the following equations,

import numpy as np
zmin = 0.0
zmax = 4*np.pi
LineZ = np.linspace(zmin, zmax, 500)
LineY = np.sin(LineZ)
LineX = np.cos(LineZ)

If you look carefully, you may notice that the X and Y coordinates described by the above equations describe a circular, repeating, line. On the contrary, the Z coordinates are described by a set of monotonically increasing values. Therefore, this 3D line must be describing a spring-like object. To plot this object, we can use the following,

%matplotlib notebook
import matplotlib.pyplot as plt
from mpl_toolkits import mplot3d
fig = plt.figure()
ax = plt.axes(projection="3d")
ax.plot3D( x_line, y_line, z_line, 'gray')
plt.show()
plt.savefig('line3D.png')

which would give a plot like the following,

### Creating 3D scatter plots

Now, suppose we want to add a set of points to this line plot. For example, we could create a set of random points around the 3D line and visualize them, as if the random points are a set of observational data and the 3D line represents a mathemtical fit to this observational dataset,

import numpy as np
nPoint = 300 # number of random points
PointZ = zmin + (zmax-zmin) * np.random.random(nPoint)      # points have uniformly-distributed random heights along the Z axis
PointY = np.sin(PointZ) + 0.1 * np.random.randn(nPoint)     # points have normally-distributed random values along the Y axis
PointX = np.cos(PointZ) + 0.1 * np.random.randn(nPoint)     # points have normally-distributed random values along the X axis
ax.scatter3D( PointX, PointY, PointZ
, c=PointZ      # color value of individual points is taken from their heights
, cmap="hsv"    # the color mapping to be used. Other example options: winter, autumn, ...
);
plt.show()
plt.savefig('lineScatter3D.png')

To get a full list of all color-maps that you can use and information about them, try,

help(plt.colormaps)
Help on function colormaps in module matplotlib.pyplot:

colormaps()
Matplotlib provides a number of colormaps, and others can be added using
:func:~matplotlib.cm.register_cmap.  This function documents the built-in
colormaps, and will also return a list of all registered colormaps if
called.

You can set the colormap for an image, pcolor, scatter, etc,
using a keyword argument::

imshow(X, cmap=cm.hot)

or using the :func:set_cmap function::

imshow(X)
pyplot.set_cmap('hot')
pyplot.set_cmap('jet')

In interactive mode, :func:set_cmap will update the colormap post-hoc,
allowing you to see which one works best for your data.

All built-in colormaps can be reversed by appending _r: For instance,
gray_r is the reverse of gray.

There are several common color schemes used in visualization:

Sequential schemes
for unipolar data that progresses from low to high
Diverging schemes
for bipolar data that emphasizes positive or negative deviations from a
central value
Cyclic schemes
for plotting values that wrap around at the endpoints, such as phase
angle, wind direction, or time of day
Qualitative schemes
for nominal data that has no inherent ordering, where color is used
only to distinguish categories

Matplotlib ships with 4 perceptually uniform color maps which are
the recommended color maps for sequential data:

=========   ===================================================
Colormap    Description
=========   ===================================================
inferno     perceptually uniform shades of black-red-yellow
magma       perceptually uniform shades of black-red-white
plasma      perceptually uniform shades of blue-red-yellow
viridis     perceptually uniform shades of blue-green-yellow
=========   ===================================================

The following colormaps are based on the ColorBrewer
<http://colorbrewer2.org>_ color specifications and designs developed by
Cynthia Brewer:

ColorBrewer Diverging (luminance is highest at the midpoint, and
decreases towards differently-colored endpoints):

========  ===================================
Colormap  Description
========  ===================================
BrBG      brown, white, blue-green
PiYG      pink, white, yellow-green
PRGn      purple, white, green
PuOr      orange, white, purple
RdBu      red, white, blue
RdGy      red, white, gray
RdYlBu    red, yellow, blue
RdYlGn    red, yellow, green
Spectral  red, orange, yellow, green, blue
========  ===================================

ColorBrewer Sequential (luminance decreases monotonically):

========  ====================================
Colormap  Description
========  ====================================
Blues     white to dark blue
BuGn      white, light blue, dark green
BuPu      white, light blue, dark purple
GnBu      white, light green, dark blue
Greens    white to dark green
Greys     white to black (not linear)
Oranges   white, orange, dark brown
OrRd      white, orange, dark red
PuBu      white, light purple, dark blue
PuBuGn    white, light purple, dark green
PuRd      white, light purple, dark red
Purples   white to dark purple
RdPu      white, pink, dark purple
Reds      white to dark red
YlGn      light yellow, dark green
YlGnBu    light yellow, light green, dark blue
YlOrBr    light yellow, orange, dark brown
YlOrRd    light yellow, orange, dark red
========  ====================================

ColorBrewer Qualitative:

(For plotting nominal data, :class:ListedColormap is used,
not :class:LinearSegmentedColormap.  Different sets of colors are
recommended for different numbers of categories.)

* Accent
* Dark2
* Paired
* Pastel1
* Pastel2
* Set1
* Set2
* Set3

A set of colormaps derived from those of the same name provided
with Matlab are also included:

=========   =======================================================
Colormap    Description
=========   =======================================================
autumn      sequential linearly-increasing shades of red-orange-yellow
bone        sequential increasing black-white color map with
a tinge of blue, to emulate X-ray film
copper      sequential increasing shades of black-copper
flag        repetitive red-white-blue-black pattern (not cyclic at
endpoints)
gray        sequential linearly-increasing black-to-white
grayscale
hot         sequential black-red-yellow-white, to emulate blackbody
radiation from an object at increasing temperatures
jet         a spectral map with dark endpoints, blue-cyan-yellow-red;
based on a fluid-jet simulation by NCSA [#]_
pink        sequential increasing pastel black-pink-white, meant
for sepia tone colorization of photographs
prism       repetitive red-yellow-green-blue-purple-...-green pattern
(not cyclic at endpoints)
summer      sequential linearly-increasing shades of green-yellow
=========   =======================================================

A set of palettes from the Yorick scientific visualisation
package <https://dhmunro.github.io/yorick-doc/>_, an evolution of
the GIST package, both by David H. Munro are included:

============  =======================================================
Colormap      Description
============  =======================================================
gist_earth    mapmaker's colors from dark blue deep ocean to green
lowlands to brown highlands to white mountains
gist_heat     sequential increasing black-red-orange-white, to emulate
blackbody radiation from an iron bar as it grows hotter
gist_ncar     pseudo-spectral black-blue-green-yellow-red-purple-white
colormap from National Center for Atmospheric
Research [#]_
gist_rainbow  runs through the colors in spectral order from red to
violet at full saturation (like *hsv* but not cyclic)
gist_stern    "Stern special" color table from Interactive Data
Language software
============  =======================================================

A set of cyclic color maps:

================  =================================================
Colormap          Description
================  =================================================
hsv               red-yellow-green-cyan-blue-magenta-red, formed by
changing the hue component in the HSV color space
white-blue-black-red-white
black-blue-white-red-black
================  =================================================

Other miscellaneous schemes:

============= =======================================================
Colormap      Description
============= =======================================================
afmhot        sequential black-orange-yellow-white blackbody
spectrum, commonly used in atomic force microscopy
brg           blue-red-green
bwr           diverging blue-white-red
coolwarm      diverging blue-gray-red, meant to avoid issues with 3D
shading, color blindness, and ordering of colors [#]_
CMRmap        "Default colormaps on color images often reproduce to
confusing grayscale images. The proposed colormap
maintains an aesthetically pleasing color image that
automatically reproduces to a monotonic grayscale with
discrete, quantifiable saturation levels." [#]_
cubehelix     Unlike most other color schemes cubehelix was designed
by D.A. Green to be monotonically increasing in terms
of perceived brightness. Also, when printed on a black
and white postscript printer, the scheme results in a
greyscale with monotonically increasing brightness.
This color scheme is named cubehelix because the r,g,b
values produced can be visualised as a squashed helix
around the diagonal in the r,g,b color cube.
(black-blue-red-yellow)
gnuplot2      sequential color printable as gray
(black-blue-violet-yellow-white)
ocean         green-blue-white
rainbow       spectral purple-blue-green-yellow-orange-red colormap
with diverging luminance
seismic       diverging blue-white-red
nipy_spectral black-purple-blue-green-yellow-red-white spectrum,
originally from the Neuroimaging in Python project
terrain       mapmaker's colors, blue-green-yellow-brown-white,
originally from IGOR Pro
============= =======================================================

The following colormaps are redundant and may be removed in future
versions.  It's recommended to use the names in the descriptions

=========  =======================================================
Colormap   Description
=========  =======================================================
gist_gray  identical to *gray*
gist_yarg  identical to *gray_r*
binary     identical to *gray_r*
=========  =======================================================

.. rubric:: Footnotes

.. [#] Rainbow colormaps, jet in particular, are considered a poor
choice for scientific visualization by many researchers: Rainbow Color
Map (Still) Considered Harmful
<http://ieeexplore.ieee.org/document/4118486/?arnumber=4118486>_

.. [#] Resembles "BkBlAqGrYeOrReViWh200" from NCAR Command
Language. See Color Table Gallery
<https://www.ncl.ucar.edu/Document/Graphics/color_table_gallery.shtml>_

.. [#] See Diverging Color Maps for Scientific Visualization
<http://www.kennethmoreland.com/color-maps/>_ by Kenneth Moreland.

.. [#] See A Color Map for Effective Black-and-White Rendering of
Color-Scale Images
<https://www.mathworks.com/matlabcentral/fileexchange/2662-cmrmap-m>_
by Carey Rappaport

### Creating 3D wire plots

Now suppose we have a 3D mathematical function that defines a surface in 3D, such as the following,

import numpy as np
def getZ(X, Y):
return np.sin(np.sqrt(X ** 2 + Y ** 2))

X = np.linspace(-5, 5, 40)
Y = np.linspace(-5, 5, 40)

X, Y = np.meshgrid(X, Y)
Z = getZ(X, Y)

With the three mesh grids (X,Y,Z) generated in the above, we can now visualize this 3D function via a wire plot like the following,

%matplotlib notebook
import matplotlib.pyplot as plt
from mpl_toolkits import mplot3d
fig = plt.figure()
ax = plt.axes(projection="3d")

ax.plot_wireframe(X, Y, Z, color='red')
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
plt.show()
plt.savefig('wire3D.png')

which will generate the following plot,

### Creating 3D surface plots

Now suppose we want to visualize the surface of the same function as in the case of the wire plot,

import numpy as np
def getZ(X, Y):
return np.sin(np.sqrt(X ** 2 + Y ** 2))

X = np.linspace(-5, 5, 40)
Y = np.linspace(-5, 5, 40)

X, Y = np.meshgrid(X, Y)
Z = getZ(X, Y)

%matplotlib notebook
import matplotlib.pyplot as plt
from mpl_toolkits import mplot3d
fig = plt.figure()
ax = plt.axes(projection="3d")

ax.plot_surface (X, Y, Z
, rstride=1 # default value is one
, cstride=1 # default value is one
, cmap='winter'
, edgecolor='none'
)
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
ax.set_title('Surface Plot');
plt.show()
plt.savefig('surface3D.png')

This will generate the following plot,

However, you may have noticed that this plot looks very coarse. To refine the image, we can increase the number of grids in the 3D mesh that we generate for the visualization. Instead of using 40 grids as used previously, we will use a larger number, for example, 300,

import numpy as np
def getZ(X, Y):
return np.sin(np.sqrt(X ** 2 + Y ** 2))

X = np.linspace(-5, 5, 300)
Y = np.linspace(-5, 5, 300)

X, Y = np.meshgrid(X, Y)
Z = getZ(X, Y)

%matplotlib notebook
import matplotlib.pyplot as plt
from mpl_toolkits import mplot3d
fig = plt.figure()
ax = plt.axes(projection="3d")

ax.plot_surface (X, Y, Z
, rstride=1 # default value is one
, cstride=1 # default value is one
, cmap='winter'
, edgecolor='none'
)
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
ax.set_title('Fine-Mesh Surface Plot');
plt.show()
plt.savefig('refinedSurface3D.png')

The resulting surface plot now looks much more refined and pleasant,

### Creating contour plots

The 3D surface or wire plots have limited usage because of their difficult interpretations, especially when they presented as fixed static images as opposed to interactive live plots. A more useful 3D plotting technique, in particular, for scientific reports is the contour plotting. Suppose we have a very weird-looking 3D function which is hard to visualize via a surface plot,

import numpy as np
def func(x, y):
return np.sin(x) ** 5 + np.cos(10 + y * x) * np.cos(x)

We can visualize this function via a contour plot by creating mesh grid of the coordinates again,

import numpy as np
def func(X, Y):
return np.sin(X) ** 5 + np.cos(10 + Y * X) * np.cos(X)
X = np.linspace(0, 2*np.pi, 500)
Y = np.linspace(0, 2*np.pi, 500)
X, Y = np.meshgrid(X, Y)
Z = func(X, Y)

%matplotlib notebook
import matplotlib.pyplot as plt
from mpl_toolkits import mplot3d
fig = plt.figure()
ax = plt.axes()

ax.contour(X, Y, Z, colors='gray');
ax.set_xlabel('x')
ax.set_ylabel('y')
plt.show()
plt.savefig('contour.png')

We could also change the color from uniform gray to a more descriptive color-map instead,

import numpy as np
def func(X, Y):
return np.sin(X) ** 5 + np.cos(10 + Y * X) * np.cos(X)
X = np.linspace(0, 2*np.pi, 500)
Y = np.linspace(0, 2*np.pi, 500)
X, Y = np.meshgrid(X, Y)
Z = func(X, Y)

%matplotlib notebook
import matplotlib.pyplot as plt
from mpl_toolkits import mplot3d
fig = plt.figure()
ax = plt.axes()

ax.contour  (X, Y, Z
, cmap = 'RdGy'
);
ax.set_xlabel('x')
ax.set_ylabel('y')
plt.show()
plt.savefig('contourColorMap.png')

or perhaps autumn color-map,

import numpy as np
def func(X, Y):
return np.sin(X) ** 5 + np.cos(10 + Y * X) * np.cos(X)
X = np.linspace(0, 2*np.pi, 500)
Y = np.linspace(0, 2*np.pi, 500)
X, Y = np.meshgrid(X, Y)
Z = func(X, Y)

%matplotlib notebook
import matplotlib.pyplot as plt
from mpl_toolkits import mplot3d
fig = plt.figure()
ax = plt.axes()

ax.contour  (X, Y, Z
, cmap = plt.cm.autumn
);
ax.set_xlabel('x')
ax.set_ylabel('y')
plt.show()
plt.savefig('contourColorMapAutumn.png')

### Creating filled contour plots

But what if we are interested not in contour lines, but an overall behavior of the function everywhere, womething like a heat-map? There is the contourf() method there to help us,

import numpy as np
def func(X, Y):
return np.sin(X) ** 5 + np.cos(10 + Y * X) * np.cos(X)
X = np.linspace(0, 2*np.pi, 500)
Y = np.linspace(0, 2*np.pi, 500)
X, Y = np.meshgrid(X, Y)
Z = func(X, Y)

%matplotlib notebook
import matplotlib.pyplot as plt
from mpl_toolkits import mplot3d
fig = plt.figure()
ax = plt.axes()

ax.contourf (X, Y, Z
, 20    # the number of color levels in the heat-map
, cmap = "RdGy"
);
ax.set_xlabel('x')
ax.set_ylabel('y')
plt.show()
plt.savefig('contourf.png')

You may again notice the low-resolution of the plot. To refine the plot, we can increase the number of color-levels in the plot, for example to 100 from 20,

import numpy as np
def func(X, Y):
return np.sin(X) ** 5 + np.cos(10 + Y * X) * np.cos(X)
X = np.linspace(0, 2*np.pi, 500)
Y = np.linspace(0, 2*np.pi, 500)
X, Y = np.meshgrid(X, Y)
Z = func(X, Y)

%matplotlib notebook
import matplotlib.pyplot as plt
from mpl_toolkits import mplot3d
fig = plt.figure()
ax = plt.axes()

ax.contourf (X, Y, Z
, 100    # the number of color levels in the heat-map
, cmap = "RdGy"
);
ax.set_xlabel('x')
ax.set_ylabel('y')
plt.show()
plt.savefig('contourfRefined.png')

However, refining filled contour plots by increasing the number of color levels could affect the performance of our script. Another way to achieve the same goal is to use another plotting method called imshow() instead of contourf(),

import numpy as np
def func(X, Y):
return np.sin(X) ** 5 + np.cos(10 + Y * X) * np.cos(X)
X = np.linspace(0, 2*np.pi, 500)
Y = np.linspace(0, 2*np.pi, 500)
X, Y = np.meshgrid(X, Y)
Z = func(X, Y)

%matplotlib notebook
import matplotlib.pyplot as plt
from mpl_toolkits import mplot3d
fig = plt.figure()
ax = plt.axes()

plt.imshow( Z
, extent=[0, 2*np.pi, 0, 2*np.pi] # doesn't accept X and Y grids, so you must manually specify the extent [xmin, xmax, ymin, ymax] of the image on the plot
, origin='lower' # default origin of the plot is the upper-left
, cmap='RdGy'
)
plt.colorbar()
ax.set_xlabel('x')
ax.set_ylabel('y')
plt.show()
plt.savefig('imshow.png')

Notice the additional color bar that we have added to the above plot via the command plt.colorbar().