@cardenelabs/cdl 0.47.0 → 0.49.0

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.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,60 @@ CDL (Chainome Diagram Language) の主要変更履歴。
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.49.0] - 2026-09-11
9
+
10
+ ### Added
11
+
12
+ - **`clearance` の違反に、 engine がなぜ直せなかったかを添える** (#810)
13
+
14
+ 違反の報告は「どの 2 つが何 px で、要求が何 px か」 しか持たず、なぜ engine が直せなかったかを
15
+ 知るには逃がし pass まで層を 3 つ読む必要があった。 札が絡む違反に `reason` を付け、
16
+ 各向きを誰が塞いだかを 1 行で返す。
17
+
18
+ ```
19
+ detail: edge-label:code-exchange ↔ edge-path:api-client gap=10.0px (need 14px)
20
+ reason: 逃げ場なし: 上=edge-label:consent-client (36→0) / 下=edge-label:token-issue (36→28) / 左=上限 200 を越える / 右=上限 200 を越える
21
+ ```
22
+
23
+ `(36→0)` は塞いだ相手との隙間が、動かす前と動かした後でいくつになるか。
24
+
25
+ 理由は **最終の位置から測り直す**。 逃がし pass と同じ候補と同じ判定 (`札の移動を塞ぐ相手を探す`)
26
+ を使うので、報告と実物がずれない。 どれか 1 つの向きでも安全なら理由を付けない
27
+ (動けるのに塞がれているとは言わない)。
28
+
29
+ `detail` は変えない = 消費側が `detail` から id を読んでいる。 `layoutWithValidation` の
30
+ console 出力は `(diagram=...)` の後ろに理由を足す。
31
+
32
+ ## [0.48.0] - 2026-09-11
33
+
34
+ ### Fixed
35
+
36
+ - **札の列を線が横切る時に隙間が足りないまま残るのを直した** (#805)
37
+
38
+ 同じ 2 つの箱を結ぶ弧が 4 本あると、札は 4 枚の列になる。 列をどこで上下に分けるかは
39
+ `placeAvoidingSiblingPaths` が決めるが、塊を動かす先を **兄弟の弧が通る帯** だけで決めており、
40
+ 帯の外を通る別の線については置いた後に「貫いているか」 しか見ていなかった。
41
+
42
+ 貫き (隙間 0) だけを見ると、線が札の縁をかすめる置き方が「貫いていない」 として通る。
43
+ 門 (`clearance` 軸) は 14 を要求するため、門だけが落ちる。
44
+ 実測 = 下を回る線が札の上端 10 の所を通り、要求に 4 足りなかった。
45
+
46
+ 後段の逃がし pass では直せない。 列は上下とも隣の札で埋まっており、どちらへ動かしても
47
+ 隣との要求 (36) を割る。 列ごとずらす案も測ったが、巻き込んだ札が自分の弧から 162 離れ、
48
+ `edge-label-proximity` の error 閾値 (160) を越えた。
49
+
50
+ 塊を「近すぎる線を越える所」 まで押し出した置き方を候補に足した。 押す量は探さずに測る =
51
+ 邪魔をしている線を札の横幅で切った Y 範囲の縁から、要求ぶん離れる位置までの差を取る。
52
+
53
+ | | 直す前 | 直した後 |
54
+ |---|---|---|
55
+ | 札と横切る線の隙間 | 10 (要求 14) | 14 |
56
+ | 札と自分の弧の距離 (最遠) | 158 | 130 |
57
+ | 4 枚の分かれ方 | 上 1 枚 / 下 3 枚 | 上 2 枚 / 下 2 枚 |
58
+
59
+ 押す前の候補も残すので、押しても越えられない図 (線が札の上下を挟む形) では従来の置き方が
60
+ 選ばれる。 越えられなかった置き方は候補にしない = 隙間が足りないまま位置だけが動くのを防ぐ。
61
+
8
62
  ## [0.47.0] - 2026-09-11
9
63
 
10
64
  ### Added
@@ -2351,7 +2405,9 @@ Initial OSS release.
2351
2405
  使わない = annotated tag を push しても GitHub Release は作られず、 Release を作っていない版で
2352
2406
  行き止まりになる。
2353
2407
 
2354
- [Unreleased]: https://github.com/cardene777/cdl/compare/v0.47.0...HEAD
2408
+ [Unreleased]: https://github.com/cardene777/cdl/compare/v0.49.0...HEAD
2409
+ [0.49.0]: https://github.com/cardene777/cdl/compare/v0.48.0...v0.49.0
2410
+ [0.48.0]: https://github.com/cardene777/cdl/compare/v0.47.0...v0.48.0
2355
2411
  [0.47.0]: https://github.com/cardene777/cdl/compare/v0.46.0...v0.47.0
2356
2412
  [0.46.0]: https://github.com/cardene777/cdl/compare/v0.45.0...v0.46.0
2357
2413
  [0.45.0]: https://github.com/cardene777/cdl/compare/v0.44.0...v0.45.0
package/dist/index.cjs CHANGED
@@ -13167,7 +13167,7 @@ function resolveEdgeLabelOverlapsWithChainAndPropagate(edges, obstacles, minClea
13167
13167
  { axis: "y", sign: dy >= 0 ? 1 : -1 },
13168
13168
  { axis: "x", sign: dx >= 0 ? 1 : -1 }
13169
13169
  ];
13170
- const \u52D5\u304B\u3057\u305F\u7BB1 = (axis, sign) => ({
13170
+ const \u52D5\u304B\u3057\u305F\u7BB12 = (axis, sign) => ({
13171
13171
  ...label,
13172
13172
  x: label.x + (axis === "x" ? sign * \u91CF : 0),
13173
13173
  y: label.y + (axis === "y" ? sign * \u91CF : 0)
@@ -13192,7 +13192,7 @@ function resolveEdgeLabelOverlapsWithChainAndPropagate(edges, obstacles, minClea
13192
13192
  return false;
13193
13193
  };
13194
13194
  for (const \u5019 of \u5019\u88DC) {
13195
- if (\u60AA\u304F\u3059\u308B(\u52D5\u304B\u3057\u305F\u7BB1(\u5019.axis, \u5019.sign))) continue;
13195
+ if (\u60AA\u304F\u3059\u308B(\u52D5\u304B\u3057\u305F\u7BB12(\u5019.axis, \u5019.sign))) continue;
13196
13196
  out.push({
13197
13197
  target: { kind: "label", id: label.id },
13198
13198
  axis: \u5019.axis,
@@ -13358,15 +13358,7 @@ function resolveEdgeLabelOverlapsWithChainAndPropagate(edges, obstacles, minClea
13358
13358
  edgePathShifts: edgePathShiftMap
13359
13359
  };
13360
13360
  }
13361
- function resolveLabelClearanceAfterPathPasses(edges, obstacles, maxIter = 8) {
13362
- if (edges.length === 0) return edges;
13363
- const labeledIds = new Set(
13364
- edges.filter((e) => e.label?.trim() || e.sub?.trim()).map((e) => e.id)
13365
- );
13366
- if (labeledIds.size === 0) return edges;
13367
- const \u7BB1\u305F\u3061 = obstacles.filter((o) => o.kind === "node" || o.kind === "lane-label");
13368
- const edgeById = /* @__PURE__ */ new Map();
13369
- for (const e of edges) edgeById.set(e.id, e);
13361
+ function \u7DDA\u306E\u6E2C\u308A\u65B9\u3092\u4F5C\u308B() {
13370
13362
  const \u533A\u9593cache = /* @__PURE__ */ new Map();
13371
13363
  const \u533A\u9593 = (id, d) => {
13372
13364
  const hit = \u533A\u9593cache.get(id);
@@ -13402,7 +13394,143 @@ function resolveLabelClearanceAfterPathPasses(edges, obstacles, maxIter = 8) {
13402
13394
  const dy = Math.max(0, Math.max(box.y - (b.y + b.h), b.y - (box.y + box.h)));
13403
13395
  return Math.hypot(dx, dy) >= \u4F59\u88D5;
13404
13396
  };
13405
- const \u6700\u5927\u79FB\u52D5 = CHAIN_SHIFT_MAX_ACCUMULATED * 2;
13397
+ return { \u533A\u9593, \u9060\u3044 };
13398
+ }
13399
+ var \u672D\u306E\u6700\u5927\u79FB\u52D5 = CHAIN_SHIFT_MAX_ACCUMULATED * 2;
13400
+ var \u96E2\u308C\u91CF = (a, b) => ({
13401
+ x: Math.max(a.x - (b.x + b.w), b.x - (a.x + a.w)),
13402
+ y: Math.max(a.y - (b.y + b.h), b.y - (a.y + a.h))
13403
+ });
13404
+ var \u52D5\u304B\u3057\u305F\u7BB1 = (box, axis, sign, amount) => ({
13405
+ ...box,
13406
+ x: box.x + (axis === "x" ? sign * amount : 0),
13407
+ y: box.y + (axis === "y" ? sign * amount : 0)
13408
+ });
13409
+ function \u672D\u306E\u79FB\u52D5\u3092\u585E\u3050\u76F8\u624B\u3092\u63A2\u3059(\u5468\u308A, label, \u884C\u304D\u5148) {
13410
+ const { \u7BB1\u305F\u3061, labels, edges, edgeById, \u6E2C\u308A\u65B9 } = \u5468\u308A;
13411
+ for (const obs of \u7BB1\u305F\u3061) {
13412
+ const \u524D = computeGap(label, obs);
13413
+ const \u5F8C = computeGap(\u884C\u304D\u5148, obs);
13414
+ if (\u5F8C < Math.min(\u524D, requiredNearClearance("edge-label", obs.kind))) return { \u76F8\u624B: `${obs.kind}:${obs.id}`, \u524D, \u5F8C };
13415
+ }
13416
+ const \u672D\u306E\u8981\u6C42 = requiredNearClearance("edge-label", "edge-label");
13417
+ for (const \u4ED6 of labels) {
13418
+ if (\u4ED6.id === label.id) continue;
13419
+ const \u524D = computeGap(label, \u4ED6);
13420
+ const \u5F8C = computeGap(\u884C\u304D\u5148, \u4ED6);
13421
+ if (\u5F8C < Math.min(\u524D, \u672D\u306E\u8981\u6C42)) return { \u76F8\u624B: `edge-label:${\u4ED6.id}`, \u524D, \u5F8C };
13422
+ }
13423
+ const \u81EA\u5206 = edgeById.get(label.id);
13424
+ const \u81EA\u5206\u306E\u533A\u9593 = \u81EA\u5206 ? \u6E2C\u308A\u65B9.\u533A\u9593(\u81EA\u5206.id, \u81EA\u5206.d) : [];
13425
+ if (\u81EA\u5206\u306E\u533A\u9593.length > 0) {
13426
+ const \u524D = measurePathLabelGapSegs(\u81EA\u5206\u306E\u533A\u9593, label);
13427
+ const \u5F8C = measurePathLabelGapSegs(\u81EA\u5206\u306E\u533A\u9593, \u884C\u304D\u5148);
13428
+ if (\u5F8C > Math.max(\u524D, DIST_LABEL_PATH_MAX)) return { \u76F8\u624B: "\u81EA\u5206\u306E\u7DDA", \u524D, \u5F8C };
13429
+ }
13430
+ for (const \u76F8\u624B of edges) {
13431
+ if (\u76F8\u624B.id === label.id) continue;
13432
+ if (\u6E2C\u308A\u65B9.\u9060\u3044(label, \u76F8\u624B.id, \u76F8\u624B.d, LABEL_TO_PATH_CLEARANCE) && \u6E2C\u308A\u65B9.\u9060\u3044(\u884C\u304D\u5148, \u76F8\u624B.id, \u76F8\u624B.d, LABEL_TO_PATH_CLEARANCE)) continue;
13433
+ const segs = segmentsExcludingOwnPath(\u6E2C\u308A\u65B9.\u533A\u9593(\u76F8\u624B.id, \u76F8\u624B.d), \u81EA\u5206\u306E\u533A\u9593);
13434
+ if (segs.length === 0) continue;
13435
+ const \u524D = measurePathLabelGapSegs(segs, label);
13436
+ const \u5F8C = measurePathLabelGapSegs(segs, \u884C\u304D\u5148);
13437
+ if (\u5F8C < Math.min(\u524D, LABEL_TO_PATH_CLEARANCE)) return { \u76F8\u624B: `edge-path:${\u76F8\u624B.id}`, \u524D, \u5F8C };
13438
+ }
13439
+ return null;
13440
+ }
13441
+ function \u77E9\u5F62\u306E\u5019\u88DC(label, static\u7BB1, required) {
13442
+ const sep = \u96E2\u308C\u91CF(label, static\u7BB1);
13443
+ const \u5411\u304D = (axis) => {
13444
+ const a = axis === "x" ? label.x + label.w / 2 : label.y + label.h / 2;
13445
+ const b = axis === "x" ? static\u7BB1.x + static\u7BB1.w / 2 : static\u7BB1.y + static\u7BB1.h / 2;
13446
+ return a >= b ? 1 : -1;
13447
+ };
13448
+ return [
13449
+ { axis: "x", sign: \u5411\u304D("x"), amount: required - sep.x + NEAR_MISS_MARGIN },
13450
+ { axis: "y", sign: \u5411\u304D("y"), amount: required - sep.y + NEAR_MISS_MARGIN }
13451
+ ];
13452
+ }
13453
+ function \u7DDA\u304B\u3089\u96E2\u308C\u308B\u91CF(segs, label, gap, axis, sign) {
13454
+ const \u6700\u5927\u79FB\u52D5 = \u672D\u306E\u6700\u5927\u79FB\u52D5;
13455
+ const \u6E80\u305F\u3059 = (\u91CF) => measurePathLabelGapSegs(segs, \u52D5\u304B\u3057\u305F\u7BB1(label, axis, sign, \u91CF)) >= LABEL_TO_PATH_CLEARANCE;
13456
+ let lo = 0;
13457
+ let hi = Math.max(4, LABEL_TO_PATH_CLEARANCE - gap);
13458
+ while (!\u6E80\u305F\u3059(hi)) {
13459
+ if (hi >= \u6700\u5927\u79FB\u52D5) return null;
13460
+ lo = hi;
13461
+ hi = Math.min(hi * 2, \u6700\u5927\u79FB\u52D5);
13462
+ }
13463
+ for (let k = 0; k < 12; k += 1) {
13464
+ const mid = (lo + hi) / 2;
13465
+ if (\u6E80\u305F\u3059(mid)) hi = mid;
13466
+ else lo = mid;
13467
+ }
13468
+ return hi + NEAR_MISS_MARGIN;
13469
+ }
13470
+ var \u5411\u304D\u306E\u9806 = [
13471
+ { axis: "y", sign: -1, \u540D\u524D: "\u4E0A" },
13472
+ { axis: "y", sign: 1, \u540D\u524D: "\u4E0B" },
13473
+ { axis: "x", sign: -1, \u540D\u524D: "\u5DE6" },
13474
+ { axis: "x", sign: 1, \u540D\u524D: "\u53F3" }
13475
+ ];
13476
+ function \u672D\u304C\u52D5\u3051\u306A\u3044\u7406\u7531(labelId, \u76F8\u624B, edges, obstacles) {
13477
+ const edgeById = new Map(edges.map((e) => [e.id, e]));
13478
+ const \u81EA\u5206 = edgeById.get(labelId);
13479
+ if (!\u81EA\u5206) return null;
13480
+ const labels = edges.filter((e) => e.label?.trim() || e.sub?.trim()).map((e) => ({ kind: "edge-label", id: e.id, ...computeLabelBBoxWorld(e) }));
13481
+ const label = labels.find((l) => l.id === labelId);
13482
+ if (!label) return null;
13483
+ const \u6E2C\u308A\u65B9 = \u7DDA\u306E\u6E2C\u308A\u65B9\u3092\u4F5C\u308B();
13484
+ const \u5468\u308A = {
13485
+ \u7BB1\u305F\u3061: obstacles.filter((o) => o.kind === "node" || o.kind === "lane-label"),
13486
+ labels,
13487
+ edges,
13488
+ edgeById,
13489
+ \u6E2C\u308A\u65B9
13490
+ };
13491
+ const \u5019\u88DC = [];
13492
+ if (\u76F8\u624B.kind === "edge-path") {
13493
+ const \u7DDA = edgeById.get(\u76F8\u624B.id);
13494
+ if (!\u7DDA) return null;
13495
+ const segs = segmentsExcludingOwnPath(\u6E2C\u308A\u65B9.\u533A\u9593(\u7DDA.id, \u7DDA.d), \u6E2C\u308A\u65B9.\u533A\u9593(\u81EA\u5206.id, \u81EA\u5206.d));
13496
+ if (segs.length === 0) return null;
13497
+ const gap = measurePathLabelGapSegs(segs, label);
13498
+ for (const \u5411\u304D of \u5411\u304D\u306E\u9806) {
13499
+ \u5019\u88DC.push({ axis: \u5411\u304D.axis, sign: \u5411\u304D.sign, amount: \u7DDA\u304B\u3089\u96E2\u308C\u308B\u91CF(segs, label, gap, \u5411\u304D.axis, \u5411\u304D.sign) });
13500
+ }
13501
+ } else {
13502
+ const required = requiredNearClearance("edge-label", \u76F8\u624B.kind);
13503
+ for (const c of \u77E9\u5F62\u306E\u5019\u88DC(label, \u76F8\u624B, required)) {
13504
+ if (c.amount > 0) \u5019\u88DC.push(c);
13505
+ }
13506
+ }
13507
+ if (\u5019\u88DC.length === 0) return null;
13508
+ const \u7406\u7531 = [];
13509
+ for (const c of \u5019\u88DC) {
13510
+ const \u9806 = \u5411\u304D\u306E\u9806.findIndex((v) => v.axis === c.axis && v.sign === c.sign);
13511
+ const \u540D\u524D = \u5411\u304D\u306E\u9806[\u9806].\u540D\u524D;
13512
+ if (c.amount === null || c.amount > \u672D\u306E\u6700\u5927\u79FB\u52D5) {
13513
+ \u7406\u7531.push({ \u9806, \u6587: `${\u540D\u524D}=\u4E0A\u9650 ${\u672D\u306E\u6700\u5927\u79FB\u52D5} \u3092\u8D8A\u3048\u308B` });
13514
+ continue;
13515
+ }
13516
+ const \u585E\u3050 = \u672D\u306E\u79FB\u52D5\u3092\u585E\u3050\u76F8\u624B\u3092\u63A2\u3059(\u5468\u308A, label, \u52D5\u304B\u3057\u305F\u7BB1(label, c.axis, c.sign, c.amount));
13517
+ if (!\u585E\u3050) return null;
13518
+ \u7406\u7531.push({ \u9806, \u6587: `${\u540D\u524D}=${\u585E\u3050.\u76F8\u624B} (${\u585E\u3050.\u524D.toFixed(0)}\u2192${\u585E\u3050.\u5F8C.toFixed(0)})` });
13519
+ }
13520
+ return `\u9003\u3052\u5834\u306A\u3057: ${\u7406\u7531.sort((a, b) => a.\u9806 - b.\u9806).map((r) => r.\u6587).join(" / ")}`;
13521
+ }
13522
+ function resolveLabelClearanceAfterPathPasses(edges, obstacles, maxIter = 8) {
13523
+ if (edges.length === 0) return edges;
13524
+ const labeledIds = new Set(
13525
+ edges.filter((e) => e.label?.trim() || e.sub?.trim()).map((e) => e.id)
13526
+ );
13527
+ if (labeledIds.size === 0) return edges;
13528
+ const \u7BB1\u305F\u3061 = obstacles.filter((o) => o.kind === "node" || o.kind === "lane-label");
13529
+ const edgeById = /* @__PURE__ */ new Map();
13530
+ for (const e of edges) edgeById.set(e.id, e);
13531
+ const \u6E2C\u308A\u65B9 = \u7DDA\u306E\u6E2C\u308A\u65B9\u3092\u4F5C\u308B();
13532
+ const { \u533A\u9593, \u9060\u3044 } = \u6E2C\u308A\u65B9;
13533
+ const \u6700\u5927\u79FB\u52D5 = \u672D\u306E\u6700\u5927\u79FB\u52D5;
13406
13534
  const shiftMap = /* @__PURE__ */ new Map();
13407
13535
  const getShift = (id) => shiftMap.get(id) ?? { dx: 0, dy: 0 };
13408
13536
  const labelBBoxes = () => {
@@ -13415,50 +13543,12 @@ function resolveLabelClearanceAfterPathPasses(edges, obstacles, maxIter = 8) {
13415
13543
  }
13416
13544
  return out;
13417
13545
  };
13418
- const \u96E2\u308C\u91CF = (a, b) => ({
13419
- x: Math.max(a.x - (b.x + b.w), b.x - (a.x + a.w)),
13420
- y: Math.max(a.y - (b.y + b.h), b.y - (a.y + a.h))
13421
- });
13422
- const \u52D5\u304B\u3057\u305F\u7BB1 = (box, axis, sign, amount) => ({
13423
- ...box,
13424
- x: box.x + (axis === "x" ? sign * amount : 0),
13425
- y: box.y + (axis === "y" ? sign * amount : 0)
13426
- });
13427
13546
  for (let iter = 0; iter < maxIter; iter += 1) {
13428
13547
  const labels = labelBBoxes();
13429
13548
  const \u672D\u306E\u8981\u6C42 = requiredNearClearance("edge-label", "edge-label");
13430
13549
  let \u52D5\u3044\u305F = false;
13431
13550
  const \u4ECA\u56DE\u52D5\u304B\u3057\u305F = /* @__PURE__ */ new Set();
13432
- const \u5B89\u5168\u304B = (label, \u884C\u304D\u5148) => {
13433
- for (const obs of \u7BB1\u305F\u3061) {
13434
- const \u524D = computeGap(label, obs);
13435
- const \u5F8C = computeGap(\u884C\u304D\u5148, obs);
13436
- if (\u5F8C < Math.min(\u524D, requiredNearClearance("edge-label", obs.kind))) return false;
13437
- }
13438
- for (const \u4ED6 of labels) {
13439
- if (\u4ED6.id === label.id) continue;
13440
- const \u524D = computeGap(label, \u4ED6);
13441
- const \u5F8C = computeGap(\u884C\u304D\u5148, \u4ED6);
13442
- if (\u5F8C < Math.min(\u524D, \u672D\u306E\u8981\u6C42)) return false;
13443
- }
13444
- const \u81EA\u5206 = edgeById.get(label.id);
13445
- const \u81EA\u5206\u306E\u533A\u9593 = \u81EA\u5206 ? \u533A\u9593(\u81EA\u5206.id, \u81EA\u5206.d) : [];
13446
- if (\u81EA\u5206\u306E\u533A\u9593.length > 0) {
13447
- const \u524D = measurePathLabelGapSegs(\u81EA\u5206\u306E\u533A\u9593, label);
13448
- const \u5F8C = measurePathLabelGapSegs(\u81EA\u5206\u306E\u533A\u9593, \u884C\u304D\u5148);
13449
- if (\u5F8C > Math.max(\u524D, DIST_LABEL_PATH_MAX)) return false;
13450
- }
13451
- for (const \u76F8\u624B of edges) {
13452
- if (\u76F8\u624B.id === label.id) continue;
13453
- if (\u9060\u3044(label, \u76F8\u624B.id, \u76F8\u624B.d, LABEL_TO_PATH_CLEARANCE) && \u9060\u3044(\u884C\u304D\u5148, \u76F8\u624B.id, \u76F8\u624B.d, LABEL_TO_PATH_CLEARANCE)) continue;
13454
- const segs = segmentsExcludingOwnPath(\u533A\u9593(\u76F8\u624B.id, \u76F8\u624B.d), \u81EA\u5206\u306E\u533A\u9593);
13455
- if (segs.length === 0) continue;
13456
- const \u524D = measurePathLabelGapSegs(segs, label);
13457
- const \u5F8C = measurePathLabelGapSegs(segs, \u884C\u304D\u5148);
13458
- if (\u5F8C < Math.min(\u524D, LABEL_TO_PATH_CLEARANCE)) return false;
13459
- }
13460
- return true;
13461
- };
13551
+ const \u5B89\u5168\u304B = (label, \u884C\u304D\u5148) => \u672D\u306E\u79FB\u52D5\u3092\u585E\u3050\u76F8\u624B\u3092\u63A2\u3059({ \u7BB1\u305F\u3061, labels, edges, edgeById, \u6E2C\u308A\u65B9 }, label, \u884C\u304D\u5148) === null;
13462
13552
  const \u9003\u304C\u3059 = (label, \u5019\u88DC) => {
13463
13553
  for (const c of \u5019\u88DC.slice().sort((a, b) => a.amount - b.amount)) {
13464
13554
  if (c.amount <= 0) continue;
@@ -13474,18 +13564,6 @@ function resolveLabelClearanceAfterPathPasses(edges, obstacles, maxIter = 8) {
13474
13564
  }
13475
13565
  return false;
13476
13566
  };
13477
- const \u77E9\u5F62\u306E\u5019\u88DC = (label, static\u7BB1, required) => {
13478
- const sep = \u96E2\u308C\u91CF(label, static\u7BB1);
13479
- const \u5411\u304D = (axis) => {
13480
- const a = axis === "x" ? label.x + label.w / 2 : label.y + label.h / 2;
13481
- const b = axis === "x" ? static\u7BB1.x + static\u7BB1.w / 2 : static\u7BB1.y + static\u7BB1.h / 2;
13482
- return a >= b ? 1 : -1;
13483
- };
13484
- return [
13485
- { axis: "x", sign: \u5411\u304D("x"), amount: required - sep.x + NEAR_MISS_MARGIN },
13486
- { axis: "y", sign: \u5411\u304D("y"), amount: required - sep.y + NEAR_MISS_MARGIN }
13487
- ];
13488
- };
13489
13567
  for (const label of labels) {
13490
13568
  if (\u4ECA\u56DE\u52D5\u304B\u3057\u305F.has(label.id)) continue;
13491
13569
  for (const obs of \u7BB1\u305F\u3061) {
@@ -13542,22 +13620,7 @@ function resolveLabelClearanceAfterPathPasses(edges, obstacles, maxIter = 8) {
13542
13620
  }
13543
13621
  }
13544
13622
  if (!\u8FD1\u3044\u70B9) continue;
13545
- const \u5FC5\u8981\u91CF = (axis, sign) => {
13546
- const \u6E80\u305F\u3059 = (\u91CF) => measurePathLabelGapSegs(segs, \u52D5\u304B\u3057\u305F\u7BB1(label, axis, sign, \u91CF)) >= LABEL_TO_PATH_CLEARANCE;
13547
- let lo = 0;
13548
- let hi = Math.max(4, LABEL_TO_PATH_CLEARANCE - gap);
13549
- while (!\u6E80\u305F\u3059(hi)) {
13550
- if (hi >= \u6700\u5927\u79FB\u52D5) return null;
13551
- lo = hi;
13552
- hi = Math.min(hi * 2, \u6700\u5927\u79FB\u52D5);
13553
- }
13554
- for (let k = 0; k < 12; k += 1) {
13555
- const mid = (lo + hi) / 2;
13556
- if (\u6E80\u305F\u3059(mid)) hi = mid;
13557
- else lo = mid;
13558
- }
13559
- return hi + NEAR_MISS_MARGIN;
13560
- };
13623
+ const \u5FC5\u8981\u91CF = (axis, sign) => \u7DDA\u304B\u3089\u96E2\u308C\u308B\u91CF(segs, label, gap, axis, sign);
13561
13624
  const dx = \u82AF.x - \u8FD1\u3044\u70B9.x;
13562
13625
  const dy = \u82AF.y - \u8FD1\u3044\u70B9.y;
13563
13626
  const \u5019\u88DC = [];
@@ -15989,9 +16052,15 @@ function visualValidateLaid(laid, diag, opts) {
15989
16052
  const counts = emptyCounts();
15990
16053
  const profile = opts?.profile ?? "production";
15991
16054
  const skipped = SKIPPED_AXES[profile];
15992
- const push = (axis, detail, severity = "error") => {
16055
+ const push = (axis, detail, severity = "error", reason) => {
15993
16056
  if (skipped.has(axis)) return;
15994
- violations.push({ axis, diagramId: safeDiagramId(diag), detail, severity });
16057
+ violations.push({
16058
+ axis,
16059
+ diagramId: safeDiagramId(diag),
16060
+ detail,
16061
+ severity,
16062
+ ...reason !== void 0 ? { reason } : {}
16063
+ });
15995
16064
  counts[axis]++;
15996
16065
  };
15997
16066
  return visualValidateInner(
@@ -16010,9 +16079,15 @@ function visualValidate(diag, opts) {
16010
16079
  const counts = emptyCounts();
16011
16080
  const profile = opts?.profile ?? "production";
16012
16081
  const skipped = SKIPPED_AXES[profile];
16013
- const push = (axis, detail, severity = "error") => {
16082
+ const push = (axis, detail, severity = "error", reason) => {
16014
16083
  if (skipped.has(axis)) return;
16015
- violations.push({ axis, diagramId: safeDiagramId(diag), detail, severity });
16084
+ violations.push({
16085
+ axis,
16086
+ diagramId: safeDiagramId(diag),
16087
+ detail,
16088
+ severity,
16089
+ ...reason !== void 0 ? { reason } : {}
16090
+ });
16016
16091
  counts[axis]++;
16017
16092
  };
16018
16093
  const diagProblems = findMalformedInput(void 0, diag).filter(
@@ -16349,10 +16424,19 @@ function runAxes(laid, diag, violations, counts, push, profile, skippedAxes, \u5
16349
16424
  }
16350
16425
  }
16351
16426
  const nears = detectNearCollisions(bboxes, laid.edges);
16427
+ const \u672D\u306E\u969C\u5BB3\u7269 = bboxes.filter((b) => b.kind === "node" || b.kind === "lane-label");
16428
+ const \u8FBA\u306E\u9806 = new Map(laid.edges.map((e, i) => [e.id, i]));
16352
16429
  for (const n of nears) {
16430
+ const a\u306F\u672D = n.a.kind === "edge-label";
16431
+ const b\u306F\u672D = n.b.kind === "edge-label";
16432
+ const \u52D5\u304B\u3059 = a\u306F\u672D && b\u306F\u672D ? (\u8FBA\u306E\u9806.get(n.a.id) ?? 0) > (\u8FBA\u306E\u9806.get(n.b.id) ?? 0) ? n.a : n.b : a\u306F\u672D ? n.a : b\u306F\u672D ? n.b : null;
16433
+ const \u76F8\u624B = \u52D5\u304B\u3059 === n.a ? n.b : n.a;
16434
+ const reason = \u52D5\u304B\u3059 !== null ? \u672D\u304C\u52D5\u3051\u306A\u3044\u7406\u7531(\u52D5\u304B\u3059.id, \u76F8\u624B, laid.edges, \u672D\u306E\u969C\u5BB3\u7269) ?? void 0 : void 0;
16353
16435
  push(
16354
16436
  "clearance",
16355
- `${n.a.kind}:${n.a.id} \u2194 ${n.b.kind}:${n.b.id} gap=${n.gap.toFixed(1)}px (need ${n.required}px)`
16437
+ `${n.a.kind}:${n.a.id} \u2194 ${n.b.kind}:${n.b.id} gap=${n.gap.toFixed(1)}px (need ${n.required}px)`,
16438
+ "error",
16439
+ reason
16356
16440
  );
16357
16441
  }
16358
16442
  const PROXIMITY_WARN_THRESHOLD = DIST_LABEL_PATH_MAX;
@@ -19578,33 +19662,73 @@ function placeAvoidingSiblingPaths(args) {
19578
19662
  }
19579
19663
  };
19580
19664
  consider([...wants]);
19665
+ const \u62BC\u3059\u91CF = (idx, side, pos) => {
19666
+ let \u8981\u308B = 0;
19667
+ for (const i of idx) {
19668
+ const box = boxes[i];
19669
+ const rect = { x: box.x, y: pos[i] - box.h / 2, w: box.w, h: box.h };
19670
+ for (const probe of probeSegs[i]) {
19671
+ if (measurePathLabelGapSegs(probe.segs, rect) >= CLEARANCE_PATH_LABEL) continue;
19672
+ let lo = Infinity;
19673
+ let hi = -Infinity;
19674
+ for (const seg of probe.segs) {
19675
+ const c = clipY(seg, box.x, box.x + box.w);
19676
+ if (!c) continue;
19677
+ lo = Math.min(lo, c.lo);
19678
+ hi = Math.max(hi, c.hi);
19679
+ }
19680
+ if (lo > hi) continue;
19681
+ const \u884C\u304D\u5148 = side === "above" ? lo - box.h / 2 - CLEARANCE_PATH_LABEL : hi + box.h / 2 + CLEARANCE_PATH_LABEL;
19682
+ \u8981\u308B = Math.max(\u8981\u308B, side === "above" ? pos[i] - \u884C\u304D\u5148 : \u884C\u304D\u5148 - pos[i]);
19683
+ }
19684
+ }
19685
+ return Math.max(0, \u8981\u308B);
19686
+ };
19581
19687
  if (bands.some((b) => b !== null)) {
19582
19688
  for (let split = 0; split <= order.length; split++) {
19583
19689
  const above = order.slice(0, split);
19584
19690
  const below = order.slice(split);
19585
- const out = new Array(members.length);
19586
- for (const [idx, side] of [
19587
- [above, "above"],
19588
- [below, "below"]
19589
- ]) {
19590
- if (idx.length === 0) continue;
19591
- const ys = spread(idx);
19592
- let shift = 0;
19593
- for (let k = 0; k < idx.length; k++) {
19594
- const band = bands[idx[k]];
19595
- if (!band) continue;
19596
- shift = side === "above" ? Math.min(shift, band.lo - ys[k]) : Math.max(shift, band.hi - ys[k]);
19597
- }
19598
- for (let k = 0; k < idx.length; k++) out[idx[k]] = ys[k] + shift;
19599
- }
19600
- if (above.length > 0 && below.length > 0) {
19691
+ const \u9593\u3092\u7A7A\u3051\u308B = (out) => {
19692
+ if (above.length === 0 || below.length === 0) return;
19601
19693
  const lastIdx = above[above.length - 1];
19602
19694
  const firstIdx = below[0];
19603
19695
  const need = (sizes[lastIdx] + sizes[firstIdx]) / 2 + CLEARANCE_LABEL_LABEL;
19604
19696
  const deficit = need - (out[firstIdx] - out[lastIdx]);
19605
- if (deficit > 0) for (const i of below) out[i] += deficit;
19606
- }
19607
- consider(out);
19697
+ if (deficit > 0) for (const i of below) out[i] = out[i] + deficit;
19698
+ };
19699
+ const \u5E2F\u3092\u8D8A\u3048\u308B = () => {
19700
+ const out = new Array(members.length);
19701
+ for (const [idx, side] of [
19702
+ [above, "above"],
19703
+ [below, "below"]
19704
+ ]) {
19705
+ if (idx.length === 0) continue;
19706
+ const ys = spread(idx);
19707
+ let shift = 0;
19708
+ for (let k = 0; k < idx.length; k++) {
19709
+ const band = bands[idx[k]];
19710
+ if (!band) continue;
19711
+ shift = side === "above" ? Math.min(shift, band.lo - ys[k]) : Math.max(shift, band.hi - ys[k]);
19712
+ }
19713
+ for (let k = 0; k < idx.length; k++) out[idx[k]] = ys[k] + shift;
19714
+ }
19715
+ \u9593\u3092\u7A7A\u3051\u308B(out);
19716
+ return out;
19717
+ };
19718
+ const \u62BC\u3057\u3066\u8D8A\u3048\u308B = () => {
19719
+ const out = \u5E2F\u3092\u8D8A\u3048\u308B();
19720
+ for (let round = 0; round < 4; round += 1) {
19721
+ const a = \u62BC\u3059\u91CF(above, "above", out);
19722
+ const b = \u62BC\u3059\u91CF(below, "below", out);
19723
+ if (a === 0 && b === 0) return out;
19724
+ for (const i of above) out[i] = out[i] - a;
19725
+ for (const i of below) out[i] = out[i] + b;
19726
+ }
19727
+ return \u62BC\u3059\u91CF(above, "above", out) === 0 && \u62BC\u3059\u91CF(below, "below", out) === 0 ? out : null;
19728
+ };
19729
+ consider(\u5E2F\u3092\u8D8A\u3048\u308B());
19730
+ const \u62BC\u3057\u305F = \u62BC\u3057\u3066\u8D8A\u3048\u308B();
19731
+ if (\u62BC\u3057\u305F) consider(\u62BC\u3057\u305F);
19608
19732
  }
19609
19733
  }
19610
19734
  const plain = spread(all);
@@ -21232,10 +21356,11 @@ function layoutWithValidation(diag, opts = {}) {
21232
21356
  }
21233
21357
  if (opts.emitConsole) {
21234
21358
  for (const v of report.violations) {
21359
+ const \u7406\u7531 = v.reason !== void 0 ? ` ${v.reason}` : "";
21235
21360
  if (v.severity === "error") {
21236
- console.error(`[cdl geometry ${v.axis}] ${v.detail} (diagram=${v.diagramId})`);
21361
+ console.error(`[cdl geometry ${v.axis}] ${v.detail} (diagram=${v.diagramId})${\u7406\u7531}`);
21237
21362
  } else {
21238
- console.warn(`[cdl geometry ${v.axis}] ${v.detail} (diagram=${v.diagramId})`);
21363
+ console.warn(`[cdl geometry ${v.axis}] ${v.detail} (diagram=${v.diagramId})${\u7406\u7531}`);
21239
21364
  }
21240
21365
  }
21241
21366
  }