Metadata-Version: 2.4
Name: mpltweak
Version: 0.1.0
Summary: 像在 PPT 里调多图排版一样调 matplotlib：拖拽排版 + AST 确定性写回脚本（调试零侵入）
Author: shdbl
License-Expression: MIT
Project-URL: Homepage, https://github.com/shdbl/mpltweak
Project-URL: Repository, https://github.com/shdbl/mpltweak
Project-URL: Issues, https://github.com/shdbl/mpltweak/issues
Keywords: matplotlib,figure,layout,plot,visualization,interactive,gui
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Science/Research
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Visualization
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: matplotlib>=3.5
Provides-Extra: qt
Requires-Dist: PyQt5>=5.15; extra == "qt"
Dynamic: license-file

<div align="center">

<img src="docs/banner.png" width="780" alt="mpltweak —— matplotlib layout, the PPT way">

# mpltweak

**像在 PPT 里调多图排版一样调 matplotlib。**

拖面板、多选对齐 / 均分、边缘吸附、悬停改字号、cartopy 重图切线框、一键裁白边。
**你调图的时候，脚本一个字都不动**；等你点头，它才把那几个数字**精确写回原文件**。

[![PyPI](https://img.shields.io/badge/pypi-mpltweak-blue)](https://pypi.org/project/mpltweak/)
[![License](https://img.shields.io/badge/license-MIT-green)](LICENSE)
[![Python](https://img.shields.io/badge/python-3.9%2B-blue)]()

`pip install mpltweak` · 只依赖 matplotlib（交互窗口另需 PyQt5 或 TkAgg）

</div>

---

## 它解决什么问题

科研绘图脚本里，最耗时的从来不是数据，是**排版**：子图挡住轴标签、间距不匀、
左边缘差 0.02、图例压住曲线、字号在论文里太小……而这一切只能靠
「改一个数字 → 重跑脚本 → 等几十秒 → 看 PNG → 再改」的循环。

mpltweak 把这段循环变成**肉眼 + 鼠标**：

```
  改代码 → 重跑 → 看图 → 再改 ...              ← 传统方式
  ──────────────────────────────────────
  mpltweak fig1.py  →  拖  →  mpltweak apply --write    ← 三步
```

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

---

## 30 秒看懂

| 步骤 | 命令 | 发生了什么 |
|---|---|---|
| ① 开窗调图 | `mpltweak fig1.py` | 脚本照常执行（写了 `matplotlib.use('Agg')` 也不用改），弹出交互窗口 |
| ② 拖、对齐、改字号 | 鼠标 + 几个键 | 只在内存里生效，**代码零改动** |
| ③ 关窗 | 关闭窗口 | 只写 `<脚本目录>/.tweak_params/fig1.json`，**代码依然没动** |
| ④ 预览 | `mpltweak apply fig1.py` | 打印改动清单（只读，不落盘） |
| ⑤ 写回 | `mpltweak apply fig1.py --write` | **原位改那几个数字** + 备份 + 重跑验证，失败自动回滚 |

第 ⑤ 步长这样（真实运行输出 + 真实 diff）：

<img src="docs/gifs/05_writeback.gif" width="720" alt="写回闭环：apply --write 的真实输出与代码 diff">

---

## 演示

<table>
<tr>
<td width="50%"><img src="docs/gifs/01_drag_layout.gif" alt="拖动面板 + 吸附参考线"><br>
<b>拖动 + 边缘吸附</b><br>拖动时显示 ghost 预览与对齐参考线，靠近对齐位置自动贴合</td>
<td width="50%"><img src="docs/gifs/02_multi_align.gif" alt="多选对齐 + 均分"><br>
<b>多选对齐 / 均分</b><br>Ctrl 加选三个面板 → 一键左对齐 + 垂直均分，从"随手写的参数"变整齐一列</td>
</tr>
<tr>
<td><img src="docs/gifs/03_fontsize.gif" alt="悬停改字号"><br>
<b>悬停改字号</b><br>鼠标悬停标题、轴标签、刻度或图例，按 <code>+</code> / <code>-</code> 直接调</td>
<td><img src="docs/gifs/04_wireframe.gif" alt="cartopy 线条模式"><br>
<b>重图切线框（空格）</b><br>cartopy 全球图全量重绘 <b>494ms → 169ms</b>，排版时不再卡</td>
</tr>
</table>

---

## 安装

```bash
pip install mpltweak            # 内核（可做写回 / 自检，无需窗口）
pip install "mpltweak[qt]"      # 带 PyQt5 交互窗口（Windows / Linux 推荐）
```

环境自检：

```bash
mpltweak doctor     # Python / matplotlib 版本、可用后端、字体等
```

> 脚本里写了 `matplotlib.use('Agg')`（科研脚本批量出图的常见写法）**不需要改**——
> 启动器会临时接管后端，`savefig` 的结果与原来完全一致。

---

## 键位速查

| 操作 | 作用 |
|---|---|
| 拖面板 / 拖边框、角 | 移动 / 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`） |

> ⚠️ **开窗前请切到英文输入法**：中文输入法打开时，`c`/`s`/`g`/`x`/`y` 等字母快捷键会被输入法吞掉。

---

## 为什么不一样

同赛道已经有做得不错的工具（例如 [Tavotto](https://www.tavotto.com/)，AGPL-3.0）。
差别不在"能不能拖"，在**改完的东西去哪**：

| | mpltweak | Tavotto |
|---|---|---|
| 形态 | pip 包 + CLI（`mpltweak` / `mpltweak apply`） | 桌面安装包（自带 Python）+ Codex 插件 |
| 排版手感 | **PPT 式**：多选、对齐、均分、吸附、裁白边、线条模式 | 图内对象编辑 + 毫米级拼版 |
| 改动去向 | **AST 确定性写回原脚本**（可 diff、可 review、有备份、有验证） | sidecar override，**脚本永不修改** |
| 参数格式 | **公开 JSON 规范（version 3）**，agent 可直接读写 | 私有 layout / override 文件 |
| 期刊预检 | 暂无 | **有**（栏宽、字号下限、DPI 的 profile 校验） |
| 矢量导出 | 由你的脚本自己 `savefig` | PDF / PNG / TIFF 同源导出 |
| 依赖 | matplotlib（窗口另需 PyQt5 / Tk） | 自带 pinned runtime |

**"零侵入"我们照写，但含义更严格**：调图全程不改代码、不加 `import`、不生成副文件——
参数静默落在 `.tweak_params/*.json`。**写回是你显式点头后的动作**，而且只改代码里
**原有的那些数字**（`add_axes([...])` 就改那 4 个数），不插一长串调整块。

---

## 可靠性：三层验证

`--write` 不是"盲改"，每一步都可回滚：

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

备份写在 `.tweak_params/<脚本>.tweak.bak`（不散落到代码目录），**绝不留下改坏的脚本**；
慢脚本超时不会被误判为失败（只提示手动确认）。

---

## `.tweak_params/*.json` 规范（version 3）

参数文件是**公开格式**——可以手工改，也可以让 AI / agent 直接生成，`--write` 一样能落实：

```jsonc
{
  "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 }
    }
  ]
}
```

---

## 局限（先看这里再决定用不用）

- **AST 写回不是万能的**：动态构造的布局（位置来自变量或循环计算）无法原位替换，
  会退化为块方式（`--style block`）或需要手动落实；
- **原位写回只改代码里已有的项**：参数里有 `grid=True` 但代码里没有 `grid(...)` 时，
  它会明确告诉你"保持原样"，不会擅自插入新语句；
- **没有期刊预检**（栏宽 / 字号下限 / DPI profile），也没有矢量导出——这两件事留给你的
  `savefig` 与投稿流程；
- **图例只吸 8 个标准位**，不支持自由坐标；
- 需要交互后端（Qt 或 Tk）；纯服务器环境只能跑 `apply` 与 `doctor`。

---

## 开发

```bash
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
