hspace到底怎么调?,深度解析Matplotlib子图重叠问题与最佳实践

第一章:hspace到底怎么调?——Matplotlib子图布局核心概念

在使用 Matplotlib 绘制多个子图时,子图之间的间距控制是影响可视化效果的关键因素。`hspace` 参数专门用于调节子图之间的垂直间距,常与 `wspace`(水平间距)配合使用,确保图形布局清晰、不重叠。

理解 hspace 的作用机制

`hspace` 是 `plt.subplots_adjust()` 中的一个参数,表示子图之间高度方向上的留白比例,其值为相对于平均子图高度的倍数。设置过小会导致子图标签重叠,过大则浪费绘图空间。

调整 hspace 的基本方法

通过 `subplots_adjust` 可以手动优化布局。以下是一个典型示例:
# 创建包含多个子图的图像
import matplotlib.pyplot as plt

fig, axs = plt.subplots(2, 2, figsize=(8, 6))

# 调整垂直和水平间距
plt.subplots_adjust(hspace=0.4, wspace=0.3)  # hspace=0.4 表示留出40%的高度作为垂直间距

plt.show()

常见 hspace 设置建议

  • 默认值通常为 0.2,适用于简单标签
  • 若子图包含旋转的 x 轴标签或 colorbar,建议设为 0.4~0.6
  • 紧凑布局可尝试 0.1~0.3,但需预览防止重叠

自动布局替代方案

除了手动设置,可使用 `tight_layout` 自动优化:
plt.tight_layout(pad=1.5, h_pad=2.0, w_pad=2.0)
其中 `h_pad` 功能类似于 `hspace`,单位为英寸,更适合精确控制。
hspace 值适用场景
0.1–0.2紧凑布局,标签简短
0.3–0.5常规多子图报告图表
0.6+含复杂注释或色条的图形

第二章:深入理解hspace与subplot_adjust机制

2.1 hspace参数的定义与坐标系原理

hspace参数的基本定义
在图像布局与渲染中,hspace参数用于控制元素水平方向的空白间距。该参数常见于HTML图像标签或图形绘制库中,表示图像左右两侧预留的空白像素值。
<img src="example.png" hspace="10" vspace="5">
上述代码中,hspace="10" 表示图像左侧和右侧各保留10像素的空白区域,避免文本或其他元素紧贴图像边缘。
坐标系中的定位机制
在二维平面坐标系中,图像位置由左上角原点决定。hspace间接影响布局盒模型的外边距,其值被解析为水平方向的margin-leftmargin-right
参数方向等效CSS
hspace="10"水平margin: 0 10px
vspace="5"垂直margin: 5px 0

2.2 subplot_adjust函数的工作流程解析

`subplot_adjust` 是 Matplotlib 中用于精细控制子图布局的核心函数,它通过调整子图与画布边缘的距离,避免图像重叠并优化可视化效果。
参数详解与调用方式
该函数主要接受以下参数:
  • left:左侧边距(占画布宽度的比例)
  • right:右侧边距
  • topbottom:上、下边距
  • wspacehspace:子图间水平与垂直间距
plt.subplots_adjust(left=0.1, right=0.9, bottom=0.1, top=0.9, wspace=0.3, hspace=0.4)
上述代码将左右边距设为画布宽度的 10%,上下边距为 10%,子图间水平与垂直间距分别设为子图宽度和高度的 30% 和 40%。该配置适用于多行多列子图布局,确保标签与标题不重叠。
执行流程
函数内部按顺序计算每个子图的新位置,先应用全局边距,再分配子图间隔,最终重绘图形。

2.3 hspace与其他间距参数的协同关系

在布局系统中,hspace 并非孤立存在,它与 vspacemarginpadding 共同构成多维间距控制体系。合理搭配这些参数,可实现精准的视觉排版。
核心参数协同机制
  • hspace:控制水平方向的外部间距
  • vspace:定义垂直方向的间距,与 hspace 正交互补
  • margin/padding:处理元素内外边距,优先级高于 hspace
