mpltweak 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mpltweak-0.1.0/LICENSE +21 -0
- mpltweak-0.1.0/PKG-INFO +253 -0
- mpltweak-0.1.0/README.md +226 -0
- mpltweak-0.1.0/pyproject.toml +56 -0
- mpltweak-0.1.0/setup.cfg +4 -0
- mpltweak-0.1.0/src/mpltweak/__init__.py +33 -0
- mpltweak-0.1.0/src/mpltweak/apply.py +291 -0
- mpltweak-0.1.0/src/mpltweak/cli.py +55 -0
- mpltweak-0.1.0/src/mpltweak/launch.py +541 -0
- mpltweak-0.1.0/src/mpltweak/params.py +174 -0
- mpltweak-0.1.0/src/mpltweak/resources/help_en.png +0 -0
- mpltweak-0.1.0/src/mpltweak/resources/help_zh.png +0 -0
- mpltweak-0.1.0/src/mpltweak/toolbox.py +3474 -0
- mpltweak-0.1.0/src/mpltweak/verify.py +247 -0
- mpltweak-0.1.0/src/mpltweak/writeback.py +1283 -0
- mpltweak-0.1.0/src/mpltweak.egg-info/PKG-INFO +253 -0
- mpltweak-0.1.0/src/mpltweak.egg-info/SOURCES.txt +23 -0
- mpltweak-0.1.0/src/mpltweak.egg-info/dependency_links.txt +1 -0
- mpltweak-0.1.0/src/mpltweak.egg-info/entry_points.txt +2 -0
- mpltweak-0.1.0/src/mpltweak.egg-info/requires.txt +4 -0
- mpltweak-0.1.0/src/mpltweak.egg-info/top_level.txt +1 -0
- mpltweak-0.1.0/tests/test_events.py +351 -0
- mpltweak-0.1.0/tests/test_redo.py +74 -0
- mpltweak-0.1.0/tests/test_tweak.py +508 -0
- mpltweak-0.1.0/tests/test_writeback.py +712 -0
mpltweak-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 shdbl
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
mpltweak-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mpltweak
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: 像在 PPT 里调多图排版一样调 matplotlib:拖拽排版 + AST 确定性写回脚本(调试零侵入)
|
|
5
|
+
Author: shdbl
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/shdbl/mpltweak
|
|
8
|
+
Project-URL: Repository, https://github.com/shdbl/mpltweak
|
|
9
|
+
Project-URL: Issues, https://github.com/shdbl/mpltweak/issues
|
|
10
|
+
Keywords: matplotlib,figure,layout,plot,visualization,interactive,gui
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
20
|
+
Requires-Python: >=3.9
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: matplotlib>=3.5
|
|
24
|
+
Provides-Extra: qt
|
|
25
|
+
Requires-Dist: PyQt5>=5.15; extra == "qt"
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
<div align="center">
|
|
29
|
+
|
|
30
|
+
<img src="docs/banner.png" width="780" alt="mpltweak —— matplotlib layout, the PPT way">
|
|
31
|
+
|
|
32
|
+
# mpltweak
|
|
33
|
+
|
|
34
|
+
**像在 PPT 里调多图排版一样调 matplotlib。**
|
|
35
|
+
|
|
36
|
+
拖面板、多选对齐 / 均分、边缘吸附、悬停改字号、cartopy 重图切线框、一键裁白边。
|
|
37
|
+
**你调图的时候,脚本一个字都不动**;等你点头,它才把那几个数字**精确写回原文件**。
|
|
38
|
+
|
|
39
|
+
[](https://pypi.org/project/mpltweak/)
|
|
40
|
+
[](LICENSE)
|
|
41
|
+
[]()
|
|
42
|
+
|
|
43
|
+
`pip install mpltweak` · 只依赖 matplotlib(交互窗口另需 PyQt5 或 TkAgg)
|
|
44
|
+
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 它解决什么问题
|
|
50
|
+
|
|
51
|
+
科研绘图脚本里,最耗时的从来不是数据,是**排版**:子图挡住轴标签、间距不匀、
|
|
52
|
+
左边缘差 0.02、图例压住曲线、字号在论文里太小……而这一切只能靠
|
|
53
|
+
「改一个数字 → 重跑脚本 → 等几十秒 → 看 PNG → 再改」的循环。
|
|
54
|
+
|
|
55
|
+
mpltweak 把这段循环变成**肉眼 + 鼠标**:
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
改代码 → 重跑 → 看图 → 再改 ... ← 传统方式
|
|
59
|
+
──────────────────────────────────────
|
|
60
|
+
mpltweak fig1.py → 拖 → mpltweak apply --write ← 三步
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**它只管样式和位置,不发明内容**——文字、数据、图形仍完全由你的代码决定。
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## 30 秒看懂
|
|
68
|
+
|
|
69
|
+
| 步骤 | 命令 | 发生了什么 |
|
|
70
|
+
|---|---|---|
|
|
71
|
+
| ① 开窗调图 | `mpltweak fig1.py` | 脚本照常执行(写了 `matplotlib.use('Agg')` 也不用改),弹出交互窗口 |
|
|
72
|
+
| ② 拖、对齐、改字号 | 鼠标 + 几个键 | 只在内存里生效,**代码零改动** |
|
|
73
|
+
| ③ 关窗 | 关闭窗口 | 只写 `<脚本目录>/.tweak_params/fig1.json`,**代码依然没动** |
|
|
74
|
+
| ④ 预览 | `mpltweak apply fig1.py` | 打印改动清单(只读,不落盘) |
|
|
75
|
+
| ⑤ 写回 | `mpltweak apply fig1.py --write` | **原位改那几个数字** + 备份 + 重跑验证,失败自动回滚 |
|
|
76
|
+
|
|
77
|
+
第 ⑤ 步长这样(真实运行输出 + 真实 diff):
|
|
78
|
+
|
|
79
|
+
<img src="docs/gifs/05_writeback.gif" width="720" alt="写回闭环:apply --write 的真实输出与代码 diff">
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## 演示
|
|
84
|
+
|
|
85
|
+
<table>
|
|
86
|
+
<tr>
|
|
87
|
+
<td width="50%"><img src="docs/gifs/01_drag_layout.gif" alt="拖动面板 + 吸附参考线"><br>
|
|
88
|
+
<b>拖动 + 边缘吸附</b><br>拖动时显示 ghost 预览与对齐参考线,靠近对齐位置自动贴合</td>
|
|
89
|
+
<td width="50%"><img src="docs/gifs/02_multi_align.gif" alt="多选对齐 + 均分"><br>
|
|
90
|
+
<b>多选对齐 / 均分</b><br>Ctrl 加选三个面板 → 一键左对齐 + 垂直均分,从"随手写的参数"变整齐一列</td>
|
|
91
|
+
</tr>
|
|
92
|
+
<tr>
|
|
93
|
+
<td><img src="docs/gifs/03_fontsize.gif" alt="悬停改字号"><br>
|
|
94
|
+
<b>悬停改字号</b><br>鼠标悬停标题、轴标签、刻度或图例,按 <code>+</code> / <code>-</code> 直接调</td>
|
|
95
|
+
<td><img src="docs/gifs/04_wireframe.gif" alt="cartopy 线条模式"><br>
|
|
96
|
+
<b>重图切线框(空格)</b><br>cartopy 全球图全量重绘 <b>494ms → 169ms</b>,排版时不再卡</td>
|
|
97
|
+
</tr>
|
|
98
|
+
</table>
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## 安装
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
pip install mpltweak # 内核(可做写回 / 自检,无需窗口)
|
|
106
|
+
pip install "mpltweak[qt]" # 带 PyQt5 交互窗口(Windows / Linux 推荐)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
环境自检:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
mpltweak doctor # Python / matplotlib 版本、可用后端、字体等
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
> 脚本里写了 `matplotlib.use('Agg')`(科研脚本批量出图的常见写法)**不需要改**——
|
|
116
|
+
> 启动器会临时接管后端,`savefig` 的结果与原来完全一致。
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## 键位速查
|
|
121
|
+
|
|
122
|
+
| 操作 | 作用 |
|
|
123
|
+
|---|---|
|
|
124
|
+
| 拖面板 / 拖边框、角 | 移动 / PPT 式缩放(对边固定;锁长宽比的轴自动等比) |
|
|
125
|
+
| **Ctrl+点击**(或 Shift+点击) | 多选加选 / 减选;拖空白处橡皮筋框选 |
|
|
126
|
+
| **Ctrl+Shift+L/R/T/B/C/M** | 对齐:左 / 右 / 上 / 下 / 水平居中 / 垂直居中 |
|
|
127
|
+
| **Ctrl+Shift+H / V** | 均分:水平 / 垂直(两端不动,中间等间距) |
|
|
128
|
+
| **Ctrl+Z** / **Ctrl+Y**(或 Ctrl+Shift+Z) | 撤销 / 重做 |
|
|
129
|
+
| 悬停文字后 `+` / `-` | 标题、轴标签、刻度、图例、colorbar 的字号 |
|
|
130
|
+
| **空格** | 线条模式(只留边框与文字,隐藏数据图元)——重图排版提速 |
|
|
131
|
+
| **Ctrl+F** | 适配画布到内容(裁掉四周白边,子图像素不变) |
|
|
132
|
+
| **方向键** / Shift+方向键 | 移动面板(PPT 习惯)/ 微调尺寸(中心不动) |
|
|
133
|
+
| 拖 colorbar 长轴端点 / 细条边 / 中点 | 调长度 / 调厚度 / 整条平移 |
|
|
134
|
+
| 拖图例 | 8 个标准位预览 + 松手吸附 |
|
|
135
|
+
| `[` `]` · `c` · `C` · `g` · `s` · `x` `y` | 线宽 / 线色 / colormap / 网格 / 边框 / 线性对数轴 |
|
|
136
|
+
| `n` · `e` · `?` | 吸附开关 / 手动导出 / 帮助 |
|
|
137
|
+
| 拖窗口边缘 | 改画布尺寸(写回 `figsize`) |
|
|
138
|
+
|
|
139
|
+
> ⚠️ **开窗前请切到英文输入法**:中文输入法打开时,`c`/`s`/`g`/`x`/`y` 等字母快捷键会被输入法吞掉。
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## 为什么不一样
|
|
144
|
+
|
|
145
|
+
同赛道已经有做得不错的工具(例如 [Tavotto](https://www.tavotto.com/),AGPL-3.0)。
|
|
146
|
+
差别不在"能不能拖",在**改完的东西去哪**:
|
|
147
|
+
|
|
148
|
+
| | mpltweak | Tavotto |
|
|
149
|
+
|---|---|---|
|
|
150
|
+
| 形态 | pip 包 + CLI(`mpltweak` / `mpltweak apply`) | 桌面安装包(自带 Python)+ Codex 插件 |
|
|
151
|
+
| 排版手感 | **PPT 式**:多选、对齐、均分、吸附、裁白边、线条模式 | 图内对象编辑 + 毫米级拼版 |
|
|
152
|
+
| 改动去向 | **AST 确定性写回原脚本**(可 diff、可 review、有备份、有验证) | sidecar override,**脚本永不修改** |
|
|
153
|
+
| 参数格式 | **公开 JSON 规范(version 3)**,agent 可直接读写 | 私有 layout / override 文件 |
|
|
154
|
+
| 期刊预检 | 暂无 | **有**(栏宽、字号下限、DPI 的 profile 校验) |
|
|
155
|
+
| 矢量导出 | 由你的脚本自己 `savefig` | PDF / PNG / TIFF 同源导出 |
|
|
156
|
+
| 依赖 | matplotlib(窗口另需 PyQt5 / Tk) | 自带 pinned runtime |
|
|
157
|
+
|
|
158
|
+
**"零侵入"我们照写,但含义更严格**:调图全程不改代码、不加 `import`、不生成副文件——
|
|
159
|
+
参数静默落在 `.tweak_params/*.json`。**写回是你显式点头后的动作**,而且只改代码里
|
|
160
|
+
**原有的那些数字**(`add_axes([...])` 就改那 4 个数),不插一长串调整块。
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## 可靠性:三层验证
|
|
165
|
+
|
|
166
|
+
`--write` 不是"盲改",每一步都可回滚:
|
|
167
|
+
|
|
168
|
+
1. **能跑通** —— 改完用 Agg 无头重跑,退出码非 0 直接回滚;
|
|
169
|
+
2. **改对了** —— 重跑后 dump 目标图状态,与参数里的位置 / 字号 / grid / spines / clim
|
|
170
|
+
逐项比对;"跑通了但布局没落到目标图"会被判失败;
|
|
171
|
+
3. **换锚点重试** —— 图号对应的 `savefig` → 主锚点 → 脚本尾,全部失败才回滚。
|
|
172
|
+
|
|
173
|
+
备份写在 `.tweak_params/<脚本>.tweak.bak`(不散落到代码目录),**绝不留下改坏的脚本**;
|
|
174
|
+
慢脚本超时不会被误判为失败(只提示手动确认)。
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## `.tweak_params/*.json` 规范(version 3)
|
|
179
|
+
|
|
180
|
+
参数文件是**公开格式**——可以手工改,也可以让 AI / agent 直接生成,`--write` 一样能落实:
|
|
181
|
+
|
|
182
|
+
```jsonc
|
|
183
|
+
{
|
|
184
|
+
"version": 3,
|
|
185
|
+
"script": "fig1.py",
|
|
186
|
+
"figsize_px": [1500, 700], // 画布逻辑像素(未 resize 为 null)
|
|
187
|
+
"figsize_in": [15.0, 7.0], // 写回 figsize 的英寸数
|
|
188
|
+
"axes": [
|
|
189
|
+
{
|
|
190
|
+
"index": 0, // = fig.axes 顺序
|
|
191
|
+
"pos": [0.048, 0.655, 0.30, 0.215], // [x0, y0, w, h],figure 归一化坐标
|
|
192
|
+
"aspect_locked": false,
|
|
193
|
+
"title_fontsize": 9.0,
|
|
194
|
+
"label_fontsize": 8.0,
|
|
195
|
+
"tick_fontsize": 8.0,
|
|
196
|
+
"grid": false,
|
|
197
|
+
"spines": { "top": false, "right": false },
|
|
198
|
+
"xscale": "linear",
|
|
199
|
+
"yscale": "linear",
|
|
200
|
+
"is_colorbar": false,
|
|
201
|
+
"clim": [-2.0, 2.0],
|
|
202
|
+
"cmap": "RdBu_r",
|
|
203
|
+
"lines": [ { "index": 0, "linewidth": 1.2, "color": "#0F4D92" } ],
|
|
204
|
+
"legend": { "loc": "upper right", "fontsize": 8.0 }
|
|
205
|
+
}
|
|
206
|
+
]
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## 局限(先看这里再决定用不用)
|
|
213
|
+
|
|
214
|
+
- **AST 写回不是万能的**:动态构造的布局(位置来自变量或循环计算)无法原位替换,
|
|
215
|
+
会退化为块方式(`--style block`)或需要手动落实;
|
|
216
|
+
- **原位写回只改代码里已有的项**:参数里有 `grid=True` 但代码里没有 `grid(...)` 时,
|
|
217
|
+
它会明确告诉你"保持原样",不会擅自插入新语句;
|
|
218
|
+
- **没有期刊预检**(栏宽 / 字号下限 / DPI profile),也没有矢量导出——这两件事留给你的
|
|
219
|
+
`savefig` 与投稿流程;
|
|
220
|
+
- **图例只吸 8 个标准位**,不支持自由坐标;
|
|
221
|
+
- 需要交互后端(Qt 或 Tk);纯服务器环境只能跑 `apply` 与 `doctor`。
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## 开发
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
git clone https://github.com/shdbl/mpltweak && cd mpltweak
|
|
229
|
+
pip install -e .
|
|
230
|
+
python tests/test_tweak.py # 交互内核
|
|
231
|
+
python tests/test_events.py # 事件路径
|
|
232
|
+
python tests/test_redo.py # 撤销 / 重做
|
|
233
|
+
python tests/test_writeback.py # AST 写回 + 三层验证
|
|
234
|
+
python tools/crosscheck.py # 环境 / API 兼容性自检
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
```
|
|
238
|
+
src/mpltweak/
|
|
239
|
+
├── cli.py # mpltweak <脚本> | apply | doctor
|
|
240
|
+
├── launch.py # 跑脚本 → 挂窗口 → 关窗存参数
|
|
241
|
+
├── toolbox.py # 交互内核(Tweak 控制器,纯 matplotlib 事件)
|
|
242
|
+
├── params.py # 参数 JSON 规范(version 3)
|
|
243
|
+
├── apply.py # 改动清单 / 写回入口
|
|
244
|
+
├── writeback.py # AST 定位 + 原位 / 块写回
|
|
245
|
+
└── verify.py # 语义验证(重跑后逐项比对)
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
`toolbox.py` 也可作**嵌入 API**(`from mpltweak.toolbox import gaitu`),
|
|
249
|
+
但主推 CLI——用户脚本里不该出现本工具的任何痕迹。
|
|
250
|
+
|
|
251
|
+
## License
|
|
252
|
+
|
|
253
|
+
MIT
|
mpltweak-0.1.0/README.md
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<img src="docs/banner.png" width="780" alt="mpltweak —— matplotlib layout, the PPT way">
|
|
4
|
+
|
|
5
|
+
# mpltweak
|
|
6
|
+
|
|
7
|
+
**像在 PPT 里调多图排版一样调 matplotlib。**
|
|
8
|
+
|
|
9
|
+
拖面板、多选对齐 / 均分、边缘吸附、悬停改字号、cartopy 重图切线框、一键裁白边。
|
|
10
|
+
**你调图的时候,脚本一个字都不动**;等你点头,它才把那几个数字**精确写回原文件**。
|
|
11
|
+
|
|
12
|
+
[](https://pypi.org/project/mpltweak/)
|
|
13
|
+
[](LICENSE)
|
|
14
|
+
[]()
|
|
15
|
+
|
|
16
|
+
`pip install mpltweak` · 只依赖 matplotlib(交互窗口另需 PyQt5 或 TkAgg)
|
|
17
|
+
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 它解决什么问题
|
|
23
|
+
|
|
24
|
+
科研绘图脚本里,最耗时的从来不是数据,是**排版**:子图挡住轴标签、间距不匀、
|
|
25
|
+
左边缘差 0.02、图例压住曲线、字号在论文里太小……而这一切只能靠
|
|
26
|
+
「改一个数字 → 重跑脚本 → 等几十秒 → 看 PNG → 再改」的循环。
|
|
27
|
+
|
|
28
|
+
mpltweak 把这段循环变成**肉眼 + 鼠标**:
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
改代码 → 重跑 → 看图 → 再改 ... ← 传统方式
|
|
32
|
+
──────────────────────────────────────
|
|
33
|
+
mpltweak fig1.py → 拖 → mpltweak apply --write ← 三步
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**它只管样式和位置,不发明内容**——文字、数据、图形仍完全由你的代码决定。
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## 30 秒看懂
|
|
41
|
+
|
|
42
|
+
| 步骤 | 命令 | 发生了什么 |
|
|
43
|
+
|---|---|---|
|
|
44
|
+
| ① 开窗调图 | `mpltweak fig1.py` | 脚本照常执行(写了 `matplotlib.use('Agg')` 也不用改),弹出交互窗口 |
|
|
45
|
+
| ② 拖、对齐、改字号 | 鼠标 + 几个键 | 只在内存里生效,**代码零改动** |
|
|
46
|
+
| ③ 关窗 | 关闭窗口 | 只写 `<脚本目录>/.tweak_params/fig1.json`,**代码依然没动** |
|
|
47
|
+
| ④ 预览 | `mpltweak apply fig1.py` | 打印改动清单(只读,不落盘) |
|
|
48
|
+
| ⑤ 写回 | `mpltweak apply fig1.py --write` | **原位改那几个数字** + 备份 + 重跑验证,失败自动回滚 |
|
|
49
|
+
|
|
50
|
+
第 ⑤ 步长这样(真实运行输出 + 真实 diff):
|
|
51
|
+
|
|
52
|
+
<img src="docs/gifs/05_writeback.gif" width="720" alt="写回闭环:apply --write 的真实输出与代码 diff">
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## 演示
|
|
57
|
+
|
|
58
|
+
<table>
|
|
59
|
+
<tr>
|
|
60
|
+
<td width="50%"><img src="docs/gifs/01_drag_layout.gif" alt="拖动面板 + 吸附参考线"><br>
|
|
61
|
+
<b>拖动 + 边缘吸附</b><br>拖动时显示 ghost 预览与对齐参考线,靠近对齐位置自动贴合</td>
|
|
62
|
+
<td width="50%"><img src="docs/gifs/02_multi_align.gif" alt="多选对齐 + 均分"><br>
|
|
63
|
+
<b>多选对齐 / 均分</b><br>Ctrl 加选三个面板 → 一键左对齐 + 垂直均分,从"随手写的参数"变整齐一列</td>
|
|
64
|
+
</tr>
|
|
65
|
+
<tr>
|
|
66
|
+
<td><img src="docs/gifs/03_fontsize.gif" alt="悬停改字号"><br>
|
|
67
|
+
<b>悬停改字号</b><br>鼠标悬停标题、轴标签、刻度或图例,按 <code>+</code> / <code>-</code> 直接调</td>
|
|
68
|
+
<td><img src="docs/gifs/04_wireframe.gif" alt="cartopy 线条模式"><br>
|
|
69
|
+
<b>重图切线框(空格)</b><br>cartopy 全球图全量重绘 <b>494ms → 169ms</b>,排版时不再卡</td>
|
|
70
|
+
</tr>
|
|
71
|
+
</table>
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## 安装
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
pip install mpltweak # 内核(可做写回 / 自检,无需窗口)
|
|
79
|
+
pip install "mpltweak[qt]" # 带 PyQt5 交互窗口(Windows / Linux 推荐)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
环境自检:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
mpltweak doctor # Python / matplotlib 版本、可用后端、字体等
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
> 脚本里写了 `matplotlib.use('Agg')`(科研脚本批量出图的常见写法)**不需要改**——
|
|
89
|
+
> 启动器会临时接管后端,`savefig` 的结果与原来完全一致。
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## 键位速查
|
|
94
|
+
|
|
95
|
+
| 操作 | 作用 |
|
|
96
|
+
|---|---|
|
|
97
|
+
| 拖面板 / 拖边框、角 | 移动 / PPT 式缩放(对边固定;锁长宽比的轴自动等比) |
|
|
98
|
+
| **Ctrl+点击**(或 Shift+点击) | 多选加选 / 减选;拖空白处橡皮筋框选 |
|
|
99
|
+
| **Ctrl+Shift+L/R/T/B/C/M** | 对齐:左 / 右 / 上 / 下 / 水平居中 / 垂直居中 |
|
|
100
|
+
| **Ctrl+Shift+H / V** | 均分:水平 / 垂直(两端不动,中间等间距) |
|
|
101
|
+
| **Ctrl+Z** / **Ctrl+Y**(或 Ctrl+Shift+Z) | 撤销 / 重做 |
|
|
102
|
+
| 悬停文字后 `+` / `-` | 标题、轴标签、刻度、图例、colorbar 的字号 |
|
|
103
|
+
| **空格** | 线条模式(只留边框与文字,隐藏数据图元)——重图排版提速 |
|
|
104
|
+
| **Ctrl+F** | 适配画布到内容(裁掉四周白边,子图像素不变) |
|
|
105
|
+
| **方向键** / Shift+方向键 | 移动面板(PPT 习惯)/ 微调尺寸(中心不动) |
|
|
106
|
+
| 拖 colorbar 长轴端点 / 细条边 / 中点 | 调长度 / 调厚度 / 整条平移 |
|
|
107
|
+
| 拖图例 | 8 个标准位预览 + 松手吸附 |
|
|
108
|
+
| `[` `]` · `c` · `C` · `g` · `s` · `x` `y` | 线宽 / 线色 / colormap / 网格 / 边框 / 线性对数轴 |
|
|
109
|
+
| `n` · `e` · `?` | 吸附开关 / 手动导出 / 帮助 |
|
|
110
|
+
| 拖窗口边缘 | 改画布尺寸(写回 `figsize`) |
|
|
111
|
+
|
|
112
|
+
> ⚠️ **开窗前请切到英文输入法**:中文输入法打开时,`c`/`s`/`g`/`x`/`y` 等字母快捷键会被输入法吞掉。
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## 为什么不一样
|
|
117
|
+
|
|
118
|
+
同赛道已经有做得不错的工具(例如 [Tavotto](https://www.tavotto.com/),AGPL-3.0)。
|
|
119
|
+
差别不在"能不能拖",在**改完的东西去哪**:
|
|
120
|
+
|
|
121
|
+
| | mpltweak | Tavotto |
|
|
122
|
+
|---|---|---|
|
|
123
|
+
| 形态 | pip 包 + CLI(`mpltweak` / `mpltweak apply`) | 桌面安装包(自带 Python)+ Codex 插件 |
|
|
124
|
+
| 排版手感 | **PPT 式**:多选、对齐、均分、吸附、裁白边、线条模式 | 图内对象编辑 + 毫米级拼版 |
|
|
125
|
+
| 改动去向 | **AST 确定性写回原脚本**(可 diff、可 review、有备份、有验证) | sidecar override,**脚本永不修改** |
|
|
126
|
+
| 参数格式 | **公开 JSON 规范(version 3)**,agent 可直接读写 | 私有 layout / override 文件 |
|
|
127
|
+
| 期刊预检 | 暂无 | **有**(栏宽、字号下限、DPI 的 profile 校验) |
|
|
128
|
+
| 矢量导出 | 由你的脚本自己 `savefig` | PDF / PNG / TIFF 同源导出 |
|
|
129
|
+
| 依赖 | matplotlib(窗口另需 PyQt5 / Tk) | 自带 pinned runtime |
|
|
130
|
+
|
|
131
|
+
**"零侵入"我们照写,但含义更严格**:调图全程不改代码、不加 `import`、不生成副文件——
|
|
132
|
+
参数静默落在 `.tweak_params/*.json`。**写回是你显式点头后的动作**,而且只改代码里
|
|
133
|
+
**原有的那些数字**(`add_axes([...])` 就改那 4 个数),不插一长串调整块。
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## 可靠性:三层验证
|
|
138
|
+
|
|
139
|
+
`--write` 不是"盲改",每一步都可回滚:
|
|
140
|
+
|
|
141
|
+
1. **能跑通** —— 改完用 Agg 无头重跑,退出码非 0 直接回滚;
|
|
142
|
+
2. **改对了** —— 重跑后 dump 目标图状态,与参数里的位置 / 字号 / grid / spines / clim
|
|
143
|
+
逐项比对;"跑通了但布局没落到目标图"会被判失败;
|
|
144
|
+
3. **换锚点重试** —— 图号对应的 `savefig` → 主锚点 → 脚本尾,全部失败才回滚。
|
|
145
|
+
|
|
146
|
+
备份写在 `.tweak_params/<脚本>.tweak.bak`(不散落到代码目录),**绝不留下改坏的脚本**;
|
|
147
|
+
慢脚本超时不会被误判为失败(只提示手动确认)。
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## `.tweak_params/*.json` 规范(version 3)
|
|
152
|
+
|
|
153
|
+
参数文件是**公开格式**——可以手工改,也可以让 AI / agent 直接生成,`--write` 一样能落实:
|
|
154
|
+
|
|
155
|
+
```jsonc
|
|
156
|
+
{
|
|
157
|
+
"version": 3,
|
|
158
|
+
"script": "fig1.py",
|
|
159
|
+
"figsize_px": [1500, 700], // 画布逻辑像素(未 resize 为 null)
|
|
160
|
+
"figsize_in": [15.0, 7.0], // 写回 figsize 的英寸数
|
|
161
|
+
"axes": [
|
|
162
|
+
{
|
|
163
|
+
"index": 0, // = fig.axes 顺序
|
|
164
|
+
"pos": [0.048, 0.655, 0.30, 0.215], // [x0, y0, w, h],figure 归一化坐标
|
|
165
|
+
"aspect_locked": false,
|
|
166
|
+
"title_fontsize": 9.0,
|
|
167
|
+
"label_fontsize": 8.0,
|
|
168
|
+
"tick_fontsize": 8.0,
|
|
169
|
+
"grid": false,
|
|
170
|
+
"spines": { "top": false, "right": false },
|
|
171
|
+
"xscale": "linear",
|
|
172
|
+
"yscale": "linear",
|
|
173
|
+
"is_colorbar": false,
|
|
174
|
+
"clim": [-2.0, 2.0],
|
|
175
|
+
"cmap": "RdBu_r",
|
|
176
|
+
"lines": [ { "index": 0, "linewidth": 1.2, "color": "#0F4D92" } ],
|
|
177
|
+
"legend": { "loc": "upper right", "fontsize": 8.0 }
|
|
178
|
+
}
|
|
179
|
+
]
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## 局限(先看这里再决定用不用)
|
|
186
|
+
|
|
187
|
+
- **AST 写回不是万能的**:动态构造的布局(位置来自变量或循环计算)无法原位替换,
|
|
188
|
+
会退化为块方式(`--style block`)或需要手动落实;
|
|
189
|
+
- **原位写回只改代码里已有的项**:参数里有 `grid=True` 但代码里没有 `grid(...)` 时,
|
|
190
|
+
它会明确告诉你"保持原样",不会擅自插入新语句;
|
|
191
|
+
- **没有期刊预检**(栏宽 / 字号下限 / DPI profile),也没有矢量导出——这两件事留给你的
|
|
192
|
+
`savefig` 与投稿流程;
|
|
193
|
+
- **图例只吸 8 个标准位**,不支持自由坐标;
|
|
194
|
+
- 需要交互后端(Qt 或 Tk);纯服务器环境只能跑 `apply` 与 `doctor`。
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## 开发
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
git clone https://github.com/shdbl/mpltweak && cd mpltweak
|
|
202
|
+
pip install -e .
|
|
203
|
+
python tests/test_tweak.py # 交互内核
|
|
204
|
+
python tests/test_events.py # 事件路径
|
|
205
|
+
python tests/test_redo.py # 撤销 / 重做
|
|
206
|
+
python tests/test_writeback.py # AST 写回 + 三层验证
|
|
207
|
+
python tools/crosscheck.py # 环境 / API 兼容性自检
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
```
|
|
211
|
+
src/mpltweak/
|
|
212
|
+
├── cli.py # mpltweak <脚本> | apply | doctor
|
|
213
|
+
├── launch.py # 跑脚本 → 挂窗口 → 关窗存参数
|
|
214
|
+
├── toolbox.py # 交互内核(Tweak 控制器,纯 matplotlib 事件)
|
|
215
|
+
├── params.py # 参数 JSON 规范(version 3)
|
|
216
|
+
├── apply.py # 改动清单 / 写回入口
|
|
217
|
+
├── writeback.py # AST 定位 + 原位 / 块写回
|
|
218
|
+
└── verify.py # 语义验证(重跑后逐项比对)
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
`toolbox.py` 也可作**嵌入 API**(`from mpltweak.toolbox import gaitu`),
|
|
222
|
+
但主推 CLI——用户脚本里不该出现本工具的任何痕迹。
|
|
223
|
+
|
|
224
|
+
## License
|
|
225
|
+
|
|
226
|
+
MIT
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "mpltweak"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "像在 PPT 里调多图排版一样调 matplotlib:拖拽排版 + AST 确定性写回脚本(调试零侵入)"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [{ name = "shdbl" }]
|
|
14
|
+
keywords = [
|
|
15
|
+
"matplotlib",
|
|
16
|
+
"figure",
|
|
17
|
+
"layout",
|
|
18
|
+
"plot",
|
|
19
|
+
"visualization",
|
|
20
|
+
"interactive",
|
|
21
|
+
"gui",
|
|
22
|
+
]
|
|
23
|
+
classifiers = [
|
|
24
|
+
"Development Status :: 3 - Alpha",
|
|
25
|
+
"Intended Audience :: Science/Research",
|
|
26
|
+
"Operating System :: OS Independent",
|
|
27
|
+
"Programming Language :: Python :: 3",
|
|
28
|
+
"Programming Language :: Python :: 3.9",
|
|
29
|
+
"Programming Language :: Python :: 3.10",
|
|
30
|
+
"Programming Language :: Python :: 3.11",
|
|
31
|
+
"Programming Language :: Python :: 3.12",
|
|
32
|
+
"Topic :: Scientific/Engineering :: Visualization",
|
|
33
|
+
]
|
|
34
|
+
dependencies = [
|
|
35
|
+
"matplotlib>=3.5",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
[project.optional-dependencies]
|
|
39
|
+
qt = ["PyQt5>=5.15"] # 交互窗口后端(纯 Agg 无窗口)
|
|
40
|
+
|
|
41
|
+
[project.urls]
|
|
42
|
+
Homepage = "https://github.com/shdbl/mpltweak"
|
|
43
|
+
Repository = "https://github.com/shdbl/mpltweak"
|
|
44
|
+
Issues = "https://github.com/shdbl/mpltweak/issues"
|
|
45
|
+
|
|
46
|
+
[project.scripts]
|
|
47
|
+
mpltweak = "mpltweak.cli:main"
|
|
48
|
+
|
|
49
|
+
[tool.setuptools.packages.find]
|
|
50
|
+
where = ["src"]
|
|
51
|
+
|
|
52
|
+
[tool.setuptools.package-data]
|
|
53
|
+
mpltweak = ["resources/*.png"]
|
|
54
|
+
|
|
55
|
+
[tool.pytest.ini_options]
|
|
56
|
+
testpaths = ["tests"]
|
mpltweak-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
mpltweak —— 交互式 matplotlib 调图(脚本零侵入)
|
|
4
|
+
|
|
5
|
+
产品形态:**库内核 + CLI 外挂入口**。
|
|
6
|
+
* 用户/agent 只接触 CLI:``mpltweak <script.py>`` 开窗调图,``mpltweak apply [--write]`` 写回;
|
|
7
|
+
* 本包是内核(toolbox / params / writeback),可被其它工具 import 嵌入;
|
|
8
|
+
* ``mpltweak.toolbox.gaitu(fig, ...)`` 是嵌入 API(Jupyter / 宿主进程内直接用),
|
|
9
|
+
文档明确标注:走它 = 在脚本里加一行 import,会破坏"脚本零侵入",仅嵌入场景用。
|
|
10
|
+
|
|
11
|
+
用户脚本里不出现本工具的任何痕迹;写回产出是纯 matplotlib 代码。
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from .params import (
|
|
15
|
+
SCHEMA_VERSION,
|
|
16
|
+
defaults,
|
|
17
|
+
dump,
|
|
18
|
+
load,
|
|
19
|
+
normalize,
|
|
20
|
+
params_path,
|
|
21
|
+
validate,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
__version__ = '0.1.0'
|
|
25
|
+
__all__ = [
|
|
26
|
+
'SCHEMA_VERSION',
|
|
27
|
+
'defaults',
|
|
28
|
+
'dump',
|
|
29
|
+
'load',
|
|
30
|
+
'normalize',
|
|
31
|
+
'params_path',
|
|
32
|
+
'validate',
|
|
33
|
+
]
|