11

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.

diffracteD
  • 758
  • 3
  • 10
  • 32

3 Answers3

9

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)

Zihe Gao
  • 45
  • 4
Moritz
  • 5,130
  • 10
  • 40
  • 81
  • If I'm getting it properly then you are plotting `X,Y,Z` and giving surface a custom color as per `Z-array`. But I've already have a white surface of (`X,Y,Z`), all I'm trying to do now is to color the surface as per the values of the `m-array`. Please correct me if I'm misunderstanding. – diffracteD Sep 06 '15 at 11:22
  • You would have to use `meshgrid` on the scaled m-array in order to get a 2D array. – Moritz Sep 06 '15 at 13:31
  • Yes, I'm using `np.meshgrid` in case of `x,y,z` fitting, but it is giving me a matrix data(of rank 3) to be passed on to `plot_surface(X,Y,Z,...)` to generate the surface. Now how can I be able to deal with a matrix rank 4 (if I include `m`) in plotting ? A little bit code regarding your thought would have been nice. – diffracteD Sep 07 '15 at 13:55
  • As long as you do not provide the array `m` and some working minimal example, I can only guess. – Moritz Sep 07 '15 at 16:28
  • 2
    @diffracteD, how does m relate to X, Y, and Z? Does it have the same number of elements, for example? How do you want to map m onto your surface? – Amy Teegarden Sep 07 '15 at 18:36
  • @AmyTeegarden well, `z=f(x,y,m)`. So I have a surface-plot representing `z=f(x,y)`, just want to use `m` to give surface color. And yes, number of elements is same. – diffracteD Sep 08 '15 at 03:44
  • can you provide your data ? – Moritz Sep 08 '15 at 19:32
  • Can you add a color bar? – kilojoules Feb 23 '16 at 04:08
  • `plt.colorbar(surf)` – Moritz Feb 23 '16 at 22:08
  • @AmyTeegarden X,Y,Z,M have the same no of elements in it. m has some values I want to color map with respect to XYZ surface. – diffracteD Feb 09 '17 at 07:46
9

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()
pingul
  • 3,351
  • 3
  • 25
  • 43
  • is there any way to remove the scaly appearance of the plot and get a glossy appearance ? – diffracteD Feb 09 '17 at 07:33
  • you would have to interpolate the data onto a finer grid. – Moritz Feb 09 '17 at 08:53
  • @diffracteD Exactly what @Moritz said. Change the 0.25 in `np.arange(-5, 5, 0.25)` to a lower value. – pingul Feb 09 '17 at 13:33
  • @pingul I'll get back to you regarding the scaly look. But more importantly, my concern is to use m value as color map over XYZ surface. Please comment on this issue. – diffracteD Feb 09 '17 at 14:01
  • @diffracteD You need to give each Z value a color value, that is `m` will need to contain the same dimensions as Z (check that `Z.shape` == `m.shape`). If that is the case, just change the code to `my_col = cm.jet(m/np.amax(m))` to normalize it. – pingul Feb 09 '17 at 15:00
0

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

umfundi
  • 95
  • 6