代码示例与逻辑分析

// 布局配置结构体
type Layout struct {
    HSpace int // 水平间距
    VSpace int // 垂直间距
    Margin int // 外边距
}
// 当 Margin > HSpace 时,Margin 主导最终渲染
上述代码表明,当 Margin 设置值大于 HSpace 时,系统将采用 Margin 作为实际渲染间距,体现参数间的覆盖优先级关系。

2.4 实际案例中hspace的默认值陷阱

在HTML早期版本中,`hspace` 属性常用于控制图像与周围内容的水平间距。然而,该属性在现代标准中已被废弃,且其默认值行为容易引发布局问题。
常见误用场景
当未显式设置 `hspace` 时,浏览器默认将其解析为0,但在某些旧版浏览器中可能继承父元素或表现出非预期空白,导致响应式布局错位。
<img src="logo.png" alt="Logo" hspace="10">
上述代码中,`hspace="10"` 等效于设置左右外边距各为10像素。但此写法不可控且不推荐。
现代替代方案
应使用CSS代替:
  • 通过 margin-leftmargin-right 精确控制间距
  • 利用 Flexbox 或 Grid 布局实现自适应排版
方法兼容性推荐程度
hspace 属性仅旧浏览器❌ 不推荐
CSS margin所有现代浏览器✅ 推荐

2.5 如何通过调试手段可视化hspace效果

在布局调试中,`hspace` 的视觉表现常因容器限制而不明显。通过插入临时背景色块,可直观观察其空间占用。
使用内联样式高亮区域
<div style="background: yellow; hspace="10">内容区块</div>
尽管 `hspace` 是旧式 HTML 属性(多用于 ``),现代浏览器对其支持有限。上述代码中,`hspace` 不产生实际效果,需改用 CSS 替代。
CSS 替代方案与调试技巧
  • 使用 margin-leftmargin-right 模拟水平间距
  • 在开发者工具中启用“盒模型高亮”实时查看间距分布
  • 通过 outline 区分 border 与 spacing 范围
图示: 盒模型中 margin/padding/border 层级示意

第三章:子图重叠问题的诊断与根源分析

3.1 常见重叠现象及其对应hspace表现

在多子图布局中,子图间的标签或坐标轴重叠是常见问题。Matplotlib通过hspace参数控制子图间的水平间距,合理设置可有效缓解重叠。
典型重叠场景
  • 多行子图共享x轴标签时,标签与上方子图内容重叠
  • 刻度标签过长导致相邻子图区域交叉
  • colorbar未预留空间挤占相邻子图位置
hspace参数对照表
hspace值视觉效果适用场景
0.1紧凑,易重叠小尺寸图表集合
0.4清晰分离含长标签的标准报告
plt.subplots_adjust(hspace=0.4)
该代码将子图垂直间距设为0.4倍子图高度,确保标签与上图间留有足够空白,适用于生成出版级图像。

3.2 字体大小、标签长度对hspace需求的影响

在可视化布局中,字体大小与标签长度直接影响元素间的水平间距(hspace)配置。较大的字体或更长的文本标签会占用更多空间,若 hspace 不随之调整,易导致文本重叠或截断。
影响因素分析
  • 字体大小:增大字体时需线性增加 hspace,以维持视觉平衡;
  • 标签长度:动态文本内容越长,所需预留宽度越大,hspace 应具备弹性适配机制。
代码示例:响应式 hspace 调整

// 根据字体大小和文本长度计算建议 hspace
function calculateHSpace(fontSize, textLength) {
  return fontSize * 0.8 + textLength * 0.5; // 经验系数调优
}
该函数通过经验公式综合字体与文本长度,动态输出合理 hspace 值。其中,fontSize 影响基础间距,textLength 按字符数线性扩展,确保布局清晰可读。

