2025, Nov 08 15:00

Why Matplotlib 3D Lines Overlap Incorrectly (Painter’s Order) and How to Fix It with MayaVi2

Learn why Matplotlib 3D lines occlude by draw order, how this skews depth perception, and the fix: use MayaVi2 for accurate, depth-sorted 3D line rendering.

When you plot multiple 3D trajectories in Python with Matplotlib, it’s natural to expect occlusion to follow spatial depth. Instead, you might see lines visually cutting in front of or behind each other purely based on the sequence of drawing. As you rotate the figure, the illusion can get even stronger, making the scene look inconsistent and difficult to interpret.

Minimal example that shows the symptom

The following sketch sets up a 3D axes object and draws a family of curves. Notice how everything renders fine syntactically, yet the perceived overlap ignores where segments truly are in space.

import matplotlib.pyplot as plt
import numpy as np
canvas = plt.figure()
panel3d = canvas.add_subplots(projection='3d')
u1, v1, w1 = np.array([...] ), np.array([...] ), np.array([...] )
u2, v2, w2 = np.array([...] ), np.array([...] ), np.array([...] )
# ...
uq, vq, wq = np.array([...] ), np.array([...] ), np.array([...] )
pack_x, pack_y, pack_z = [u1, u2, ..., uq], [v1, v2, ..., vq], [w1, w2, ..., wq]
for idx in range(q):
    panel3d.plot(pack_x[idx], pack_y[idx], pack_z[idx])
plt.show()

What’s actually happening and why it looks wrong

Matplotlib renders objects in the order they are drawn, not by their actual 3D position. This behavior is documented in their FAQ. With multiple lines on the same axes, the result is that later artists can visually occlude earlier ones, regardless of which segments are closer to the viewer. That’s why rotating the figure doesn’t fix the artifact: the painter’s sequence remains the same, so visual overlap still reflects drawing order rather than depth.

A practical way out: switch to MayaVi2

The recommended workaround is to use MayaVi2. Its API for simple 3D plotting is close enough to feel familiar if you already use Matplotlib for visualization. Rewriting the loop is straightforward and yields plots where overlap aligns with spatial layout rather than draw order.

from mayavi import mlab
import numpy as np
p1x, p1y, p1z = np.array([...] ), np.array([...] ), np.array([...] )
p2x, p2y, p2z = np.array([...] ), np.array([...] ), np.array([...] )
# ...
pkx, pky, pkz = np.array([...] ), np.array([...] ), np.array([...] )
sx, sy, sz = [p1x, p2x, ..., pkx], [p1y, p2y, ..., pky], [p1z, p2z, ..., pkz]
for j in range(k):
    mlab.plot3d(sx[j], sy[j], sz[j])
mlab.show()

Why knowing this matters

3D visualizations are often used to reason about topology, collision, orbits, and spatial relationships. If the renderer prioritizes the order of artists over their depth, you can misread intersections and relative positions. That’s particularly risky when you’re exploring complex families of curves and rely on rotation to build intuition.

Takeaways

If you need 3D lines to respect depth during rendering, don’t expect Matplotlib’s default 3D stack to resolve overlaps by spatial order. Use MayaVi2 for plots where occlusion reflects geometry, not the sequence of draw calls. And when you ask for help or file an issue, provide a minimal, reproducible example along with a small visual illustration of the artifact—this makes the behavior unambiguous and speeds up troubleshooting.

The article is based on a question from StackOverflow by Albannach5446 and an answer by Ennpeh.