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

mpltweak

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

PyPI Python License Tests

中文 | English

它解决什么问题

调一张多面板图(一张画布里并排放好几张小图)时,麻烦通常出在排版而不是数据:挪一下子图,旁边的轴标签就被挡住;字号放大一点,图例又压到曲线上 —— 而每改一次,都要重跑脚本、再看一遍图。

mpltweak 的做法:像在 PPT 里摆放图片那样直接拖,拖完把那几个数字写回源码。它只管样式和位置,不发明内容 —— 文字、数据、图形仍完全由你的代码决定。

零侵入:不改你的代码、不加 import、不生成副文件 —— 拖出来的参数单独存在 .tweak_params/fig1.json;只有你明确要求写回,.py 才会变。

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

演示:

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

两条路,挑一条

mpltweak 自带一份给 AI 助手(Agent)看的手册(Skill):把这份手册交给能读写你电脑文件的 Agent,它会替你把命令全部敲完。所以有两条路可选——用的是同一个工具、同一份 Skill,区别只在"谁敲命令":

  • 路线 A:交给 Agent(不熟悉命令行就选这条):你只负责看图、说话。← 下面先讲这条
  • 路线 B:自己敲命令:几条命令,十分钟能跑通。→ 跳到文末

路线 A:交给 Agent

你不需要懂命令行 —— 把这件事交给 Agent 就行。 (要能直接读写你电脑上文件的那种 Agent;只在网页聊天窗口里聊天的不行。)

把下面这句话直接复制给你正在用的 Agent(Claude Code / Codex / Cursor / DeepSeek Harness 等,只要它能读写你电脑上的文件):

请读一下 https://github.com/shdbl/mpltweak/blob/main/skills/mpltweak/SKILL.md ,然后按里面的说明把 mpltweak 装好;装好之后先跑一下示例给我看效果。

先花两分钟看看效果(让 Agent 跑示例)

不想拿自己的脚本冒险?跟 Agent 说一句:

跑一下 mpltweak demo 给我看看

它会装好依赖、生成一份示例脚本(四面板合成图,纯 matplotlib 写法)并打开调图窗口,你随便拖、随便改字号;关掉窗口后它会用人话告诉你你改了哪几处、并把代码会变成什么样逐行给你看。

觉得不错,就让它把改动写回那份示例脚本、重跑一遍,并给你看改前 / 改后两张图。

全程只在当前目录下新建一个 mpltweak-demo/ 文件夹,不会碰你自己写的任何脚本。

日常使用中跟 Agent 协作的流程

  1. 让 Agent 帮你画图。(如果你已经有画图脚本、只想改排版,可以直接跳到第 3 步,并告诉它脚本名,比如 fig1.py。) 你会看到:Agent 把画好的图贴给你看。
  2. 先确认图的内容对不对(这一步别急着调排版)。坐标轴、标签、数据是不是你要的?不对就现在说,让 Agent 改。 你会看到:内容定稿的版本 —— 后面要调的只是"排版",不是"画什么"。
  3. 跟 Agent 说一句"我要交互式修改"。 它会自己把调图窗口弹出来(你不用装东西、也不用打任何命令)。 你会看到:一个窗口弹出来,里面就是你的图,可以用鼠标拖、缩放。
  4. 你在窗口里改。 拖面板、拖图例、调字号、对齐、均分(在窗口里按 ?,随时调出全部键位说明)。 你会看到:拖动时是半透明预览,松手才落位。
  5. Agent 用人话告诉你它改了什么,并等你确认。 比如"子图 (a) 往右挪了一点、标题字号从 12 调到 14"。 这一步代码还没动 —— 你不同意就继续说,它接着改。 你会看到:一份"改了什么"的人话清单 + 一句"要不要写进代码?"
  6. 你说"可以",Agent 才把改动写回代码,然后重跑给你看。 重跑之后对着第 2 步的内容再看一眼,确认没被改坏。 你会看到:重跑出来的新图 + 它改了哪几处;不满意可以接着调,或者让它退回上一次。

全程你不需要打开命令行、不需要记任何命令 —— 你只需要看图 + 说话。

