2012-12-03 38 views
46

3次元グラフのアスペクト比を同じに設定すると、z軸は '等しく'変わりません。したがって、この:Z軸の単位長さは、x軸およびyユニットに等しくない明らか enter image description herematplotlib(等しい単位長さ): '等しい'アスペクト比の場合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() 

は、以下の私が得られます。

3軸の単位長さをどのように等しくすることができますか?私が見つけたすべての解決策はうまくいかなかった。ありがとうございました。

答えて

42

私はmatplotlibがまだ3次元で正しく等しい軸を設定していないと信じています...しかし、私はそれを使って適応したトリックを何度か前に見つけました。概念は、あなたのデータの周りに偽の立方体のバウンディングボックスを作成することです。次のコードでそれをテストすることができ :

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

Zデータは、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が(Xから)100になると、x-rangeは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

平均点として手段を使用することは、すべての場合に機能しないことに注意してください。中点を使用する必要があります。タウランの答えに対する私のコメントを見てください。 –

+0

上記の私のコードはデータの平均をとらないので、既存のプロット制限の平均値をとります。したがって、私の関数は、呼び出される前に設定されたプロット制限に従って、ビューにあったポイントを常に表示することが保証されます。ユーザーがすでにプロット制限を設定しすぎてすべてのデータポイントを表示していない場合、それは別の問題です。私の機能では、データのサブセットのみを表示したいので、柔軟性が向上します。私がしているのは、アスペクト比が1:1:1になるように軸の範囲を拡大することだけです。 – karlo

2

EDIT: - existantエラーこの答えはおそらく非を修正しようとしたものの user2525140のコードは、完全に正常に動作する必要があります。ここに新しいバージョンがあります。以下の回答は重複した(代替)実装に過ぎません。

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]) 
関連する問題