diffracteD
diffracteD

Reputation: 758

color of a 3D surface plot in python

I'm using following line for plotting a 3D surface:

surf = ax3.plot_surface(X, Y, Z, rstride=1, cstride=1, alpha=0.5, linewidth=0, cmap=cm.jet,antialiased=True)

Now the color comes very nice, although a bit scaly appearance, though fine.
But I want to change the surface color w.r.t. another data, stored in list as:

m = [104.48, 111.73,109.93,139.95,95.05,150.49,136.96,157.75]

I was trying with:

norm = cls.Normalize() # Norm to map the 'm' values to [0,1]
norm.autoscale(m)
cmap = cm.ScalarMappable(norm, 'jet')
surf = ax3.plot_surface(X, Y, Z, rstride=5, cstride=5, alpha=0.5, linewidth=0, color=cmap.to_rgba(m), antialiased=True)

But this is raising an error as cmap.to_rgba takes 1D arrays only. Any suggestions on how can I be able to change the colormap of the surface would be highly appreciated.

Upvotes: 11

Views: 45894

Answers (3)

umfundi
umfundi

Reputation: 105

I find the previous answers somewhat misleading. The facecolors should be one column and row smaller than the grid values! Otherwise, the faces will be colorized shifted according to one value in the face corner (may not matter for many values). This becomes obvious for a few symetric values (see example below). You could either calculate new values in the middle of the faces or average the values at the face corners, which is what I needed for measured values. Likewise, it would be possible to linearly interpolate the intermediate values and plot more faces. Here is an example for comparison:

import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np

x_lst = np.linspace(-5, 5, 4)
y_lst = np.linspace(-5, 5, 4)
x_grid, y_grid = np.meshgrid(x_lst, y_lst)
z_grid = x_grid + y_grid

# values at face corners (at the grid points)
c_grid = np.abs(x_grid + y_grid)  # same size like x_val, y_val, z_val
print(c_grid)

# averaged corner values for each face
c_faces = np.lib.stride_tricks.sliding_window_view(c_grid, (2,2))
c_faces = np.mean(c_faces, axis=(2, 3))  # size reduces by 1
print(c_faces)

# color map and normalization
c_min, c_max = np.min(c_grid), np.max(c_grid)  # could also be min and max of c_faces
norm = mpl.colors.Normalize(vmin=c_min, vmax=c_max)
cmap = mpl.cm.viridis

for c_vals in [c_grid,    # values at face corners
               c_faces]:  # averaged corner values
    fig = plt.figure()
    ax = fig.add_subplot(111, projection='3d')
    surf = ax.plot_surface(x_grid, y_grid, z_grid,
                           rstride=1, cstride=1,  # no downsampling
                           facecolors = cmap(norm(c_vals)), shade=False)
    cbar = plt.colorbar(mpl.cm.ScalarMappable(norm=norm, cmap=cmap), ax=ax, pad=0.2)
    ax.dist = 8
    plt.show()

Shifted plot with grid values: enter image description here Plot with averaged values: enter image description here

Upvotes: 0

Moritz
Moritz

Reputation: 5408

Well, it looks awful but I think you can adapt it:

from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
from matplotlib.ticker import LinearLocator, FormatStrFormatter
import matplotlib.pyplot as plt
import numpy as np

fig = plt.figure()
ax = fig.gca(projection='3d')
X = np.arange(-5, 5, 0.25)
Y = np.arange(-5, 5, 0.25)
X, Y = np.meshgrid(X, Y)
R = np.sqrt(X**2 + Y**2)
Z = np.sin(R)
my_col = cm.jet(np.random.rand(Z.shape[0],Z.shape[1]))

surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1, facecolors = my_col,
        linewidth=0, antialiased=False)
ax.set_zlim(-1.01, 1.01)

result plot I would not use jet but some linear colormap like cubehelix. You can trick the eye easily using the wrong colormap (one of many posts on that topic)

Upvotes: 10

pingul
pingul

Reputation: 3473

To get the correct colors, use the Z values to pick values from the color map:

my_col = cm.jet(Z/np.amax(Z))

The result:

surface plot

using otherwise the same code as @Moritz.

from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
from matplotlib.ticker import LinearLocator, FormatStrFormatter
import matplotlib.pyplot as plt
import numpy as np

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
X = np.arange(-5, 5, 0.25)
Y = np.arange(-5, 5, 0.25)
X, Y = np.meshgrid(X, Y)
R = np.sqrt(X**2 + Y**2)
Z = np.sin(R)
my_col = cm.jet(Z/np.amax(Z))

surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1, facecolors = my_col,
        linewidth=0, antialiased=False)
ax.set_zlim(-1.01, 1.01)

plt.show()

Upvotes: 9

Related Questions