Search code examples
pythonmatplotlibaxisaspect-ratiomatplotlib-3d

How to set the 'equal' aspect ratio for all axes (x, y, z)


When I set up an equal aspect ratio for a 3d graph, the z-axis does not change to 'equal'. So this:

fig = pylab.figure()
mesFig = fig.gca(projection='3d', adjustable='box')
mesFig.axis('equal')
mesFig.plot(xC, yC, zC, 'r.')
mesFig.plot(xO, yO, zO, 'b.')
pyplot.show()

Gives me the following:

img1

Where obviously the unit length of z-axis is not equal to x- and y- units.

How can I make the unit length of all three axes equal? All the solutions I found did not work.


Solution

  • I believe matplotlib does not yet set correctly equal axis in 3D... But I found a trick some times ago (I don't remember where) that I've adapted using it. The concept is to create a fake cubic bounding box around your data. You can test it with the following code:

    from mpl_toolkits.mplot3d import Axes3D
    from matplotlib import cm
    import matplotlib.pyplot as plt
    import numpy as np
    
    fig = plt.figure()
    ax = fig.add_subplot(projection='3d')
    ax.set_aspect('equal')
    
    X = np.random.rand(100)*10+5
    Y = np.random.rand(100)*5+2.5
    Z = np.random.rand(100)*50+25
    
    scat = ax.scatter(X, Y, Z)
    
    # Create cubic bounding box to simulate equal aspect ratio
    max_range = np.array([X.max()-X.min(), Y.max()-Y.min(), Z.max()-Z.min()]).max()
    Xb = 0.5*max_range*np.mgrid[-1:2:2,-1:2:2,-1:2:2][0].flatten() + 0.5*(X.max()+X.min())
    Yb = 0.5*max_range*np.mgrid[-1:2:2,-1:2:2,-1:2:2][1].flatten() + 0.5*(Y.max()+Y.min())
    Zb = 0.5*max_range*np.mgrid[-1:2:2,-1:2:2,-1:2:2][2].flatten() + 0.5*(Z.max()+Z.min())
    # Comment or uncomment following both lines to test the fake bounding box:
    for xb, yb, zb in zip(Xb, Yb, Zb):
       ax.plot([xb], [yb], [zb], 'w')
    
    plt.grid()
    plt.show()
    

    z data are about an order of magnitude larger than x and y, but even with equal axis option, matplotlib autoscale z axis:

    bad

    But if you add the bounding box, you obtain a correct scaling:

    enter image description here