flowchart LR
    A["① 你对 Agent 说:我要什么图"]:::script --> B["② Agent 写脚本 + 跑出图"]:::win
    B --> C{"③ 你看图:内容对不对?"}:::check
    C -->|不对| B
    C -->|对| D["④ 你说:要交互式修改<br/>Agent 弹出调图窗口"]:::win
    D --> E["你在窗口里拖着改<br/>(按 ? 看键位)"]:::win
    E --> F{"⑤ Agent 用人话说明改了哪些地方"}:::check
    F -->|不满意| E
    F -->|满意| G["⑥ Agent 写回代码 + 重跑"]:::win
    G --> Z(["你看新图,完事"]):::ok

    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

窗口里的键位(按 ? 随时调出全部):拖动 / 多选、对齐 / 均分、吸附、Ctrl+F 裁白边、空格线框提速、悬停文字后按 + / - 调字号、图例 8 个标准位、colorbar 调长 / 调厚、叠在一起时 Alt+点击 穿透、Tab 循环同级元素。完整版见 docs/interaction.md。

它会怎么改你的代码?三种结果

它对每份脚本的处理结果,事先就能说清,只有三种:

情况 它会怎么做 结果
源码里明写着这个数字
(fig.add_axes([0.08, 0.66, 0.30, 0.21]))
原位改那一个数,不插任何东西 代码里那一行变了,别处一个字不动
源码里没有能改的数字
(plt.subplots() / GridSpec 画的图)
在文件末尾追加一小段自包含代码,不加 import、不动你原有逻辑 代码末尾多出一个带 auto layout 标记的块
它证明不了该改哪一个
(fig.axes[i] / plt.gca() / 循环里建轴 —— 运行时才知道指向谁)
不写。它不会猜一个"大概是这张"的对象 代码一个字节都不变;并给出一条能让 Agent 照着动手的定位说明(改哪个符号、改成什么)

1. 能原位改的:直接改那一行

改前:

fig = plt.figure(figsize=(10, 8))
ax1 = fig.add_axes([0.08, 0.66, 0.30, 0.21])
ax1.set_title('(a)', fontsize=12)

改后:

fig = plt.figure(figsize=(10, 8))
ax1 = fig.add_axes([0.06, 0.68, 0.32, 0.23])   # ← 就改这几个数字
ax1.set_title('(a)', fontsize=15)              # ← 字号补在同一行末尾

没有新增任何行,别处一个字不动。

2. 不能原位改的:文件末尾追加一小段

改前(subplots 画的图,源码里没有能直接改的数字):

fig, axes = plt.subplots(1, 2, figsize=(10, 4))
axes[0].plot(np.arange(5), np.arange(5))
axes[0].set_title('(a)')
axes[1].plot(np.arange(5), np.arange(5) * 2)
axes[1].set_title('(b)')
fig.savefig('fig1.png', dpi=150)

改后(下面整段是真跑输出的原文,只有时间戳随生成时刻改变):

fig, axes = plt.subplots(1, 2, figsize=(10, 4))
axes[0].plot(np.arange(5), np.arange(5))
axes[0].set_title('(a)')
axes[1].plot(np.arange(5), np.arange(5) * 2)
axes[1].set_title('(b)')
# ===== auto layout (generated) - do not edit inside =====
# 本块由 mpltweak 自动生成于 <时间>(可手动微调,勿删上下两行标记)
# 轴身份(调图时):ax0=网格[1, 2, 0, 0],ax1=网格[1, 2, 0, 1]
axes[0].set_position([0.0800, 0.1500, 0.3800, 0.7500])
axes[1].set_position([0.5477, 0.1100, 0.3523, 0.7700])
axes[0].lines[0].set_linewidth(1.5)
axes[0].lines[0].set_color('#1f77b4')
axes[1].lines[0].set_linewidth(1.5)
axes[1].lines[0].set_color('#1f77b4')
# ===== end auto layout =====
fig.savefig('fig1.png', dpi=150)

你原来写的代码一行都没被改写;不满意就整段删掉,或一条命令撤回(见下节)。

3. 证明不了的:不写,告诉你怎么办

改前(轴是循环建的,运行时才知道 fig.axes[i] 指的是哪一根):

