@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
|
@@ -118,7 +118,7 @@ declare function resolveLang(value: unknown): CdlLang;
|
|
|
118
118
|
declare const TONES: readonly ["accent", "teal", "success", "error", "warning", "info"];
|
|
119
119
|
type Tone = (typeof TONES)[number];
|
|
120
120
|
/** 描画できる箱の種類。 検査・記法一覧・型はこの 1 つの列を出所にする。 */
|
|
121
|
-
declare const NODE_KINDS: readonly ["dyn-rect", "dyn-circle", "dyn-arc", "dyn-wave", "dyn-polygon", "actor", "function", "storage", "event", "card", "person", "user-group", "admin", "developer", "external-user", "database", "cache", "queue", "message-bus", "cloud", "cdn", "service", "api", "frontend", "backend", "webhook", "microservice", "signer", "oracle", "merkle-tree", "decision", "chart-pie", "chart-line", "chart-bar", "chart-gauge", "chart-radial", "chart-stat", "chart-waffle", "chart-stacked-bar", "chart-slope", "gantt-timeline", "mind-map", "funnel-stages", "quadrant-matrix", "tree-hierarchy", "journey-map", "shape-file", "shape-folder", "shape-cloud", "shape-cylinder", "shape-hexagon", "shape-diamond", "shape-stack", "shape-person", "shape-window", "shape-terminal", "shape-code-block", "shape-kanban-card", "shape-message-bubble", "shape-gear", "shape-server-rack", "shape-network-node", "shape-mobile-device", "shape-iot-sensor", "shape-robot-arm", "shape-satellite", "shape-smart-contract", "shape-blockchain-block", "shape-rpc-node", "shape-wallet", "shape-nft", "shape-token", "shape-blockchain", "shape-bitcoin-chain", "shape-ethereum-chain", "shape-blockchain-node", "shape-bank", "shape-trust-bank", "shape-payment-provider", "shape-credit-card", "shape-brokerage", "shape-exchange", "shape-atm", "shape-website", "shape-storefront", "shape-warehouse", "shape-online-shop", "shape-cdn-edge", "shape-api-gateway", "shape-auditor", "shape-regulator", "shape-notary", "shape-lawyer", "shape-trader", "shape-customer-service", "mark-start", "mark-end", "sequence-board"];
|
|
121
|
+
declare const NODE_KINDS: readonly ["dyn-rect", "dyn-circle", "dyn-arc", "dyn-wave", "dyn-polygon", "actor", "function", "storage", "event", "card", "person", "user-group", "admin", "developer", "external-user", "database", "cache", "queue", "message-bus", "cloud", "cdn", "service", "api", "frontend", "backend", "webhook", "microservice", "signer", "oracle", "merkle-tree", "decision", "chart-pie", "chart-line", "chart-bar", "chart-gauge", "chart-radial", "chart-stat", "chart-waffle", "chart-stacked-bar", "chart-slope", "gantt-timeline", "mind-map", "funnel-stages", "quadrant-matrix", "tree-hierarchy", "journey-map", "shape-file", "shape-folder", "shape-cloud", "shape-cylinder", "shape-hexagon", "shape-diamond", "shape-stack", "shape-person", "shape-window", "shape-terminal", "shape-code-block", "shape-kanban-card", "shape-message-bubble", "shape-gear", "shape-server-rack", "shape-network-node", "shape-mobile-device", "shape-iot-sensor", "shape-robot-arm", "shape-satellite", "shape-smart-contract", "shape-blockchain-block", "shape-rpc-node", "shape-wallet", "shape-nft", "shape-token", "shape-blockchain", "shape-bitcoin-chain", "shape-ethereum-chain", "shape-blockchain-node", "shape-bank", "shape-trust-bank", "shape-payment-provider", "shape-credit-card", "shape-brokerage", "shape-exchange", "shape-atm", "shape-website", "shape-storefront", "shape-warehouse", "shape-online-shop", "shape-cdn-edge", "shape-api-gateway", "shape-auditor", "shape-regulator", "shape-notary", "shape-lawyer", "shape-trader", "shape-customer-service", "mark-start", "mark-end", "station", "sequence-board"];
|
|
122
122
|
type NodeKind = (typeof NODE_KINDS)[number];
|
|
123
123
|
type Side = "top" | "right" | "bottom" | "left";
|
|
124
124
|
/**
|
|
@@ -133,6 +133,17 @@ type Side = "top" | "right" | "bottom" | "left";
|
|
|
133
133
|
* ER の「識別しない」 のように **刻みが意味を持つ関係** はこちらを使う。
|
|
134
134
|
*/
|
|
135
135
|
type EdgeStyle = "solid" | "dotted-flow" | "dashed";
|
|
136
|
+
/**
|
|
137
|
+
* 線の道筋。
|
|
138
|
+
*
|
|
139
|
+
* - `"default"` ... 自動。 箱を避け、角を丸めた直角の線を引く。 書かない時も同じ
|
|
140
|
+
* - `"back-detour"` ... 箱の上を回って前の箱へ戻る
|
|
141
|
+
* - `"curve"` ... 始点から水平に出て終点へ水平に入る 3 次曲線。 途中の箱は避けず、
|
|
142
|
+
* 2 点が同じ高さなら直線になる。 終点の矢印は水平を向く
|
|
143
|
+
* - `"metro"` ... 横線と傾き ±1 の斜線だけで結び、角を半径 r の円弧で丸める。
|
|
144
|
+
* 途中の箱は避けず、2 点が同じ高さなら横 1 本になる
|
|
145
|
+
*/
|
|
146
|
+
type EdgeRouting = "default" | "back-detour" | "curve" | "metro";
|
|
136
147
|
/** 辺の役目 (#618)。 `main` = 図の主となる道 */
|
|
137
148
|
type EdgeRole = "main";
|
|
138
149
|
/**
|
|
@@ -393,8 +404,8 @@ type CdlNode = {
|
|
|
393
404
|
* 変わるのは kind の既定色を使って描かれる部分で、 枠線 / eyebrow の文字色 / icon /
|
|
394
405
|
* 強調表示中の行の値。 本文の文字色と背景は変わらない。
|
|
395
406
|
*
|
|
396
|
-
* 効くのは `actor` / `function` / `storage` / `event` / `card
|
|
397
|
-
*
|
|
407
|
+
* 効くのは `actor` / `function` / `storage` / `event` / `card`、`person` 以降の汎用 kind、
|
|
408
|
+
* 流れの印、路線図の駅。 それ以外は自前の配色を持つため指定しても変わらず、検査も通す。
|
|
398
409
|
* 一覧は `test/node-styling.test.tsx` が実装から計算した集合と突き合わせている。
|
|
399
410
|
*
|
|
400
411
|
* 既定色と同じ名前を明示指定しても色が変わる kind がある (既定色の hex が `render/tone.ts`
|
|
@@ -746,8 +757,12 @@ type CdlEdge = {
|
|
|
746
757
|
labelOffsetX?: number;
|
|
747
758
|
/** label の y 方向 fine offset (default 0、 + で下、 - で上) */
|
|
748
759
|
labelOffsetY?: number;
|
|
749
|
-
/**
|
|
750
|
-
|
|
760
|
+
/**
|
|
761
|
+
* 線の道筋。 `default` は箱を避ける自動経路、 `back-detour` は箱の上を回る戻り線、
|
|
762
|
+
* `curve` は途中の箱を避けず、両端で水平になる 3 次曲線。 `metro` は途中の箱を避けず、
|
|
763
|
+
* 横線と傾き ±1 の斜線を半径 r の円弧で結ぶ。2 点が同じ高さなら横 1 本。書かなければ `default`。
|
|
764
|
+
*/
|
|
765
|
+
routing?: EdgeRouting;
|
|
751
766
|
/**
|
|
752
767
|
* CAR-549 SSOT ... label を edge path 上に「被せる」 か「離す」 かの制御 flag。
|
|
753
768
|
* default (false or undefined) = LABEL_INIT_CLEARANCE (34 world、 CAR-630 で 26 → 34 復帰) 離して配置。
|
|
@@ -3035,4 +3050,4 @@ type CdlDiagramViewProps = {
|
|
|
3035
3050
|
};
|
|
3036
3051
|
declare function CdlDiagramView({ diagram, laid: laidProp, hideHeader, hideMiniPhaseIndicator, focusPhaseId, debug, emitGeometryWarn, headingLevel, interactiveHandlers }: CdlDiagramViewProps): JSX.Element;
|
|
3037
3052
|
|
|
3038
|
-
export {
|
|
3053
|
+
export { TONES as $, type ChartSlopeForm as A, type BBox as B, type CdlDiagram as C, EDGE_HEADS as D, type EdgeStyle as E, EDGE_HEAD_DEFAULT as F, EDGE_HEAD_FILLS as G, EDGE_HEAD_FILL_DEFAULT as H, EDGE_REVEALS as I, type JourneyEmotion as J, EDGE_REVEAL_DEFAULT as K, type LaidDiagram as L, EDGE_STYLES as M, type NodeKind as N, type EdgeReveal as O, type InteractiveHandlerMap as P, type QuadrantKey as Q, type RowMark as R, type Signal as S, type Tone as T, type InteractiveSignalsAccessor as U, type LaidLane as V, NODE_KINDS as W, RELATION_FOCUSES as X, RELATION_FOCUS_DEFAULT as Y, type RelationFocus as Z, type ScrollProgressHandle as _, type CdlInput as a, computeEventBindingKey as a0, computeFormulaKey as a1, computeInputSignalKey as a2, computeScrollTriggerKey as a3, computed as a4, createInteractiveSignalsAccessor as a5, createScrollProgressHandle as a6, createScrollProgressSignals as a7, hasInteractivePrimitives as a8, resolveLang as a9, sequenceBoardMetrics as aa, sequenceStepId as ab, signal as ac, type CdlLane as b, type CdlNode as c, type Side as d, type EdgeRole as e, type EdgeHead as f, type EdgeHeadFill as g, type EdgeRouting as h, type CdlState as i, type CdlInputOption as j, type CdlEventTarget as k, type CdlLang as l, type Computed as m, type CdlDerivedValue as n, type CdlScrollTrigger as o, type CdlDiagramViewProps as p, type LaidEdge as q, type LaidNode as r, type CdlEdge as s, type TreeNodePayload as t, CdlDiagramView as u, type CdlEventBinding as v, type CdlEventKind as w, type CdlFormula as x, type CdlPhase as y, type ChartPieForm as z };
|
|
@@ -118,7 +118,7 @@ declare function resolveLang(value: unknown): CdlLang;
|
|
|
118
118
|
declare const TONES: readonly ["accent", "teal", "success", "error", "warning", "info"];
|
|
119
119
|
type Tone = (typeof TONES)[number];
|
|
120
120
|
/** 描画できる箱の種類。 検査・記法一覧・型はこの 1 つの列を出所にする。 */
|
|
121
|
-
declare const NODE_KINDS: readonly ["dyn-rect", "dyn-circle", "dyn-arc", "dyn-wave", "dyn-polygon", "actor", "function", "storage", "event", "card", "person", "user-group", "admin", "developer", "external-user", "database", "cache", "queue", "message-bus", "cloud", "cdn", "service", "api", "frontend", "backend", "webhook", "microservice", "signer", "oracle", "merkle-tree", "decision", "chart-pie", "chart-line", "chart-bar", "chart-gauge", "chart-radial", "chart-stat", "chart-waffle", "chart-stacked-bar", "chart-slope", "gantt-timeline", "mind-map", "funnel-stages", "quadrant-matrix", "tree-hierarchy", "journey-map", "shape-file", "shape-folder", "shape-cloud", "shape-cylinder", "shape-hexagon", "shape-diamond", "shape-stack", "shape-person", "shape-window", "shape-terminal", "shape-code-block", "shape-kanban-card", "shape-message-bubble", "shape-gear", "shape-server-rack", "shape-network-node", "shape-mobile-device", "shape-iot-sensor", "shape-robot-arm", "shape-satellite", "shape-smart-contract", "shape-blockchain-block", "shape-rpc-node", "shape-wallet", "shape-nft", "shape-token", "shape-blockchain", "shape-bitcoin-chain", "shape-ethereum-chain", "shape-blockchain-node", "shape-bank", "shape-trust-bank", "shape-payment-provider", "shape-credit-card", "shape-brokerage", "shape-exchange", "shape-atm", "shape-website", "shape-storefront", "shape-warehouse", "shape-online-shop", "shape-cdn-edge", "shape-api-gateway", "shape-auditor", "shape-regulator", "shape-notary", "shape-lawyer", "shape-trader", "shape-customer-service", "mark-start", "mark-end", "sequence-board"];
|
|
121
|
+
declare const NODE_KINDS: readonly ["dyn-rect", "dyn-circle", "dyn-arc", "dyn-wave", "dyn-polygon", "actor", "function", "storage", "event", "card", "person", "user-group", "admin", "developer", "external-user", "database", "cache", "queue", "message-bus", "cloud", "cdn", "service", "api", "frontend", "backend", "webhook", "microservice", "signer", "oracle", "merkle-tree", "decision", "chart-pie", "chart-line", "chart-bar", "chart-gauge", "chart-radial", "chart-stat", "chart-waffle", "chart-stacked-bar", "chart-slope", "gantt-timeline", "mind-map", "funnel-stages", "quadrant-matrix", "tree-hierarchy", "journey-map", "shape-file", "shape-folder", "shape-cloud", "shape-cylinder", "shape-hexagon", "shape-diamond", "shape-stack", "shape-person", "shape-window", "shape-terminal", "shape-code-block", "shape-kanban-card", "shape-message-bubble", "shape-gear", "shape-server-rack", "shape-network-node", "shape-mobile-device", "shape-iot-sensor", "shape-robot-arm", "shape-satellite", "shape-smart-contract", "shape-blockchain-block", "shape-rpc-node", "shape-wallet", "shape-nft", "shape-token", "shape-blockchain", "shape-bitcoin-chain", "shape-ethereum-chain", "shape-blockchain-node", "shape-bank", "shape-trust-bank", "shape-payment-provider", "shape-credit-card", "shape-brokerage", "shape-exchange", "shape-atm", "shape-website", "shape-storefront", "shape-warehouse", "shape-online-shop", "shape-cdn-edge", "shape-api-gateway", "shape-auditor", "shape-regulator", "shape-notary", "shape-lawyer", "shape-trader", "shape-customer-service", "mark-start", "mark-end", "station", "sequence-board"];
|
|
122
122
|
type NodeKind = (typeof NODE_KINDS)[number];
|
|
123
123
|
type Side = "top" | "right" | "bottom" | "left";
|
|
124
124
|
/**
|
|
@@ -133,6 +133,17 @@ type Side = "top" | "right" | "bottom" | "left";
|
|
|
133
133
|
* ER の「識別しない」 のように **刻みが意味を持つ関係** はこちらを使う。
|
|
134
134
|
*/
|
|
135
135
|
type EdgeStyle = "solid" | "dotted-flow" | "dashed";
|
|
136
|
+
/**
|
|
137
|
+
* 線の道筋。
|
|
138
|
+
*
|
|
139
|
+
* - `"default"` ... 自動。 箱を避け、角を丸めた直角の線を引く。 書かない時も同じ
|
|
140
|
+
* - `"back-detour"` ... 箱の上を回って前の箱へ戻る
|
|
141
|
+
* - `"curve"` ... 始点から水平に出て終点へ水平に入る 3 次曲線。 途中の箱は避けず、
|
|
142
|
+
* 2 点が同じ高さなら直線になる。 終点の矢印は水平を向く
|
|
143
|
+
* - `"metro"` ... 横線と傾き ±1 の斜線だけで結び、角を半径 r の円弧で丸める。
|
|
144
|
+
* 途中の箱は避けず、2 点が同じ高さなら横 1 本になる
|
|
145
|
+
*/
|
|
146
|
+
type EdgeRouting = "default" | "back-detour" | "curve" | "metro";
|
|
136
147
|
/** 辺の役目 (#618)。 `main` = 図の主となる道 */
|
|
137
148
|
type EdgeRole = "main";
|
|
138
149
|
/**
|
|
@@ -393,8 +404,8 @@ type CdlNode = {
|
|
|
393
404
|
* 変わるのは kind の既定色を使って描かれる部分で、 枠線 / eyebrow の文字色 / icon /
|
|
394
405
|
* 強調表示中の行の値。 本文の文字色と背景は変わらない。
|
|
395
406
|
*
|
|
396
|
-
* 効くのは `actor` / `function` / `storage` / `event` / `card
|
|
397
|
-
*
|
|
407
|
+
* 効くのは `actor` / `function` / `storage` / `event` / `card`、`person` 以降の汎用 kind、
|
|
408
|
+
* 流れの印、路線図の駅。 それ以外は自前の配色を持つため指定しても変わらず、検査も通す。
|
|
398
409
|
* 一覧は `test/node-styling.test.tsx` が実装から計算した集合と突き合わせている。
|
|
399
410
|
*
|
|
400
411
|
* 既定色と同じ名前を明示指定しても色が変わる kind がある (既定色の hex が `render/tone.ts`
|
|
@@ -746,8 +757,12 @@ type CdlEdge = {
|
|
|
746
757
|
labelOffsetX?: number;
|
|
747
758
|
/** label の y 方向 fine offset (default 0、 + で下、 - で上) */
|
|
748
759
|
labelOffsetY?: number;
|
|
749
|
-
/**
|
|
750
|
-
|
|
760
|
+
/**
|
|
761
|
+
* 線の道筋。 `default` は箱を避ける自動経路、 `back-detour` は箱の上を回る戻り線、
|
|
762
|
+
* `curve` は途中の箱を避けず、両端で水平になる 3 次曲線。 `metro` は途中の箱を避けず、
|
|
763
|
+
* 横線と傾き ±1 の斜線を半径 r の円弧で結ぶ。2 点が同じ高さなら横 1 本。書かなければ `default`。
|
|
764
|
+
*/
|
|
765
|
+
routing?: EdgeRouting;
|
|
751
766
|
/**
|
|
752
767
|
* CAR-549 SSOT ... label を edge path 上に「被せる」 か「離す」 かの制御 flag。
|
|
753
768
|
* default (false or undefined) = LABEL_INIT_CLEARANCE (34 world、 CAR-630 で 26 → 34 復帰) 離して配置。
|
|
@@ -3035,4 +3050,4 @@ type CdlDiagramViewProps = {
|
|
|
3035
3050
|
};
|
|
3036
3051
|
declare function CdlDiagramView({ diagram, laid: laidProp, hideHeader, hideMiniPhaseIndicator, focusPhaseId, debug, emitGeometryWarn, headingLevel, interactiveHandlers }: CdlDiagramViewProps): JSX.Element;
|
|
3037
3052
|
|
|
3038
|
-
export {
|
|
3053
|
+
export { TONES as $, type ChartSlopeForm as A, type BBox as B, type CdlDiagram as C, EDGE_HEADS as D, type EdgeStyle as E, EDGE_HEAD_DEFAULT as F, EDGE_HEAD_FILLS as G, EDGE_HEAD_FILL_DEFAULT as H, EDGE_REVEALS as I, type JourneyEmotion as J, EDGE_REVEAL_DEFAULT as K, type LaidDiagram as L, EDGE_STYLES as M, type NodeKind as N, type EdgeReveal as O, type InteractiveHandlerMap as P, type QuadrantKey as Q, type RowMark as R, type Signal as S, type Tone as T, type InteractiveSignalsAccessor as U, type LaidLane as V, NODE_KINDS as W, RELATION_FOCUSES as X, RELATION_FOCUS_DEFAULT as Y, type RelationFocus as Z, type ScrollProgressHandle as _, type CdlInput as a, computeEventBindingKey as a0, computeFormulaKey as a1, computeInputSignalKey as a2, computeScrollTriggerKey as a3, computed as a4, createInteractiveSignalsAccessor as a5, createScrollProgressHandle as a6, createScrollProgressSignals as a7, hasInteractivePrimitives as a8, resolveLang as a9, sequenceBoardMetrics as aa, sequenceStepId as ab, signal as ac, type CdlLane as b, type CdlNode as c, type Side as d, type EdgeRole as e, type EdgeHead as f, type EdgeHeadFill as g, type EdgeRouting as h, type CdlState as i, type CdlInputOption as j, type CdlEventTarget as k, type CdlLang as l, type Computed as m, type CdlDerivedValue as n, type CdlScrollTrigger as o, type CdlDiagramViewProps as p, type LaidEdge as q, type LaidNode as r, type CdlEdge as s, type TreeNodePayload as t, CdlDiagramView as u, type CdlEventBinding as v, type CdlEventKind as w, type CdlFormula as x, type CdlPhase as y, type ChartPieForm as z };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cardenelabs/cdl",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.122.0",
|
|
4
4
|
"description": "CDL (Chainome Diagram Language). Mermaid-like declarative DSL that compiles to animated SVG diagrams. Built for blockchain / Solidity flows, generic enough for sequence / flow / state / ER / topology diagrams.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "cardene777",
|
package/src/builder.ts
CHANGED
|
@@ -17,6 +17,7 @@ import type {
|
|
|
17
17
|
CdlState,
|
|
18
18
|
EdgeHead,
|
|
19
19
|
EdgeHeadFill,
|
|
20
|
+
EdgeRouting,
|
|
20
21
|
EdgeStyle,
|
|
21
22
|
EdgeRole,
|
|
22
23
|
NodeKind,
|
|
@@ -1461,7 +1462,7 @@ export type DiagramBuilder = {
|
|
|
1461
1462
|
tailLabel?: string;
|
|
1462
1463
|
labelOffsetX?: number;
|
|
1463
1464
|
labelOffsetY?: number;
|
|
1464
|
-
routing?:
|
|
1465
|
+
routing?: EdgeRouting;
|
|
1465
1466
|
/** CAR-549 SSOT ... true で label を edge path 上に重ねる (flowchart condition label 用) */
|
|
1466
1467
|
overlay?: boolean;
|
|
1467
1468
|
/** signal binding: stroke-width template (CAR edge-signal-binding) */
|
|
@@ -1573,7 +1574,7 @@ export type DiagramBuilder = {
|
|
|
1573
1574
|
tailLabel?: string;
|
|
1574
1575
|
labelOffsetX?: number;
|
|
1575
1576
|
labelOffsetY?: number;
|
|
1576
|
-
routing?:
|
|
1577
|
+
routing?: EdgeRouting;
|
|
1577
1578
|
/** CAR-549 SSOT ... true で label を edge path 上に重ねる (flowchart condition label 用) */
|
|
1578
1579
|
overlay?: boolean;
|
|
1579
1580
|
}>,
|
package/src/index.ts
CHANGED
package/src/kinds/dyn-shape.tsx
CHANGED
|
@@ -75,24 +75,37 @@ export function DynShapeNode({
|
|
|
75
75
|
}
|
|
76
76
|
})();
|
|
77
77
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
78
|
+
const 揃え = 箱いっぱいに描く(shape.kind) ? "start" : "middle";
|
|
79
|
+
return renderShapeWithOutsideLabels(形, node, 揃え);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* 名前と値は箱の外に置く (#925)。 中に置くと、内側の塗りが値で動くたびに文字と重なる。
|
|
84
|
+
* 駅は塗りが動かないが、丸が小さく名前が丸より広いので外に置く。
|
|
85
|
+
*
|
|
86
|
+
* **差し込みはここで通さない**。 `render/nodes.tsx` が `{名前}` を解決した節を渡してくるので、
|
|
87
|
+
* ここで通しても何も変わらない (実測 = 通さない形に変えても検査が 1 件も落ちなかった)。
|
|
88
|
+
* しかも向こうは値ごとの呼び名の表を持っており、ここには無い = 二重に通すと、
|
|
89
|
+
* 呼び名を持つ図だけ結果が変わる余地を作る。
|
|
90
|
+
*/
|
|
91
|
+
export function renderShapeWithOutsideLabels(
|
|
92
|
+
形: JSX.Element,
|
|
93
|
+
node: LaidNode,
|
|
94
|
+
揃え: "start" | "middle",
|
|
95
|
+
): JSX.Element {
|
|
96
|
+
const cx = node.cx;
|
|
97
|
+
const y = node.cy - node.h / 2;
|
|
84
98
|
const 名前 = (node.title ?? "").trim();
|
|
85
99
|
const 値 = (node.subtitle ?? "").trim();
|
|
86
100
|
// どちらも書かれていない節では包む `<g>` も足さない = 書かない図の DOM を 1 要素も変えない
|
|
87
101
|
if (名前 === "" && 値 === "") return 形;
|
|
88
|
-
const 揃え = 箱いっぱいに描く(shape.kind) ? "start" : "middle";
|
|
89
102
|
return (
|
|
90
103
|
<g>
|
|
91
104
|
{形}
|
|
92
105
|
{名前 !== "" && (
|
|
93
106
|
<text
|
|
94
107
|
data-cdl-role="shape-name"
|
|
95
|
-
x={揃え === "start" ?
|
|
108
|
+
x={揃え === "start" ? node.cx - node.w / 2 : cx}
|
|
96
109
|
y={y - NAME_GAP}
|
|
97
110
|
textAnchor={揃え}
|
|
98
111
|
fontSize={13}
|
|
@@ -106,7 +119,7 @@ export function DynShapeNode({
|
|
|
106
119
|
<text
|
|
107
120
|
data-cdl-role="shape-value"
|
|
108
121
|
x={cx}
|
|
109
|
-
y={y + h + VALUE_GAP}
|
|
122
|
+
y={y + node.h + VALUE_GAP}
|
|
110
123
|
textAnchor="middle"
|
|
111
124
|
fontSize={12}
|
|
112
125
|
fill="var(--cdl-text-dim, #5a6270)"
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { JSX } from "react";
|
|
2
|
+
|
|
3
|
+
import type { LaidNode } from "../types";
|
|
4
|
+
import { renderShapeWithOutsideLabels } from "./dyn-shape";
|
|
5
|
+
import { nodeAccent } from "./node-tone";
|
|
6
|
+
|
|
7
|
+
const STATION_STROKE_ACTIVE = 4;
|
|
8
|
+
const STATION_STROKE_IDLE = 3;
|
|
9
|
+
const STATION_DEFAULT = "var(--cdl-tone-accent, #2d6a8f)";
|
|
10
|
+
|
|
11
|
+
/** 路線図の線路上に置く駅。名前と説明は丸を隠さないよう外側に置く。 */
|
|
12
|
+
export function StationNode({ node, active }: { node: LaidNode; active: boolean }): JSX.Element {
|
|
13
|
+
const strokeWidth = active ? STATION_STROKE_ACTIVE : STATION_STROKE_IDLE;
|
|
14
|
+
// 線は箱の辺で止まるため、枠線の外縁を箱の辺へ合わせると線路と丸の間が空かない。
|
|
15
|
+
const radius = Math.max(0, Math.min(node.w, node.h) / 2 - strokeWidth / 2);
|
|
16
|
+
const 形 = (
|
|
17
|
+
<circle
|
|
18
|
+
data-cdl-role="node-body"
|
|
19
|
+
data-cdl-mark="station"
|
|
20
|
+
cx={node.cx}
|
|
21
|
+
cy={node.cy}
|
|
22
|
+
r={radius}
|
|
23
|
+
fill="var(--cdl-node-fill, #ffffff)"
|
|
24
|
+
stroke={nodeAccent(node, STATION_DEFAULT)}
|
|
25
|
+
strokeWidth={strokeWidth}
|
|
26
|
+
/>
|
|
27
|
+
);
|
|
28
|
+
return renderShapeWithOutsideLabels(形, node, "middle");
|
|
29
|
+
}
|
package/src/layout/collisions.ts
CHANGED
|
@@ -688,7 +688,7 @@ export function detectCollisions(
|
|
|
688
688
|
/**
|
|
689
689
|
* path d 文字列を、 描画する線分の列に分解
|
|
690
690
|
*
|
|
691
|
-
* 曲線 (Q / C) は端点を結ぶ弦に落とす。 交差判定には十分だが、 弦は実際の曲線が通る場所とは
|
|
691
|
+
* 曲線 (Q / C / A) は端点を結ぶ弦に落とす。 交差判定には十分だが、 弦は実際の曲線が通る場所とは
|
|
692
692
|
* ずれる。 「この線分は本当に真っ直ぐか」 を知る必要がある呼出側のために、 由来の command
|
|
693
693
|
* (`cmd`) と制御点 (`ctrl`、 曲線のみ) を併せて返す。 弦を直線として扱うと、 端点が同じ高さで
|
|
694
694
|
* 大きく膨らむ曲線を「水平な直線」 と誤認する。
|
|
@@ -700,7 +700,7 @@ export function extractPathSegments(
|
|
|
700
700
|
y1: number;
|
|
701
701
|
x2: number;
|
|
702
702
|
y2: number;
|
|
703
|
-
cmd: "L" | "Q" | "C";
|
|
703
|
+
cmd: "L" | "Q" | "C" | "A";
|
|
704
704
|
ctrl?: Array<{ x: number; y: number }>;
|
|
705
705
|
}> {
|
|
706
706
|
const segs: Array<{
|
|
@@ -708,10 +708,10 @@ export function extractPathSegments(
|
|
|
708
708
|
y1: number;
|
|
709
709
|
x2: number;
|
|
710
710
|
y2: number;
|
|
711
|
-
cmd: "L" | "Q" | "C";
|
|
711
|
+
cmd: "L" | "Q" | "C" | "A";
|
|
712
712
|
ctrl?: Array<{ x: number; y: number }>;
|
|
713
713
|
}> = [];
|
|
714
|
-
const commandRegex = /([
|
|
714
|
+
const commandRegex = /([MLCQAZmlcqaz])([^MLCQAZmlcqaz]*)/g;
|
|
715
715
|
let cur: { x: number; y: number } = { x: 0, y: 0 };
|
|
716
716
|
let m: RegExpExecArray | null;
|
|
717
717
|
while ((m = commandRegex.exec(d)) !== null) {
|
|
@@ -719,7 +719,7 @@ export function extractPathSegments(
|
|
|
719
719
|
const args = (m[2] ?? "").trim().split(/[\s,]+/).filter(Boolean).map(parseFloat);
|
|
720
720
|
// 引数が足りない command は読み飛ばす。 `args[n]!` で埋めると undefined が座標に入り、
|
|
721
721
|
// 以降の segment が全て NaN になる = 距離判定が黙って対象外になる (#370)。
|
|
722
|
-
const need = cmd === "M" || cmd === "L" ? 2 : cmd === "Q" ? 4 : cmd === "C" ? 6 : 0;
|
|
722
|
+
const need = cmd === "M" || cmd === "L" ? 2 : cmd === "Q" ? 4 : cmd === "C" ? 6 : cmd === "A" ? 7 : 0;
|
|
723
723
|
if (need > 0 && (args.length < need || !args.slice(0, need).every(Number.isFinite))) continue;
|
|
724
724
|
if (cmd === "M") {
|
|
725
725
|
cur = { x: args[0]!, y: args[1]! };
|
|
@@ -741,13 +741,17 @@ export function extractPathSegments(
|
|
|
741
741
|
ctrl: [{ x: args[0]!, y: args[1]! }, { x: args[2]!, y: args[3]! }],
|
|
742
742
|
});
|
|
743
743
|
cur = next;
|
|
744
|
+
} else if (cmd === "A") {
|
|
745
|
+
const next = { x: args[5]!, y: args[6]! };
|
|
746
|
+
segs.push({ x1: cur.x, y1: cur.y, x2: next.x, y2: next.y, cmd: "A" });
|
|
747
|
+
cur = next;
|
|
744
748
|
}
|
|
745
749
|
}
|
|
746
750
|
return segs;
|
|
747
751
|
}
|
|
748
752
|
|
|
749
753
|
/**
|
|
750
|
-
* 曲線 (Q / C) を、
|
|
754
|
+
* 曲線 (Q / C) を、 実際に描かれる線に沿った折れ線に開く。円弧 (A) は弦のまま返す。
|
|
751
755
|
*
|
|
752
756
|
* `extractPathSegments` は曲線を端点どうしの弦に落とす。 交差判定はそれで足りるが、
|
|
753
757
|
* 「線からどれだけ離れているか」 を測る用途では弦は使えない = 弦は描かれていないため、
|
|
@@ -788,7 +792,7 @@ export function extractPathSegments(
|
|
|
788
792
|
* 無い形を勝手に作ることになる。
|
|
789
793
|
*
|
|
790
794
|
* @param segs `extractPathSegments()` の出力
|
|
791
|
-
* @returns 直線のみからなる segment 列。 `L`
|
|
795
|
+
* @returns 直線のみからなる segment 列。 `L` と `A` の弦はそのまま。`Q` / `C` は `FLATTEN_MIN_DIVISIONS` 以上
|
|
792
796
|
* `FLATTEN_MAX_DIVISIONS` 以下の本数に開く。 誤差が `FLATTEN_TOLERANCE` 以下に収まるのは、
|
|
793
797
|
* 必要な分割数が上限以下に収まる曲線 (`dev ≤ 4 · 許容差 · 上限²`) に限る。 上限を超える
|
|
794
798
|
* 曲線では保証されず、 許容差を超えることがある (幅 4e7 の対称な弧では式の上限が 0.596
|
|
@@ -804,7 +808,7 @@ export function flattenPathSegments(
|
|
|
804
808
|
y1: number;
|
|
805
809
|
x2: number;
|
|
806
810
|
y2: number;
|
|
807
|
-
cmd: "L" | "Q" | "C";
|
|
811
|
+
cmd: "L" | "Q" | "C" | "A";
|
|
808
812
|
ctrl?: ReadonlyArray<{ x: number; y: number }>;
|
|
809
813
|
}>,
|
|
810
814
|
): Array<{ x1: number; y1: number; x2: number; y2: number }> {
|
|
@@ -2357,16 +2361,17 @@ export function resolveEdgeLabelOverlapsWithChainAndPropagate(
|
|
|
2357
2361
|
}
|
|
2358
2362
|
|
|
2359
2363
|
/** 札の逃がし先を測るのに使う、 線の区間と外接矩形。 同じ図の中で使い回す (#810)。 */
|
|
2360
|
-
interface 線の測り方 {
|
|
2364
|
+
export interface 線の測り方 {
|
|
2361
2365
|
readonly 区間: (id: string, d: string) => Seg[];
|
|
2362
2366
|
readonly 遠い: (box: BBox, edgeId: string, d: string, 余裕: number) => boolean;
|
|
2363
2367
|
}
|
|
2364
2368
|
|
|
2365
2369
|
/**
|
|
2366
|
-
* 線の測り方を作る。 逃がし pass
|
|
2367
|
-
* (`札が動けない理由`)
|
|
2370
|
+
* 線の測り方を作る。 逃がし pass、 門が「なぜ動かせなかったか」 を説明する側
|
|
2371
|
+
* (`札が動けない理由`)、 終点をずらした線の札の追従 (`followLabelsToFannedTails`、 #1002) が
|
|
2372
|
+
* 同じ測り方を引く = どれかに別の測り方を書くと、 置けるかの判断がずれる。
|
|
2368
2373
|
*/
|
|
2369
|
-
function 線の測り方を作る(): 線の測り方 {
|
|
2374
|
+
export function 線の測り方を作る(): 線の測り方 {
|
|
2370
2375
|
/*
|
|
2371
2376
|
* 線の区間は **曲線を開いた形** で持つ (`detectCollisions` の札 branch と同じ)。
|
|
2372
2377
|
*
|
|
@@ -2453,7 +2458,7 @@ export interface 札の移動を塞ぐ相手 {
|
|
|
2453
2458
|
}
|
|
2454
2459
|
|
|
2455
2460
|
/** 札を動かす判定に使う、 図の中の相手 */
|
|
2456
|
-
interface 札の周り {
|
|
2461
|
+
export interface 札の周り {
|
|
2457
2462
|
readonly 箱たち: readonly BBox[];
|
|
2458
2463
|
readonly labels: readonly BBox[];
|
|
2459
2464
|
readonly edges: readonly LaidEdge[];
|
|
@@ -2464,10 +2469,11 @@ interface 札の周り {
|
|
|
2464
2469
|
/**
|
|
2465
2470
|
* 札 1 つを動かした先が、 いまより近づく相手を作らないか。 作るなら最初の 1 つを返す。
|
|
2466
2471
|
*
|
|
2467
|
-
* 逃がし pass の
|
|
2468
|
-
*
|
|
2472
|
+
* 逃がし pass の `安全か`、 門の説明 (`札が動けない理由`)、 終点をずらした線の札の追従
|
|
2473
|
+
* (`followLabelsToFannedTails`、 #1002) が同じものを引く (#810)。 別に書くと、 追従で置いた札を
|
|
2474
|
+
* 逃がし pass が押し直すか、 「pass は動かせないのに説明は動かせると言う」 ずれが生まれる。
|
|
2469
2475
|
*/
|
|
2470
|
-
function 札の移動を塞ぐ相手を探す(
|
|
2476
|
+
export function 札の移動を塞ぐ相手を探す(
|
|
2471
2477
|
周り: 札の周り,
|
|
2472
2478
|
label: BBox,
|
|
2473
2479
|
行き先: BBox,
|
|
@@ -2649,10 +2655,14 @@ export function 札が動けない理由(
|
|
|
2649
2655
|
*
|
|
2650
2656
|
* ## なぜ最終 pass が要るか
|
|
2651
2657
|
*
|
|
2652
|
-
* `resolveEdgeLabelOverlapsWithChainAndPropagate`
|
|
2658
|
+
* `resolveEdgeLabelOverlapsWithChainAndPropagate` の chain 伝搬後に、 経路を動かす 4 つの pass
|
|
2653
2659
|
* (`bowSharedStraightPaths` / `pullOutgoingFirstCorner` / `fanIncomingEndpoints` /
|
|
2654
|
-
* `separateSharedStraightRuns`)
|
|
2655
|
-
*
|
|
2660
|
+
* `separateSharedStraightRuns`) が走る。 4 つとも経路を動かすが、 札も動かすのは
|
|
2661
|
+
* `pullOutgoingFirstCorner` だけで、 動かす区間に一番近い札に限る (`edges.ts` の `labelも動かす`)。
|
|
2662
|
+
*
|
|
2663
|
+
* `bowSharedStraightPaths` と `separateSharedStraightRuns` は札を動かさず、 `fanIncomingEndpoints` が
|
|
2664
|
+
* 残した札は `followLabelsToFannedTails` が追従させる。 追従も `札の移動を塞ぐ相手を探す` で
|
|
2665
|
+
* 置けるかを決める = 別の判定で余白を割る位置へ置くと、 この pass が同じ札を押し直す (#1002)。
|
|
2656
2666
|
*
|
|
2657
2667
|
* 0.30.0 で `pullOutgoingFirstCorner` (#651) と `fanIncomingEndpoints` (#649) が経路を
|
|
2658
2668
|
* 大きく動かすようになり、 消費側の見本帳で札が 11 件動いた (実測 = 最大 217 world)。
|
|
@@ -2886,4 +2896,3 @@ function applyEdgeLabelAndPathShifts(
|
|
|
2886
2896
|
return next;
|
|
2887
2897
|
});
|
|
2888
2898
|
}
|
|
2889
|
-
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import type { CdlEdge, LaidNode, Side } from "../types";
|
|
2
|
+
import { computeLabelBoxW, EDGE_STUB_OUT, labelPathClearanceHorizontal, labelPillH } from "./spec";
|
|
3
|
+
|
|
4
|
+
type 箱 = Pick<LaidNode, "cx" | "cy" | "w" | "h">;
|
|
5
|
+
|
|
6
|
+
type 曲線の結果 = {
|
|
7
|
+
d: string;
|
|
8
|
+
fromSide: Side;
|
|
9
|
+
toSide: Side;
|
|
10
|
+
labelX: number;
|
|
11
|
+
labelY: number;
|
|
12
|
+
labelAnchor: "middle";
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/** `routePath` が横の直線を選ぶ値と揃え、既存の直線判定との境を増やさない。 */
|
|
16
|
+
export const SAME_HEIGHT_THRESHOLD = 1;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 2 つの箱を、両端で水平になる線で結ぶ。
|
|
20
|
+
*
|
|
21
|
+
* 行き先が左右へ離れていれば向かい合う左右の辺を使い、横の範囲が重なる時は両方の右辺を
|
|
22
|
+
* 使う。 2 点が同じ高さなら曲線にしても形が変わらないため、余分な制御点を持たない直線にする。
|
|
23
|
+
* 張り出しには `EDGE_STUB_OUT` の下限を置く。 制御点が端点に重なって接線が縦へ倒れるのを防ぎ、
|
|
24
|
+
* 箱を出てすぐ曲がらないことで、どの箱から出た線かを読めるようにするため。
|
|
25
|
+
*
|
|
26
|
+
* 札は曲線の中点から接線の法線方向へ離す。 線に沿ってずらすより、線と札の間の隙間を直接
|
|
27
|
+
* 保てるため。 この道筋は書き手が形を選ぶためのもので、途中の箱は避けない。
|
|
28
|
+
*/
|
|
29
|
+
export function curvePath(
|
|
30
|
+
from: 箱,
|
|
31
|
+
to: 箱,
|
|
32
|
+
edge?: Pick<CdlEdge, "label" | "sub" | "style" | "overlay">,
|
|
33
|
+
labelMarginExtra = 0,
|
|
34
|
+
): 曲線の結果 {
|
|
35
|
+
const fromLeft = from.cx - from.w / 2;
|
|
36
|
+
const fromRight = from.cx + from.w / 2;
|
|
37
|
+
const toLeft = to.cx - to.w / 2;
|
|
38
|
+
const toRight = to.cx + to.w / 2;
|
|
39
|
+
|
|
40
|
+
let fromSide: Side;
|
|
41
|
+
let toSide: Side;
|
|
42
|
+
let x1: number;
|
|
43
|
+
let x2: number;
|
|
44
|
+
|
|
45
|
+
if (toLeft >= fromRight) {
|
|
46
|
+
fromSide = "right";
|
|
47
|
+
toSide = "left";
|
|
48
|
+
x1 = fromRight;
|
|
49
|
+
x2 = toLeft;
|
|
50
|
+
} else if (toRight <= fromLeft) {
|
|
51
|
+
fromSide = "left";
|
|
52
|
+
toSide = "right";
|
|
53
|
+
x1 = fromLeft;
|
|
54
|
+
x2 = toRight;
|
|
55
|
+
} else {
|
|
56
|
+
fromSide = "right";
|
|
57
|
+
toSide = "right";
|
|
58
|
+
x1 = fromRight;
|
|
59
|
+
x2 = toRight;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const y1 = from.cy;
|
|
63
|
+
const y2 = to.cy;
|
|
64
|
+
const sameHeight = Math.abs(y2 - y1) < SAME_HEIGHT_THRESHOLD;
|
|
65
|
+
|
|
66
|
+
let d: string;
|
|
67
|
+
let midX: number;
|
|
68
|
+
let midY: number;
|
|
69
|
+
let tangentX: number;
|
|
70
|
+
let tangentY: number;
|
|
71
|
+
|
|
72
|
+
if (sameHeight) {
|
|
73
|
+
d = `M ${x1} ${y1} L ${x2} ${y2}`;
|
|
74
|
+
midX = (x1 + x2) / 2;
|
|
75
|
+
midY = (y1 + y2) / 2;
|
|
76
|
+
tangentX = x2 - x1;
|
|
77
|
+
tangentY = y2 - y1;
|
|
78
|
+
} else {
|
|
79
|
+
let c1x: number;
|
|
80
|
+
let c2x: number;
|
|
81
|
+
if (fromSide !== toSide) {
|
|
82
|
+
const direction = x2 > x1 ? 1 : -1;
|
|
83
|
+
const bulge = Math.max(Math.abs(x2 - x1) / 2, EDGE_STUB_OUT);
|
|
84
|
+
c1x = x1 + direction * bulge;
|
|
85
|
+
c2x = x2 - direction * bulge;
|
|
86
|
+
} else {
|
|
87
|
+
const bulgeX = Math.max(x1, x2) + EDGE_STUB_OUT;
|
|
88
|
+
c1x = bulgeX;
|
|
89
|
+
c2x = bulgeX;
|
|
90
|
+
}
|
|
91
|
+
d = `M ${x1} ${y1} C ${c1x} ${y1}, ${c2x} ${y2}, ${x2} ${y2}`;
|
|
92
|
+
midX = (x1 + 3 * c1x + 3 * c2x + x2) / 8;
|
|
93
|
+
midY = (y1 + 3 * y1 + 3 * y2 + y2) / 8;
|
|
94
|
+
tangentX = x2 + c2x - c1x - x1;
|
|
95
|
+
tangentY = y2 + y2 - y1 - y1;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (edge?.overlay) {
|
|
99
|
+
return { d, fromSide, toSide, labelX: midX, labelY: midY, labelAnchor: "middle" };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const tangentLength = Math.hypot(tangentX, tangentY);
|
|
103
|
+
let normalX = tangentY / tangentLength;
|
|
104
|
+
let normalY = -tangentX / tangentLength;
|
|
105
|
+
if (normalY > Number.EPSILON || (Math.abs(normalY) <= Number.EPSILON && normalX < 0)) {
|
|
106
|
+
normalX *= -1;
|
|
107
|
+
normalY *= -1;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const labelW = computeLabelBoxW(edge?.label, edge?.sub);
|
|
111
|
+
const labelH = labelPillH(edge?.sub);
|
|
112
|
+
const distance =
|
|
113
|
+
labelPathClearanceHorizontal(edge) +
|
|
114
|
+
(Math.abs(normalX) * labelW + Math.abs(normalY) * labelH) / 2 +
|
|
115
|
+
labelMarginExtra;
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
d,
|
|
119
|
+
fromSide,
|
|
120
|
+
toSide,
|
|
121
|
+
labelX: midX + normalX * distance,
|
|
122
|
+
labelY: midY + normalY * distance,
|
|
123
|
+
labelAnchor: "middle",
|
|
124
|
+
};
|
|
125
|
+
}
|