3.3 不同图形尺寸下hspace的敏感性测试

在多子图布局中,`hspace` 参数控制子图间的水平间距。其敏感性随图形整体尺寸变化而显著不同,需系统测试以确定最优配置。
测试方案设计
  • 固定画布高度为600像素,宽度从400递增至1200
  • 每组尺寸下测试 hspace 值从0.1到0.5(步长0.1)
  • 记录视觉拥挤度与空白区域占比
关键代码实现
plt.subplots_adjust(hspace=0.3, wspace=0.4)
# hspace: 子图间垂直间距相对值
# wspace: 水平间距,此处用于对比分析
该参数为归一化值,实际空白像素 = 图形总尺寸 × hspace / (1 + hspace),因此大尺寸图形对小 hspace 变化更敏感。
结果趋势
图形宽度最佳hspace说明
400px0.2避免重叠
800px0.3视觉平衡
1200px0.4利用富余空间

第四章:优化子图布局的最佳实践策略

4.1 动态调整hspace适配多子图场景

在绘制包含多个子图的复合图表时,子图间的水平间距(hspace)若设置不当,易导致标签重叠或空白浪费。通过动态计算子图数量与画布宽度的比例关系,可实现hspace的自适应调节。
自适应间距算法逻辑
  • 获取当前图形对象的子图布局行列数(nrows, ncols)
  • 根据画布宽度与子图宽度比值估算理想间距
  • 调用 plt.subplots_adjust(hspace=...) 实时修正纵向间距
import matplotlib.pyplot as plt

fig, axes = plt.subplots(2, 3, figsize=(10, 6))
fig.subplots_adjust(hspace=0.4)  # 动态赋值,避免标题重叠
上述代码中,hspace=0.4 表示子图间留出40%字体高度的垂直间距,数值越大,间隔越宽,适用于具有独立标题的多子图布局。

4.2 结合gridspec实现更精细的布局控制

在 Matplotlib 中,`GridSpec` 提供了比 `subplots` 更加灵活的子图布局管理方式,允许用户精确控制子图的位置和跨域范围。
GridSpec 基础用法
通过定义行数和列数,可以创建一个网格布局模板:
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec

fig = plt.figure(figsize=(8, 6))
gs = gridspec.GridSpec(3, 3)

ax1 = fig.add_subplot(gs[0, :])   # 第一行占据整行
ax2 = fig.add_subplot(gs[1, :-1]) # 第二行前两列
ax3 = fig.add_subplot(gs[1:, -1]) # 右下角两个单元格垂直合并
ax4 = fig.add_subplot(gs[-1, :-1])# 最后一行前两列
上述代码中,`gs[0, :]` 表示第一行所有列,`gs[1:, -1]` 实现最后一列从第二行开始的跨行合并,体现了对网格的灵活索引能力。
调整间距与响应式设计
可使用 wspacehspace 控制子图间的水平与垂直间距: gs.update(wspace=0.3, hspace=0.4) 这使得复杂布局在不同尺寸下仍保持清晰结构。

4.3 自动化布局工具与hspace的配合使用

在现代前端工程中,自动化布局工具如Webpack、Vite等能高效处理资源编排。通过配置插件,可动态注入`hspace`属性以控制元素间距。
配置示例

// vite.config.js
export default {
  plugins: [
    {
      name: 'add-hspace',
      transformIndexHtml(html) {
        return html.replace(/<img/g, '<img hspace="10"');
      }
    }
  ]
}
该插件在构建时自动为所有 `` 标签添加 `hspace="10"`,实现统一外边距。`transformIndexHtml` 钩子拦截HTML输出,正则替换确保轻量高效。
适用场景对比
工具是否支持hspace注入配置复杂度
Webpack是(via HTML Plugin)
Vite是(via transform hook)

4.4 发布级图表中hspace的标准化设置建议

