Python 3D Plot with Matplotlib mplot3d

Python can create three-dimensional charts with Matplotlib’s mplot3d toolkit. A 3D axes object can display points, lines, surfaces, wireframes, contours, bars, and other data positioned along x, y, and z axes.

This tutorial explains how to create a Matplotlib 3D axes, draw common 3D plot types, change the viewing angle, rotate an interactive plot, apply labels and color maps, and save the completed figure.

Install Matplotlib and NumPy for 3D Plotting

Install Matplotlib before running the examples. NumPy is also used to generate coordinate arrays and calculate surface values.

</>
Copy
python -m pip install matplotlib numpy

When using Anaconda or Miniconda, install the packages with Conda:

</>
Copy
conda install matplotlib numpy

Most current Matplotlib installations register the 3D projection automatically. The examples can therefore create a 3D axes by passing projection='3d' to add_subplot() or subplots().

Create a Matplotlib 3D Axes with projection=’3d’

A Matplotlib figure is the complete drawing area, while an axes object is the region where data is plotted. To create a three-dimensional axes, set its projection to '3d'.

</>
Copy
import matplotlib.pyplot as plt

fig = plt.figure(figsize=(8, 6))
ax = fig.add_subplot(111, projection='3d')

ax.set_title('Empty Matplotlib 3D Axes')
ax.set_xlabel('X axis')
ax.set_ylabel('Y axis')
ax.set_zlabel('Z axis')

plt.show()

The value 111 means that the figure contains one row, one column, and the first subplot. The projection='3d' argument creates an axes that accepts x, y, and z coordinates.

The same axes can also be created with plt.subplots():

</>
Copy
fig, ax = plt.subplots(subplot_kw={'projection': '3d'})

Matplotlib 3D Line Plot Example

A 3D line plot connects a sequence of points identified by corresponding x, y, and z values. It is useful for trajectories, parametric curves, and values that change along three dimensions.

</>
Copy
import numpy as np
import matplotlib.pyplot as plt

z = np.linspace(0, 15, 300)
x = np.sin(z)
y = np.cos(z)

fig = plt.figure(figsize=(8, 6))
ax = fig.add_subplot(111, projection='3d')

ax.plot(x, y, z, linewidth=2, label='Helical path')
ax.set_title('Matplotlib 3D Line Plot')
ax.set_xlabel('sin(z)')
ax.set_ylabel('cos(z)')
ax.set_zlabel('z')
ax.legend()

plt.show()

The arrays x, y, and z must have matching lengths. Each position across the three arrays defines one point, and ax.plot() connects those points in order.

Matplotlib 3D Scatter Plot Example

A 3D scatter plot displays individual observations in three-dimensional space. It can help inspect clusters, relationships, and outliers involving three numerical variables.

</>
Copy
import numpy as np
import matplotlib.pyplot as plt

rng = np.random.default_rng(7)
x = rng.normal(size=100)
y = rng.normal(size=100)
z = x * 0.6 + y * 0.3 + rng.normal(scale=0.4, size=100)

fig = plt.figure(figsize=(8, 6))
ax = fig.add_subplot(111, projection='3d')

points = ax.scatter(x, y, z, c=z, cmap='viridis', s=40, alpha=0.8)
ax.set_title('Matplotlib 3D Scatter Plot')
ax.set_xlabel('X value')
ax.set_ylabel('Y value')
ax.set_zlabel('Z value')
fig.colorbar(points, ax=ax, shrink=0.7, label='Z value')

plt.show()

The c=z argument maps point colors to z-values, while cmap='viridis' selects the color map. The color bar explains how colors correspond to numerical values.

Matplotlib 3D Surface Plot with plot_surface()

A 3D surface plot represents a function or gridded dataset as a continuous surface. Matplotlib’s plot_surface() method expects two-dimensional arrays containing x, y, and z coordinates.

numpy.meshgrid() converts separate one-dimensional x and y coordinate sequences into a rectangular coordinate grid. A z-value can then be calculated for every point on that grid.

</>
Copy
import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(-5, 5, 100)
y = np.linspace(-5, 5, 100)
X, Y = np.meshgrid(x, y)

R = np.sqrt(X**2 + Y**2)
Z = np.sin(R)

fig = plt.figure(figsize=(9, 6))
ax = fig.add_subplot(111, projection='3d')

surface = ax.plot_surface(
    X,
    Y,
    Z,
    cmap='viridis',
    edgecolor='none'
)

