2012-12-03 82 views
46

當我爲三維圖形設置等縱橫比時,z軸不會變爲'等於'。所以,這樣的:matplotlib(相等的單位長度):'等於'長寬比z軸不等於x-和y-

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

給我以下: enter image description here

其中明顯z軸的單位長度不等於X方向和Y單元。

如何讓所有三個軸的單位長度相等?我能找到的所有解決方案都無效。謝謝。

回答

42

我相信matplotlib還沒有在3D中設置正確的等軸......但是我發現了一些前段時間(我不記得是在哪裏)使用它的技巧。這個概念是在你的數據周圍創建一個假立方體邊界框。 可以用下面的代碼測試:

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

fig = plt.figure() 
ax = fig.gca(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() 

ž數據是大約比x和y大一個數量級,但即使有等於軸線選項,matplotlib自動定標Z軸:

bad

但如果添加邊框,您獲得正確的縮放:

enter image description here

+0

謝謝。它很棒! – Olexandr

+0

在這種情況下,你甚至不需要'equal'語句 - 它總是相等的。 – Olexandr

+0

你可能需要注意調用一個變量「scat」...... – Ludwik

30

我通過使用set_x/y/zlimfunctions簡化了Remy F的解決方案。

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

fig = plt.figure() 
ax = fig.gca(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) 

max_range = np.array([X.max()-X.min(), Y.max()-Y.min(), Z.max()-Z.min()]).max()/2.0 

mid_x = (X.max()+X.min()) * 0.5 
mid_y = (Y.max()+Y.min()) * 0.5 
mid_z = (Z.max()+Z.min()) * 0.5 
ax.set_xlim(mid_x - max_range, mid_x + max_range) 
ax.set_ylim(mid_y - max_range, mid_y + max_range) 
ax.set_zlim(mid_z - max_range, mid_z + max_range) 

plt.show() 

enter image description here

+0

接縫工作正常。謝謝。 – Olexandr

+1

我喜歡簡化的代碼。請注意,有些(很少)數據點可能無法繪製。例如,假設X = [0,0,0,100]以便X.mean()= 25。如果max_range是100(來自X),那麼你的x範圍是25 + - 50,所以[-25,75],你會錯過X [3]數據點。這個想法雖然很好,並且易於修改,以確保您獲得所有要點。 – TravisJ

+1

注意以手段爲中心是不正確的。你應該使用'midpoint_x = np.mean([X.max(),X.min()])'',然後將限制設置爲'midpoint_x' +/-'max_range'。如果均值位於數據集的中點,使用均值僅適用,但並非總是如此。另外,提示:如果邊界附近或邊界上有點,則可以調整max_range以使圖形看起來更好。 –

16

我喜歡上面的解決方案,但他們確實有,你需要跟蹤的範圍和手段上所有數據的缺點。如果您有多個數據集將被繪製在一起,這可能會很麻煩。爲了解決這個問題,我使用了ax.get_ [xyz] lim3d()方法,並將整個事件放到一個獨立的函數中,在調用plt.show()之前只能調用一次。這是新版本:

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

def set_axes_equal(ax): 
    '''Make axes of 3D plot have equal scale so that spheres appear as spheres, 
    cubes as cubes, etc.. This is one possible solution to Matplotlib's 
    ax.set_aspect('equal') and ax.axis('equal') not working for 3D. 

    Input 
     ax: a matplotlib axis, e.g., as output from plt.gca(). 
    ''' 

    x_limits = ax.get_xlim3d() 
    y_limits = ax.get_ylim3d() 
    z_limits = ax.get_zlim3d() 

    x_range = abs(x_limits[1] - x_limits[0]) 
    x_middle = np.mean(x_limits) 
    y_range = abs(y_limits[1] - y_limits[0]) 
    y_middle = np.mean(y_limits) 
    z_range = abs(z_limits[1] - z_limits[0]) 
    z_middle = np.mean(z_limits) 

    # The plot bounding box is a sphere in the sense of the infinity 
    # norm, hence I call half the max range the plot radius. 
    plot_radius = 0.5*max([x_range, y_range, z_range]) 

    ax.set_xlim3d([x_middle - plot_radius, x_middle + plot_radius]) 
    ax.set_ylim3d([y_middle - plot_radius, y_middle + plot_radius]) 
    ax.set_zlim3d([z_middle - plot_radius, z_middle + plot_radius]) 

fig = plt.figure() 
ax = fig.gca(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) 

set_axes_equal(ax) 
plt.show() 
+0

謝謝你的另一個版本。我現在沒有時間測試它,但稍後我會回到它。 – Olexandr

+0

請注意,使用方式作爲中心點在任何情況下都不起作用,您應該使用中點。看到我對tauran的答案的評論。 –

+0

我上面的代碼不考慮數據的平均值,它取現有繪圖限制的平均值。因此,我的功能可以保證根據調用之前設定的繪圖限制保留在視圖中的任何點。如果用戶已經限制了繪圖限制以查看所有數據點,那麼這是一個單獨的問題。我的功能允許更多的靈活性,因爲您可能只想查看一部分數據。我所做的只是展開軸限制,因此縱橫比爲1:1:1。 – karlo

2

編輯: user2525140的代碼應該工作完全正常,雖然這個答案據稱試圖修復一個非 - existant錯誤。下面的答案只是一個重複(替代)實現:

def set_aspect_equal_3d(ax): 
    """Fix equal aspect bug for 3D plots.""" 

    xlim = ax.get_xlim3d() 
    ylim = ax.get_ylim3d() 
    zlim = ax.get_zlim3d() 

    from numpy import mean 
    xmean = mean(xlim) 
    ymean = mean(ylim) 
    zmean = mean(zlim) 

    plot_radius = max([abs(lim - mean_) 
         for lims, mean_ in ((xlim, xmean), 
              (ylim, ymean), 
              (zlim, zmean)) 
         for lim in lims]) 

    ax.set_xlim3d([xmean - plot_radius, xmean + plot_radius]) 
    ax.set_ylim3d([ymean - plot_radius, ymean + plot_radius]) 
    ax.set_zlim3d([zmean - plot_radius, zmean + plot_radius])