2012-12-03 6 views
46

Wenn ich gleiches Seitenverhältnis für 3D-Grafik einrichten die Z-Achse ändert sich nicht auf 'gleich'. Also, das:matplotlib (gleiche Längeneinheit): mit 'gleich' Seitenverhältnis z-Achse ist nicht gleich x- und 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() 

gibt mir folgendes: enter image description here

wo offensichtlich die Einheitslänge der z-Achse nicht gleich x- und y-Einheiten.

Wie kann ich die Einheitslänge aller drei Achsen gleich machen? Alle Lösungen, die ich finden konnte, funktionierten nicht. Vielen Dank.

Antwort

42

Ich glaube, dass Matplotlib noch nicht gleich die gleiche Achse in 3D gesetzt ... Aber ich habe vor einiger Zeit einen Trick gefunden (ich weiß nicht mehr wo), dass ich mich daran angepasst habe. Das Konzept besteht darin, eine falsche kubische Begrenzungsbox um Ihre Daten herum zu erstellen. Sie können es mit dem folgenden Code testen:

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 Daten sind etwa eine Größenordnung größer als x und y, aber auch mit dem gleichen Achse Option, matplotlib automatischen Skalierung z-Achse:

bad

Aber wenn Sie den Begrenzungsrahmen hinzufügen, erhalten Sie eine korrekte Skalierung:

enter image description here

+0

Vielen Dank. Es funktioniert super! – Olexandr

+0

In diesem Fall brauchen Sie nicht einmal die "Gleich" -Anweisung - es wird immer gleich sein. – Olexandr

+0

Vielleicht möchten Sie mit dem Aufrufen einer Variablen "scat" aufpassen ... – Ludwik

30

Ich vereinfachte die Lösung von Remy F unter Verwendung der set_x/y/zlimfunctions.

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

Nähte funktionieren gut. Vielen Dank. – Olexandr

+1

Ich mag den vereinfachten Code. Beachten Sie jedoch, dass einige (sehr wenige) Datenpunkte möglicherweise nicht geplottet werden. Angenommen, X = [0, 0, 0, 100], also X.mean() = 25. Wenn max_range 100 ergibt (aus X), dann ist der x-Bereich 25 + - 50, also [-25, 75] und Sie werden den X [3] Datenpunkt vermissen.Die Idee ist jedoch sehr nett und einfach zu ändern, um sicherzustellen, dass Sie alle Punkte erhalten. – TravisJ

+1

Beachten Sie, dass die Verwendung von Mitteln als das Zentrum nicht korrekt ist. Sie sollten etwas wie 'midpoint_x = np.mean ([X.max(), X.min()])' 'verwenden und dann die Grenzen auf' midpoint_x' +/- 'max_range' setzen. Die Verwendung des Mittelwerts funktioniert nur, wenn der Mittelwert im Mittelpunkt des Datasets liegt, was nicht immer der Fall ist. Auch ein Tipp: Sie können max_range skalieren, um das Diagramm schöner aussehen zu lassen, wenn es Punkte in der Nähe oder an den Grenzen gibt. –

16

Ich mag die oben genannten Lösungen, aber sie haben den Nachteil, dass Sie den Überblick über die Bereiche zu halten brauchen, und Mittel über alle Ihre Daten. Dies könnte umständlich sein, wenn Sie mehrere Datensätze haben, die zusammen geplottet werden. Um dies zu beheben, habe ich die Methoden ax.get_ [xyz] lim3d() verwendet und das Ganze in eine eigenständige Funktion eingefügt, die nur einmal aufgerufen werden kann, bevor du plt.show() aufruft. Hier ist die neue Version:

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

Vielen Dank für eine andere Version. Ich habe keine Zeit, es jetzt zu testen, aber ich werde später darauf zurückkommen. – Olexandr

+0

Beachten Sie, dass die Verwendung von Mitteln als Mittelpunkt nicht in allen Fällen funktioniert, Sie sollten Mittelpunkte verwenden. Siehe meinen Kommentar zu Taurans Antwort. –

+0

Mein Code oben nimmt nicht den Mittelwert der Daten, es nimmt den Mittelwert der vorhandenen Plot Grenzen. Meine Funktion ist also garantiert, dass Sie alle Punkte im Auge behalten, die gemäß den vor dem Aufruf festgelegten Plot-Limits angezeigt wurden. Wenn der Benutzer Plotlimits bereits zu restriktiv gesetzt hat, um alle Datenpunkte zu sehen, ist dies ein separates Problem. Meine Funktion bietet mehr Flexibilität, da Sie möglicherweise nur eine Teilmenge der Daten anzeigen möchten. Ich dehne nur Achsengrenzen aus, so dass das Seitenverhältnis 1: 1: 1 ist. – karlo

2

EDIT: user2525140 der Code sollte perfekt funktionieren, obwohl diese Antwort angeblich einen nicht zu beheben versucht - existant Fehler. Die Antwort unten ist nur eine doppelte (alternative) Implementierung:

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