@cardenelabs/cdl 0.64.0 → 0.65.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 +76 -1
- package/dist/index.cjs +43 -28
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +43 -28
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +43 -28
- package/dist/react.cjs.map +1 -1
- package/dist/react.js +43 -28
- package/dist/react.js.map +1 -1
- package/package.json +1 -1
- package/src/layout/edges.ts +46 -27
- package/src/layout/spec.ts +59 -1
- package/src/visual-validate.ts +12 -21
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,79 @@ CDL (Chainome Diagram Language) の主要変更履歴。
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [0.65.0] - 2026-09-15
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **横に並んだ箱の高さが近い時に、矢印の角が裏返るのを直した** (#865)
|
|
13
|
+
|
|
14
|
+
横に並んだ箱を繋ぐ経路は「横に走る → 縦に折れる → 横に走る」 の L 字で、角は「手前で 14 だけ直線を止め、弧で
|
|
15
|
+
14 先へ抜ける」 形で丸める。 縦の走りは両端の 2 角が 14 ずつ使うため 28 が要るが、**その長さがあるかを
|
|
16
|
+
一度も見ていなかった**。
|
|
17
|
+
|
|
18
|
+
2 つの箱の中心の高さが 28 より近いと、止めた点が走りの向こう側へ回り込む。 直線が逆走し、角の外へ突起が出る。
|
|
19
|
+
中心の高さが 506 と 516 の対で実測した形。
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
変更前 ... M 360 506 L 486 506 Q 500 506, 500 520 L 500 502 Q 500 516, 514 516 L 640 516
|
|
23
|
+
変更後 ... M 360 506 L 495 506 Q 500 506, 500 511 L 500 511 Q 500 516, 505 516 L 640 516
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
変更前の縦の走りは `520` から `502` へ 18 戻っている。 進む向きは下 (506 → 516) なので逆走。
|
|
27
|
+
|
|
28
|
+
丸みを、その角が使える長さで頭打ちにする。 縦の走りは両端の 2 角で使うので半分、横の走りは片端の 1 角しか
|
|
29
|
+
使わないのでそのまま渡す。
|
|
30
|
+
|
|
31
|
+
| 走り | 渡す長さ | 効く場面 |
|
|
32
|
+
|---|---|---|
|
|
33
|
+
| 縦 (折れる位置の上下) | `|B.y - A.y| / 2` | 中心の高さが 28 より近い対 |
|
|
34
|
+
| 横 (箱から折れる位置まで) | `|midX - A.x|` と `|B.x - midX|` | 箱どうしの隙間が 28 より狭い対 |
|
|
35
|
+
|
|
36
|
+
頭打ちは `midX` (折れる位置) が決まってから計算する。 説明文を避ける繰り返しが `midX` を中央から動かすため、
|
|
37
|
+
中央のつもりで先に計算すると、動いた後の横の走りを測り損ねる。
|
|
38
|
+
|
|
39
|
+
同じ規則は経路を組み直す側 (`verticesToPath`) が既に持っており、L 字の分岐だけが持っていなかった。
|
|
40
|
+
丸みが足りている対 (中心の高さの差 28 以上、隙間 28 以上) の `d` は 1 文字も変わらない。
|
|
41
|
+
|
|
42
|
+
dragon の見本帳で、高さの違う部品どうしを矢印で繋ぐ形が出せなかった原因 (dragon#2012)。
|
|
43
|
+
|
|
44
|
+
### Fixed (公開作業の取りこぼし)
|
|
45
|
+
|
|
46
|
+
- **`v0.64.1` の tag と比較 link を後から付けた**
|
|
47
|
+
|
|
48
|
+
0.64.1 は npm に公開済だったが、tag も比較 link も残っていなかった。 版から commit を辿れない状態で、
|
|
49
|
+
変更履歴の検査 3 件が落ちたままになっていた。 tag は公開済の commit (`f2a4be9`) に付けた。
|
|
50
|
+
|
|
51
|
+
## [0.64.1] - 2026-09-15
|
|
52
|
+
|
|
53
|
+
### Fixed
|
|
54
|
+
|
|
55
|
+
- **別の段に置いた lane の枠線を、説明文が跨いだと指摘しないようにした** (#863)
|
|
56
|
+
|
|
57
|
+
検証器の軸 64 (`lane-border-clearance`) は説明文の箱と lane の左右の枠線を横の範囲だけで比べ、全ての lane を
|
|
58
|
+
「図の上から下まで続く列」 として扱っていた。 縦の範囲を持つ lane の真下や真上に別の段があると、その段の
|
|
59
|
+
説明文が枠線の延長上に来ただけで指摘していた。 枠線はそこに描かれていないので、見た目は何も跨いでいない。
|
|
60
|
+
|
|
61
|
+
縦の範囲を持つ lane は、説明文と縦に重なる時だけ比べる。
|
|
62
|
+
|
|
63
|
+
| lane | 縦の範囲 | 比べ方 |
|
|
64
|
+
|---|---|---|
|
|
65
|
+
| `contain: true` | 中の node を囲む枠 (`y` / `height`) | 枠と縦に重なる説明文だけ |
|
|
66
|
+
| `posX` / `posY` / `posH` を揃えた lane | 置いた `posY` から `posH` まで | 同上 |
|
|
67
|
+
| 上の 2 つ以外 | 持たない (`height` は名札の帯の高さ) | 変更前と同じく上下いっぱいの列 |
|
|
68
|
+
|
|
69
|
+
範囲を持つ lane でも、中の node が範囲の外にあれば その node の上下まで範囲を広げる (高さを指定した lane に
|
|
70
|
+
座標を持たない node を入れると、node は段の位置に並んで範囲の外に出るため)。
|
|
71
|
+
|
|
72
|
+
判定は `rectCrossesLaneBorder` (`layout/spec.ts`) に 1 本化し、engine が説明文の置き場所を選ぶ 2 か所
|
|
73
|
+
(左への迂回 `leftRouteIsBlocked` と説明文の反転 `flipLabelToFreeSide`) も同じ関数を呼ぶ。 これまでは
|
|
74
|
+
「検証器と同一規約」 と書きながら別々に実装しており、検証器だけを直すと engine が別の段の枠線を避け続けて
|
|
75
|
+
空いている側へ説明文を移さなくなる。
|
|
76
|
+
|
|
77
|
+
dragon のカタログ 502 図で配置 (edge の経路と説明文の位置) は 1 図も変わらない。 効くのは部品のように
|
|
78
|
+
`posX` / `posY` / `posH` で段に分けて置いた図で、dragon は部品 80 種を 2 段に並べた時の指摘が消える
|
|
79
|
+
(dragon#1993)。
|
|
80
|
+
|
|
8
81
|
## [0.64.0] - 2026-09-14
|
|
9
82
|
|
|
10
83
|
### Fixed
|
|
@@ -2697,7 +2770,9 @@ Initial OSS release.
|
|
|
2697
2770
|
使わない = annotated tag を push しても GitHub Release は作られず、 Release を作っていない版で
|
|
2698
2771
|
行き止まりになる。
|
|
2699
2772
|
|
|
2700
|
-
[Unreleased]: https://github.com/cardene777/cdl/compare/v0.
|
|
2773
|
+
[Unreleased]: https://github.com/cardene777/cdl/compare/v0.65.0...HEAD
|
|
2774
|
+
[0.65.0]: https://github.com/cardene777/cdl/compare/v0.64.1...v0.65.0
|
|
2775
|
+
[0.64.1]: https://github.com/cardene777/cdl/compare/v0.64.0...v0.64.1
|
|
2701
2776
|
[0.64.0]: https://github.com/cardene777/cdl/compare/v0.63.0...v0.64.0
|
|
2702
2777
|
[0.63.0]: https://github.com/cardene777/cdl/compare/v0.62.0...v0.63.0
|
|
2703
2778
|
[0.62.0]: https://github.com/cardene777/cdl/compare/v0.61.0...v0.62.0
|
package/dist/index.cjs
CHANGED
|
@@ -12557,6 +12557,25 @@ var ARROW_ENDPOINT_CENTER_TOL = 8;
|
|
|
12557
12557
|
var ROW_GAP_VARIANCE_TOL = 40;
|
|
12558
12558
|
var COLUMN_GAP_VARIANCE_TOL = 40;
|
|
12559
12559
|
var LANE_BORDER_CLEARANCE_TOL = 2;
|
|
12560
|
+
function laneVerticalRange(lane, nodes) {
|
|
12561
|
+
const pinnedWithHeight = lane.posX !== void 0 && lane.posY !== void 0 && lane.posH !== void 0;
|
|
12562
|
+
if (!lane.contain && !pinnedWithHeight) return void 0;
|
|
12563
|
+
let top = lane.y;
|
|
12564
|
+
let bottom = lane.y + lane.height;
|
|
12565
|
+
for (const n of nodes) {
|
|
12566
|
+
if (n.lane !== lane.id) continue;
|
|
12567
|
+
top = Math.min(top, n.cy - n.h / 2);
|
|
12568
|
+
bottom = Math.max(bottom, n.cy + n.h / 2);
|
|
12569
|
+
}
|
|
12570
|
+
return { top, bottom };
|
|
12571
|
+
}
|
|
12572
|
+
function rectCrossesLaneBorder(rect, lane, range) {
|
|
12573
|
+
if (range !== void 0 && (rect.y + rect.h <= range.top || rect.y >= range.bottom)) return void 0;
|
|
12574
|
+
const crosses = (lineX) => rect.x < lineX - LANE_BORDER_CLEARANCE_TOL && rect.x + rect.w > lineX + LANE_BORDER_CLEARANCE_TOL;
|
|
12575
|
+
if (crosses(lane.x)) return "left";
|
|
12576
|
+
if (crosses(lane.x + lane.width)) return "right";
|
|
12577
|
+
return void 0;
|
|
12578
|
+
}
|
|
12560
12579
|
var NEAR_COLLISION_POLICY = {
|
|
12561
12580
|
"node|node": 70,
|
|
12562
12581
|
"node|edge-label": 32,
|
|
@@ -18148,6 +18167,7 @@ function runAxes(laid, diag, violations, counts, push, profile, skippedAxes, \u5
|
|
|
18148
18167
|
}
|
|
18149
18168
|
}
|
|
18150
18169
|
{
|
|
18170
|
+
const rangeByLane = new Map(laid.lanes.map((lane) => [lane.id, laneVerticalRange(lane, laid.nodes)]));
|
|
18151
18171
|
for (const e of laid.edges) {
|
|
18152
18172
|
if (!hasRenderedLabel(e)) continue;
|
|
18153
18173
|
const fromNode = nodeById.get(e.from);
|
|
@@ -18156,21 +18176,14 @@ function runAxes(laid, diag, violations, counts, push, profile, skippedAxes, \u5
|
|
|
18156
18176
|
const allowedLanes = /* @__PURE__ */ new Set();
|
|
18157
18177
|
if (fromNode) allowedLanes.add(fromNode.lane);
|
|
18158
18178
|
if (toNode) allowedLanes.add(toNode.lane);
|
|
18159
|
-
const
|
|
18160
|
-
const anchor = e.labelAnchor;
|
|
18161
|
-
const boxLeftX = anchor === "start" ? 0 : anchor === "end" ? -boxW : -boxW / 2;
|
|
18162
|
-
const bx = e.labelX + boxLeftX;
|
|
18163
|
-
const bxRight = bx + boxW;
|
|
18179
|
+
const box = computeLabelBBoxWorld(e);
|
|
18164
18180
|
for (const lane of laid.lanes) {
|
|
18165
18181
|
if (allowedLanes.has(lane.id)) continue;
|
|
18166
|
-
const
|
|
18167
|
-
|
|
18168
|
-
const crossesLeft = bx < laneLeft - LANE_BORDER_CLEARANCE_TOL && bxRight > laneLeft + LANE_BORDER_CLEARANCE_TOL;
|
|
18169
|
-
const crossesRight = bx < laneRight - LANE_BORDER_CLEARANCE_TOL && bxRight > laneRight + LANE_BORDER_CLEARANCE_TOL;
|
|
18170
|
-
if (crossesLeft || crossesRight) {
|
|
18182
|
+
const side = rectCrossesLaneBorder(box, lane, rangeByLane.get(lane.id));
|
|
18183
|
+
if (side !== void 0) {
|
|
18171
18184
|
push(
|
|
18172
18185
|
"lane-border-clearance",
|
|
18173
|
-
`edge "${e.id}" label bbox (${
|
|
18186
|
+
`edge "${e.id}" label bbox (${box.x.toFixed(0)}..${(box.x + box.w).toFixed(0)}) \u304C\u975E\u767A\u7740 lane "${lane.id}" (${lane.x.toFixed(0)}..${(lane.x + lane.width).toFixed(0)}) \u306E ${side === "left" ? "\u5DE6" : "\u53F3"} border \u3092\u8CAB\u901A`
|
|
18174
18187
|
);
|
|
18175
18188
|
break;
|
|
18176
18189
|
}
|
|
@@ -18744,6 +18757,9 @@ var VERTICAL_DETOUR_CORNER = 14;
|
|
|
18744
18757
|
function verticalCornerDir(from, to) {
|
|
18745
18758
|
return to < from ? -1 : 1;
|
|
18746
18759
|
}
|
|
18760
|
+
function cornerRadiusFor(\u534A\u5F84, ...\u8D70\u308A) {
|
|
18761
|
+
return Math.min(\u534A\u5F84, ...\u8D70\u308A.map((v) => Math.abs(v)));
|
|
18762
|
+
}
|
|
18747
18763
|
var VERTICAL_DETOUR_EXIT = 45;
|
|
18748
18764
|
function nearestBoxDistance(anchorX, dir, verticalTop, verticalBottom, boxes) {
|
|
18749
18765
|
let nearest = Infinity;
|
|
@@ -18793,6 +18809,7 @@ function layoutEdges(diag, nodes, lanes = []) {
|
|
|
18793
18809
|
const labelMarginExtra = (diag.viewport?.labelMargin ?? diag.viewport?.gap ?? DEFAULT_LABEL_MARGIN) - DEFAULT_LABEL_MARGIN;
|
|
18794
18810
|
const nmap = new Map(nodes.map((n) => [n.id, n]));
|
|
18795
18811
|
const lmap = new Map(lanes.map((l) => [l.id, l]));
|
|
18812
|
+
const laneRangeById = new Map(lanes.map((l) => [l.id, laneVerticalRange(l, nodes)]));
|
|
18796
18813
|
const positionedEdges = [];
|
|
18797
18814
|
const autoEdges = [];
|
|
18798
18815
|
const selfLoopEdges = [];
|
|
@@ -19017,7 +19034,7 @@ function layoutEdges(diag, nodes, lanes = []) {
|
|
|
19017
19034
|
nodes.filter((n) => n.id === c.from || n.id === c.to).map((n) => n.lane)
|
|
19018
19035
|
);
|
|
19019
19036
|
return lanes.some(
|
|
19020
|
-
(l) => !endpointLanes.has(l.id) && (
|
|
19037
|
+
(l) => !endpointLanes.has(l.id) && rectCrossesLaneBorder(pill, l, laneRangeById.get(l.id)) !== void 0
|
|
19021
19038
|
);
|
|
19022
19039
|
};
|
|
19023
19040
|
const assignDetourSides = () => {
|
|
@@ -19237,7 +19254,7 @@ function layoutEdges(diag, nodes, lanes = []) {
|
|
|
19237
19254
|
);
|
|
19238
19255
|
const crossesLaneBorder = lanes.some((l) => {
|
|
19239
19256
|
if (endpointLanes.has(l.id)) return false;
|
|
19240
|
-
return
|
|
19257
|
+
return rectCrossesLaneBorder(alt, l, laneRangeById.get(l.id)) !== void 0;
|
|
19241
19258
|
});
|
|
19242
19259
|
if (crossesLaneBorder) continue;
|
|
19243
19260
|
r.baseLabelX = r.labelAltX;
|
|
@@ -19528,9 +19545,6 @@ function segmentsBBox2(segs) {
|
|
|
19528
19545
|
}
|
|
19529
19546
|
return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
|
|
19530
19547
|
}
|
|
19531
|
-
function crossesVerticalLine(rect, lineX) {
|
|
19532
|
-
return rect.x < lineX - LANE_BORDER_CLEARANCE_TOL && rect.x + rect.w > lineX + LANE_BORDER_CLEARANCE_TOL;
|
|
19533
|
-
}
|
|
19534
19548
|
function distributeBySides(list) {
|
|
19535
19549
|
const from = list[0].from;
|
|
19536
19550
|
const tol = 20;
|
|
@@ -19753,6 +19767,7 @@ function routePath(from, to, fromSide, toSide, obstacles = [], detourSide, _from
|
|
|
19753
19767
|
iter++;
|
|
19754
19768
|
}
|
|
19755
19769
|
}
|
|
19770
|
+
const lCorner = cornerRadiusFor(corner, (B.y - A.y) / 2, midX2 - A.x, B.x - midX2);
|
|
19756
19771
|
let verticalMidY = (A.y + B.y) / 2;
|
|
19757
19772
|
if (labelHalfW > 0) {
|
|
19758
19773
|
const labelBoxH = 36;
|
|
@@ -19790,10 +19805,10 @@ function routePath(from, to, fromSide, toSide, obstacles = [], detourSide, _from
|
|
|
19790
19805
|
}
|
|
19791
19806
|
}
|
|
19792
19807
|
if (edge?.overlay) {
|
|
19793
|
-
const exitEndX = midX2 - xDir *
|
|
19794
|
-
const enterStartX = midX2 + xDir *
|
|
19795
|
-
const midStartY = A.y + yDir *
|
|
19796
|
-
const midEndY = B.y - yDir *
|
|
19808
|
+
const exitEndX = midX2 - xDir * lCorner;
|
|
19809
|
+
const enterStartX = midX2 + xDir * lCorner;
|
|
19810
|
+
const midStartY = A.y + yDir * lCorner;
|
|
19811
|
+
const midEndY = B.y - yDir * lCorner;
|
|
19797
19812
|
const segments = [
|
|
19798
19813
|
{ len: Math.abs(exitEndX - A.x), midXVal: (A.x + exitEndX) / 2, midYVal: A.y },
|
|
19799
19814
|
{ len: Math.abs(midEndY - midStartY), midXVal: midX2, midYVal: (midStartY + midEndY) / 2 },
|
|
@@ -19802,10 +19817,10 @@ function routePath(from, to, fromSide, toSide, obstacles = [], detourSide, _from
|
|
|
19802
19817
|
const longest = segments.reduce((a, b) => b.len > a.len ? b : a);
|
|
19803
19818
|
return {
|
|
19804
19819
|
d: `M ${A.x} ${A.y}
|
|
19805
|
-
L ${midX2 - xDir *
|
|
19806
|
-
Q ${midX2} ${A.y}, ${midX2} ${A.y + yDir *
|
|
19807
|
-
L ${midX2} ${B.y - yDir *
|
|
19808
|
-
Q ${midX2} ${B.y}, ${midX2 + xDir *
|
|
19820
|
+
L ${midX2 - xDir * lCorner} ${A.y}
|
|
19821
|
+
Q ${midX2} ${A.y}, ${midX2} ${A.y + yDir * lCorner}
|
|
19822
|
+
L ${midX2} ${B.y - yDir * lCorner}
|
|
19823
|
+
Q ${midX2} ${B.y}, ${midX2 + xDir * lCorner} ${B.y}
|
|
19809
19824
|
L ${B.x} ${B.y}`,
|
|
19810
19825
|
labelX: longest.midXVal,
|
|
19811
19826
|
labelY: longest.midYVal,
|
|
@@ -19814,10 +19829,10 @@ function routePath(from, to, fromSide, toSide, obstacles = [], detourSide, _from
|
|
|
19814
19829
|
}
|
|
19815
19830
|
return {
|
|
19816
19831
|
d: `M ${A.x} ${A.y}
|
|
19817
|
-
L ${midX2 - xDir *
|
|
19818
|
-
Q ${midX2} ${A.y}, ${midX2} ${A.y + yDir *
|
|
19819
|
-
L ${midX2} ${B.y - yDir *
|
|
19820
|
-
Q ${midX2} ${B.y}, ${midX2 + xDir *
|
|
19832
|
+
L ${midX2 - xDir * lCorner} ${A.y}
|
|
19833
|
+
Q ${midX2} ${A.y}, ${midX2} ${A.y + yDir * lCorner}
|
|
19834
|
+
L ${midX2} ${B.y - yDir * lCorner}
|
|
19835
|
+
Q ${midX2} ${B.y}, ${midX2 + xDir * lCorner} ${B.y}
|
|
19821
19836
|
L ${B.x} ${B.y}`,
|
|
19822
19837
|
labelX: midX2,
|
|
19823
19838
|
labelY: verticalMidY,
|