Skip to main content
mpltweak — matplotlib layout, the PPT way

mpltweak

像在 PPT 里调多图排版一样调 matplotlib,然后把参数写回你的代码。

PyPI Python License

中文 | English

它解决什么问题

多面板图的排版参数互相牵连:挪一下子图,旁边的轴标签就被挡住;字号放大一点, 图例又压到曲线上。而每改一次,都要重跑脚本、再看一遍图。

如果能像在 PPT 里摆放图片那样直接拖,就不必一遍遍改数字了 —— mpltweak 做的就是这件事。

方式 流程
传统方式 改一个数字 → 重跑脚本 → 等几十秒 → 看 PNG → 还是不对 → 再改
mpltweak 打开窗口 → 拖到位 → mpltweak apply --write → 那几个数字回到代码里

它只管样式和位置,不发明内容 —— 文字、数据、图形仍完全由你的代码决定。

演示

拖动面板 + 吸附参考线
拖动 + 边缘吸附
拖动时给出 ghost 预览与对齐参考线,靠近对齐位置自动贴合
多选对齐 + 均分
多选对齐 / 均分
Ctrl 加选三个面板 → 一键左对齐 + 垂直均分,从"随手写的参数"变整齐一列
悬停改字号
悬停改字号
鼠标悬停标题、轴标签、刻度或图例,按 + / - 直接调
cartopy 线条模式
重图切线框(空格)
cartopy 全球图全量重绘 494ms → 169ms,排版时不再卡

它到底改了什么

以 fig1.py 为例。动手前(手写的数字,通常都不齐):

ax1 = fig.add_axes([0.08, 0.66, 0.30, 0.21])
ax2 = fig.add_axes([0.11, 0.38, 0.30, 0.21])
ax3 = fig.add_axes([0.05, 0.08, 0.30, 0.21])

你拖完之后(mpltweak apply --write 的结果):

-ax1 = fig.add_axes([0.08, 0.66, 0.30, 0.21])
+ax1 = fig.add_axes([0.08, 0.655, 0.300, 0.215])
-ax2 = fig.add_axes([0.11, 0.38, 0.30, 0.21])
+ax2 = fig.add_axes([0.08, 0.380, 0.300, 0.215])
-ax3 = fig.add_axes([0.05, 0.08, 0.30, 0.21])
+ax3 = fig.add_axes([0.08, 0.085, 0.300, 0.215])

只动了这些数字:没有新增一行、没有 import、没有插入调整块、没有留下它的痕迹。 字号、图例位置、网格、colorbar 同理 —— 改的都是你代码里原本就有的参数。

安装

下面所有命令都在终端里执行 —— Windows 用 cmd / PowerShell,macOS / Linux 用 Terminal, 在 PyCharm 里则是底部的 Terminal 面板(不是 Python Console / 交互式解释器)。 调图前先 cd 到脚本所在目录。

pip install mpltweak

快速开始

1 · 你原来的脚本,一行都不用改

# fig1.py —— 就是平时写的 matplotlib
import matplotlib.pyplot as plt
import numpy as np

fig = plt.figure(figsize=(10, 8))
x = np.linspace(0, 1, 200)

ax1 = fig.add_axes([0.08, 0.66, 0.30, 0.21])      # 位置就是这几个数字
ax1.plot(x, np.sin(6 * x))
ax1.set_title('(a)')

ax2 = fig.add_axes([0.11, 0.38, 0.30, 0.21])      # 手写的数字,通常都不齐
ax2.scatter(np.random.rand(60), np.random.rand(60), s=8)

ax3 = fig.add_axes([0.05, 0.08, 0.30, 0.21])
ax3.hist(np.random.randn(300), bins=20)

fig.savefig('fig1.png', dpi=150)

不需要 import mpltweak,不需要任何钩子。

2 · 打开调图窗口

mpltweak fig1.py

窗口里:拖面板移动、拖边 / 角缩放、Ctrl 加选多个面板 → Ctrl+Shift+L 左对齐 / Ctrl+Shift+V 垂直均分、鼠标悬停文字按 + - 调字号、 空格切线框(大图提速)、Ctrl+F 裁掉白边。按 ? 随时打开键位帮助页。

调完直接关窗。 此时脚本一个字符都没变,参数落在 .tweak_params/fig1.json。

3 · 先看它打算改什么(只读)

