I am trying to create a 3D colored bar chart using ideas from: this stackoverflow post.
First I create a 3D bar chart with the following code:
import numpy as np
import matplotlib.colors as colors
import matplotlib.cm as cm
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
samples = np.random.randint(91,size=(5000,2))
F = np.zeros([91,91])
for s in samples:
F[s[0],s[1]] += 1
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
x_data, y_data = np.meshgrid( np.arange(F.shape[1]),
np.arange(F.shape[0]) )
x_data = x_data.flatten()
y_data = y_data.flatten()
z_data = F.flatten()
ax.bar3d(x_data,y_data,np.zeros(len(z_data)),1,1,z_data )
plt.show()
The following is the output:
Now I try to color the bars using code verbatim from: this stackoverflow post. Here is the code:
import numpy as np
import matplotlib.colors as colors
import matplotlib.cm as cm
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
samples = np.random.randint(91,size=(5000,2))
F = np.zeros([91,91])
for s in samples:
F[s[0],s[1]] += 1
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
x_data, y_data = np.meshgrid( np.arange(F.shape[1]),
np.arange(F.shape[0]) )
x_data = x_data.flatten()
y_data = y_data.flatten()
z_data = F.flatten()
dz = F
offset = dz + np.abs(dz.min())
fracs = offset.astype(float)/offset.max()
norm = colors.Normalize(fracs.min(), fracs.max())
colors = cm.jet(norm(fracs))
# colors = np.random.rand(91,91,4)
ax.bar3d(x_data,y_data,np.zeros(len(z_data)),1,1,z_data,color=colors )
plt.show()
However I get: ValueError: Invalid RGBA argument:
Now I am unable to debug the Invalid RGBA argument because I don't understand what is causing the error. I even tried to use random colors instead with colors = np.random.rand(91,91,4) and still the error persists.
I have checked stackoverflow posts regarding Invalid RGBA argument (for example this,this,this and this) and none of that seems to answer my problem.
I want to know what could be causing this error. I am using the standard Anaconda distribution for python on Ubuntu Mate 16.
Could it be that due to recent updates in python, the solution as in the original stackoverflow post becomes obsolete?
The error message is misleading. You're getting a ValueError because the shape of colors is wrong, not because an RGBA value is invalid.
When coloring each bar a single color, color should be an array of length N, where N is the number of bars. Since there are 8281 bars,
In [121]: x_data.shape
Out[121]: (8281,)
colors should have shape (8281, 4). But instead, the posted code generates an array of shape (91, 91, 4):
In [123]: colors.shape
Out[123]: (91, 91, 4)
So to fix the problem, use color=colors.reshape(-1,4).
import numpy as np
import matplotlib.colors as colors
import matplotlib.cm as cm
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
samples = np.random.randint(91,size=(5000,2))
F = np.zeros([91,91])
for s in samples:
F[s[0],s[1]] += 1
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
x_data, y_data = np.meshgrid( np.arange(F.shape[1]),
np.arange(F.shape[0]) )
x_data = x_data.flatten()
y_data = y_data.flatten()
z_data = F.flatten()
dz = F
offset = dz + np.abs(dz.min())
fracs = offset.astype(float)/offset.max()
norm = colors.Normalize(fracs.min(), fracs.max())
colors = cm.jet(norm(fracs))
ax.bar3d(x_data,y_data,np.zeros(len(z_data)),1,1,z_data,color=colors.reshape(-1,4) )
plt.show()
The color argument expects a 1D array, similar to all other arguments of bar3d.
Hence, you need to replace the line offset = dz + np.abs(dz.min())
by
offset = z_data + np.abs(z_data.min())
for your case. dz is not useful here (maybe it was in the linked example).
Note that color=np.random.rand(len(z_data),4) would equally work.
Then the result will be
Related
I am using Basemap with a 3D graph to display ray paths. I would like to implement one of basemap's topo, shaded relief or bluemarble layers but I am running into the same issue over and over again:
NotImplementedError: It is not currently possible to manually set the aspect on 3D axes
I have already implemented fixed_aspect=False into calling the basemap
and have also tried ax.set_aspect('equal') which gives me the same error
I am using matplotlib ==2.2.3
Here is my code:
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.basemap import Basemap
import numpy as np
from io import StringIO
import re
f = open("best-working4D.ray_paths", 'r') #125 waves - 125 "lines"
data = f.read()
lines = data.split('\n ')
fig = plt.figure()
ax = plt.axes(projection='3d')
extent = [300, 360, 50, 75]
bm = Basemap(llcrnrlon=extent[0], llcrnrlat=extent[2],
urcrnrlon=extent[1], urcrnrlat=extent[3], resolution='l', fix_aspect= False)
bm.bluemarble()
for i in range(1, 119):
wave = lines[i]
j = wave.split('\n')
k = []
for i in j:
k.append(i.split())
x=[]
y=[]
z=[]
n= 0
for m in k[1:]:
x.append(m[0])
y.append(m[1])
z.append(m[2])
x= np.array(x).astype('float32')
y= np.array(y).astype('float32')
z= np.array(z).astype('float32')
ax.plot3D(x,y,z, color='red')
##Plotting Tropopause
T_hi = 20
xx, yy = np.meshgrid(range(300,360), range(50,75))
zz = yy*0 + T_hi
ax.plot_surface(xx, yy, zz, alpha=0.15)
ax.set_xlabel("Latitude [deg]")
ax.set_ylabel("Longitude [deg]")
ax.set_zlabel("Altitude [km]")
ax.add_collection3d(bm.drawcoastlines(linewidth=0.25))
plt.show()
The basemap IS working for the bm.drawcoastlines just nothing else.
IMAGELINK
I would greatly appreciate any ideas!
I have seen this thread but my data are a little different. I want to create a 3D plot of multiple files containing x,y,z coordinates and color code each file with a unique color, not each point coordinate
Code thus far:
import meshio
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
import glob
import matplotlib.cm as cm
files = sorted(glob.glob('mesh_files/*.vtk'))
mesh = []
fig = plt.figure(figsize = (16, 10))
ax = plt.axes(projection = '3d')
colors = cm.rainbow(np.linspace(0, 1, 16))
for file in files:
mesh.append(meshio.read(file))
x = [m.points[:, 0] for m in mesh]
y = [m.points[:, 1] for m in mesh]
z = [m.points[:, 2] for m in mesh]
for a,b,c,d in zip(x,y,z,colors):
plt.scatter(a,b,c,color=d)
Background
x, y and z are all lists containing numpy arrays
<<len(x)
16
<<len(x[0])
99937
<<x[0].shape
(99937,)
<<type(x)
<class 'list'>
<<type(x[0])
<class 'numpy.ndarray'>
I believe the issue is with the colors and a possible mismatch in sizes
<<len(colors)
16
<<len(colors[0])
4
Error
RuntimeWarning: invalid value encountered in sqrt
EDIT: I can individually call scatter and manually enter a different color to create the below plot, but this would take forever with 10+ files, so I want it in a loop or function of some sort.
EDIT2: I was able to get this plot, which is nice that the colors are different for each files' data, but the z scale is too small, compared to the first plot, and it looks like data are missing, it should like like the first plot in terms of z depth values, but with 16 unique colors as in the second plot. The first plot is only plotting 3 files manually
If you don't need the meshes afterwards you can avoid allocating a bunch of memory
...
colors = iter(cm.rainbow(np.linspace(0, 1, 16)))
for file in files:
plt.scatter(*meshio.read(file).points.T, c=[next(colors)], label=file)
plt.legend()
plt.show()
or, if you need the meshes afterwards we can use a container
...
meshes = []
colors = iter(cm.rainbow(np.linspace(0, 1, 16)))
for file in files:
meshes.append(meshio.read(file))
plt.scatter(*meshes[-1].points.T, c=[next(colors)], label=file)
plt.legend()
plt.show()
NB scatter in 3D needs x, y and z, all with shape (N,), while meshobj.points has shape (N, 3) so we first transpose it (shape is now (3, N)) and finally we unpack (using the star "*" operator) the 2D array to get the requested three (N,) arrays.
I think you mistake comes from the mesh list that you are updating at every step. You plot the whole mesh list every step, such that your first file is plotted 16 times, in 16 different colors.
The simplest code could be:
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
import glob
import matplotlib.cm as cm
files = sorted(glob.glob('mesh_files/*.vtk'))
fig = plt.figure(figsize = (16, 10))
ax = plt.axes(projection = '3d')
colors = cm.rainbow(np.linspace(0, 1, len(files)))
for file in files:
data = meshio.read(file).points
x = data[:, 0]
y = data[:, 1]
z = data[:, 2]
plt.scatter(x, y, z, color = colors[files.index(file)])
If you want to store all the points in a list called mesh, you can modify it as :
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
import glob
import matplotlib.cm as cm
files = sorted(glob.glob('mesh_files/*.vtk'))
mesh = []
fig = plt.figure(figsize = (16, 10))
ax = plt.axes(projection = '3d')
colors = cm.rainbow(np.linspace(0, 1, len(files)))
for file in files:
mesh.append(meshio.read(file).points)
x = mesh[-1][:, 0]
y = mesh[-1][:, 1]
z = mesh[-1][:, 2]
plt.scatter(x, y, z, color = colors[files.index(file)])
such that you only plot the points corresponding the file you just read at every step.
As was mentioned previously, the problem you're experiencing is which loop the color selection is occurring in.
color = iter(cm.rainbow(np.linspace(0, 1, len(files))))
for file in files:
d = next(color) #set the color for each file instead of inside the loop
mesh.append(meshio.read(file))
x = [m.points[:, 0] for m in mesh]
y = [m.points[:, 1] for m in mesh]
z = [m.points[:, 2] for m in mesh]
for a,b,c in zip(x,y,z):
plt.scatter(a,b,c,color=d)
This code below is currently working for me, for the most part.
I changed plt.scatter... to ax.scatter... and it fixed the z-axis scaling issue that I mentioned in EDIT 2 above.
I also changed to ax = Axes3D(fig)
Thanks to everyone's help! I will work with this for now.
import meshio
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
import glob
import matplotlib.cm as cm
files = sorted(glob.glob('path/to/vtk/files/*_mesh.vtk'))
meshes = []
fig = plt.figure(figsize = (16, 10))
ax = Axes3D(fig)
colors = iter(cm.rainbow(np.linspace(0, 1, len(files))))
for fyle in files:
ax.scatter(*meshio.read(fyle).points.T, c=[next(colors)])
plt.legend() #legend isn't plotting, will have to fix this
plt.show()
I have a list like below -
array1 = [[1,2,3,0.56],[12,5,30,0.23],[10,12,17,89.65]]
This represents co-ordinates- [[x1,y1,z1,c1],[x2,y2,z2,c2],[x3,y3,z3,c3]].
I used 4D plot with 4th dimension[c1,c2,c3] being the color. I am stuck at plotting. I would like to have a color for c1 at co-ordinates [x1,y1,z1] and similarly to other co-ordinates.
I used the below methods -
import matplotlib.pyplot as plt
import numpy as np
1) for p in range(len(array1)-1):
x = np.append([array1[p][0]], array1[p+1][0])
y = np.append([array1[p][1]], array1[p+1][1])
z = np.append([array1[p][2]], array1[p+1][2])
c = np.append([array1[p][3]], array1[p+1][3])
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.scatter(x,y,z,c=c,cmap = cmap)
plt.show()
The problem in method 1 is, its not plotting all the elements of the list. I guess there is a mistake in executing append. I am not getting any errors but its just not plotting every data.
2) fig = plt.figure()
ax = fig.gca(projection='3d')
for p in range(len(array1)-1):
ax.scatter(array1[p][0],array1[p][1],array1[p][2],array1[p][3],cmap =
cmap)
plt.show()
So in method 2, I tried to plot iteratively but its giving me 'float object unsubscriptable' error.
So can somebody tell me where I am going wrong. Or is there any other method to do this ?
This is one way of doing it by converting your list to array which allows you to slice all the elements directly as x, y, z and color coordinates.
import matplotlib.cm as cm
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
ax = fig.gca(projection='3d')
new_array = = np.array(array1)
ax.scatter(new_array[:,0],new_array[:,1],new_array[:,2], c=new_array[:,3], cmap=cm.RdBu, s=100)
I would like to add a fourth dimension to the scatter plot by defining the ellipticity of the markers depending on a variable. Is that possible somehow ?
EDIT:
I would like to avoid a 3D-plot. In my opinion these plots are usually not very informative.
You can place Ellipse patches directly onto your axes, as demonstrated in this matplotlib example. To adapt it to use eccentricity as your "third dimension") keeping the marker area constant:
from pylab import figure, show, rand
from matplotlib.patches import Ellipse
import numpy as np
import matplotlib.pyplot as plt
N = 25
# ellipse centers
xy = np.random.rand(N, 2)*10
# ellipse eccentrities
eccs = np.random.rand(N) * 0.8 + 0.1
fig = plt.figure()
ax = fig.add_subplot(111, aspect='equal')
A = 0.1
for pos, e in zip(xy, eccs):
# semi-minor, semi-major axes, b and a:
b = np.sqrt(A/np.pi * np.sqrt(1-e**2))
a = A / np.pi / b
ellipse = Ellipse(xy=pos, width=2*a, height=2*b)
ax.add_artist(ellipse)
ax.set_xlim(0, 10)
ax.set_ylim(0, 10)
show()
Of course, you need to scale your marker area to your x-, y- values in this case.
You can use colorbar as the 4th dimension to your 3D plot. One example is as shown below:
import matplotlib.cm as cmx
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import matplotlib
import numpy as np
def scatter3d(x,y,z, cs, colorsMap='jet'):
cm = plt.get_cmap(colorsMap)
cNorm = matplotlib.colors.Normalize(vmin=min(cs), vmax=max(cs))
scalarMap = cmx.ScalarMappable(norm=cNorm, cmap=cm)
fig = plt.figure()
ax = Axes3D(fig)
ax.scatter(x, y, z, c=scalarMap.to_rgba(cs))
scalarMap.set_array(cs)
fig.colorbar(scalarMap,label='Test')
plt.show()
x = np.random.uniform(0,1,50)
y = np.random.uniform(0,1,50)
z = np.random.uniform(0,1,50)
so scatter3D(x,y,z,x+y) produces:
with x+y being the 4th dimension shown in color. You can add your calculated ellipticity depending on your specific variable instead of x+y to get what you want.
To change the ellipticity of the markers you will have to create them manually as such a feature is not implemented yet. However, I believe you can show 4 dimensions with a 2D scatter plot by using color and size as additional dimensions. You will have to take care of the scaling from data to marker size yourself. I added a simple function to handle that in the example below:
import matplotlib.pyplot as plt
import numpy as np
data = np.random.rand(60,4)
def scale_size(data, data_min=None, data_max=None, size_min=10, size_max=60):
# if the data limits are set to None we will just infer them from the data
if data_min is None:
data_min = data.min()
if data_max is None:
data_max = data.max()
size_range = size_max - size_min
data_range = data_max - data_min
return ((data - data_min) * size_range / data_range) + size_min
plt.scatter(data[:,0], data[:,1], c=data[:,2], s=scale_size(data[:,3]))
plt.colorbar()
plt.show()
Result:
I cannot add a colorbar to my 3D scatter plot that is coloured in range of min and max according to the value of bifurWidth. I've tried various attempts shown on stackoverflow, none have had any success. Any help would really be appreciated, as I am at a major loss with this.
My most recent attempt is hashed out of the code shown below.
My code:
from glob import glob
from pylab import *
import numpy as np
from matplotlib import cm
from mpl_toolkits.mplot3d import Axes3D
def myScatter(x0,y0,power_array,c,lw,s,vmin,vmax,cmap,label,ax):
ax.scatter(x0,y0,power_array,c=c,lw=lw,s=s,vmin=min,vmax=max,cmap=cmhot,label=label)
fig = figure()
ax = fig.add_subplot(111, projection='3d')
cmhot = get_cmap("jet")
fig.tight_layout()
fig.set_size_inches(25,15)
min = 3 #colorbar range
max = 10
lw = 0 #linewidth
s = 10 #scatter size
for idx, p in enumerate(dataSorted[:,1]):
powerLoop = dataSorted[idx,0]
powerLoop = powerLoop.astype(np.float)
bifurWidthLoop = dataSorted[idx,2]
bifurWidthLoop = bifurWidthLoop.astype(np.float)
y0 = genfromtxt(p, unpack=True, usecols=[0], skiprows=19, skip_footer=1)
length = len(x0)
power_array = [powerLoop] * length
bifurWidth_array = [bifurWidthLoop] * length
label = str(bifurWidth)
a = myScatter(x0,power_array,y0,bifurWidth_array,lw,s,min,max,cmhot,label,ax)
#cax = ax.imshow(y0, interpolation='nearest', vmin=min, vmax=max)
#fig.colorbar(cax)
fig.savefig('test.png',dpi=300)
Example of an attempt and its error:
If I use fig.colorbar(a) inside or outside of the plotting for loop, I return NoneType oject has no attribute autoscale_None.
Your code doesn't run (x0,dataSorted,y0,etc missing) so can't get it to work (also note x0,power_array,y0 are wrong order in fn call). You need to return the handle to the scatter plot in order to plot a colorbar. If you change your myScatter function to return the handle,
def myScatter(x0,y0,power_array,c,lw,s,vmin,vmax,cmap,label,ax):
return ax.scatter(x0,y0,power_array,c=c,lw=lw,s=s,vmin=min,vmax=max,cmap=cmhot,label=label)
and then call plt.colorbar(a). A minimal(ish) example would be,
from glob import glob
from pylab import *
import numpy as np
from matplotlib import cm
from mpl_toolkits.mplot3d import Axes3D
def myScatter(x0,y0,power_array,c,lw,s,vmin,vmax,cmap,label,ax):
return ax.scatter(x0,y0,power_array,c=c,lw=lw,s=s,vmin=min,vmax=max,cmap=cmhot,label=label)
fig = figure()
ax = fig.add_subplot(111, projection='3d')
cmhot = get_cmap("jet")
fig.tight_layout()
fig.set_size_inches(25,15)
min = 3 #colorbar range
max = 10
lw = 0 #linewidth
s = 10 #scatter size
label = 'test'
power_array = np.random.random((100,10))
bifurWidth_array = np.random.random((100,10))*(max-min)+min
x0 = np.random.random((100,10))
y0 = np.random.random((100,10))
a = myScatter(x0,power_array,y0,bifurWidth_array,lw,s,min,max,cmhot,label,ax)
plt.colorbar(a)
plt.show()