fig = plt.figure(figsize=(8, 3))
for i, name in enumerate(['a', 'b', 'c']):
    fig.add_subplot(1, 3, i + 1).plot(np.arange(5), np.arange(5) * (i + 1))
    fig.axes[i].set_title(name)
fig.savefig('fig2.png', dpi=150)

它给出的是(真跑输出的原文,节选最后一行):

· 保持原样: 块内只能按**静态可证的变量名**定位目标对象;这些字段证不出目标
  (没有可证的变量名,而运行期下标 fig.axes[i] / plt.gcf() 会写到别的轴或别的图上)
  → 按 fail-closed 处理,本次不写:ax0.lines、ax0.pos、ax1.lines、ax1.pos、ax2.lines、ax2.pos

退出码 1。

代码一个字节都没变。 这不是死路:那段话直接告诉你改哪一处就能改。如果是 Agent 在替你操作,它看到这句话会自己把脚本改好、再回来接着调图 —— 你不用复制、不用转达。

它可靠吗?

四道保险,从松到紧:

  1. 只要你不要求写回,你的 .py 就保持原样。 开窗、拖动、关窗,全程只往 .tweak_params/ 里写参数,代码不动。
  2. 写回前可以先看。 让它先把"要动哪几行、从什么改成什么"逐行给你看——只看不改。
  3. 真写回会自动备份,一条命令就能退回写回前。
  4. 证明不了的写法一律不写,并说明原因 —— 宁可这次不生效,也不给你的代码里塞一个指错对象的东西。

每次真写回还会自动过三道检查:语法(改完的代码还能不能正常解析)→ 重跑(脚本能不能真的再跑一遍、图还能不能出)→ 语义(图上的位置、字号是不是跟你拖的一致)。任何一道不过,它会自动退回写回前,你拿到的仍是原来那份脚本。

拿 10,158 份真实科研绘图脚本、1,780 份真跑、13,000 次随机操作试过:"说了改好、其实没改" 这类事 0 次。

(静态差分那一批语料扩到 16,126 份 .py = 10,158 份第三方公开绘图脚本 + 5,968 份本项目自身脚手架;谈"真实用户脚本"时分母只用前者,两者不混着说。)

完整的样本分母、判据与逐族结果见 docs/reliability.md;不适用场景与不声称项见 docs/boundaries.md。

路线 B:自己敲命令

安装

pip install mpltweak            # 核心:describe / check / apply
pip install "mpltweak[qt]"      # 要开窗拖图再加这个(装 PyQt5 图形后端)
mpltweak --version      # 版本

退出码(脚本里判断用):0 成功 / 1 检查发现问题 或本次不写(含 fail-closed 拒写)/ 2 跑不起来(脚本找不到、脚本自身报错、参数非法)/ 4 没有可用图形后端。四个入口口径一致。

[qt] 提供 PyQt5 后端;只跑 describe / check / apply 不需要。没有 Qt 时回退 Tk(交互能力受限,开窗会提示);一个后端都没有时开窗失败并说明原因。

后端对照表、各平台装法、mpltweak doctor 探测顺序与开窗失败逐条处理 —— docs/interaction.md。 哪些命令会执行你的脚本、退出码怎么算 —— docs/exec-boundaries.md。

示例

mpltweak demo            # 生成 ./mpltweak-demo/ 并开窗;不写盘
mpltweak demo --write    # 写回示例脚本 + 重跑 + 生成 before.png / after.png

关窗后输出改动清单与逐行 diff;不带 --write 时示例脚本一个字节不变。目录已存在则复用,不覆盖。

六步

cd 你的脚本目录

fig1.py(零侵入:不需要 import mpltweak,不改动任何现有代码):

