51
votes
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

Setting the aspect ratio works for 2d plots:

ax = plt.axes()
ax.plot([0,1],[0,10])
ax.set_aspect('equal','box')

But does not for 3d:

ax = plt.axes(projection='3d')
ax.plot([0,1],[0,1],[0,10])
ax.set_aspect('equal','box')

Is there a different syntax for the 3d case, or it's not implemented?

8

8 Answers

4
votes

My understanding is basically that this isn't implemented yet (see this bug in GitHub). I'm also hoping that it is implemented soon. See This link for a possible solution (I haven't tested it myself).

28
votes

I didn't try all of these answers, but this kludge did it for me:

def axisEqual3D(ax):
    extents = np.array([getattr(ax, 'get_{}lim'.format(dim))() for dim in 'xyz'])
    sz = extents[:,1] - extents[:,0]
    centers = np.mean(extents, axis=1)
    maxsize = max(abs(sz))
    r = maxsize/2
    for ctr, dim in zip(centers, 'xyz'):
        getattr(ax, 'set_{}lim'.format(dim))(ctr - r, ctr + r)
24
votes

As of matplotlib 3.3.0, Axes3D.set_box_aspect seems to be the recommended approach.

import numpy as np
import matplotlib.pyplot as plt

xs, ys, zs = ...
ax = plt.axes(projection='3d')

ax.set_box_aspect((np.ptp(xs), np.ptp(ys), np.ptp(zs)))  # aspect ratio is 1:1:1 in data space

ax.plot(xs, ys, zs)
20
votes

Looks like this feature has since been added so thought I'd add an answer for people who come by this thread in the future like I did:

fig = plt.figure(figsize=plt.figaspect(0.5)*1.5) #Adjusts the aspect ratio and enlarges the figure (text does not enlarge)
ax = fig.gca(projection='3d')

figaspect(0.5) makes the figure twice as wide as it is tall. Then the *1.5 increases the size of the figure. The labels etc won't increase so this is a way to make the graph look less cluttered by the labels.

13
votes

If you know the bounds, eg. +-3 centered around (0,0,0), you can add invisible points like this:

import numpy as np
import pylab as pl
from mpl_toolkits.mplot3d import Axes3D
fig = pl.figure()
ax = fig.gca(projection='3d')
ax.set_aspect('equal')
MAX = 3
for direction in (-1, 1):
    for point in np.diag(direction * MAX * np.array([1,1,1])):
        ax.plot([point[0]], [point[1]], [point[2]], 'w')
9
votes

If you know the bounds you can also set the aspect ratio this way:

ax.auto_scale_xyz([minbound, maxbound], [minbound, maxbound], [minbound, maxbound])
4
votes

I think setting the correct "box aspect" is a good solution:

ax.set_box_aspect(aspect = (1,1,1))

import matplotlib.pyplot as plt

fig = plt.figure()
ax = fig.gca(projection='3d')
ax.set_box_aspect(aspect = (1,1,1))

ax.plot(dataX,dataY,dataZ)

https://matplotlib.org/stable/api/_as_gen/mpl_toolkits.mplot3d.axes3d.Axes3D.html?highlight=3d%20set_box_aspect#mpl_toolkits.mplot3d.axes3d.Axes3D.set_box_aspect

3
votes

Another helpful (hopefully) solution when, for example, it is necessary to update an already existing figure:

world_limits = ax.get_w_lims()
ax.set_box_aspect((world_limits[1]-world_limits[0],world_limits[3]-world_limits[2],world_limits[5]-world_limits[4]))

get_w_lims()

set_box_aspect()