Je crois que matplotlib ne définit pas encore correctement l'axe égal en 3D ... Mais j'ai trouvé il y a quelques temps une astuce (je ne me souviens pas où) que j'ai adaptée en l'utilisant. Le concept est de créer une fausse boîte englobante cubique autour de vos données. Vous pouvez le tester avec le code suivant:
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()
Les données z sont d'un ordre de grandeur supérieur à x et y, mais même avec l'option d'axe égal, axe z autoscale matplotlib:
Mais si vous ajoutez la boîte englobante, vous obtenez une mise à l'échelle correcte:
equal
déclaration - elle sera toujours égale.equal
déclaration.J'aime les solutions ci-dessus, mais elles présentent l'inconvénient dont vous avez besoin pour garder une trace des plages et des moyens sur toutes vos données. Cela peut être fastidieux si vous disposez de plusieurs ensembles de données qui seront tracés ensemble. Pour résoudre ce problème, j'ai utilisé les méthodes ax.get_ [xyz] lim3d () et mis le tout dans une fonction autonome qui peut être appelée une seule fois avant d'appeler plt.show (). Voici la nouvelle 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()
la source
J'ai simplifié la solution de Remy F en utilisant les
set_x/y/zlim
fonctions .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()
la source
midpoint_x = np.mean([X.max(),X.min()])
, puis définir les limites surmidpoint_x
+/-max_range
. L'utilisation de la moyenne ne fonctionne que si la moyenne est située au milieu de l'ensemble de données, ce qui n'est pas toujours vrai. Aussi, un conseil: vous pouvez mettre à l'échelle max_range pour rendre le graphique plus joli s'il y a des points près ou sur les limites.set_aspect('equal')
, utilisezset_box_aspect([1,1,1])
, comme décrit dans ma réponse ci-dessous. Cela fonctionne pour moi dans la version 3.3.1 de matplotlib!Adapté de la réponse de @ karlo pour rendre les choses encore plus propres:
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])
Usage:
fig = plt.figure() ax = fig.gca(projection='3d') ax.set_aspect('equal') # important! # ...draw here... set_axes_equal(ax) # important! plt.show()
EDIT: Cette réponse ne fonctionne pas sur les versions plus récentes de Matplotlib en raison des modifications fusionnées dans
pull-request #13474
, qui sont suivies dansissue #17172
etissue #1077
. Pour contourner ce problème, vous pouvez supprimer les lignes nouvellement ajoutées danslib/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')
la source
ax.set_box_aspect([1,1,1])
avant d'appelerset_axes_equal
Solution simple!
J'ai réussi à faire fonctionner cela dans la version 3.3.1.
Il semble que ce problème ait peut-être été résolu dans le PR # 17172 ; Vous pouvez utiliser la
ax.set_box_aspect([1,1,1])
fonction pour vous assurer que l'aspect est correct (voir les notes de la fonction set_aspect ). Lorsqu'elles sont utilisées en conjonction avec les fonctions de boîte englobante fournies par @karlo et / ou @Matee Ulhaq, les tracés semblent maintenant corrects en 3D!Exemple de travail minimum
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()
la source
EDIT: le code de user2525140 devrait fonctionner parfaitement bien, même si cette réponse aurait tenté de corriger une erreur inexistante. La réponse ci-dessous est juste une implémentation en double (alternative):
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])
la source
ax.set_aspect('equal')
ou les valeurs de graduation peuvent être foirées. Sinon bonne solution. Merci,Depuis matplotlib 3.3.0, Axes3D.set_box_aspect semble être l'approche recommandée.
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))
la source