Ich glaube, matplotlib hat die gleiche Achse in 3D noch nicht richtig eingestellt ... Aber ich habe vor einiger Zeit einen Trick gefunden (ich weiß nicht mehr, wo), den ich damit angepasst habe. Das Konzept besteht darin, einen gefälschten kubischen Begrenzungsrahmen um Ihre Daten zu erstellen. Sie können es mit folgendem 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)
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())
for xb, yb, zb in zip(Xb, Yb, Zb):
ax.plot([xb], [yb], [zb], 'w')
plt.grid()
plt.show()
z-Daten sind ungefähr eine Größenordnung größer als x und y, aber selbst bei gleicher Achsenoption kann die matplotlib-Z-Achse automatisch skaliert werden:
Wenn Sie jedoch den Begrenzungsrahmen hinzufügen, erhalten Sie eine korrekte Skalierung:
equal
Aussage nicht einmal - sie ist immer gleich.equal
Anweisung bei mir nicht funktioniert hat .Ich mag die oben genannten Lösungen, aber sie haben den Nachteil, dass Sie die Bereiche und Mittelwerte über alle Ihre Daten hinweg verfolgen müssen. Dies kann 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 Sie plt.show () aufrufen. 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()
quelle
Ich habe die Lösung von Remy F mithilfe der
set_x/y/zlim
Funktionen vereinfacht .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()
quelle
midpoint_x = np.mean([X.max(),X.min()])
und dann die Grenzwerte aufmidpoint_x
+/- setzenmax_range
. Die Verwendung des Mittelwerts funktioniert nur, wenn sich der Mittelwert in der Mitte des Datensatzes befindet, was nicht immer der Fall ist. Ein Tipp: Sie können max_range skalieren, damit das Diagramm besser aussieht, wenn sich Punkte in der Nähe oder an den Grenzen befinden.set_aspect('equal')
, verwenden Sieset_box_aspect([1,1,1])
, wie in meiner Antwort unten beschrieben. Es funktioniert für mich in Matplotlib Version 3.3.1!Angepasst an die Antwort von @ karlo, um die Dinge noch sauberer zu machen:
def set_axes_equal(ax: plt.Axes): """Set 3D plot axes to equal scale. Make axes of 3D plot have equal scale so that spheres appear as spheres and cubes as cubes. Required since `ax.axis('equal')` and `ax.set_aspect('equal')` don't work on 3D. """ limits = np.array([ ax.get_xlim3d(), ax.get_ylim3d(), ax.get_zlim3d(), ]) origin = np.mean(limits, axis=1) radius = 0.5 * np.max(np.abs(limits[:, 1] - limits[:, 0])) _set_axes_radius(ax, origin, radius) def _set_axes_radius(ax, origin, radius): x, y, z = origin ax.set_xlim3d([x - radius, x + radius]) ax.set_ylim3d([y - radius, y + radius]) ax.set_zlim3d([z - radius, z + radius])
Verwendung:
fig = plt.figure() ax = fig.gca(projection='3d') ax.set_aspect('equal') # important! # ...draw here... set_axes_equal(ax) # important! plt.show()
BEARBEITEN: Diese Antwort funktioniert bei neueren Versionen von Matplotlib aufgrund der eingeführten Änderungen
pull-request #13474
, die inissue #17172
und nachverfolgt werden, nichtissue #1077
. Um dies vorübergehend zu umgehen, können Sie die neu hinzugefügten Zeilen entfernen inlib/matplotlib/axes/_base.py
:class _AxesBase(martist.Artist): ... def set_aspect(self, aspect, adjustable=None, anchor=None, share=False): ... + if (not cbook._str_equal(aspect, 'auto')) and self.name == '3d': + raise NotImplementedError( + 'It is not currently possible to manually set the aspect ' + 'on 3D axes')
quelle
ax.set_box_aspect([1,1,1])
bevor Sie anrufenset_axes_equal
Einfache Lösung!
Ich habe es geschafft, dies in Version 3.3.1 zum Laufen zu bringen.
Es sieht so aus, als ob dieses Problem möglicherweise in PR # 17172 behoben wurde . Mit der
ax.set_box_aspect([1,1,1])
Funktion können Sie sicherstellen, dass der Aspekt korrekt ist (siehe Hinweise zur Funktion set_aspect ). In Verbindung mit den von @karlo und / oder @Matee Ulhaq bereitgestellten Begrenzungsrahmenfunktionen sehen die Diagramme jetzt in 3D korrekt aus!Minimum Arbeitsbeispiel
import matplotlib.pyplot as plt import mpl_toolkits.mplot3d import numpy as np # Functions from @Mateen Ulhaq and @karlo def set_axes_equal(ax: plt.Axes): """Set 3D plot axes to equal scale. Make axes of 3D plot have equal scale so that spheres appear as spheres and cubes as cubes. Required since `ax.axis('equal')` and `ax.set_aspect('equal')` don't work on 3D. """ limits = np.array([ ax.get_xlim3d(), ax.get_ylim3d(), ax.get_zlim3d(), ]) origin = np.mean(limits, axis=1) radius = 0.5 * np.max(np.abs(limits[:, 1] - limits[:, 0])) _set_axes_radius(ax, origin, radius) def _set_axes_radius(ax, origin, radius): x, y, z = origin ax.set_xlim3d([x - radius, x + radius]) ax.set_ylim3d([y - radius, y + radius]) ax.set_zlim3d([z - radius, z + radius]) # Generate and plot a unit sphere u = np.linspace(0, 2*np.pi, 100) v = np.linspace(0, np.pi, 100) x = np.outer(np.cos(u), np.sin(v)) # np.outer() -> outer vector product y = np.outer(np.sin(u), np.sin(v)) z = np.outer(np.ones(np.size(u)), np.cos(v)) fig = plt.figure() ax = fig.gca(projection='3d') ax.plot_surface(x, y, z) ax.set_box_aspect([1,1,1]) # IMPORTANT - this is the new, key line # ax.set_proj_type('ortho') # OPTIONAL - default is perspective (shown in image above) set_axes_equal(ax) # IMPORTANT - this is also required plt.show()
quelle
BEARBEITEN : Der Code von user2525140 sollte einwandfrei funktionieren, obwohl diese Antwort angeblich versucht hat, einen nicht vorhandenen Fehler zu beheben. Die folgende Antwort 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])
quelle
ax.set_aspect('equal')
oder die Tick-Werte können vermasselt werden. Ansonsten gute Lösung. Vielen Dank,Ab matplotlib 3.3.0 scheint Axes3D.set_box_aspect der empfohlene Ansatz zu sein.
import numpy as np xs, ys, zs = <your data> ax = <your axes> # Option 1: aspect ratio is 1:1:1 in data space ax.set_box_aspect((np.ptp(xs), np.ptp(ys), np.ptp(zs))) # Option 2: aspect ratio 1:1:1 in view space ax.set_box_aspect((1, 1, 1))
quelle