# 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)
步 命令 你会看到
1 · 装 pip install mpltweak
pip install "mpltweak[qt]"
核心包 / 加 PyQt5 后端(后者才需要开窗)
2 · 开窗拖 mpltweak fig1.py 调图窗口:拖面板改位置、拖边 / 角缩放、鼠标悬停文字按 + - 调字号、空格切线框提速、Ctrl+F 裁白边;按 ? 出全部键位
3 · 关窗 —— 脚本一个字符都没变;参数落在 .tweak_params/fig1.json(没调好就再开窗接着调)。真拖过东西的话,关窗时终端会给你下一条可复制的命令
4 · 预览 mpltweak apply fig1.py
mpltweak apply fig1.py --diff
前者只读列出每轴参数与「原位改 / 插块 / 拒写」判定;后者给逐行 diff({line,type,old,new}),type ∈ inplace / insert_block / replace。两者都不执行脚本、不写文件;--diff 的 preview_sha256 与真实写盘文件 sha256 一致(跨分钟时看 preview_sha256_norm)。要机器可读就加 --json
5 · 写回 mpltweak apply fig1.py --write 原子写(临时文件 + rename)→ 备份到 .tweak_params/fig1.tweak.bak → 子进程重跑验证 → 语义比对。成功只报一行;幂等(同参数重复写回不产生新差异);撤回:mpltweak revert fig1.py --write(先不带 --write 是预览)
6 · 重跑看图 python fig1.py 图就是你拖完的样子

写回是确定性的,不靠猜。 它不是文本层正则替换(那样别处的 0.08 会被误伤),而是把脚本解析成 AST(语法树),定位到"哪一句在建图、哪一个 Constant 是位置",再顺着变量名绑定到目标对象;写回是 AST 确定性重写 + 原文保真(未命中的行逐字节不变)。整个过程没有 LLM 参与:同一输入、同一参数,输出恒定。

写回分两条通道:源码里是字面量 ⇒ 原位改常量(不插任何节点);否则在 savefig/show 前插入一段自包含的 auto layout 块。两条通道都要过三道验证(语法可解析 → 子进程重跑 → 语义比对),任一不过 默认回滚;写回原子落地(临时文件 + rename),备份在 .tweak_params/fig1.tweak.bak,幂等(同一参数重复写回不产生新差异)。--keep-unverified 可保留未验证结果。

想看机器可读结果:--json 给 files[].diff(逐行 {line,type,old,new})、preview_sha256(与真实写盘文件 sha256 一致)、stage(卡在哪一步)、next_step、attribution。契约见 docs/agent.md。

卡住了见 docs/troubleshooting.md(拖了没反应 / 拖了弹回去 / 报错怎么办,每条都是「现象 → 一句话原因 → 怎么做」)。 完整输出样张、逐处清单 -v、--style inplace / --style block、参数字段表 —— docs/verify.md 与 docs/params.md。

License

MIT


要看完整版: 键位与手势 docs/interaction.md · 参数文件字段表 docs/params.md · 三道验证与备份 / revert docs/verify.md · 卡住了 docs/troubleshooting.md · 实测数字 docs/reliability.md · 边界 docs/boundaries.md · 执行边界与退出码 docs/exec-boundaries.md · 给 Agent 的手册 docs/agent.md 与 skills/mpltweak/SKILL.md · 开发与跑测试 docs/dev.md

Metadata

Release files for mpltweak 0.1.16

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.16
File Size Uploaded Provenance
mpltweak-0.1.16.tar.gz 1.5 MB With provenance Details

Built distribution (wheel)

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

Total release size: 2.5 MB

Release files / mpltweak-0.1.16.tar.gz

Download URL mpltweak-0.1.16.tar.gz
Size 1.5 MB
Tags Source
SHA-256 checksum
How to use checksums
5c947788eee5550a4d7b002b59ae15c0ab6fad6fc97910b85130bb4e31709304
BLAKE2b-256 checksum
How to use checksums
b9f2b0f0fc0dad745926b7b82fb4d281841b15a65696a217e3f4cc6185e5f176
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.

Transparency log

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

Download URL mpltweak-0.1.16-py3-none-any.whl
Size 949.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
368115c24814fb2f18604bb1a39bef316bb84c36f966661bcb9a580877fb5fd9
BLAKE2b-256 checksum
How to use checksums
f4b76dc8b5f38dbd1b8b16c2619624620661bd13e7749938658842a28cd28106
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.

Transparency log

Security for mpltweak 0.1.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for mpltweak-0.1.16.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 24, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

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