ax.set_title('Matplotlib 3D Surface Plot')
ax.set_xlabel('X axis')
ax.set_ylabel('Y axis')
ax.set_zlabel('sin(sqrt(x² + y²))')
fig.colorbar(surface, ax=ax, shrink=0.65, label='Z value')

plt.show()

The shape of X, Y, and Z must match. In this example, all three arrays contain a 100 by 100 grid. Setting edgecolor='none' removes the grid edges from the rendered surface.

Matplotlib 3D Wireframe Plot Example

A wireframe displays the grid structure of a surface without filling the faces between grid lines. It can make the curvature and sampling density easier to inspect.

</>
Copy
import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(-4, 4, 50)
y = np.linspace(-4, 4, 50)
X, Y = np.meshgrid(x, y)
Z = np.cos(np.sqrt(X**2 + Y**2))

fig = plt.figure(figsize=(8, 6))
ax = fig.add_subplot(111, projection='3d')

ax.plot_wireframe(X, Y, Z, rstride=2, cstride=2)
ax.set_title('Matplotlib 3D Wireframe Plot')
ax.set_xlabel('X axis')
ax.set_ylabel('Y axis')
ax.set_zlabel('Z axis')

plt.show()

The rstride and cstride arguments control how frequently rows and columns are drawn. Larger stride values show fewer grid lines and can make a dense wireframe easier to read.

Matplotlib 3D Contour Plot Example

A 3D contour plot draws lines or filled regions for selected z-levels. It can be combined with a surface or projected onto one of the axes to show how values change across the x-y plane.

</>
Copy
import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(-3, 3, 80)
y = np.linspace(-3, 3, 80)
X, Y = np.meshgrid(x, y)
Z = np.exp(-(X**2 + Y**2))

fig = plt.figure(figsize=(8, 6))
ax = fig.add_subplot(111, projection='3d')

contours = ax.contour3D(X, Y, Z, levels=20, cmap='plasma')
ax.set_title('Matplotlib 3D Contour Plot')
ax.set_xlabel('X axis')
ax.set_ylabel('Y axis')
ax.set_zlabel('Z axis')
fig.colorbar(contours, ax=ax, shrink=0.7, label='Contour level')

plt.show()

The levels argument controls the number or positions of contour levels. More levels provide finer visual detail but may make a small chart appear crowded.

Matplotlib 3D Bar Plot with bar3d()

The bar3d() method draws rectangular bars positioned with x, y, and z coordinates. The first three coordinate arguments specify each bar’s starting position, while dx, dy, and dz specify its width, depth, and height.

</>
Copy
import numpy as np
import matplotlib.pyplot as plt

x = np.array([0, 1, 2, 3])
y = np.array([0, 0, 0, 0])
z = np.zeros(4)

width = np.full(4, 0.6)
depth = np.full(4, 0.6)
height = np.array([4, 7, 5, 9])

fig = plt.figure(figsize=(8, 6))
ax = fig.add_subplot(111, projection='3d')

ax.bar3d(x, y, z, width, depth, height, shade=True)
ax.set_title('Matplotlib 3D Bar Plot')
ax.set_xlabel('Category position')
ax.set_ylabel('Series position')
ax.set_zlabel('Value')
ax.set_xticks(x + width / 2)
ax.set_xticklabels(['A', 'B', 'C', 'D'])

plt.show()

For ordinary category comparisons, a two-dimensional bar chart is often easier to read. A 3D bar chart is more appropriate when the additional spatial dimension represents meaningful groups or positions rather than decoration.

Plot a 3D NumPy Array as Points or a Surface

The phrase “3D array” can refer to different data structures, so the plotting method depends on what each dimension represents.

  • If an array has the shape (n, 3), each row can represent one x, y, z point and can be passed to scatter().
  • If separate two-dimensional arrays contain gridded x, y, and z coordinates, use plot_surface() or plot_wireframe().
  • If a three-dimensional array represents a volume of scalar values, a single Matplotlib surface cannot display every internal value directly. Select slices, extract an isosurface with an appropriate library, or visualize selected coordinates.

The following example plots an (n, 3) NumPy array as a 3D point cloud:

</>
Copy
import numpy as np
import matplotlib.pyplot as plt

points = np.array([
    [1.0, 2.0, 3.0],
    [2.0, 1.5, 4.0],
    [3.0, 3.5, 2.5],
    [4.0, 2.5, 5.0]
])

x = points[:, 0]
y = points[:, 1]
z = points[:, 2]