mpltweak apply fig1.py

打印每个轴的位置、字号清单,不写任何文件。

4 · 写回

mpltweak apply fig1.py --write
✓ 已原位写回: fig1.py
  原位修改 4 处: ax0.pos, ax1.pos, ax2.pos, ax3.pos
  备份: .tweak_params/fig1.tweak.bak
  ✓ Agg 重跑验证通过
  ✓ 语义验证通过(目标图状态 == 参数)

它在你的脚本里只改 add_axes([...]) 里那几个数字;任何一步验证不过就自动回滚。

5 · 重跑看图

python fig1.py

常见情况

现象 处理
没弹窗 运行 mpltweak doctor 看诊断
想从头调 删掉 .tweak_params/fig1.json(不删就是接着上次续调)
多图脚本 每张图都会弹窗,改哪张记哪张;mpltweak apply 逐张写回
脚本要读数据 / 跑很久 --write --no-verify 跳过重跑验证,或 --timeout 600 放宽
拖动特别卡 按 空格 切线框模式:只画边框、坐标轴和文字,不渲染数据图元(cartopy 大图实测 494ms → 169ms),挪完再按一次空格恢复
手动改过代码后再开窗 会自动不套用上次参数(避免静默覆盖你的改动)并给出提示;确要接着上次调,加 --force-resume

工作流

flowchart TD
    A["fig1.py<br/>纯 matplotlib,一行都不用改"]:::script
    B(["交互窗口<br/>拖 · 对齐 · 吸附 · 调字号"]):::win
    C[".tweak_params/fig1.json<br/>参数静默落盘,代码没动"]:::script
    D["AST 定位 → 原位改写画图参数"]:::script
    E{"三层验证"}:::check
    F(["提交改动 + 留备份"]):::ok
    G(["自动回滚,绝不留下改坏的脚本"]):::bad

    A -->|mpltweak fig1.py| B
    B -->|关窗| C
    C -->|mpltweak apply --write| D
    D -->|自动比对| E
    E -->|通过| F
    E -->|失败| G

    classDef script fill:#F7F8FA,stroke:#C9D1DB,stroke-width:1.4px,color:#16181D
    classDef win fill:#2F6FEB,stroke:#2F6FEB,stroke-width:1.6px,color:#FFFFFF
    classDef check fill:#F5F8FF,stroke:#2F6FEB,stroke-width:1.6px,color:#16181D
    classDef ok fill:#EAF7EF,stroke:#2E9E5B,stroke-width:1.4px,color:#16181D
    classDef bad fill:#FEF3E2,stroke:#D97706,stroke-width:1.4px,color:#16181D

从打开窗口到 mpltweak apply --write,中间任何一步(包括关窗之后)脚本一个字符都不会变; 写回是你在第三步显式点头后的动作。

键位速查

操作 作用
? 打开 / 关闭键位帮助页(画布内浮层:Esc 关闭,L 切换中英文)
拖面板 / 拖边框、角 移动 / PPT 式缩放(对边固定;锁长宽比的轴自动等比)
Ctrl+点击(或 Shift+点击) 多选加选 / 减选;拖空白处橡皮筋框选
Ctrl+Shift+L/R/T/B/C/M 对齐:左 / 右 / 上 / 下 / 水平居中 / 垂直居中
Ctrl+Shift+H / V 均分:水平 / 垂直(两端不动,中间等间距)
Ctrl+Z / Ctrl+Y(或 Ctrl+Shift+Z) 撤销 / 重做
悬停文字后 + / - 标题、轴标签、刻度、图例、colorbar 的字号
空格 线条模式(只留边框与文字,隐藏数据图元)
Ctrl+F 适配画布到内容(裁掉四周白边,子图像素不变)
方向键 / Shift+方向键 移动面板(PPT 习惯)/ 微调尺寸(中心不动)
拖 colorbar 长轴端点 / 细条边 / 中点 调长度 / 调厚度 / 整条平移
拖图例 8 个标准位预览 + 松手吸附
[ ] · c · C · g · s · x y 线宽 / 线色 / colormap / 网格 / 边框 / 线性对数轴
n · e 吸附开关 / 手动导出
拖窗口边缘 改画布尺寸(写回 figsize)

三层验证

