使用ax.scatter绘制3D散点图时,如何绘制匹配数据范围的12条坐标轴?
问题描述
使用ax.scatter绘制3D散点图时,希望画出立方体的全部12条棱作为坐标轴框架,但通过ax.get_xlim()/ax.get_ylim()/ax.get_zlim()获取的轴范围与散点实际数据范围不匹配,导致绘制的线条和散点范围错位。
附相关代码、数据说明及生成的效果图:
数据为固定宽度格式的文本文件,处理后提取__net对应的数据点进行3D绘制。
import pandas as pd import matplotlib.pyplot as plt file_path = 'data.txt' col_widths = [12, 13, 18, 13, 13, 16, 16, 15, 16, 15] data = pd.read_fwf(file_path, widths=col_widths, header=0) columns_to_format = ['Sum', 'Mean', 'Std', 'Min', 'Max'] for col in columns_to_format: data[col] = data[col].apply(lambda x: '{:14.8e}'.format(float(x)) if pd.notnull(x) else x) net_data = data[data['Variable'] == '__net'].copy() net_data['b'] = net_data['b'].replace({'10^11': 1e11, '10^13': 1e13}) c_floats = net_data['Mean'].astype(float).to_numpy() fig = plt.figure(figsize=(10, 8)) ax = fig.add_subplot(111, projection='3d') x = net_data['x'].astype(float).to_numpy() y = net_data['y'].astype(float).to_numpy() z = net_data['b'].astype(float).to_numpy() point_size = 50 img = ax.scatter(x, y, z, c=c_floats, s=point_size, cmap='jet', vmin=c_floats.min(), vmax=c_floats.max() ) ax.set_xlabel('x') ax.set_ylabel('y') ax.set_zlabel('b') x_min, x_max = ax.get_xlim() y_min, y_max = ax.get_ylim() z_min, z_max = ax.get_zlim() # 绘制12条立方体棱线 ax.plot([x_min, x_max], [y_min, y_min], [z_min, z_min], color='black') ax.plot([x_min, x_max], [y_max, y_max], [z_min, z_min], color='black') ax.plot([x_min, x_max], [y_min, y_min], [z_max, z_max], color='black') ax.plot([x_min, x_max], [y_max, y_max], [z_max, z_max], color='black') ax.plot([x_min, x_min], [y_min, y_max], [z_min, z_min], color='black') ax.plot([x_max, x_max], [y_min, y_max], [z_min, z_min], color='black') ax.plot([x_min, x_min], [y_min, y_max], [z_max, z_max], color='black') ax.plot([x_max, x_max], [y_min, y_max], [z_max, z_max], color='black') ax.plot([x_min, x_min], [y_min, y_min], [z_min, z_max], color='black') ax.plot([x_max, x_max], [y_min, y_min], [z_min, z_max], color='black') ax.plot([x_min, x_min], [y_max, y_max], [z_min, z_max], color='black') ax.plot([x_max, x_max], [y_max, y_max], [z_min, z_max], color='black') offset = 0.01 for (i, txt) in enumerate(c_floats): ax.text(x[i] + offset, y[i] + offset, z[i] + offset, '{:6.4f}'.format(txt), size=6, zorder=1, color='k') elev = 45 azim = 45 ax.view_init(elev=elev, azim=azim) cbar = fig.colorbar(img, ax=ax, shrink=0.5) cbar.set_label('Mean') plot_save_path = 'plot.png' fig.savefig(plot_save_path, dpi=300, bbox_inches='tight')
生成的效果图:
解决方法
问题根源是Matplotlib的3D轴默认会给数据范围添加额外边距(padding),ax.get_xlim()获取的是添加边距后的轴范围,而非原始数据的最小/最大值。因此直接从原始数据数组中计算范围即可匹配散点的实际分布。
修改要点
- 直接从
x、y、z数组中提取最小/最大值,替代ax.get_xlim()等方法 - 可选:添加少量边距,让框架线条不紧贴散点,提升视觉效果
修改后的代码
import pandas as pd import matplotlib.pyplot as plt file_path = 'data.txt' col_widths = [12, 13, 18, 13, 13, 16, 16, 15, 16, 15] data = pd.read_fwf(file_path, widths=col_widths, header=0) columns_to_format = ['Sum', 'Mean', 'Std', 'Min', 'Max'] for col in columns_to_format: data[col] = data[col].apply(lambda x: '{:14.8e}'.format(float(x)) if pd.notnull(x) else x) net_data = data[data['Variable'] == '__net'].copy() net_data['b'] = net_data['b'].replace({'10^11': 1e11, '10^13': 1e13}) c_floats = net_data['Mean'].astype(float).to_numpy() fig = plt.figure(figsize=(10, 8)) ax = fig.add_subplot(111, projection='3d') x = net_data['x'].astype(float).to_numpy() y = net_data['y'].astype(float).to_numpy() z = net_data['b'].astype(float).to_numpy() point_size = 50 img = ax.scatter(x, y, z, c=c_floats, s=point_size, cmap='jet', vmin=c_floats.min(), vmax=c_floats.max() ) ax.set_xlabel('x') ax.set_ylabel('y') ax.set_zlabel('b') # 直接从数据中获取范围,可选添加5%的边距 padding_ratio = 0.05 x_min = x.min() - padding_ratio * (x.max() - x.min()) x_max = x.max() + padding_ratio * (x.max() - x.min()) y_min = y.min() - padding_ratio * (y.max() - y.min()) y_max = y.max() + padding_ratio * (y.max() - y.min()) z_min = z.min() - padding_ratio * (z.max() - z.min()) z_max = z.max() + padding_ratio * (z.max() - z.min()) # 绘制12条立方体棱线 ax.plot([x_min, x_max], [y_min, y_min], [z_min, z_min], color='black') ax.plot([x_min, x_max], [y_max, y_max], [z_min, z_min], color='black') ax.plot([x_min, x_max], [y_min, y_min], [z_max, z_max], color='black') ax.plot([x_min, x_max], [y_max, y_max], [z_max, z_max], color='black') ax.plot([x_min, x_min], [y_min, y_max], [z_min, z_min], color='black') ax.plot([x_max, x_max], [y_min, y_max], [z_min, z_min], color='black') ax.plot([x_min, x_min], [y_min, y_max], [z_max, z_max], color='black') ax.plot([x_max, x_max], [y_min, y_max], [z_max, z_max], color='black') ax.plot([x_min, x_min], [y_min, y_min], [z_min, z_max], color='black') ax.plot([x_max, x_max], [y_min, y_min], [z_min, z_max], color='black') ax.plot([x_min, x_min], [y_max, y_max], [z_min, z_max], color='black') ax.plot([x_max, x_max], [y_max, y_max], [z_min, z_max], color='black') offset = 0.01 for (i, txt) in enumerate(c_floats): ax.text(x[i] + offset, y[i] + offset, z[i] + offset, '{:6.4f}'.format(txt), size=6, zorder=1, color='k') elev = 45 azim = 45 ax.view_init(elev=elev, azim=azim) # 同步轴范围到计算出的范围(可选,确保轴显示范围和框架一致) ax.set_xlim(x_min, x_max) ax.set_ylim(y_min, y_max) ax.set_zlim(z_min, z_max) cbar = fig.colorbar(img, ax=ax, shrink=0.5) cbar.set_label('Mean') plot_save_path = 'plot.png' fig.savefig(plot_save_path, dpi=300, bbox_inches='tight')
说明
- 直接从数据数组获取min/max,确保框架线条完全匹配散点的分布范围
- 添加的
padding_ratio可以根据需要调整,若不需要边距,设为0即可 - 最后调用
ax.set_xlim()等方法同步轴显示范围,确保整个图表的轴范围和绘制的框架一致
内容的提问来源于stack exchange,提问作者len
相关产品推荐
相关产品推荐

