set matplotlib 3d plot aspect ratio

PythonMatplotlib

Python Problem Overview


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?

Python Solutions


Solution 1 - Python

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)

Solution 2 - Python

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)

Solution 3 - Python

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.add_subplot(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.

Solution 4 - Python

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.add_subplot(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')

Solution 5 - Python

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.add_subplot(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

Solution 6 - Python

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

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

Solution 7 - Python

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).

Solution 8 - Python

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()

Solution 9 - Python

I tried several methods, such as ax.set_box_aspect(aspect = (1,1,1)) and it does not work. I want a sphere to show up as a sphere -- not ellipsoid. I wrote this function and tried it on a variety of data. It is a hack and it is not perfect, but pretty close.

def set_aspect_equal(ax):
    """ 
    Fix the 3D graph to have similar scale on all the axes.
    Call this after you do all the plot3D, but before show
    """
    X = ax.get_xlim3d()
    Y = ax.get_ylim3d()
    Z = ax.get_zlim3d()
    a = [X[1]-X[0],Y[1]-Y[0],Z[1]-Z[0]]
    b = np.amax(a)
    ax.set_xlim3d(X[0]-(b-a[0])/2,X[1]+(b-a[0])/2)
    ax.set_ylim3d(Y[0]-(b-a[1])/2,Y[1]+(b-a[1])/2)
    ax.set_zlim3d(Z[0]-(b-a[2])/2,Z[1]+(b-a[2])/2)
    ax.set_box_aspect(aspect = (1,1,1))

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionhatmatrixView Question on Stackoverflow
Solution 1 - PythonMatt PanzerView Answer on Stackoverflow
Solution 2 - PythonBenView Answer on Stackoverflow
Solution 3 - PythonDanView Answer on Stackoverflow
Solution 4 - PythonJens NymanView Answer on Stackoverflow
Solution 5 - PythonNicolas MARTINView Answer on Stackoverflow
Solution 6 - PythonCrazymoominView Answer on Stackoverflow
Solution 7 - PythonScott BView Answer on Stackoverflow
Solution 8 - PythonbrezylView Answer on Stackoverflow
Solution 9 - PythonJohn HenckelView Answer on Stackoverflow