Saya percaya matplotlib belum mengatur sumbu yang sama dengan benar dalam 3D ... Tetapi saya menemukan trik beberapa waktu lalu (saya tidak ingat di mana) yang telah saya adaptasi menggunakannya. Konsepnya adalah membuat kotak pembatas kubik palsu di sekitar data Anda. Anda dapat mengujinya dengan kode berikut:
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 data memiliki urutan besarnya lebih besar dari x dan y, tetapi bahkan dengan opsi sumbu yang sama, matplotlib autoscale sumbu z:
Tetapi jika Anda menambahkan kotak pembatas, Anda mendapatkan penskalaan yang benar:
equal
pernyataan itu - pernyataan itu akan selalu sama.equal
pernyataan tersebut.Saya suka solusi di atas, tetapi mereka memiliki kekurangan yaitu Anda perlu melacak rentang dan sarana di atas semua data Anda. Ini bisa merepotkan jika Anda memiliki beberapa kumpulan data yang akan diplot bersama. Untuk memperbaikinya, saya menggunakan metode ax.get_ [xyz] lim3d () dan meletakkan semuanya ke dalam fungsi mandiri yang dapat dipanggil hanya sekali sebelum Anda memanggil plt.show (). Ini versi barunya:
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()
sumber
Saya menyederhanakan solusi Remy F dengan menggunakan
set_x/y/zlim
fungsi .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()
sumber
midpoint_x = np.mean([X.max(),X.min()])
dan kemudian menetapkan batas kemidpoint_x
+/-max_range
. Menggunakan mean hanya berfungsi jika mean terletak di titik tengah kumpulan data, yang tidak selalu benar. Juga, tip: Anda dapat menskalakan max_range untuk membuat grafik terlihat lebih bagus jika ada titik di dekat atau di perbatasan.set_aspect('equal')
, gunakanset_box_aspect([1,1,1])
, seperti yang dijelaskan dalam jawaban saya di bawah ini. Ini berfungsi untuk saya di matplotlib versi 3.3.1!Diadaptasi dari jawaban @ karlo untuk membuat segalanya lebih bersih:
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])
Pemakaian:
fig = plt.figure() ax = fig.gca(projection='3d') ax.set_aspect('equal') # important! # ...draw here... set_axes_equal(ax) # important! plt.show()
EDIT: Jawaban ini tidak berfungsi pada versi Matplotlib yang lebih baru karena perubahan yang digabungkan
pull-request #13474
, yang dilacak dalamissue #17172
danissue #1077
. Sebagai solusi sementara untuk ini, seseorang dapat menghapus baris yang baru ditambahkan dilib/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')
sumber
ax.set_box_aspect([1,1,1])
sebelum meneleponset_axes_equal
Perbaikan sederhana!
Saya telah berhasil membuatnya berfungsi di versi 3.3.1.
Sepertinya masalah ini mungkin telah diselesaikan dalam PR # 17172 ; Anda dapat menggunakan
ax.set_box_aspect([1,1,1])
fungsi untuk memastikan aspeknya benar (lihat catatan untuk fungsi set_aspect ). Saat digunakan bersama dengan fungsi kotak pembatas yang disediakan oleh @karlo dan / atau @Matee Ulhaq, plot sekarang terlihat benar dalam 3D!Contoh Kerja 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()
sumber
EDIT: kode user2525140 seharusnya bekerja dengan baik, meskipun jawaban ini seharusnya mencoba untuk memperbaiki kesalahan yang tidak ada. Jawaban di bawah ini hanyalah implementasi duplikat (alternatif):
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])
sumber
ax.set_aspect('equal')
atau nilai centang mungkin kacau. Jika tidak, solusi yang bagus. Terima kasih,Pada matplotlib 3.3.0, Axes3D.set_box_aspect tampaknya menjadi pendekatan yang direkomendasikan.
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))
sumber