Я считаю, что 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)
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 примерно на порядок больше, чем x и y, но даже с опцией равной оси, matplotlib autoscale z axis:
Но если вы добавите ограничивающую рамку, вы получите правильное масштабирование:
equal
утверждение - оно всегда будет равно.equal
утверждения.Мне нравятся приведенные выше решения, но у них есть недостаток, заключающийся в том, что вам необходимо отслеживать диапазоны и средства по всем вашим данным. Это может быть обременительно, если у вас есть несколько наборов данных, которые будут построены вместе. Чтобы исправить это, я использовал методы 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()
источник
Я упростил решение Реми Ф, используя
set_x/y/zlim
функции .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()
источник
midpoint_x = np.mean([X.max(),X.min()])
а затем установить пределы наmidpoint_x
+/-max_range
. Использование среднего работает только в том случае, если среднее значение находится в средней точке набора данных, что не всегда верно. Также совет: вы можете масштабировать max_range, чтобы график выглядел лучше, если есть точки рядом или на границах.set_aspect('equal')
, используйтеset_box_aspect([1,1,1])
, как описано в моем ответе ниже. У меня это работает в matplotlib версии 3.3.1!Адаптировано из ответа @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])
Применение:
fig = plt.figure() ax = fig.gca(projection='3d') ax.set_aspect('equal') # important! # ...draw here... set_axes_equal(ax) # important! plt.show()
РЕДАКТИРОВАТЬ: этот ответ не работает в более поздних версиях Matplotlib из-за внесенных изменений
pull-request #13474
, которые отслеживаются вissue #17172
иissue #1077
. В качестве временного обходного пути можно удалить недавно добавленные строки вlib/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')
источник
ax.set_box_aspect([1,1,1])
передset_axes_equal
Простое исправление!
Мне удалось заставить это работать в версии 3.3.1.
Похоже, эта проблема, возможно, была решена в PR # 17172 ; Вы можете использовать эту
ax.set_box_aspect([1,1,1])
функцию, чтобы убедиться в правильности аспекта (см. Примечания к функции set_aspect ). При использовании в сочетании с функцией (ами) ограничивающего прямоугольника, предоставляемой @karlo и / или @Matee Ulhaq, графики теперь выглядят правильно в 3D!Минимальный рабочий пример
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()
источник
РЕДАКТИРОВАТЬ: код 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])
источник
ax.set_aspect('equal')
или значения тиков могут быть напортачены. В остальном хорошее решение. Спасибо,Для matplotlib 3.3.0 рекомендуется использовать Axes3D.set_box_aspect .
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))
источник