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
      cool        linearly-decreasing shades of cyan-magenta
      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)
      spring      linearly-increasing shades of magenta-yellow
      summer      sequential linearly-increasing shades of green-yellow
      winter      linearly-increasing shades of blue-green
      =========   =======================================================
    
    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
      twilight          perceptually uniform shades of
                        white-blue-black-red-white
      twilight_shifted  perceptually uniform shades of
                        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.
      gnuplot       gnuplot's traditional pm3d scheme
                    (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
    instead, which produce identical output:
    
      =========  =======================================================
      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().