0

I was trying to replicate the answer found here with my own data, which happens to be a 3D numpy array of integers. I got close with the following code:

import numpy as np
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
import matplotlib.pyplot as plt

data = np.random.randint(0,6,size=(49,512,512))

x = y = np.arange(0, 512, 1)

z = 20
i = data[z,:,:]

z1 = 21
i1 = data[z1,:,:]

z2 = 22
i2 = data[z2,:,:]

# here are the x,y and respective z values
X, Y = np.meshgrid(x, y)
Z = z*np.ones(X.shape)
Z1 = z1*np.ones(X.shape)
Z2 = z2*np.ones(X.shape)

# create the figure, add a 3d axis, set the viewing angle
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.view_init(10,60)

# here we create the surface plot, but pass V through a colormap
# to create a different color for each patch
im  = ax.plot_surface(X, Y, Z, facecolors=cm.viridis(i))
ax.plot_surface(X, Y, Z1, facecolors=cm.viridis(i1))
ax.plot_surface(X, Y, Z2, facecolors=cm.viridis(i2))

But this produces the plot below.

enter image description here

There are two things wrong with this plot: (1) the surfaces are a constant color and (2) the color bar doesn't seem to be referencing the data.

Following the advice here, I found that (1) can be solved by replacing data with a set of random numbers data = np.random.random(size=(49,512,512)), which produces the below image.

enter image description here

I think this suggests the integer data in the first image needs to be normalized before displaying properly, but, if it's possible, I would really like to make this plot without normalization; I want integer values to display like the second image. Also, I'm not sure why the color bar isn't connected to the color scale of the images themselves and could use advice on how to fix that. Ideally, the color bar to be connected to all three surfaces, not just the im surface.

Thanks in advance!

T Walker
  • 330
  • 1
  • 3
  • 12

1 Answers1

1

First, you have to normalize your data. Then, you pass the normalized data into the colormap to create the face colors:

import numpy as np
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
import matplotlib.pyplot as plt
import matplotlib.colors as colors

data = np.random.randint(0,6,size=(49,512,512))

# create a Normalize object with the correct range
norm = colors.Normalize(vmin=data.min(), vmax=data.max())
# normalized_data contains values between 0 and 1
normalized_data = norm(data)
# extract the appropriate values
z = 20
z1 = 21
z2 = 22
i = normalized_data[z,:,:]
i1 = normalized_data[z1,:,:]
i2 = normalized_data[z2,:,:]

x = y = np.arange(0, 512, 1)


# here are the x,y and respective z values
X, Y = np.meshgrid(x, y)
Z = z*np.ones(X.shape)
Z1 = z1*np.ones(X.shape)
Z2 = z2*np.ones(X.shape)

# create the figure, add a 3d axis, set the viewing angle
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.view_init(10,60)

# here we create the surface plot, but pass V through a colormap
# to create a different color for each patch
im  = ax.plot_surface(X, Y, Z, facecolors=cm.viridis(i))
ax.plot_surface(X, Y, Z1, facecolors=cm.viridis(i1))
ax.plot_surface(X, Y, Z2, facecolors=cm.viridis(i2))

# create a scalar mappable to create an appropriate colorbar
sm = cm.ScalarMappable(cmap=cm.viridis, norm=norm)
fig.colorbar(sm)
Davide_sd
  • 10,578
  • 3
  • 18
  • 30
  • I like this workaround, thank you! It's also helpful that I can change `vmin` and `vmax` in the `norm` variable to change the surfaces and colorbar. Do you happen to know why the values in `plot_surface` need to be normalized to display? It's clear the color scale will display in terms of the original integer values, but I'm still not sure why `plot_surface` will not display the original integer values. Thanks again! – T Walker Jun 13 '22 at 00:28
  • I believe it was a design decision made by Matplotlib's developers. Don't know what motivated them to use this approach. – Davide_sd Jun 13 '22 at 07:39