--write 不是"盲改",每一步都可回滚:

  1. 能跑通 —— 改完用 Agg 无头重跑,退出码非 0 直接回滚;
  2. 改对了 —— 重跑后 dump 目标图状态,与参数里的位置 / 字号 / grid / spines / clim 逐项比对;"跑通了但布局没落到目标图"会被判失败;
  3. 换锚点重试 —— 图号对应的 savefig → 主锚点 → 脚本尾,全部失败才回滚。

备份写在 .tweak_params/<脚本>.tweak.bak(不散落到代码目录); 慢脚本超时不会被误判为失败,只提示手动确认。

参数文件(.tweak_params/*.json)

参数文件是公开格式 —— 可以手工改,也可以让 AI / agent 直接生成,--write 一样能落实。 顶层 version 字段标记格式版本(当前为 3),工具据此判断跨版本兼容性:

{
  "version": 3,
  "script": "fig1.py",
  "figsize_px": [1500, 700],             // 画布逻辑像素(未 resize 为 null)
  "figsize_in": [15.0, 7.0],             // 写回 figsize 的英寸数
  "axes": [
    {
      "index": 0,                        // = fig.axes 顺序
      "pos": [0.048, 0.655, 0.30, 0.215],  // [x0, y0, w, h],figure 归一化坐标
      "aspect_locked": false,
      "title_fontsize": 9.0,
      "label_fontsize": 8.0,
      "tick_fontsize": 8.0,
      "grid": false,
      "spines": { "top": false, "right": false },
      "xscale": "linear",
      "yscale": "linear",
      "is_colorbar": false,
      "clim": [-2.0, 2.0],
      "cmap": "RdBu_r",
      "lines": [ { "index": 0, "linewidth": 1.2, "color": "#0F4D92" } ],
      "legend": { "loc": "upper right", "fontsize": 8.0 }
    }
  ]
}

开发

git clone https://github.com/shdbl/mpltweak && cd mpltweak
pip install -e .
python tests/test_tweak.py       # 交互内核
python tests/test_events.py      # 事件路径
python tests/test_redo.py        # 撤销 / 重做
python tests/test_writeback.py   # AST 写回 + 三层验证
python tools/crosscheck.py       # 环境 / API 兼容性自检
src/mpltweak/
├── cli.py          # mpltweak <脚本> | apply | doctor
├── launch.py       # 跑脚本 → 挂窗口 → 关窗存参数
├── toolbox.py      # 交互内核(Tweak 控制器,纯 matplotlib 事件)
├── params.py       # 参数 JSON 规范
├── apply.py        # 改动清单 / 写回入口
├── writeback.py    # AST 定位 + 原位 / 块写回
└── verify.py       # 语义验证(重跑后逐项比对)

toolbox.py 也可作嵌入 API(from mpltweak.toolbox import gaitu), 但主推 CLI —— 用户脚本里不该出现本工具的任何痕迹。

License

MIT

Metadata

Release files for mpltweak 0.1.3

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for mpltweak 0.1.3
File Size Uploaded
mpltweak-0.1.3.tar.gz 636.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for mpltweak 0.1.3
File Interpreter ABI Platform
mpltweak-0.1.3-py3-none-any.whl Python 3 none any Details

Total release size: 1.2 MB

Release files / mpltweak-0.1.3.tar.gz

Download URL mpltweak-0.1.3.tar.gz
Size 636.2 kB
Tags Source
SHA-256 checksum
How to use checksums
27ea7d84d691da3b915920601c8fa2b381ce936331b2757a339288d1d1962053
BLAKE2b-256 checksum
How to use checksums
d7dcb4c7dbcec92fc0043f13489deca4d7e20ea4b778b81d210e2624eeed2559
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.10.19

Release files / mpltweak-0.1.3-py3-none-any.whl

Download URL mpltweak-0.1.3-py3-none-any.whl
Size 612.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ec9e265c4ec32df555531e62f703be888f19fc4a0276d5fd03f6d08e03d99e70
BLAKE2b-256 checksum
How to use checksums
1d4f5d8f4948d66e6f05e49483a65d98709637e5f6e2fff024ba42c7116d5c41
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.10.19

Release history Release notifications | RSS feed

0.1.10

2 release files

0.1.9

2 release files

0.1.8

2 release files

0.1.7

2 release files

0.1.6

2 release files

0.1.5

2 release files

This release

0.1.3 This release

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page