fig = plt.figure(figsize=(8, 6))
ax = fig.add_subplot(111, projection='3d')
ax.scatter(x, y, z, s=60)

ax.set_title('3D NumPy Point Array')
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')

plt.show()

Change the Matplotlib 3D Plot View Angle

The apparent shape of a 3D chart depends on the camera angle. Use view_init() to set the elevation and azimuth.

</>
Copy
ax.view_init(elev=30, azim=45)
  • elev controls the vertical viewing angle in degrees.
  • azim controls rotation around the z-axis in degrees.

The following complete example creates a surface and displays it from a selected angle:

</>
Copy
import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(-3, 3, 70)
y = np.linspace(-3, 3, 70)
X, Y = np.meshgrid(x, y)
Z = np.sin(X) * np.cos(Y)

fig = plt.figure(figsize=(8, 6))
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(X, Y, Z, cmap='coolwarm')

ax.view_init(elev=25, azim=135)
ax.set_title('3D Surface with a Custom View Angle')
ax.set_xlabel('X axis')
ax.set_ylabel('Y axis')
ax.set_zlabel('Z axis')

plt.show()

Try several angles when labels, peaks, or important data points overlap. A view that works for one dataset may hide details in another.

Rotate a Matplotlib 3D Plot with the Mouse

Matplotlib 3D figures can be rotated interactively when they are displayed with an interactive backend. In a desktop figure window, dragging with the mouse typically rotates the axes, while toolbar controls support zooming, panning, resetting the view, and saving the figure.

In Jupyter environments, a static inline backend displays a fixed image. Select an interactive notebook backend supported by the installed environment when mouse rotation is required. For example, some Jupyter setups support:

</>
Copy
%matplotlib widget

The widget backend may require the corresponding Jupyter Matplotlib integration to be installed. Backend availability differs between Jupyter Notebook, JupyterLab, IDEs, and desktop Python installations.

Animate the Rotation of a Matplotlib 3D Plot

A rotating view can be generated by updating the axes azimuth for each animation frame. The data does not change in this example; only the camera angle changes.

</>
Copy
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

x = np.linspace(-4, 4, 80)
y = np.linspace(-4, 4, 80)
X, Y = np.meshgrid(x, y)
Z = np.sin(np.sqrt(X**2 + Y**2))

fig = plt.figure(figsize=(8, 6))
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(X, Y, Z, cmap='viridis')
ax.set_title('Rotating Matplotlib 3D Surface')


def update(frame):
    ax.view_init(elev=30, azim=frame)
    return (ax,)


animation = FuncAnimation(
    fig,
    update,
    frames=range(0, 360, 2),
    interval=40
)

plt.show()

Keep a reference to the FuncAnimation object until the figure is displayed or saved. Otherwise, Python may remove the animation object before rendering is complete.

Set 3D Axis Limits, Ticks, and Aspect Ratio

Axis limits and tick locations can be controlled with the same general methods used on two-dimensional Matplotlib axes.

</>
Copy
ax.set_xlim(-5, 5)
ax.set_ylim(-5, 5)
ax.set_zlim(-2, 2)

ax.set_xticks([-5, 0, 5])
ax.set_yticks([-5, 0, 5])
ax.set_zticks([-2, 0, 2])

For data whose dimensions should be visually comparable, use set_box_aspect() to control the displayed x, y, and z proportions:

</>
Copy
ax.set_box_aspect((1, 1, 0.5))

This example makes the displayed z dimension half the visual size of the x and y dimensions. The setting changes the axes box proportions; it does not modify the underlying data.

Add Text and Annotations to a Matplotlib 3D Plot

Use ax.text() to place a label at a specified x, y, and z coordinate. This is useful for identifying selected points or regions.

</>
Copy
import matplotlib.pyplot as plt

x = [1, 2, 3, 4]
y = [2, 1, 4, 3]
z = [3, 5, 2, 6]

fig = plt.figure(figsize=(8, 6))
ax = fig.add_subplot(111, projection='3d')
ax.scatter(x, y, z, s=60)

for index, (x_value, y_value, z_value) in enumerate(zip(x, y, z), start=1):
    ax.text(x_value, y_value, z_value, f' P{index}')

ax.set_title('Annotated Matplotlib 3D Scatter Plot')
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')

plt.show()

Labels can overlap when many points are close together. Annotate only the observations that need explanation, or use an interactive plotting tool when every point must expose detailed information.

Save a Matplotlib 3D Plot as PNG, SVG, or PDF

