@cardenelabs/cdl 0.123.0 → 0.124.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,17 @@ CDL (Chainome Diagram Language) の主要変更履歴。
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.124.0] - 2026-10-06
9
+
10
+ ### Changed
11
+
12
+ - **箱の上を回る戻り線が、途中の箱を貫かない道筋を選べるようにした** (#1014)
13
+
14
+ 上へ回る今までの道筋が両端以外の箱と交わる時だけ、左右の外側の列を通る道筋に替え、交わる箱が少なく短い方を選ぶ。
15
+ 両端は今までどおり上辺から出て、上辺へ入る。
16
+ 1 段前へ戻る線と間に箱が無い戻り線の座標は変わらない。
17
+ 外側を回っても避けられない箱は `edge-node-cross` として検査結果に残る。
18
+
8
19
  ## [0.123.0] - 2026-10-05
9
20
 
10
21
  ### Added
@@ -4864,7 +4875,8 @@ Initial OSS release.
4864
4875
  使わない = annotated tag を push しても GitHub Release は作られず、 Release を作っていない版で
4865
4876
  行き止まりになる。
4866
4877
 
4867
- [Unreleased]: https://github.com/cardene777/cdl/compare/v0.123.0...HEAD
4878
+ [Unreleased]: https://github.com/cardene777/cdl/compare/v0.124.0...HEAD
4879
+ [0.124.0]: https://github.com/cardene777/cdl/compare/v0.123.0...v0.124.0
4868
4880
  [0.123.0]: https://github.com/cardene777/cdl/compare/v0.122.0...v0.123.0
4869
4881
  [0.122.0]: https://github.com/cardene777/cdl/compare/v0.121.0...v0.122.0
4870
4882
  [0.121.0]: https://github.com/cardene777/cdl/compare/v0.120.0...v0.121.0
package/README.md CHANGED
@@ -268,10 +268,15 @@ export const custom = diagram("custom", { topic: "Custom" })
268
268
  | 値 | 道筋 | 向く場面 |
269
269
  |---|---|---|
270
270
  | `default` | 箱を避け、角を丸めた直角の線 | 箱を避けたい図、直角の線に揃えたい図 |
271
- | `back-detour` | 箱の上を回って戻る線 | 前の箱へ戻る関係 |
271
+ | `back-detour` | 箱の上を回って戻り、途中の箱と交わる時は外側を回る線 | 前の箱へ戻る関係 |
272
272
  | `curve` | 両端で水平になる 3 次曲線 | 段や列をまたぐ関係を柔らかくつなぐ図 |
273
273
  | `metro` | 横線と傾き ±1 の斜線を円弧で結ぶ線 | 路線図の線路と乗り換え |
274
274
 
275
+ `back-detour` は始点と終点の上辺を結び、両端より 100 上を横に渡る。
276
+ 途中の箱と交わる時だけ、縦に並ぶ箱を避けた最も近い外側の列を通り、交わる箱が少なく短い方を選ぶ。
277
+ 両端は外側へ回る時も上辺から出て、上辺へ入る。
278
+ 外側を回っても避けられない箱は `edge-node-cross` として検査結果に残る。
279
+
275
280
  `curve` は、行き先が右なら始点の右辺から終点の左辺へ、左なら始点の左辺から終点の右辺へ結ぶ。
276
281
  2 つの箱の横の範囲が重なる時は、両方の右辺を結ぶ。
277
282
  2 点の高さが同じなら直線になる。
package/dist/index.cjs CHANGED
@@ -20514,6 +20514,55 @@ function bowSharedStraightPaths(edges, nodes, skipIds, textMetrics = "proportion
20514
20514
  function nodeRect(n) {
20515
20515
  return { x: n.cx - n.w / 2, y: n.cy - n.h / 2, w: n.w, h: n.h };
20516
20516
  }
20517
+ function countPathObstacleCrossings(d, obstacles) {
20518
+ const segments = flattenPathSegments(extractPathSegments(d));
20519
+ return obstacles.filter((obstacle) => segmentsCrossRect(segments, nodeRect(obstacle))).length;
20520
+ }
20521
+ function makeBackDetourCandidate(A, B, from, to, obstacles, detourY, side) {
20522
+ const lowIsA = A.y >= B.y;
20523
+ const low = lowIsA ? A : B;
20524
+ const lowBendY = low.y - VERTICAL_DETOUR_EXIT;
20525
+ const bandTop = Math.min(detourY, lowBendY);
20526
+ const bandBottom = Math.max(detourY, lowBendY);
20527
+ const bandObstacles = obstacles.map(nodeRect).filter((rect) => rect.y + rect.h >= bandTop && rect.y <= bandBottom);
20528
+ let columnX = detourColumnFor([from, to], side);
20529
+ while (true) {
20530
+ const blockers = bandObstacles.filter(
20531
+ (rect) => rect.x - VERTICAL_DETOUR_GAP < columnX && columnX < rect.x + rect.w + VERTICAL_DETOUR_GAP
20532
+ );
20533
+ if (blockers.length === 0) break;
20534
+ columnX = side === "left" ? Math.min(...blockers.map((rect) => rect.x - VERTICAL_DETOUR_GAP)) : Math.max(...blockers.map((rect) => rect.x + rect.w + VERTICAL_DETOUR_GAP));
20535
+ }
20536
+ const bendAY = lowIsA ? lowBendY : detourY;
20537
+ const bendBY = lowIsA ? detourY : lowBendY;
20538
+ const vertices = [
20539
+ A,
20540
+ { x: A.x, y: bendAY },
20541
+ { x: columnX, y: bendAY },
20542
+ { x: columnX, y: bendBY },
20543
+ { x: B.x, y: bendBY },
20544
+ B
20545
+ ];
20546
+ const d = verticesToPath(vertices);
20547
+ const high = lowIsA ? B : A;
20548
+ return {
20549
+ d,
20550
+ labelX: (high.x + columnX) / 2,
20551
+ crossings: countPathObstacleCrossings(d, obstacles),
20552
+ length: vertices.slice(1).reduce((sum, vertex, index) => {
20553
+ const previous = vertices[index];
20554
+ return sum + Math.hypot(vertex.x - previous.x, vertex.y - previous.y);
20555
+ }, 0)
20556
+ };
20557
+ }
20558
+ function chooseBackDetourCandidate(A, B, from, to, obstacles, detourY) {
20559
+ const left = makeBackDetourCandidate(A, B, from, to, obstacles, detourY, "left");
20560
+ const right = makeBackDetourCandidate(A, B, from, to, obstacles, detourY, "right");
20561
+ if (left.crossings !== right.crossings) return left.crossings < right.crossings ? left : right;
20562
+ if (Math.abs(left.length - right.length) >= 0.5) return left.length < right.length ? left : right;
20563
+ const preferred = Math.abs(A.x - B.x) < 0.5 ? "right" : A.x < B.x ? "left" : "right";
20564
+ return preferred === "left" ? left : right;
20565
+ }
20517
20566
  function rectGap(a, b) {
20518
20567
  const dx = Math.max(0, a.x - (b.x + b.w), b.x - (a.x + a.w));
20519
20568
  const dy = Math.max(0, a.y - (b.y + b.h), b.y - (a.y + a.h));
@@ -20964,7 +21013,7 @@ function routePath(from, to, fromSide, toSide, obstacles = [], detourSide, _from
20964
21013
  const detourY = Math.min(A.y, B.y) - 100;
20965
21014
  const corner = 14;
20966
21015
  const xDir = B.x > A.x ? 1 : -1;
20967
- return {
21016
+ const existing = {
20968
21017
  d: `M ${A.x} ${A.y}
20969
21018
  L ${A.x} ${detourY + corner}
20970
21019
  Q ${A.x} ${detourY}, ${A.x + xDir * corner} ${detourY}
@@ -20978,6 +21027,16 @@ function routePath(from, to, fromSide, toSide, obstacles = [], detourSide, _from
20978
21027
  labelY: detourY - labelInit,
20979
21028
  labelAnchor: "middle"
20980
21029
  };
21030
+ const existingCrossings = countPathObstacleCrossings(existing.d, obstacles);
21031
+ if (existingCrossings === 0) return existing;
21032
+ const candidate = chooseBackDetourCandidate(A, B, from, to, obstacles, detourY);
21033
+ if (candidate.crossings >= existingCrossings) return existing;
21034
+ return {
21035
+ d: candidate.d,
21036
+ labelX: candidate.labelX,
21037
+ labelY: detourY - labelInit,
21038
+ labelAnchor: "middle"
21039
+ };
20981
21040
  }
20982
21041
  const midX = fromSide === "left" || fromSide === "right" ? B.x : A.x;
20983
21042
  const midY = fromSide === "top" || fromSide === "bottom" ? B.y : A.y;