0

I Have the following code taken from Here, in this code a figure of height two is generated by two cubes (upper and bottom), I want generate the figure of height two by a only one figure, likewise the figure of height 3,4,5,...

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

def cuboid_data(center, N, size=(1,1,1)):
    # code taken from
    # https://stackoverflow.com/questions/30715083/python-plotting-a-wireframe-3d-cuboid?noredirect=1&lq=1
    # suppose axis direction: x: to left; y: to inside; z: to upper
    # get the (left, outside, bottom) point
    o = [a - b / 2 for a, b in zip(center, size)]

    l, w, h = size


    x = [[o[0], o[0] + l, o[0] + l, o[0], o[0]],  # x coordinate of points in bottom surface
         [o[0], o[0] + l, o[0] + l, o[0], o[0]],  # x coordinate of points in upper surface
         [o[0], o[0] + l, o[0] + l, o[0], o[0]],  # x coordinate of points in outside surface
         [o[0], o[0] + l, o[0] + l, o[0], o[0]]]  # x coordinate of points in inside surface
    y = [[o[1], o[1], o[1] + w, o[1] + w, o[1]],  # y coordinate of points in bottom surface
         [o[1], o[1], o[1] + w, o[1] + w, o[1]],  # y coordinate of points in upper surface
         [o[1], o[1], o[1], o[1], o[1]],          # y coordinate of points in outside surface
         [o[1] + w, o[1] + w, o[1] + w, o[1] + w, o[1] + w]]    # y coordinate of points in inside surface
    z =      [[0,0,0,0,0],
         [N,N,N,N,N],    
         [0, 0, N,N, 0],
         [0, 0, N, N, 0]]
    return x, y, z

def plotCubeAt(pos=(0,0), N=0, ax=None):
    # Plotting N cube elements at position pos
    if ax !=None:
        if N > 0:
            #for n in range(N):
            X, Y, Z = cuboid_data( (pos[0],pos[1],N),N )
            ax.plot_surface(X, Y, Z, color='y', rstride=1, cstride=1)#,linewidth=0)

def plotIsoMatrix(ax, matrix):
    # plot a Matrix 
    # where matrix[i,j] cubes are added at position (i,j) 
    for i  in range(matrix.shape[0]):
            for j in range(matrix.shape[1]):
                plotCubeAt(pos=(i,j), N=matrix[i,j], ax=ax)

    l = max(matrix.shape[0], matrix.shape[1], matrix.max())
    #bb = np.array([(0,0,0), (0,l,0), (l,0,0), (l,l,0),(0,0,l), (0,l,l), (l,0,l), (l,l,l)])
    #ax.plot(bb[:,0], bb[:,1], bb[:,2], "w", alpha=0.0)            



if __name__ == '__main__':
    fig = plt.figure()
    ax = fig.gca(projection='3d')
    #ax.set_aspect('equal')
    matrix = np.array([[2,2],[1,2]])
    plotIsoMatrix(ax, matrix)
    #ax.set_axis_off()
    plt.ion()
    plt.show()

This generate the following figure:

enter image description here

I want to generate the following figure:

enter image description here

How fix this ?

Thanks

Community
  • 1
  • 1
ignacio.saravia
  • 328
  • 1
  • 4
  • 15

1 Answers1

2

Solution usiing matplotlib

First of all, if you do not want to have single cubes, but cuboids, there is a much simpler solution - using matplotlib bar3d.

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

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.set_aspect('equal')
matrix = np.array([[2,2],[1,2]])
xpos, ypos = np.meshgrid(np.arange(matrix.shape[0]),np.arange(matrix.shape[1]) )
xpos = xpos.flatten('F')
ypos = ypos.flatten('F')
zpos = np.zeros_like(xpos)
dx = np.ones_like(zpos)
dy = dx.copy()
dz = matrix.flatten()

ax.bar3d(xpos, ypos, zpos, dx, dy, dz, color='y', zsort='average', linewidth=0)

l = max(matrix.shape[0], matrix.shape[1], matrix.max())
bb = np.array([(0,0,0), (0,l,0), (l,0,0), (l,l,0),(0,0,l), (0,l,l), (l,0,l), (l,l,l)])
ax.plot(bb[:,0], bb[:,1], bb[:,2], "w", alpha=0.0)
ax.set_axis_off()      
plt.show()

enter image description here

Concerning the overlapping faces of the cuboids, there is no solution in matplotlib. I think this behaviour is commonly considered to be an unresolvable bug, as is also described in the Matplotlib 3D FAQ. Also, manually setting zorder will not work. The good news is, however, that this overlapping is angle dependent. So you will always find a viewing angle (rotate the plot with the mouse) where it looks good.

Solution using mayavi

Using Mayavi, one does not have the problem of overlapping faces at all. Also the barchart in mayavi is much more convenient, making this only 5 lines of code:

import numpy as np
import mayavi.mlab as mlab
mlab.figure(bgcolor=(1,1,1))
matrix = np.array([[2,2],[1,2]])
mlab.barchart(matrix, color=(1.,0.86, 0.12), lateral_scale=1.0)
mlab.show()

enter image description here

ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712