Use savefig() to export the current 3D view. The saved image uses the camera angle configured before the file is written.

</>
Copy
import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(-2, 2, 60)
y = np.linspace(-2, 2, 60)
X, Y = np.meshgrid(x, y)
Z = X * np.exp(-(X**2 + Y**2))

fig = plt.figure(figsize=(8, 6))
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(X, Y, Z, cmap='viridis')
ax.view_init(elev=30, azim=120)
ax.set_title('Saved Matplotlib 3D Surface')

fig.savefig('python-3d-surface.png', dpi=300, bbox_inches='tight')
fig.savefig('python-3d-surface.svg', bbox_inches='tight')
fig.savefig('python-3d-surface.pdf', bbox_inches='tight')

plt.show()

PNG is a raster format, so dpi affects its output resolution. SVG and PDF are vector formats, although complex surfaces may still contain many graphical elements and produce relatively large files.

Common Matplotlib mplot3d Errors and Fixes

  • Unknown projection ‘3d’: confirm that Matplotlib is installed correctly and update an outdated installation. Older code may explicitly import mpl_toolkits.mplot3d before creating the projection.
  • Shape mismatch in plot_surface(): ensure that the X, Y, and Z arrays have matching two-dimensional shapes.
  • x, y, and z have different lengths: line and scatter plots require one coordinate from each array for every plotted point.
  • The 3D plot does not rotate: a static notebook backend cannot provide mouse interaction. Use a supported interactive backend or open the chart in a desktop figure window.
  • The surface appears too slow: reduce the number of grid points, increase row and column strides, or use downsampled data for interactive inspection.
  • Labels or the z-axis are clipped: increase the figure size, call fig.tight_layout(), or save with bbox_inches='tight'.
  • Important points are hidden behind the surface: change the view angle, adjust transparency with alpha, or display the points and surface in separate subplots.

When to Use Matplotlib 3D Plots

A 3D plot is appropriate when all three spatial or numerical dimensions are necessary to interpret the data. Examples include a measured surface, a three-dimensional path, a function of two independent variables, or a point cloud with meaningful x, y, and z coordinates.

Three-dimensional perspective can also make exact comparisons harder because points may overlap and apparent distances depend on the viewing angle. Consider a two-dimensional contour plot, heatmap, pair of coordinated charts, or small multiples when those alternatives communicate the values more clearly.

Python 3D Plot FAQs

How do I create a 3D plot in Python with Matplotlib?

Import matplotlib.pyplot, create a figure, and add an axes with projection='3d'. You can then call methods such as plot(), scatter(), plot_surface(), plot_wireframe(), or bar3d() on that axes.

How do I create a Matplotlib 3D surface plot?

Create two-dimensional coordinate grids with numpy.meshgrid(), calculate a matching two-dimensional z array, and pass all three arrays to ax.plot_surface(X, Y, Z). The three arrays must have the same shape.

How do I rotate a Python 3D plot with the mouse?

Display the Matplotlib figure through an interactive backend. A desktop plot window normally supports mouse rotation, while Jupyter may require a compatible interactive backend such as the widget backend. A static inline image cannot be rotated after rendering.

How do I set the view angle of a Matplotlib 3D plot?

Call ax.view_init(elev=value, azim=value). The elevation sets the vertical viewing angle, and the azimuth rotates the view around the z-axis.

Can Matplotlib display an interactive 3D plot?

Yes. Matplotlib supports interactive rotation and zooming when an interactive backend is active. The available interaction depends on whether the chart is displayed in a desktop application, IDE, Jupyter Notebook, or JupyterLab environment.

Python 3D Plot Editorial QA Checklist

  • Confirm that every example creates its axes with projection='3d'.
  • Verify that x, y, and z arrays have matching lengths in line and scatter examples.
  • Check that surface, wireframe, and contour examples use equally shaped two-dimensional coordinate arrays.
  • Run each added example with supported Matplotlib and NumPy versions.
  • Confirm that every 3D axes includes clear x, y, and z labels where the dimensions have defined meanings.
  • Review color-mapped examples to ensure that a color bar identifies the mapped variable.
  • Test the stated view angles and confirm that the relevant data remains visible.
  • Verify interactive-rotation guidance in the intended desktop or Jupyter environment instead of assuming every backend behaves identically.
  • Check exported PNG, SVG, and PDF examples for clipped labels and the intended camera angle.
  • Confirm that all new WordPress code blocks use valid PrismJS language, syntax, or output classes.