在发布级图表渲染中,`hspace` 参数控制图像与周围元素的水平间距,其标准化设置直接影响文档排版的一致性与专业性。
推荐配置值
  • 印刷级输出:建议设置为 12pt,确保边距清晰可读
  • 屏幕展示模式:推荐 8pt,兼顾紧凑性与视觉舒适度
  • 自动化构建流程:应通过变量统一管理,避免硬编码
典型代码实现

\includegraphics[hspace=12pt, width=\textwidth]{chart.pdf}
该 LaTeX 配置确保图表在 PDF 发布版本中保持统一的水平边距。参数 `hspace=12pt` 明确定义了左右空白区域,配合 `width=\textwidth` 实现自适应布局,提升多平台兼容性。

第五章:从hspace出发,构建专业的可视化规范体系

在数据可视化实践中,图表的可读性不仅依赖于色彩与布局,更受制于元素间的间距控制。`hspace` 作为控制水平间距的关键参数,在 Matplotlib、Plotly 等库中广泛使用,是构建统一视觉规范的基础组件。
定义标准化间距层级
通过预设间距变量,确保所有图表在不同上下文中保持一致的呼吸感:

# spacing_config.py
SPACING_TINY = 0.1   # 子图间紧凑排列
SPACING_SMALL = 0.3  # 同组图表间距
SPACING_MEDIUM = 0.5 # 不同模块间间距
SPACING_LARGE = 0.8  # 报告级图表隔离
在子图布局中应用 hspace
使用 `plt.subplots_adjust(hspace=SPACING_MEDIUM)` 统一控制垂直间距,避免视觉拥挤。例如在监控仪表盘中,6 个时序图采用 `hspace=0.4`,使标题与坐标轴标签清晰分离。
响应式间距策略
根据输出场景动态调整:
  • 屏幕展示:hspace=0.3,提升信息密度
  • PPT嵌入:hspace=0.6,适配投影阅读
  • 打印报告:hspace=0.5,兼顾纸张利用率与可读性
