from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
X = np.arange(1, 4, 0.2)
Y = np.copy(X)
X, Y = np.meshgrid(X, Y)
Z1 = np.copy(X)
Z2 = 2/X
fig = plt.figure()
ax = fig.gca(projection='3d')
surf1 = ax.plot_surface(X, Y, Z1, rstride=1, cstride=1, color='b')
surf2 = ax.plot_surface(X, Y, Z2, rstride=1, cstride=1, color='r')
plt.show()
When I run the code above, I get the following. Note, I set the viewpoint from the bottom for purposes of illustration.
Two questions:
Why does the red plane have a nice, pretty color gradient, but the blue plane has this irregular pattern? How do I make the blue's pattern like the red's or make the coloring uniform?
I want to make the two planes such that, from the viewpoint of the viewer, whichever plane portion is "in front" of the other is visible. For example, in the above image, most of the red plane shown would be visible, but above the intersection line of z = sqrt(2) ~= 1.41, it would be hidden behind the blue plane. How can I get Matplotlib to do this?
Your example exposes a bug in matplotlib's shading algorithm. The shading algorithm computes the normals to each facet in the surface, and uses the color and its normal vector to shade the facet:
colset = self._shade_colors(color, normals)
Although in theory the normals are all identical for a plane, in practice, there are very slight variations in the numerical values due to the vagaries of floating-point arithmetic. These very slight variations are magnified by normalization since this stretches the minimum and maximum shades to lie between 0 and 1.
Thus all surfaces which are completely planar are prone to this shading bug.
When the color is uniform (e.g. color='b'
) and the normals are all identical (as is
the case for a plane), the shading should be the same for each facet. Normalization should make the shading zero. So for a plane the shading should not
change the color at all.
Thus, to work around the bug, turn off shading with shade=False
:
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
X = np.arange(1, 4, 0.2)
Y = np.copy(X)
X, Y = np.meshgrid(X, Y)
Z1 = np.copy(X)
Z2 = 2/X
fig = plt.figure()
ax = fig.gca(projection='3d')
surf1 = ax.plot_surface(
X, Y, Z1, rstride=1, cstride=1, color='b',
shade=False, alpha=1.0)
surf2 = ax.plot_surface(
X, Y, Z2, rstride=1, cstride=1, color='r',
shade=True, alpha=1.0)
ax.view_init(elev=-45, azim=-30)
plt.show()
PS. If you'd like to see the exact location where the normalization goes awry, change shade=False
to shade=True
in the code above, then place these print
statements in your installation's matplotlib/colors.py
:
resdat = result.data
resdat -= vmin
print(resdat[0, :10])
resdat /= (vmax - vmin)
print(resdat[0, :10])
result = np.ma.array(resdat, mask=result.mask, copy=False)
Running the script above then prints
[ 0.00000000e+00 0.00000000e+00 0.00000000e+00 0.00000000e+00
0.00000000e+00 0.00000000e+00 2.22044605e-16 0.00000000e+00
0.00000000e+00 2.22044605e-16]
[ 0. 0. 0. 0. 0. 0. 1. 0. 0. 1.]
If resdat
had been uniformly constant, all values in resdat
would be zero after normalization. Instead, the tiny errors in resdat
get magnified to equal 1. That leads to the funny shading you are seeing on the blue surface.