三面拆分⑯:手绘图水平居中 —— 画幅下限的余白对称分布

症状(用户报):图保存以后不居中。根因:手绘布局给画幅垫了 1000 的宽度下限,
内容却总从左缘 PAD=20 起 —— 画得比 1000 窄时余白全堆右边,图左贴;
保存时序列化归一化(整体平移到非负)后左贴更明显。

修法:layoutManualFlow 定完画幅宽度后把内容平移到正中,两侧余白对称
(纯呈现层平移,存储坐标不动);纵向保持收顶不居中。

另:用户测试保存产生的 v5 与 v4 几何完全一致,且带着上一轮已拔除的
自动重排 bug 的 id 旋转(保存时浏览器还跑着旧代码)—— 按「图只留 v4」删除。

五闸退出码全过。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
This commit is contained in:
2026-07-28 19:44:19 +08:00
co-authored by Claude Fable 5
parent 6988a750ae
commit 2246b26438
+23 -1
View File
@@ -428,11 +428,33 @@ function layoutManualFlow(graph: FlowGraph): FlowLayoutResult {
maxY = Math.max(maxY, caption.y + 4); maxY = Math.max(maxY, caption.y + 4);
} }
/*
* 水平居中:画幅有 1000 的下限,内容却总从左缘 PAD 起 —— 画得比 1000 窄时
* 余白全堆在右边,图看着左贴(保存后经归一化尤其明显)。
* 定完画幅宽度后把内容平移到正中,两侧余白对称;纵向保持收顶,不居中。
*/
let cMinX = Infinity;
for (const shape of shapes) cMinX = Math.min(cMinX, shape.x);
for (const connection of connections) for (const p of connection.waypoints) cMinX = Math.min(cMinX, p.x);
for (const caption of captions) cMinX = Math.min(cMinX, caption.x - estTextWidth(caption.text, 10) / 2);
if (!Number.isFinite(cMinX)) cMinX = 0;
const contentW = maxX - cMinX;
const width = Math.max(1000, contentW + 40);
const dxCenter = (width - contentW) / 2 - cMinX;
if (dxCenter !== 0) {
for (const shape of shapes) shape.x += dxCenter;
for (const caption of captions) caption.x += dxCenter;
for (const connection of connections) {
connection.waypoints = connection.waypoints.map((p) => ({ x: p.x + dxCenter, y: p.y }));
if (connection.label) connection.label = { ...connection.label, x: connection.label.x + dxCenter };
}
}
return { return {
shapes, shapes,
captions, captions,
connections, connections,
width: Math.max(1000, maxX + 40), width,
height: maxY + 44, height: maxY + 44,
ghost: false, ghost: false,
}; };