团队协作中的规范落地
将间距配置纳入共享可视化包:
场景hspace值适用图表类型
实时看板0.25折线图、热力图
月度报告0.5柱状图、散点图
[配置中心] → 加载 spacing.yaml → 渲染引擎 → 输出图表 ↘ 审核规则校验 → 拒绝非法间距值
我正在编辑【python】代码,遇到了 【raise RuntimeError("Another Axes already grabs mouse input") RuntimeError: Another Axes already grabs mouse input Ignoring fixed y limits to fulfill fixed data aspect with adjustable data limits.】 ,请帮我检查并改正错误点。我的原始代码如下: 【# 主函数 def main(): # 创建光滑轨迹 md_smooth, tvd_smooth, north_smooth, east_smooth = create_smooth_trajectory(md, tvd, north, east) # 创建深度转换插值器 md_to_tvd, tvd_to_md = create_depth_interpolators(md_smooth, tvd_smooth) # 计算对应的垂深值 valid_md, multi_arm_tvd = calculate_corresponding_tvd(arm_md, md_to_tvd) arm_md0 = get_3d_coordinates_by_md(valid_md, md_smooth, east_smooth, north_smooth, tvd_smooth) arm1_3d = get_3d_coordinates_by_md(arm_md1, md_smooth, east_smooth, north_smooth, tvd_smooth) arm2_3d = get_3d_coordinates_by_md(arm_md2, md_smooth, east_smooth, north_smooth, tvd_smooth) print('arm_md0', arm_md0) diff_arm = arm1_3d - arm2_3d sq_diff = diff_arm ** 2 sum_sq = np.sum(sq_diff, axis=1) distances = np.sqrt(sum_sq) angle_x = np.divide(diff_arm[:,0], distances) angle_y = np.divide(diff_arm[:,1], distances) angle_z = np.divide(diff_arm[:,2], distances) print('angle', angle_x, '\n', angle_y, '\n',angle_z) # 建立坐标变换矩阵 trans_matrix = np.zeros((len(angle_x), 3, 3)) for i in range(len(angle_x)): trans_matrix[i, 0, 0] = - angle_y[i]/np.sqrt(angle_x[i] ** 2 + angle_y[i] ** 2) trans_matrix[i, 0, 1] = angle_x[i] * angle_z[i]/np.sqrt(angle_x[i] ** 2 + angle_y[i] ** 2) trans_matrix[i, 0, 2] = angle_x[i] trans_matrix[i, 1, 0] = angle_x[i]/np.sqrt(angle_x[i] ** 2 + angle_y[i] ** 2) trans_matrix[i, 1, 1] = (angle_z[i] * angle_y[i])/np.sqrt(angle_x[i] ** 2 + angle_y[i] ** 2) trans_matrix[i, 1, 2] = angle_y[i] trans_matrix[i, 2, 0] = 0 trans_matrix[i, 2, 1] = np.sqrt(angle_x[i] ** 2 + angle_y[i] ** 2) trans_matrix[i, 2, 2] = - angle_z[i] print(trans_matrix) # 各接触点的局部坐标计算 loc_positions_x = np.zeros((len(afilter), 40)) loc_positions_y = np.zeros((len(afilter), 40)) loc_positions_z = np.zeros((len(afilter), 40)) for i in range(len(afilter)): for j in range(40): loc_positions_x[i, j] = afilter[i, j] * np.cos(np.pi/20 * j) loc_positions_y[i, j] = afilter[i, j] * np.sin(np.pi/20 * j) # 组合为三维数组 sum_loc = np.stack((loc_positions_x, loc_positions_y, loc_positions_z), axis=0) print('111', sum_loc.shape) # 获取40臂各臂腿曲线采集点的实际坐标值 arms_positions = np.zeros((len(angle_x), 40, 3, 1)) for i in range(len(afilter)): for j in range(40): ddn = sum_loc[:, i , j].reshape(-1, 1) ddw = trans_matrix[i, :, :] ddm = arm_md0[i, :].reshape(-1, 1) arms_positions[i, j, :, :] = ddw @ ddn + ddm print('arms_positions', arms_positions) # 创建插值函数(根据垂深查询坐标) north_interp = PchipInterpolator(tvd_smooth, north_smooth) east_interp = PchipInterpolator(tvd_smooth, east_smooth) # 创建形和3D坐标轴 fig = plt.figure(figsize=(16, 14)) # 使用GridSpec创建更灵活的布局 gs = GridSpec(4, 3, height_ratios=[1, 0.1, 0.05, 0.08], width_ratios=[0.7, 0.15, 0.15]) # 3D主视 ax_main = fig.add_subplot(gs[0, 0], projection='3d') # 水平剖面 ax_profile = fig.add_subplot(gs[0, 1:]) ax_profile.set_aspect('equal', adjustable='datalim') # 滑块区域 ax_slider = fig.add_subplot(gs[1, 0]) # 坐标比例控制区域 ax_scale = fig.add_subplot(gs[1, 1:]) ax_scale.axis('off') # 按钮区域 ax_buttons = fig.add_subplot(gs[2, 0]) ax_buttons.axis('off') # 文本信息区域 ax_text = fig.add_subplot(gs[2, 1:]) ax_text.axis('off') # 底部信息区域 ax_bottom = fig.add_subplot(gs[3, :]) ax_bottom.axis('off') # 整布局 plt.subplots_adjust(left=0.05, right=0.95, bottom=0.05, top=0.95, wspace=0.15, hspace=0.25) # 绘制多臂井径 all_points, ax_main, n_depths = plot_40_arms_smooth(arms_positions, ax_main) # 绘制光滑轨迹 ax_main.plot(north_smooth, east_smooth, tvd_smooth, 'b-', linewidth=3, alpha=0.9, label='Smooth borehole trajectory') # 绘制原始数据点 ax_main.scatter(north, east, tvd, c='r', s=1, label='origin_point') # 收集所有点用于范围计算 all_points = np.vstack(all_points) if all_points else np.array([[0, 0, 0]]) all_north = np.concatenate([all_points[:, 0], north]) all_east = np.concatenate([all_points[:, 1], east]) all_tvd = np.concatenate([all_points[:, 2], tvd]) # 计算范围 max_range = np.array([ all_north.max() - all_north.min(), all_east.max() - all_east.min(), all_tvd.max() - all_tvd.min() ]).max() / 2.0 mid_x = (all_north.max() + all_north.min()) * 0.5 mid_y = (all_east.max() + all_east.min()) * 0.5 mid_z = (all_tvd.max() + all_tvd.min()) * 0.5 # 设置主视范围 ax_main.set_xlim(mid_x - max_range, mid_x + max_range) ax_main.set_ylim(mid_y - max_range, mid_y + max_range) ax_main.set_zlim(mid_z - max_range, mid_z + max_range) # 保存全局范围用于重置 global_limits = { 'xlim': ax_main.get_xlim(), 'ylim': ax_main.get_ylim(), 'zlim': ax_main.get_zlim() } # 设置坐标轴标签 ax_main.set_xlabel('North (m)', fontsize=10) ax_main.set_ylabel('East (m)', fontsize=10) ax_main.set_zlabel('Depth (m)', fontsize=10) ax_main.set_title('Combined Caliper & Trajectory Visualization', fontsize=12) ax_main.legend(loc='upper right', fontsize=9) ax_main.grid(True, alpha=0.3) # 设置视角 ax_main.view_init(elev=25, azim=45) # 计算每个深度的椭圆参数 ellipse_params = [] # 获取深度级别 depth_levels = arms_positions[:, 0, 2, 0] # 假设所有臂在相同深度 for depth_idx in range(n_depths): points_at_depth = arms_positions[depth_idx, :, :2, 0] # (n_arms, 2) # 计算中心点 center = np.mean(points_at_depth, axis=0) # 计算点集协方差矩阵 cov_matrix = np.cov(points_at_depth.T) # 计算特征值和特征向量 eigenvalues, eigenvectors = np.linalg.eig(cov_matrix) # 计算椭圆参数 angle = np.degrees(np.arctan2(*eigenvectors[:, 0][::-1])) width = 2 * np.sqrt(eigenvalues[0]) height = 2 * np.sqrt(eigenvalues[1]) # 计算椭圆度 (长轴/短轴) ellipse_ratio = max(width, height) / min(width, height) if min(width, height) > 0 else 1.0 ellipse_params.append({ 'center': center, 'width': width, 'height': height, 'angle': angle, 'ratio': ellipse_ratio, 'depth': depth_levels[depth_idx], 'points': points_at_depth }) # 按深度排序椭圆参数 ellipse_params.sort(key=lambda x: x['depth']) # 初始垂深值(取中间点) init_depth = np.median(tvd_smooth) # 计算初始坐标 init_east = east_interp(init_depth) init_north = north_interp(init_depth) # 在主视中绘制初始点 point = ax_main.scatter([init_north], [init_east], [init_depth], c='g', s=100, label='Current Depth') # 添加深度滑块 slider = Slider( ax=ax_slider, label='Depth (m)', valmin=tvd.min(), valmax=tvd.max(), valinit=init_depth, valstep=0.5, facecolor='lightblue' ) # 添加坐标比例输入框 scale_text_props = dict(fontsize=9, ha='right', va='center') input_props = dict(fontsize=9, bbox=dict(facecolor='white', alpha=0.7)) # X轴范围 ax_scale.text(0.05, 0.75, "X Range (min,max):", **scale_text_props) x_range_box = TextBox(fig.add_axes([0.25, 0.75, 0.2, 0.03]), "", initial=f"{mid_x - max_range:.2f}, {mid_x + max_range:.2f}") # Y轴范围 ax_scale.text(0.05, 0.5, "Y Range (min,max):", **scale_text_props) y_range_box = TextBox(fig.add_axes([0.25, 0.5, 0.2, 0.03]), "", initial=f"{mid_y - max_range:.2f}, {mid_y + max_range:.2f}") # Z轴范围 ax_scale.text(0.05, 0.25, "Z Range (min,max):", **scale_text_props) z_range_box = TextBox(fig.add_axes([0.25, 0.25, 0.2, 0.03]), "", initial=f"{mid_z - max_range:.2f}, {mid_z + max_range:.2f}") # 比例应用按钮 apply_ax = plt.axes([0.5, 0.25, 0.1, 0.03]) apply_button = Button(apply_ax, 'Apply Scale', color='lightgreen') apply_button.label.set_fontsize(9) # 比例重置按钮 reset_scale_ax = plt.axes([0.62, 0.25, 0.1, 0.03]) reset_scale_button = Button(reset_scale_ax, 'Reset Scale', color='lightcoral') reset_scale_button.label.set_fontsize(9) # 在按钮区域创建两个小按钮 reset_ax = plt.axes([0.15, 0.04, 0.15, 0.03]) # [左, 下, 宽, 高] rotate_ax = plt.axes([0.35, 0.04, 0.15, 0.03]) # 创建更小的按钮 reset_button = Button(reset_ax, 'Reset View', color='lightgoldenrodyellow') rotate_button = Button(rotate_ax, 'Rotate View', color='lightblue') # 设置按钮字体大小 for button in [reset_button, rotate_button]: button.label.set_fontsize(8) # 添加坐标显示文本 coord_text = ax_text.text( 0.5, 0.5, f'Depth: {init_depth:.2f}m | East: {init_east:.2f}m | North: {init_north:.2f}m', ha='center', va='center', fontsize=10 ) # 添加底部信息 ax_bottom.text(0.5, 0.8, "3D Borehole Visualization with Caliper Data", ha='center', va='center', fontsize=12, weight='bold') ax_bottom.text(0.5, 0.4, "Use sliders and buttons to explore the data | Adjust scales for detailed views", ha='center', va='center', fontsize=9, alpha=0.7) # 绘制初始水平剖面 def plot_depth_profile(depth): # 找到最接近的深度索引 closest_idx = np.argmin(np.abs(depth_levels - depth)) params = ellipse_params[closest_idx] # 清除之前的绘 ax_profile.clear() # 设置标题和标签 ax_profile.set_title(f'Horizontal Profile at {params["depth"]:.1f}m Depth', fontsize=10) ax_profile.set_xlabel('North (m)', fontsize=9) ax_profile.set_ylabel('East (m)', fontsize=9) ax_profile.grid(True, alpha=0.3) # 绘制所有臂点 ax_profile.scatter(params['points'][:, 0], params['points'][:, 1], c='b', s=30, alpha=0.7, label='Caliper Points') # 绘制中心点 ax_profile.scatter(params['center'][0], params['center'][1], c='r', s=80, marker='x', label='Center') # 绘制拟合椭圆 ellipse = Ellipse( xy=params['center'], width=params['width'], height=params['height'], angle=params['angle'], edgecolor='g', facecolor='none', linewidth=2, alpha=0.8, label=f'Ellipticity: {params["ratio"]:.2f}' ) ax_profile.add_patch(ellipse) # 添加椭圆度信息 ax_profile.text(0.05, 0.95, f'Ellipticity: {params["ratio"]:.2f}\n' f'Major Axis: {max(params["width"], params["height"]):.2f}m\n' f'Minor Axis: {min(params["width"], params["height"]):.2f}m', transform=ax_profile.transAxes, fontsize=9, verticalalignment='top', bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.5)) # 添加例 ax_profile.legend(loc='lower right', fontsize=8) # 设置相同的比例 ax_profile.set_aspect('equal', adjustable='datalim') # 设置坐标轴范围(包含所有点) margin = 0.1 # 10%的边距 x_min, x_max = np.min(params['points'][:, 0]), np.max(params['points'][:, 0]) y_min, y_max = np.min(params['points'][:, 1]), np.max(params['points'][:, 1]) x_range = x_max - x_min y_range = y_max - y_min max_range = max(x_range, y_range) * (1 + margin) / 2 mid_x = (x_min + x_max) / 2 mid_y = (y_min + y_max) / 2 ax_profile.set_xlim(mid_x - max_range, mid_x + max_range) ax_profile.set_ylim(mid_y - max_range, mid_y + max_range) # 初始绘制 plot_depth_profile(init_depth) # 更新函数 def update(val): depth = slider.val # 计算新坐标 new_east = east_interp(depth) new_north = north_interp(depth) # 更新主视点位置 point._offsets3d = ([new_north], [new_east], [depth]) # 更新水平剖面 plot_depth_profile(depth) # 更新文本 coord_text.set_text(f'Depth: {depth:.2f}m | East: {new_east:.2f}m | North: {new_north:.2f}m') # 重绘形 fig.canvas.draw_idle() slider.on_changed(update) # 重置视函数 def reset_view(event): ax_main.set_xlim(global_limits['xlim'][0], global_limits['xlim'][1]) ax_main.set_ylim(global_limits['ylim'][0], global_limits['ylim'][1]) ax_main.set_zlim(global_limits['zlim'][0], global_limits['zlim'][1]) ax_main.view_init(elev=25, azim=45) # 更新输入框值 x_range_box.set_val(f"{global_limits['xlim'][0]:.2f}, {global_limits['xlim'][1]:.2f}") y_range_box.set_val(f"{global_limits['ylim'][0]:.2f}, {global_limits['ylim'][1]:.2f}") z_range_box.set_val(f"{global_limits['zlim'][0]:.2f}, {global_limits['zlim'][1]:.2f}") fig.canvas.draw_idle() reset_button.on_clicked(reset_view) # 旋转视函数 def rotate_view(event): current_azim = ax_main.azim ax_main.view_init(elev=5, azim=current_azim + 20) fig.canvas.draw_idle() rotate_button.on_clicked(rotate_view) # 应用比例函数 def apply_scale(event): try: # 解析X范围 x_min, x_max = map(float, x_range_box.text.split(',')) ax_main.set_xlim(x_min, x_max) # 解析Y范围 y_min, y_max = map(float, y_range_box.text.split(',')) ax_main.set_ylim(y_min, y_max) # 解析Z范围 z_min, z_max = map(float, z_range_box.text.split(',')) ax_main.set_zlim(z_min, z_max) fig.canvas.draw_idle() except Exception as e: print(f"Invalid scale input: {e}") apply_button.on_clicked(apply_scale) # 重置比例函数 def reset_scale(event): ax_main.set_xlim(global_limits['xlim'][0], global_limits['xlim'][1]) ax_main.set_ylim(global_limits['ylim'][0], global_limits['ylim'][1]) ax_main.set_zlim(global_limits['zlim'][0], global_limits['zlim'][1]) # 更新输入框值 x_range_box.set_val(f"{global_limits['xlim'][0]:.2f}, {global_limits['xlim'][1]:.2f}") y_range_box.set_val(f"{global_limits['ylim'][0]:.2f}, {global_limits['ylim'][1]:.2f}") z_range_box.set_val(f"{global_limits['zlim'][0]:.2f}, {global_limits['zlim'][1]:.2f}") fig.canvas.draw_idle() reset_scale_button.on_clicked(reset_scale) plt.show()】
06-14
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值