mpltweak
像在 PPT 里调多图排版一样调 matplotlib,然后把参数写回你的代码。
matplotlib 事件驱动 · 零依赖 · AST 确定性写回 · 三层验证
pip install mpltweak · English
它解决什么问题
科研绘图脚本里,最耗时的从来不是数据,是排版:子图挡住轴标签、间距不匀、 左边缘差 0.02、图例压住曲线、字号在论文里太小……而这一切只能靠 「改一个数字 → 重跑脚本 → 等几十秒 → 看 PNG → 再改」的循环。
mpltweak 把这段循环变成肉眼 + 鼠标:
改代码 → 重跑 → 看图 → 再改 ... ← 传统方式
──────────────────────────────────────────────
mpltweak fig1.py → 拖 → mpltweak apply --write ← 三步
它只管样式和位置,不发明内容 —— 文字、数据、图形仍完全由你的代码决定。
演示
拖动 + 边缘吸附 拖动时给出 ghost 预览与对齐参考线,靠近对齐位置自动贴合 |
多选对齐 / 均分 Ctrl 加选三个面板 → 一键左对齐 + 垂直均分,从"随手写的参数"变整齐一列 |
悬停改字号 鼠标悬停标题、轴标签、刻度或图例,按 + / - 直接调 |
重图切线框(空格) cartopy 全球图全量重绘 494ms → 169ms,排版时不再卡 |
工作流
flowchart TD
A["fig1.py<br/>纯 matplotlib,一行都不用改"]:::script
B(["交互窗口<br/>拖 · 对齐 · 吸附 · 调字号"]):::win
C[".tweak_params/fig1.json<br/>参数静默落盘,代码没动"]:::script
D["AST 定位 → 原位改写<br/>只改已有的那几个数字"]:::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
关窗之前,脚本一个字符都不会变;写回是你在第三步显式点头后的动作。
安装
pip install mpltweak
装上就能弹窗 —— 交互窗口后端(PyQt5)是默认依赖,不需要方括号选项:
| 装了什么 | 说明 |
|---|---|
| matplotlib | 绘图内核 |
| PyQt5 | 交互窗口后端 |
| CLI | mpltweak · mpltweak apply · mpltweak doctor |
装完先自检:mpltweak doctor(Python / matplotlib 版本、可用后端、字体)
纯服务器 / CI 上只想要写回功能,可以
pip install mpltweak --no-deps只装代码 (再单独pip install matplotlib),省掉 Qt 的体积; 装了别的 Qt 绑定(PySide6 / PyQt6)也能用,mpltweak doctor会告诉你它选了哪个。
脚本里写了
matplotlib.use('Agg')(科研脚本批量出图的常见写法)不需要改 —— 启动器会临时接管后端,savefig的结果与原来完全一致。
快速开始
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 看诊断;缺 Qt 就 pip install "mpltweak[qt]" |
| 想从头调 | 删掉 .tweak_params/fig1.json(不删就是接着上次续调) |
| 多图脚本 | 每张图都会弹窗,改哪张记哪张;mpltweak apply 逐张写回 |
| 脚本要读数据 / 跑很久 | --write --no-verify 跳过重跑验证,或 --timeout 600 放宽 |
| 拖动特别卡 | 按 空格 切线框模式:只画边框、坐标轴和文字,不渲染数据图元(cartopy 大图实测 494ms → 169ms),挪完再按一次空格恢复 |
| 手动改过代码后再开窗 | 会自动不套用上次参数(避免静默覆盖你的改动)并给出提示;确要接着上次调,加 --force-resume |
键位速查
| 操作 | 作用 |
|---|---|
| 拖面板 / 拖边框、角 | 移动 / 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 不是"盲改",每一步都可回滚:
- 能跑通 —— 改完用 Agg 无头重跑,退出码非 0 直接回滚;
- 改对了 —— 重跑后 dump 目标图状态,与参数里的位置 / 字号 / grid / spines / clim 逐项比对;"跑通了但布局没落到目标图"会被判失败;
- 换锚点重试 —— 图号对应的
savefig→ 主锚点 → 脚本尾,全部失败才回滚。
备份写在 .tweak_params/<脚本>.tweak.bak(不散落到代码目录);
慢脚本超时不会被误判为失败,只提示手动确认。
.tweak_params/*.json 规范(version 3)
参数文件是公开格式 —— 可以手工改,也可以让 AI / agent 直接生成,--write 一样能落实:
{
"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 规范(version 3)
├── 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.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| mpltweak-0.1.2.tar.gz | 636.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| mpltweak-0.1.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 1.2 MB
Release files / mpltweak-0.1.2.tar.gz
| Download URL | mpltweak-0.1.2.tar.gz |
|---|---|
| Size | 636.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1ca1c8729971f28224b47c281dacd77b47de0a746e2dd821c27889cfc45619bf
|
|
BLAKE2b-256 checksum How to use checksums |
9773bff29f7dbbf4366f8341ab5f61ba9451ce68c5e2a32ef23d87f0df7193ce
|
| 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.2-py3-none-any.whl
| Download URL | mpltweak-0.1.2-py3-none-any.whl |
|---|---|
| Size | 612.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0f545b8e1d6bc56dc576c44e93502afa047c2cac12f2da91fa5802eff47eb18d
|
|
BLAKE2b-256 checksum How to use checksums |
ab4478718ac69a2defa7059c116f453b3d5f06bd762be1497f90c435357894ce
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.10.19
|