I'm trying to plot cuboids of different sizes using matplotlib, such that: after rotation the cuboids do not overlap visually in a non-physical way, the cubes have different colors and a box drawn around them.
I've read several blog posts and stackoverflow pages referencing similar problems, but always with a slight difference; none which have worked for me. The easiest way to overcome the overlapping problem was to use voxels (as in https://matplotlib.org/api/_as_gen/mpl_toolkits.mplot3d.axes3d.Axes3D.html?highlight=voxel#mpl_toolkits.mplot3d.axes3d.Axes3D.voxels), but these do not allow me to draw boxes around them. What's the easiest way to do this in matplotlib?
The image below shows what I have on the left, and what I want on the right.
EDIT: I've looked into several approaches that can give the desired effect, of which the main ones are:
The former seemed easier to execute, but I'm still stumped.
Poly3DCollection
An option is to create a Poly3DCollection
of the faces of the cuboids. As the overlapping issue is not present for artists of the same collection, this might best serve the purpose here.
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
import numpy as np
import matplotlib.pyplot as plt
def cuboid_data2(o, size=(1,1,1)):
X = [[[0, 1, 0], [0, 0, 0], [1, 0, 0], [1, 1, 0]],
[[0, 0, 0], [0, 0, 1], [1, 0, 1], [1, 0, 0]],
[[1, 0, 1], [1, 0, 0], [1, 1, 0], [1, 1, 1]],
[[0, 0, 1], [0, 0, 0], [0, 1, 0], [0, 1, 1]],
[[0, 1, 0], [0, 1, 1], [1, 1, 1], [1, 1, 0]],
[[0, 1, 1], [0, 0, 1], [1, 0, 1], [1, 1, 1]]]
X = np.array(X).astype(float)
for i in range(3):
X[:,:,i] *= size[i]
X += np.array(o)
return X
def plotCubeAt2(positions,sizes=None,colors=None, **kwargs):
if not isinstance(colors,(list,np.ndarray)): colors=["C0"]*len(positions)
if not isinstance(sizes,(list,np.ndarray)): sizes=[(1,1,1)]*len(positions)
g = []
for p,s,c in zip(positions,sizes,colors):
g.append( cuboid_data2(p, size=s) )
return Poly3DCollection(np.concatenate(g),
facecolors=np.repeat(colors,6), **kwargs)
positions = [(-3,5,-2),(1,7,1)]
sizes = [(4,5,3), (3,3,7)]
colors = ["crimson","limegreen"]
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.set_aspect('equal')
pc = plotCubeAt2(positions,sizes,colors=colors, edgecolor="k")
ax.add_collection3d(pc)
ax.set_xlim([-4,6])
ax.set_ylim([4,13])
ax.set_zlim([-3,9])
plt.show()
plot_surface
Adapting the solution from this question, which uses plot_surface
, and allow for different sizes as desired here seems to work just fine for most cases:
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
import matplotlib.pyplot as plt
def cuboid_data(o, size=(1,1,1)):
# code taken from
# https://stackoverflow.com/a/35978146/4124317
# suppose axis direction: x: to left; y: to inside; z: to upper
# get the length, width, and height
l, w, h = size
x = [[o[0], o[0] + l, o[0] + l, o[0], o[0]],
[o[0], o[0] + l, o[0] + l, o[0], o[0]],
[o[0], o[0] + l, o[0] + l, o[0], o[0]],
[o[0], o[0] + l, o[0] + l, o[0], o[0]]]
y = [[o[1], o[1], o[1] + w, o[1] + w, o[1]],
[o[1], o[1], o[1] + w, o[1] + w, o[1]],
[o[1], o[1], o[1], o[1], o[1]],
[o[1] + w, o[1] + w, o[1] + w, o[1] + w, o[1] + w]]
z = [[o[2], o[2], o[2], o[2], o[2]],
[o[2] + h, o[2] + h, o[2] + h, o[2] + h, o[2] + h],
[o[2], o[2], o[2] + h, o[2] + h, o[2]],
[o[2], o[2], o[2] + h, o[2] + h, o[2]]]
return np.array(x), np.array(y), np.array(z)
def plotCubeAt(pos=(0,0,0), size=(1,1,1), ax=None,**kwargs):
# Plotting a cube element at position pos
if ax !=None:
X, Y, Z = cuboid_data( pos, size )
ax.plot_surface(X, Y, Z, rstride=1, cstride=1, **kwargs)
positions = [(-3,5,-2),(1,7,1)]
sizes = [(4,5,3), (3,3,7)]
colors = ["crimson","limegreen"]
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.set_aspect('equal')
for p,s,c in zip(positions,sizes,colors):
plotCubeAt(pos=p, size=s, ax=ax, color=c)
plt.show()
The following code will not only work for cuboid but for any polygon
Type your coordinates for x, y and z respectively
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
#input values
x=[1,10,50,100,150]
y=[1,300,350,250,50]
z=[0,1]
def edgecoord(pointx,pointy,pointz):
edgex=[pointx[0],pointx[1],pointx[1],pointx[0]]
edgey=[pointy[0],pointy[1],pointy[1],pointy[0]]
edgez=[pointz[0],pointz[0],pointz[1],pointz[1]]
return list(zip(edgex,edgey,edgez))
def coordConvert(x,y,lheight,uheight):
if len(x) != len(y) and len(x)>2:
return
vertices=[]
#Top layer
vertices.append(list(zip(x,y,list(np.full(len(x),uheight)))))
# Side layers
for it in np.arange(len(x)):
it1=it+1
if it1>=len(x):
it1=0
vertices.append(edgecoord([x[it],x[it1]],[y[it],y[it1]],[lheight,uheight]))
#Bottom layer
vertices.append(list(zip(x,y,list(np.full(len(x),lheight)))))
print(np.array(vertices))
return vertices
vec=coordConvert(x,y,z[0],z[1])
plt.figure()
plt.subplot(111,projection='3d')
plt.gca().add_collection3d(Poly3DCollection(vec, alpha=.75,edgecolor='k', facecolor='teal'))
plt.xlim([0,200])
plt.ylim([0,400])
plt.show()
Polygon Prism
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With