@cardenelabs/cdl 0.120.0 → 0.122.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 +75 -1
- package/README.md +73 -0
- package/SPEC.md +1 -1
- package/dist/index.cjs +470 -70
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +16 -10
- package/dist/index.d.ts +16 -10
- package/dist/index.js +470 -70
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +459 -61
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.js +459 -61
- package/dist/react.js.map +1 -1
- package/dist/{render-A5AKjNuy.d.cts → render-U_pC-Ksh.d.cts} +21 -6
- package/dist/{render-A5AKjNuy.d.ts → render-U_pC-Ksh.d.ts} +21 -6
- package/package.json +1 -1
- package/src/builder.ts +3 -2
- package/src/index.ts +1 -0
- package/src/kinds/dyn-shape.tsx +22 -9
- package/src/kinds/station.tsx +29 -0
- package/src/layout/collisions.ts +29 -20
- package/src/layout/curve-path.ts +125 -0
- package/src/layout/edges.ts +207 -39
- package/src/layout/head-label.ts +26 -12
- package/src/layout/metro-path.ts +184 -0
- package/src/layout/spec.ts +16 -1
- package/src/layout/tokens.ts +2 -0
- package/src/layout-with-validation.ts +10 -5
- package/src/layout.ts +22 -12
- package/src/render/edges.tsx +19 -3
- package/src/render/nodes.tsx +3 -0
- package/src/types.ts +22 -4
- package/src/visual-validate.ts +24 -4
package/src/layout/spec.ts
CHANGED
|
@@ -175,6 +175,7 @@ const KINDS_DRAWING_TITLE_OUTSIDE: ReadonlySet<string> = new Set([
|
|
|
175
175
|
"dyn-arc",
|
|
176
176
|
"dyn-wave",
|
|
177
177
|
"dyn-polygon",
|
|
178
|
+
"station",
|
|
178
179
|
...KINDS_DRAWING_OWN_PAYLOAD,
|
|
179
180
|
]);
|
|
180
181
|
|
|
@@ -199,7 +200,7 @@ export function drawsTitleOutsideBox(kind: string | undefined): boolean {
|
|
|
199
200
|
*/
|
|
200
201
|
const TITLE_FONT_SIZE_DEFAULT = 18;
|
|
201
202
|
const TITLE_FONT_SIZE: ReadonlyMap<string, number> = new Map<string, number>([
|
|
202
|
-
...(["dyn-rect", "dyn-circle", "dyn-arc", "dyn-wave", "dyn-polygon"] as const).map(
|
|
203
|
+
...(["dyn-rect", "dyn-circle", "dyn-arc", "dyn-wave", "dyn-polygon", "station"] as const).map(
|
|
203
204
|
(k) => [k, 13] as const,
|
|
204
205
|
),
|
|
205
206
|
...(
|
|
@@ -264,6 +265,19 @@ export function titleFontSize(kind: string | undefined): number {
|
|
|
264
265
|
return TITLE_FONT_SIZE.get(kind ?? "") ?? TITLE_FONT_SIZE_DEFAULT;
|
|
265
266
|
}
|
|
266
267
|
|
|
268
|
+
/**
|
|
269
|
+
* 題の幅を node の箱幅で測らない種別。
|
|
270
|
+
*
|
|
271
|
+
* 駅の名前は小さな丸の外に置き、丸の幅で切らない。箱幅 24 を上限にすると、通常の駅名まで
|
|
272
|
+
* `text-readability` が読めない語として挙げるため、幅を箱の中で測る軸からだけ外す。
|
|
273
|
+
*/
|
|
274
|
+
const KINDS_WITH_TITLE_WIDTH_OUTSIDE_NODE: ReadonlySet<string> = new Set(["station"]);
|
|
275
|
+
|
|
276
|
+
/** その node の題を箱幅に収める前提が成り立たないか */
|
|
277
|
+
export function skipsNodeTitleWidthCheck(kind: string | undefined): boolean {
|
|
278
|
+
return KINDS_WITH_TITLE_WIDTH_OUTSIDE_NODE.has(kind ?? "");
|
|
279
|
+
}
|
|
280
|
+
|
|
267
281
|
/**
|
|
268
282
|
* 題を箱の中に描く種別で、題が使えない左右の余白 (world)。
|
|
269
283
|
*
|
|
@@ -428,6 +442,7 @@ const KINDS_DRAWING_ONLY_SHAPE: readonly string[] = [
|
|
|
428
442
|
"dyn-arc",
|
|
429
443
|
"dyn-wave",
|
|
430
444
|
"dyn-polygon",
|
|
445
|
+
"station",
|
|
431
446
|
];
|
|
432
447
|
|
|
433
448
|
/**
|
package/src/layout/tokens.ts
CHANGED
|
@@ -140,6 +140,8 @@ export const TOKENS = {
|
|
|
140
140
|
// 箱としての最小 (80x40) より大きく、名前を持つ箱 (card 320x150) よりは明らかに小さい
|
|
141
141
|
"mark-start": { w: 96, h: 96 },
|
|
142
142
|
"mark-end": { w: 96, h: 96 },
|
|
143
|
+
// 路線図の駅 (#996) ... 正方形にして小さな丸が縦横どちらにも歪まないようにする。
|
|
144
|
+
station: { w: 24, h: 24 },
|
|
143
145
|
// 時系列のやり取り (#578)。 実寸は中身から決まる (`sequenceBoardMetrics`) ので、
|
|
144
146
|
// ここは書かなかった時の受け皿
|
|
145
147
|
"sequence-board": { w: 960, h: 640 },
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
* 持つ edge の labelOffsetX/Y を 0 にリセット (作者の手動 offset で bbox 範囲外に押し出された
|
|
15
15
|
* label を default 位置に戻す)
|
|
16
16
|
* - h2 node-shift-down ... node-overlap error node pair の下 node の stack を +1
|
|
17
|
-
* - h3 edge-back-detour ... edge-node-cross error edge の routing を
|
|
17
|
+
* - h3 edge-back-detour ... edge-node-cross error のうち、自動経路の edge の routing を
|
|
18
|
+
* "back-detour" に切替。 書き手が選んだ曲線と路線図の線は途中の箱を避けない約束なので変えない
|
|
18
19
|
*/
|
|
19
20
|
import type { CdlDiagram, CdlEdge, LaidDiagram } from "./types";
|
|
20
21
|
import { layout } from "./layout";
|
|
@@ -152,11 +153,15 @@ function applyHeuristics(
|
|
|
152
153
|
.filter((id): id is string => id !== null),
|
|
153
154
|
);
|
|
154
155
|
if (h3Edges.size > 0) {
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
156
|
+
const eligibleEdges = new Set(
|
|
157
|
+
edges
|
|
158
|
+
.filter((e) => h3Edges.has(e.id) && (e.routing === undefined || e.routing === "default"))
|
|
159
|
+
.map((e) => e.id),
|
|
158
160
|
);
|
|
159
|
-
if (
|
|
161
|
+
if (eligibleEdges.size > 0) {
|
|
162
|
+
edges = edges.map((e): CdlEdge =>
|
|
163
|
+
eligibleEdges.has(e.id) ? { ...e, routing: "back-detour" } : e,
|
|
164
|
+
);
|
|
160
165
|
applied.push("h3-edge-back-detour");
|
|
161
166
|
mutated = true;
|
|
162
167
|
}
|
package/src/layout.ts
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
import {
|
|
16
16
|
bowSharedStraightPaths,
|
|
17
17
|
fanIncomingEndpoints,
|
|
18
|
+
followLabelsToFannedTails,
|
|
18
19
|
hopEdgeCrossings,
|
|
19
20
|
layoutEdges,
|
|
20
21
|
pullOutgoingFirstCorner,
|
|
@@ -212,7 +213,8 @@ export function applyRowLevelNodeShifts<
|
|
|
212
213
|
* - node は lane 内 stack 順で縦並び、 各 stack 間 gap 80px、 上端 80px
|
|
213
214
|
* - lane label は lane 上部 30px 高
|
|
214
215
|
* - contain: true の lane は内部 node 全部を 24px padding で囲む boundary
|
|
215
|
-
* - edge routing は orthogonal L 字 (起点 side → 中継 → 終点 side)
|
|
216
|
+
* - edge routing は orthogonal L 字 (起点 side → 中継 → 終点 side)。`curve` は 3 次曲線、
|
|
217
|
+
* `metro` は横線と傾き ±1 の斜線を円弧で結ぶ
|
|
216
218
|
* - edge label は path 中点から path 進行方向の垂直方向に 50-80px、 node bbox を避ける
|
|
217
219
|
* - viewBox は cdl が auto 計算 (右端 +80 / 下端 +80)
|
|
218
220
|
*/
|
|
@@ -339,10 +341,15 @@ export function layout(diag: CdlDiagram): LaidDiagram {
|
|
|
339
341
|
|
|
340
342
|
// 同じ 2 点を結ぶ複数の線を弓なりに分ける (dragon #941)。 label と path が確定した後に動かす =
|
|
341
343
|
// 確定前に判定すると、 後から動く label の位置を見て分離を諦める。 座標を明示した edge
|
|
342
|
-
// (canvas pivot)
|
|
344
|
+
// (canvas pivot)、`curve`、`metro` は「指定した形が最終 path」 が公開契約なので対象から外す。
|
|
343
345
|
const positionedEdgeIds = new Set(
|
|
344
346
|
diag.edges
|
|
345
|
-
.filter(
|
|
347
|
+
.filter(
|
|
348
|
+
(e) =>
|
|
349
|
+
e.routing === "curve" ||
|
|
350
|
+
e.routing === "metro" ||
|
|
351
|
+
(e.posX1 !== undefined && e.posY1 !== undefined && e.posX2 !== undefined && e.posY2 !== undefined),
|
|
352
|
+
)
|
|
346
353
|
.map((e) => e.id),
|
|
347
354
|
);
|
|
348
355
|
bowSharedStraightPaths(edges, nodesFinal, positionedEdgeIds);
|
|
@@ -354,7 +361,7 @@ export function layout(diag: CdlDiagram): LaidDiagram {
|
|
|
354
361
|
pullOutgoingFirstCorner(edges, nodesFinal);
|
|
355
362
|
// 同じ辺に入る線の終点を辺に沿って離す (#611)。 経路を引き終えてから当てる = 終点を先に
|
|
356
363
|
// 動かすと引き方の分岐が変わり、 迂回していた線が箱を貫通する (実測で 0 件が 7 件になった)。
|
|
357
|
-
fanIncomingEndpoints(edges, nodesFinal);
|
|
364
|
+
const fannedTailShifts = fanIncomingEndpoints(edges, nodesFinal);
|
|
358
365
|
// 同じ通り道を並んで走る区間をずらす (#610)。 弓の後に当てる = 弓で分かれた線を数え直さない。
|
|
359
366
|
// 弧より先に当てる = ずらした後の形で交点を数えないと、 消えた交点に弧が残る。
|
|
360
367
|
//
|
|
@@ -364,6 +371,14 @@ export function layout(diag: CdlDiagram): LaidDiagram {
|
|
|
364
371
|
// 交わる所で上を通る側に弧を差し込む (#608)。 経路が確定してから当てる = 弓や迂回で
|
|
365
372
|
// 形が変わった後の交点を見る。 札を置く前に当てるので、 札は弧を含んだ経路の上に載る。
|
|
366
373
|
hopEdgeCrossings(edges);
|
|
374
|
+
// 終点を離す / 共有区間を分ける / 交差へ弧を入れる pass が全て終わった座標で札を見る (#1000)。
|
|
375
|
+
// 先に見ると、 その後に経路が動いて札が線を覆う形が戻る。
|
|
376
|
+
// 追従と最後の逃がし pass は、 `collectBBoxes` が含める下端の名札の箱上へのはみ出しと
|
|
377
|
+
// 縦列の題を同じ矩形で測る。 追従だけ箱本体で測ると、 置いた札を最後の pass が押し直す (#1002)。
|
|
378
|
+
const labelClearanceObstacles = collectBBoxes(containedLanesFinal, nodesFinal, []).filter(
|
|
379
|
+
(b) => b.kind === "node" || b.kind === "lane-label",
|
|
380
|
+
);
|
|
381
|
+
followLabelsToFannedTails(edges, labelClearanceObstacles, fannedTailShifts);
|
|
367
382
|
repositionParallelLabels(edges, nodesFinal);
|
|
368
383
|
// 縦にずらすだけでは逃げ場が無い札を、 自分の線に沿って動かす (#601)。 ここまでの
|
|
369
384
|
// 置き直しは全て縦方向で、 線に沿って動かす経路が無かった。 いま被っている札だけを
|
|
@@ -371,16 +386,11 @@ export function layout(diag: CdlDiagram): LaidDiagram {
|
|
|
371
386
|
slideLabelsAlongOwnPath(edges, nodesFinal);
|
|
372
387
|
// 経路を動かす pass が全て終わってから、 札を箱と札から離し直す (#659)。
|
|
373
388
|
//
|
|
374
|
-
// 上の chain 伝搬はここより前に走るため、
|
|
375
|
-
//
|
|
389
|
+
// 上の chain 伝搬はここより前に走るため、 その後の経路 pass が動かした最終経路との隙間を
|
|
390
|
+
// ここで見直す。
|
|
376
391
|
// ここで動かすのは札だけで、 箱も経路も触らない = 触ると直前の pass が確定させた形を崩す。
|
|
377
392
|
if (!isChainEdgeLabelDisabled()) {
|
|
378
|
-
edges = resolveLabelClearanceAfterPathPasses(
|
|
379
|
-
edges,
|
|
380
|
-
collectBBoxes(containedLanesFinal, nodesFinal, []).filter(
|
|
381
|
-
(b) => b.kind === "node" || b.kind === "lane-label",
|
|
382
|
-
),
|
|
383
|
-
);
|
|
393
|
+
edges = resolveLabelClearanceAfterPathPasses(edges, labelClearanceObstacles);
|
|
384
394
|
}
|
|
385
395
|
|
|
386
396
|
const bboxes = collectBBoxes(containedLanesFinal, nodesFinal, edges);
|
package/src/render/edges.tsx
CHANGED
|
@@ -110,14 +110,16 @@ export function CdlEdgeView({
|
|
|
110
110
|
// 貫通効果が必要な場合は別途 phase activation で表現する。
|
|
111
111
|
// 丸の通り道は線の両端から印の長さだけ縮める (#988)。 縮めないと丸が端の印の中まで
|
|
112
112
|
// 入り、輪郭だけの三角に濃い丸が重なって印に穴が開いたように見える。
|
|
113
|
-
// 点線の粒を `shrinkPathEnd`
|
|
114
|
-
|
|
113
|
+
// 点線の粒を `shrinkPathEnd` で隠しているのと同じ理由で、向きが両端にあるぶん対で縮める。
|
|
114
|
+
// 縮める幅は芯ではなく **玉の一番外の縁** で測る (#994) = 使う側が縁を足すと芯だけでは足りない
|
|
115
|
+
const 通り道を縮める = EDGE_HEAD_MARKER_SIZE.既定 + 走る丸の外側;
|
|
115
116
|
const glowPathD = shrinkPathStart(shrinkPathEnd(edge.d, 通り道を縮める), 通り道を縮める);
|
|
116
117
|
void isPassThrough;
|
|
117
118
|
|
|
118
119
|
return (
|
|
119
120
|
<g
|
|
120
121
|
data-cdl-edge={edge.id}
|
|
122
|
+
data-cdl-routing={edge.routing === "metro" ? "metro" : undefined}
|
|
121
123
|
data-cdl-from={edge.from}
|
|
122
124
|
data-cdl-to={edge.to}
|
|
123
125
|
data-cdl-active={active ? "true" : "false"}
|
|
@@ -267,7 +269,21 @@ function SolidEdgePath({
|
|
|
267
269
|
* 見えるので、印の長さに芯の半径を足した分だけ通り道を縮める。 ここに数を直に書くと、
|
|
268
270
|
* 丸を大きくした時に芯だけが印へはみ出す。
|
|
269
271
|
*/
|
|
270
|
-
export const 走る丸の半径 = { 外: 28, 中: 18, 芯:
|
|
272
|
+
export const 走る丸の半径 = { 外: 28, 中: 18, 芯: 12 } as const;
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* 使う側が芯に足す縁の太さの見込み (world、半分の値)。
|
|
276
|
+
*
|
|
277
|
+
* 描く側は芯を塗りだけで描くが、使う側は紙の色の縁を回して線から浮かせることがある
|
|
278
|
+
* (見本帳は 3.5 の縁を足しており、外側は 12 + 1.75 = 13.75 になる)。
|
|
279
|
+
* その縁は CSS で足されるので描く側からは見えない = 見込みとして持つしかない。
|
|
280
|
+
*
|
|
281
|
+
* 2 にするのは見本帳の 1.75 を丸めた値。 **これより太い縁を足すと玉が印に入る**。
|
|
282
|
+
*/
|
|
283
|
+
export const 走る丸の縁の見込み = 2;
|
|
284
|
+
|
|
285
|
+
/** 玉の一番外の縁までの長さ (world)。 通り道を端から縮める幅の基準になる */
|
|
286
|
+
export const 走る丸の外側 = 走る丸の半径.芯 + 走る丸の縁の見込み;
|
|
271
287
|
|
|
272
288
|
function EdgeGlowCircles({ color, edgeId }: { color: string; edgeId: string }): JSX.Element {
|
|
273
289
|
const ref = `#cdl-edge-${edgeId}`;
|
package/src/render/nodes.tsx
CHANGED
|
@@ -7,6 +7,7 @@ import { FunctionNode } from "../kinds/function";
|
|
|
7
7
|
import { StorageNode } from "../kinds/storage";
|
|
8
8
|
import { GenericNode } from "../kinds/generic";
|
|
9
9
|
import { MarkStartNode, MarkEndNode } from "../kinds/terminal-mark";
|
|
10
|
+
import { StationNode } from "../kinds/station";
|
|
10
11
|
import { DynShapeNode } from "../kinds/dyn-shape";
|
|
11
12
|
import { ChartLineNode, type ChartLineScale } from "../kinds/chart-line";
|
|
12
13
|
import { ChartPieNode } from "../kinds/chart-pie";
|
|
@@ -449,6 +450,8 @@ export function CdlNodeView({
|
|
|
449
450
|
return <MarkStartNode node={resolvedNode} active={active} />;
|
|
450
451
|
case "mark-end":
|
|
451
452
|
return <MarkEndNode node={resolvedNode} active={active} />;
|
|
453
|
+
case "station":
|
|
454
|
+
return <StationNode node={resolvedNode} active={active} />;
|
|
452
455
|
default:
|
|
453
456
|
// 新 NodeKind 25 種は GenericNode で汎用描画 (kind 別 icon / color / shape)
|
|
454
457
|
return <GenericNode node={resolvedNode} active={active} value={node.value} stateValues={stateValues} 値の名前={値の名前} />;
|
package/src/types.ts
CHANGED
|
@@ -186,6 +186,8 @@ export const NODE_KINDS = [
|
|
|
186
186
|
// 状態遷移図が使う。 印そのものが意味を持つので文字は描かない
|
|
187
187
|
"mark-start",
|
|
188
188
|
"mark-end",
|
|
189
|
+
// 路線図の駅 (#996) ... 線路上の点を小さな丸で示し、名前と説明は丸の外に置く
|
|
190
|
+
"station",
|
|
189
191
|
// 時系列のやり取り (#578) ... 1 つの箱が図を丸ごと描く。 縦が時間で、参加者は箱を持たない
|
|
190
192
|
"sequence-board",
|
|
191
193
|
] as const;
|
|
@@ -207,6 +209,18 @@ export type Side = "top" | "right" | "bottom" | "left";
|
|
|
207
209
|
*/
|
|
208
210
|
export type EdgeStyle = "solid" | "dotted-flow" | "dashed";
|
|
209
211
|
|
|
212
|
+
/**
|
|
213
|
+
* 線の道筋。
|
|
214
|
+
*
|
|
215
|
+
* - `"default"` ... 自動。 箱を避け、角を丸めた直角の線を引く。 書かない時も同じ
|
|
216
|
+
* - `"back-detour"` ... 箱の上を回って前の箱へ戻る
|
|
217
|
+
* - `"curve"` ... 始点から水平に出て終点へ水平に入る 3 次曲線。 途中の箱は避けず、
|
|
218
|
+
* 2 点が同じ高さなら直線になる。 終点の矢印は水平を向く
|
|
219
|
+
* - `"metro"` ... 横線と傾き ±1 の斜線だけで結び、角を半径 r の円弧で丸める。
|
|
220
|
+
* 途中の箱は避けず、2 点が同じ高さなら横 1 本になる
|
|
221
|
+
*/
|
|
222
|
+
export type EdgeRouting = "default" | "back-detour" | "curve" | "metro";
|
|
223
|
+
|
|
210
224
|
/** 辺の役目 (#618)。 `main` = 図の主となる道 */
|
|
211
225
|
export type EdgeRole = "main";
|
|
212
226
|
|
|
@@ -506,8 +520,8 @@ export type CdlNode = {
|
|
|
506
520
|
* 変わるのは kind の既定色を使って描かれる部分で、 枠線 / eyebrow の文字色 / icon /
|
|
507
521
|
* 強調表示中の行の値。 本文の文字色と背景は変わらない。
|
|
508
522
|
*
|
|
509
|
-
* 効くのは `actor` / `function` / `storage` / `event` / `card
|
|
510
|
-
*
|
|
523
|
+
* 効くのは `actor` / `function` / `storage` / `event` / `card`、`person` 以降の汎用 kind、
|
|
524
|
+
* 流れの印、路線図の駅。 それ以外は自前の配色を持つため指定しても変わらず、検査も通す。
|
|
511
525
|
* 一覧は `test/node-styling.test.tsx` が実装から計算した集合と突き合わせている。
|
|
512
526
|
*
|
|
513
527
|
* 既定色と同じ名前を明示指定しても色が変わる kind がある (既定色の hex が `render/tone.ts`
|
|
@@ -867,8 +881,12 @@ export type CdlEdge = {
|
|
|
867
881
|
labelOffsetX?: number;
|
|
868
882
|
/** label の y 方向 fine offset (default 0、 + で下、 - で上) */
|
|
869
883
|
labelOffsetY?: number;
|
|
870
|
-
/**
|
|
871
|
-
|
|
884
|
+
/**
|
|
885
|
+
* 線の道筋。 `default` は箱を避ける自動経路、 `back-detour` は箱の上を回る戻り線、
|
|
886
|
+
* `curve` は途中の箱を避けず、両端で水平になる 3 次曲線。 `metro` は途中の箱を避けず、
|
|
887
|
+
* 横線と傾き ±1 の斜線を半径 r の円弧で結ぶ。2 点が同じ高さなら横 1 本。書かなければ `default`。
|
|
888
|
+
*/
|
|
889
|
+
routing?: EdgeRouting;
|
|
872
890
|
/**
|
|
873
891
|
* CAR-549 SSOT ... label を edge path 上に「被せる」 か「離す」 かの制御 flag。
|
|
874
892
|
* default (false or undefined) = LABEL_INIT_CLEARANCE (34 world、 CAR-630 で 26 → 34 復帰) 離して配置。
|
package/src/visual-validate.ts
CHANGED
|
@@ -143,6 +143,7 @@ import {
|
|
|
143
143
|
TITLE_ROW_MIN_GAP,
|
|
144
144
|
rendersTitleText,
|
|
145
145
|
fitsOwnTitleWidth,
|
|
146
|
+
skipsNodeTitleWidthCheck,
|
|
146
147
|
drawsTitleOutsideBox,
|
|
147
148
|
titleScalesToFit,
|
|
148
149
|
MIN_TITLE_FONT_SIZE,
|
|
@@ -1980,6 +1981,8 @@ function runAxes(
|
|
|
1980
1981
|
const LIFELINE_W_THRESHOLD = 8;
|
|
1981
1982
|
for (const n of laid.nodes) {
|
|
1982
1983
|
if (n.w < LIFELINE_W_THRESHOLD) continue;
|
|
1984
|
+
// 駅は文字を収める箱ではなく線路上の印なので、箱向けの最小寸法を要求しない。
|
|
1985
|
+
if (n.kind === "station") continue;
|
|
1983
1986
|
if (n.w < MIN_NODE_W || n.h < MIN_NODE_H) {
|
|
1984
1987
|
push(
|
|
1985
1988
|
"node-visibility",
|
|
@@ -2016,6 +2019,8 @@ function runAxes(
|
|
|
2016
2019
|
// 自分で幅に合わせて切る種別も同じ。 字数から幅を出す下の近似は字の大きさ 22 を前提に
|
|
2017
2020
|
// しており、 それより小さい字で描く種別に当てると偽陽性になる (#469)
|
|
2018
2021
|
if (fitsOwnTitleWidth(n.kind)) continue;
|
|
2022
|
+
// 箱の外に出す駅名は小さな丸の幅で切らないため、箱幅を上限にする前提が成り立たない。
|
|
2023
|
+
if (skipsNodeTitleWidthCheck(n.kind)) continue;
|
|
2019
2024
|
// 常に非表示の node の文字は画面に出ないので、 切れようがない。
|
|
2020
2025
|
if (!isRenderedNode(n)) continue;
|
|
2021
2026
|
const titleTrimmed = n.title?.trim() ?? "";
|
|
@@ -3111,6 +3116,13 @@ function runAxes(
|
|
|
3111
3116
|
for (let i = 0; i + 1 < sorted.length; i++) {
|
|
3112
3117
|
const upper = sorted[i]!;
|
|
3113
3118
|
const lower = sorted[i + 1]!;
|
|
3119
|
+
// 横の範囲が重ならない箱は同じ lane に横並びで置かれた組なので、縦の隙間を測らない。
|
|
3120
|
+
if (
|
|
3121
|
+
upper.cx + upper.w / 2 <= lower.cx - lower.w / 2 ||
|
|
3122
|
+
lower.cx + lower.w / 2 <= upper.cx - upper.w / 2
|
|
3123
|
+
) {
|
|
3124
|
+
continue;
|
|
3125
|
+
}
|
|
3114
3126
|
const upperBottom = upper.cy + upper.h / 2;
|
|
3115
3127
|
const lowerTop = lower.cy - lower.h / 2;
|
|
3116
3128
|
const gap = lowerTop - upperBottom;
|
|
@@ -4214,17 +4226,25 @@ function runAxes(
|
|
|
4214
4226
|
y1: number;
|
|
4215
4227
|
x2: number;
|
|
4216
4228
|
y2: number;
|
|
4217
|
-
cmd: "L" | "Q" | "C";
|
|
4229
|
+
cmd: "L" | "Q" | "C" | "A";
|
|
4218
4230
|
ctrl?: ReadonlyArray<{ x: number; y: number }>;
|
|
4219
4231
|
}>,
|
|
4220
4232
|
peakY: number,
|
|
4221
4233
|
): Array<{ minX: number; maxX: number }> => {
|
|
4222
4234
|
const out: Array<{ minX: number; maxX: number }> = [];
|
|
4235
|
+
let 円弧の始点: { x: number; y: number } | null = null;
|
|
4223
4236
|
for (const s of segs) {
|
|
4224
|
-
if (
|
|
4225
|
-
|
|
4237
|
+
if (s.cmd === "A") {
|
|
4238
|
+
// 飛び越しの弧は同じ高さの通り道の上の小さな山で、山の前後は 1 本の通り道になる。
|
|
4239
|
+
円弧の始点 ??= { x: s.x1, y: s.y1 };
|
|
4240
|
+
continue;
|
|
4241
|
+
}
|
|
4242
|
+
const 始点 = 円弧の始点 ?? { x: s.x1, y: s.y1 };
|
|
4243
|
+
円弧の始点 = null;
|
|
4244
|
+
if (Math.abs(始点.y - s.y2) > 1) continue;
|
|
4245
|
+
if (Math.abs(始点.y - peakY) > 1) continue;
|
|
4226
4246
|
if (s.cmd !== "L" && !(s.ctrl ?? []).every((c) => Math.abs(c.y - peakY) <= 1)) continue;
|
|
4227
|
-
out.push({ minX: Math.min(
|
|
4247
|
+
out.push({ minX: Math.min(始点.x, s.x2), maxX: Math.max(始点.x, s.x2) });
|
|
4228
4248
|
}
|
|
4229
4249
|
return out;
|
|
4230
4250
|
};
|