@cardenelabs/cdl 0.123.0 → 0.125.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 +55 -1
- package/README.md +14 -1
- package/dist/index.cjs +924 -134
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +924 -135
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +909 -134
- 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 +909 -134
- package/dist/react.js.map +1 -1
- package/dist/{render-CVczwfRb.d.cts → render-7yEHIUK1.d.cts} +60 -5
- package/dist/{render-CVczwfRb.d.ts → render-7yEHIUK1.d.ts} +60 -5
- package/package.json +12 -9
- package/src/builder.ts +6 -0
- package/src/index.ts +4 -0
- package/src/kinds/card.tsx +12 -4
- package/src/kinds/generic.tsx +19 -9
- package/src/kinds/station.tsx +105 -19
- package/src/kinds/terminal-mark.tsx +36 -9
- package/src/layout/edges.ts +169 -17
- package/src/layout/lanes.ts +69 -5
- package/src/layout/legend.ts +68 -0
- package/src/layout/metro-path.ts +123 -30
- package/src/layout/nodes.ts +13 -2
- package/src/layout/spec.ts +17 -1
- package/src/layout/tokens.ts +8 -1
- package/src/layout.ts +20 -2
- package/src/render/edge-head.ts +6 -0
- package/src/render/edges.tsx +27 -6
- package/src/render/legend.tsx +120 -0
- package/src/render/nodes.tsx +21 -5
- package/src/render/stage.tsx +255 -10
- package/src/render/template-fields.ts +2 -0
- package/src/types.ts +109 -7
- package/src/validate.ts +5 -0
- package/src/visual-validate.ts +7 -7
package/src/render/nodes.tsx
CHANGED
|
@@ -7,7 +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
|
+
import { StationNode, TimelineNumberNode } from "../kinds/station";
|
|
11
11
|
import { DynShapeNode } from "../kinds/dyn-shape";
|
|
12
12
|
import { ChartLineNode, type ChartLineScale } from "../kinds/chart-line";
|
|
13
13
|
import { ChartPieNode } from "../kinds/chart-pie";
|
|
@@ -119,10 +119,14 @@ export function CdlNodeView({
|
|
|
119
119
|
stateValues,
|
|
120
120
|
currentPhaseId,
|
|
121
121
|
chartLineScale,
|
|
122
|
+
compactTitleInset,
|
|
122
123
|
関係の受け口,
|
|
123
124
|
lang,
|
|
124
125
|
textMetrics,
|
|
125
126
|
値の名前,
|
|
127
|
+
metroDiagram = false,
|
|
128
|
+
metroDecision = false,
|
|
129
|
+
stationNameRight = false,
|
|
126
130
|
}: {
|
|
127
131
|
node: LaidNode;
|
|
128
132
|
active: boolean;
|
|
@@ -144,8 +148,16 @@ export function CdlNodeView({
|
|
|
144
148
|
stateValues: Record<string, string>;
|
|
145
149
|
currentPhaseId: string | undefined;
|
|
146
150
|
chartLineScale?: ChartLineScale;
|
|
151
|
+
/** 小型 card の名前を左寄せにする時の左端からの距離。段階列の札だけが渡す。 */
|
|
152
|
+
compactTitleInset?: number;
|
|
147
153
|
/** 触れた箱の関係を光らせる時の受け口 (#806)。 書かない図では `undefined` */
|
|
148
154
|
関係の受け口?: 関係の受け口;
|
|
155
|
+
/** metro 線と駅を持つ図だけが担当の名札と端の印を路線図向けに描く。 */
|
|
156
|
+
metroDiagram?: boolean;
|
|
157
|
+
/** `routing: "metro"` の線が出入りする分かれ道だけを路線図向けに描く。 */
|
|
158
|
+
metroDecision?: boolean;
|
|
159
|
+
/** 真上から線路が入る駅の名前を右へ置く。 */
|
|
160
|
+
stationNameRight?: boolean;
|
|
149
161
|
}): JSX.Element {
|
|
150
162
|
const value = node.value ? interpolate(node.value, stateValues, 値の名前) : undefined;
|
|
151
163
|
const rows = (node.rows ?? []).map((r) => interpolate(r, stateValues, 値の名前));
|
|
@@ -201,7 +213,7 @@ export function CdlNodeView({
|
|
|
201
213
|
case "event":
|
|
202
214
|
return <EventNode node={resolvedNode} active={active} progress={progress} />;
|
|
203
215
|
case "card":
|
|
204
|
-
return <CardNode node={resolvedNode} active={active} />;
|
|
216
|
+
return <CardNode node={resolvedNode} active={active} compactTitleInset={compactTitleInset} />;
|
|
205
217
|
// 時系列のやり取り (#578)。 1 つの箱が図を丸ごと描く = 縦が時間で、参加者は箱を持たない
|
|
206
218
|
case "sequence-board":
|
|
207
219
|
return (
|
|
@@ -479,11 +491,13 @@ export function CdlNodeView({
|
|
|
479
491
|
case "shape-customer-service":
|
|
480
492
|
return <ShapeCustomerServiceNode node={resolvedNode} active={active} />;
|
|
481
493
|
case "mark-start":
|
|
482
|
-
return <MarkStartNode node={resolvedNode} active={active} />;
|
|
494
|
+
return <MarkStartNode node={resolvedNode} active={active} metro={metroDiagram} />;
|
|
483
495
|
case "mark-end":
|
|
484
|
-
return <MarkEndNode node={resolvedNode} active={active} />;
|
|
496
|
+
return <MarkEndNode node={resolvedNode} active={active} metro={metroDiagram} />;
|
|
485
497
|
case "station":
|
|
486
|
-
return <StationNode node={resolvedNode} active={active} />;
|
|
498
|
+
return <StationNode node={resolvedNode} active={active} nameRight={stationNameRight} />;
|
|
499
|
+
case "timeline-number":
|
|
500
|
+
return <TimelineNumberNode node={resolvedNode} />;
|
|
487
501
|
default:
|
|
488
502
|
// 新 NodeKind 25 種は GenericNode で汎用描画 (kind 別 icon / color / shape)
|
|
489
503
|
return (
|
|
@@ -494,6 +508,8 @@ export function CdlNodeView({
|
|
|
494
508
|
stateValues={stateValues}
|
|
495
509
|
値の名前={値の名前}
|
|
496
510
|
textMetrics={textMetrics}
|
|
511
|
+
showKindIcon={!(metroDiagram && node.kind === "decision")}
|
|
512
|
+
metroDecision={metroDecision}
|
|
497
513
|
/>
|
|
498
514
|
);
|
|
499
515
|
}
|
package/src/render/stage.tsx
CHANGED
|
@@ -3,7 +3,8 @@ import { 折れ線の目盛, type ChartLineScale } from "../kinds/chart-line";
|
|
|
3
3
|
import { 描く進みに直す } from "../kinds/draw-ratio";
|
|
4
4
|
import { useReducedMotion } from "../anim/react/useReducedMotion";
|
|
5
5
|
import { lifelineStartYs, uniformLifelineEndY } from "../layout/lifeline";
|
|
6
|
-
import { buildDiagramAltText, isRenderedNode } from "../layout/spec";
|
|
6
|
+
import { buildDiagramAltText, isRenderedNode, TIMELINE_NUMBER_STROKE } from "../layout/spec";
|
|
7
|
+
import { STAGE_CARD_TEXT_INSET, STAGE_COLUMN_GAP } from "../layout/tokens";
|
|
7
8
|
import {
|
|
8
9
|
EDGE_HEADS,
|
|
9
10
|
EDGE_HEAD_FILLS,
|
|
@@ -13,6 +14,7 @@ import {
|
|
|
13
14
|
RELATION_FOCUS_DEFAULT,
|
|
14
15
|
type CdlPhase,
|
|
15
16
|
type LaidDiagram,
|
|
17
|
+
type LaidNode,
|
|
16
18
|
} from "../types";
|
|
17
19
|
import {
|
|
18
20
|
EDGE_HEAD_MUTED_MARKER_ID,
|
|
@@ -20,15 +22,23 @@ import {
|
|
|
20
22
|
edgeHeadMarkerId,
|
|
21
23
|
edgeHeadMarkerName,
|
|
22
24
|
edgeHeadPaint,
|
|
25
|
+
METRO_RETURN_ARROW_D,
|
|
26
|
+
metroReturnArrowMarkerId,
|
|
23
27
|
} from "./edge-head";
|
|
24
28
|
import { CdlEdgeView, CdlEdgeGlowView, CdlEdgeEndLabelView, CdlEdgeLabelView } from "./edges";
|
|
25
29
|
import { CdlNodeView } from "./nodes";
|
|
30
|
+
import { CdlLegend } from "./legend";
|
|
26
31
|
import { resolveChartData } from "./payload-binding";
|
|
27
32
|
import { relatedFocusSet } from "./relation-focus";
|
|
28
33
|
import { TONE, TONE_HEX } from "./tone";
|
|
29
|
-
import { computeStateValues } from "./utils";
|
|
34
|
+
import { computeStateValues, interpolate } from "./utils";
|
|
30
35
|
import type { 値の名前の表 } from "../input-option";
|
|
31
36
|
|
|
37
|
+
const METRO_LANE_BADGE_WIDTH = 200;
|
|
38
|
+
const METRO_LANE_BADGE_HEIGHT = 88;
|
|
39
|
+
const METRO_LANE_BADGE_INSET = 24;
|
|
40
|
+
const METRO_LANE_GUIDE_GAP = 24;
|
|
41
|
+
|
|
32
42
|
/**
|
|
33
43
|
* その図に描くものがあるか (#923)。
|
|
34
44
|
*
|
|
@@ -42,9 +52,17 @@ import type { 値の名前の表 } from "../input-option";
|
|
|
42
52
|
* 描かれない。 `{signal}` を含む条件は段しだいで出るので、出る側に数える。
|
|
43
53
|
*/
|
|
44
54
|
function 描くものがある(laid: LaidDiagram): boolean {
|
|
55
|
+
if (laid.legend && laid.legend.length > 0) return true;
|
|
45
56
|
if (laid.edges.length > 0) return true;
|
|
46
57
|
if (laid.nodes.some((n) => isRenderedNode(n))) return true;
|
|
47
|
-
return laid.lanes.some(
|
|
58
|
+
return laid.lanes.some(
|
|
59
|
+
(l) =>
|
|
60
|
+
l.contain === true ||
|
|
61
|
+
l.lifeline === true ||
|
|
62
|
+
Boolean(l.label) ||
|
|
63
|
+
l.stage !== undefined ||
|
|
64
|
+
l.metro !== undefined,
|
|
65
|
+
);
|
|
48
66
|
}
|
|
49
67
|
|
|
50
68
|
export function CdlStage({
|
|
@@ -121,6 +139,8 @@ export function CdlStage({
|
|
|
121
139
|
[関係を光らせる, 焦点, laid.edges],
|
|
122
140
|
);
|
|
123
141
|
const activeSet = 焦点の集合 ?? new Set(currentPhase?.activate ?? []);
|
|
142
|
+
const 路線図 = laid.edges.some((edge) => edge.routing === "metro") &&
|
|
143
|
+
laid.nodes.some((node) => node.kind === "station");
|
|
124
144
|
/** 箱に付ける受け口。 指定を書かない図では `undefined` = DOM も挙動も変わらない */
|
|
125
145
|
const 箱の受け口 = 関係を光らせる
|
|
126
146
|
? { 触れた, 離れた, 押した, 焦点: 焦点?.id ?? null }
|
|
@@ -167,8 +187,15 @@ export function CdlStage({
|
|
|
167
187
|
if (first === undefined) return false;
|
|
168
188
|
return 今 === first;
|
|
169
189
|
},
|
|
190
|
+
/** 初出の段より前にある区間だけを、通過前として選べるようにする。 */
|
|
191
|
+
まだか: (edgeId: string): boolean => {
|
|
192
|
+
const first = 初出.get(edgeId);
|
|
193
|
+
return first !== undefined && 今 >= 0 && 今 < first;
|
|
194
|
+
},
|
|
170
195
|
};
|
|
171
196
|
}, [laid.phases, currentPhase, laid.edgeReveal]);
|
|
197
|
+
const 線を描く = (edge: (typeof laid.edges)[number]): boolean =>
|
|
198
|
+
edge.routing === "metro" || 矢印の出番.出るか(edge.id);
|
|
172
199
|
// この phase で左の起点から描き始める node (cdl#512)。 `activate` とは別集合で持つ =
|
|
173
200
|
// 焦点が当たり続ける図で毎 phase 引き直すことになるため、 光っているかを引き金にしない
|
|
174
201
|
const drawSet = new Set(currentPhase?.draw ?? []);
|
|
@@ -280,7 +307,56 @@ export function CdlStage({
|
|
|
280
307
|
),
|
|
281
308
|
);
|
|
282
309
|
}, [laid]);
|
|
310
|
+
const metroReturnArrowMarkers = useMemo(() => {
|
|
311
|
+
const tones = new Set(
|
|
312
|
+
laid.edges
|
|
313
|
+
.filter((edge) => edge.metroReturn === true)
|
|
314
|
+
.map((edge) => edge.tone),
|
|
315
|
+
);
|
|
316
|
+
return [...tones].map((tone) => (
|
|
317
|
+
<marker
|
|
318
|
+
key={tone}
|
|
319
|
+
id={metroReturnArrowMarkerId(tone)}
|
|
320
|
+
viewBox="0 -9.5 21 19"
|
|
321
|
+
refX="0"
|
|
322
|
+
refY="0"
|
|
323
|
+
markerWidth="21"
|
|
324
|
+
markerHeight="19"
|
|
325
|
+
markerUnits="userSpaceOnUse"
|
|
326
|
+
orient="auto"
|
|
327
|
+
>
|
|
328
|
+
<path
|
|
329
|
+
data-cdl-role="edge-arrowhead"
|
|
330
|
+
data-cdl-mark="metro-return-arrow"
|
|
331
|
+
d={METRO_RETURN_ARROW_D}
|
|
332
|
+
fill={TONE[tone]}
|
|
333
|
+
/>
|
|
334
|
+
</marker>
|
|
335
|
+
));
|
|
336
|
+
}, [laid.edges]);
|
|
283
337
|
const diagramScale = laid.diagramScale ?? 1;
|
|
338
|
+
const stageLaneIds = new Set(
|
|
339
|
+
laid.lanes.filter((lane) => lane.stage !== undefined).map((lane) => lane.id),
|
|
340
|
+
);
|
|
341
|
+
const stageNoteNodes = laid.nodes.filter(
|
|
342
|
+
(node): node is LaidNode & { subtitle: string } =>
|
|
343
|
+
node.kind === "card" &&
|
|
344
|
+
(stageLaneIds.has(node.lane) || node.leaderTo !== undefined) &&
|
|
345
|
+
isRenderedNode(node) &&
|
|
346
|
+
Boolean(node.subtitle),
|
|
347
|
+
);
|
|
348
|
+
const stageNoteNodeIds = new Set(stageNoteNodes.map((node) => node.id));
|
|
349
|
+
const nodesById = new Map(laid.nodes.map((node) => [node.id, node]));
|
|
350
|
+
const metroDecisionIds = new Set<string>();
|
|
351
|
+
const stationNamesRight = new Set<string>();
|
|
352
|
+
for (const edge of laid.edges) {
|
|
353
|
+
if (edge.routing !== "metro") continue;
|
|
354
|
+
if (nodesById.get(edge.from)?.kind === "decision") metroDecisionIds.add(edge.from);
|
|
355
|
+
if (nodesById.get(edge.to)?.kind === "decision") metroDecisionIds.add(edge.to);
|
|
356
|
+
if (edge.toSide === "top" && nodesById.get(edge.to)?.kind === "station") {
|
|
357
|
+
stationNamesRight.add(edge.to);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
284
360
|
// 描くものが 1 つも無い図は面を取らない (#923)。
|
|
285
361
|
// 札 (`interactive-panel`) は `render.tsx` が別に出すので、題と札は残る。
|
|
286
362
|
// hook は全て上で呼び終えているので、ここで返しても呼ぶ順は変わらない。
|
|
@@ -343,6 +419,7 @@ export function CdlStage({
|
|
|
343
419
|
に適用する。 marker (cdl-arrow-*) は edge routing の SSOT なので cdl 内に維持。 */}
|
|
344
420
|
{/* 大小は phase ごとに入れ替わるため、配置が参照する両方を保持する。 */}
|
|
345
421
|
{edgeHeadMarkers}
|
|
422
|
+
{metroReturnArrowMarkers}
|
|
346
423
|
{/* tone が TONE 定義に無い edge の受け皿。 visual-validate の tone-marker-ref 軸が
|
|
347
424
|
dead ref を検知するが、 検知するまでの間に矢頭が消えないようにする。 */}
|
|
348
425
|
<marker
|
|
@@ -378,7 +455,53 @@ export function CdlStage({
|
|
|
378
455
|
stroke="none"
|
|
379
456
|
pointerEvents="none"
|
|
380
457
|
/>
|
|
381
|
-
{lane.
|
|
458
|
+
{lane.stage && (
|
|
459
|
+
<>
|
|
460
|
+
<rect
|
|
461
|
+
data-cdl-role="stage-column"
|
|
462
|
+
x={lane.x + STAGE_COLUMN_GAP / 2}
|
|
463
|
+
y={lane.y}
|
|
464
|
+
width={lane.width - STAGE_COLUMN_GAP}
|
|
465
|
+
height={lane.height}
|
|
466
|
+
rx={16}
|
|
467
|
+
fill="var(--cdl-stage-bg, #fafafa)"
|
|
468
|
+
fillOpacity={0.5}
|
|
469
|
+
stroke="var(--cdl-text-mute, #8a8678)"
|
|
470
|
+
strokeWidth={1.5}
|
|
471
|
+
/>
|
|
472
|
+
<text
|
|
473
|
+
data-cdl-role="stage-number"
|
|
474
|
+
x={lane.x + STAGE_COLUMN_GAP / 2 + 20}
|
|
475
|
+
y={lane.y + 24}
|
|
476
|
+
fontSize={14}
|
|
477
|
+
fontWeight={500}
|
|
478
|
+
letterSpacing={1.5}
|
|
479
|
+
fill="var(--cdl-text-mute, #8a8678)"
|
|
480
|
+
>
|
|
481
|
+
{`段階 ${lane.stage.number}`}
|
|
482
|
+
</text>
|
|
483
|
+
<text
|
|
484
|
+
data-cdl-role="stage-name"
|
|
485
|
+
x={lane.x + STAGE_COLUMN_GAP / 2 + 20}
|
|
486
|
+
y={lane.y + 55}
|
|
487
|
+
fontSize={22}
|
|
488
|
+
fontWeight={700}
|
|
489
|
+
fill="var(--cdl-text, #1a1f2a)"
|
|
490
|
+
>
|
|
491
|
+
{lane.stage.name}
|
|
492
|
+
</text>
|
|
493
|
+
<line
|
|
494
|
+
data-cdl-role="stage-divider"
|
|
495
|
+
x1={lane.x + STAGE_COLUMN_GAP / 2}
|
|
496
|
+
y1={lane.y + 72}
|
|
497
|
+
x2={lane.x + lane.width - STAGE_COLUMN_GAP / 2}
|
|
498
|
+
y2={lane.y + 72}
|
|
499
|
+
stroke="var(--cdl-divider, #d4d4d8)"
|
|
500
|
+
strokeWidth={1.5}
|
|
501
|
+
/>
|
|
502
|
+
</>
|
|
503
|
+
)}
|
|
504
|
+
{lane.contain && lane.stage === undefined && (
|
|
382
505
|
<rect
|
|
383
506
|
data-cdl-role="lane-container"
|
|
384
507
|
x={lane.x}
|
|
@@ -414,7 +537,76 @@ export function CdlStage({
|
|
|
414
537
|
/>
|
|
415
538
|
);
|
|
416
539
|
})()}
|
|
417
|
-
{lane.
|
|
540
|
+
{lane.timelineAxis && (() => {
|
|
541
|
+
const ys = laid.nodes
|
|
542
|
+
.filter((node) => node.lane === lane.id && isRenderedNode(node))
|
|
543
|
+
.map((node) => node.cy);
|
|
544
|
+
if (ys.length < 2) return null;
|
|
545
|
+
const y1 = Math.min(...ys);
|
|
546
|
+
const y2 = Math.max(...ys);
|
|
547
|
+
if (y2 <= y1) return null;
|
|
548
|
+
return (
|
|
549
|
+
<line
|
|
550
|
+
data-cdl-role="timeline-axis"
|
|
551
|
+
x1={lane.x + lane.width / 2}
|
|
552
|
+
y1={y1}
|
|
553
|
+
x2={lane.x + lane.width / 2}
|
|
554
|
+
y2={y2}
|
|
555
|
+
stroke="var(--cdl-tone-accent, #2d6a8f)"
|
|
556
|
+
strokeWidth={TIMELINE_NUMBER_STROKE}
|
|
557
|
+
/>
|
|
558
|
+
);
|
|
559
|
+
})()}
|
|
560
|
+
{lane.metro !== undefined && (
|
|
561
|
+
<>
|
|
562
|
+
<line
|
|
563
|
+
data-cdl-role="metro-lane-guide"
|
|
564
|
+
x1={lane.x + METRO_LANE_BADGE_INSET + METRO_LANE_BADGE_WIDTH + METRO_LANE_GUIDE_GAP}
|
|
565
|
+
y1={lane.y + lane.height / 2}
|
|
566
|
+
x2={lane.x + lane.width - METRO_LANE_BADGE_INSET}
|
|
567
|
+
y2={lane.y + lane.height / 2}
|
|
568
|
+
stroke="var(--cdl-line-mute, #a89d8f)"
|
|
569
|
+
strokeWidth={2}
|
|
570
|
+
strokeDasharray="22 7 5 7"
|
|
571
|
+
opacity={0.45}
|
|
572
|
+
/>
|
|
573
|
+
<g
|
|
574
|
+
data-cdl-role="metro-lane-badge"
|
|
575
|
+
transform={`translate(${lane.x + METRO_LANE_BADGE_INSET} ${lane.y + lane.height / 2 - METRO_LANE_BADGE_HEIGHT / 2})`}
|
|
576
|
+
>
|
|
577
|
+
<rect
|
|
578
|
+
width={METRO_LANE_BADGE_WIDTH}
|
|
579
|
+
height={METRO_LANE_BADGE_HEIGHT}
|
|
580
|
+
rx={12}
|
|
581
|
+
fill="var(--cdl-node-fill, #ffffff)"
|
|
582
|
+
stroke="var(--cdl-tone-accent, #2d6a8f)"
|
|
583
|
+
strokeWidth={1.25}
|
|
584
|
+
/>
|
|
585
|
+
<text
|
|
586
|
+
x={METRO_LANE_BADGE_WIDTH / 2}
|
|
587
|
+
y={lane.metro.subtitle ? 37 : 53}
|
|
588
|
+
fontSize={26}
|
|
589
|
+
fontWeight={700}
|
|
590
|
+
fill="var(--cdl-text, #1a1f2a)"
|
|
591
|
+
textAnchor="middle"
|
|
592
|
+
>
|
|
593
|
+
{lane.label}
|
|
594
|
+
</text>
|
|
595
|
+
{lane.metro.subtitle && (
|
|
596
|
+
<text
|
|
597
|
+
x={METRO_LANE_BADGE_WIDTH / 2}
|
|
598
|
+
y={67}
|
|
599
|
+
fontSize={18}
|
|
600
|
+
fill="var(--cdl-text-dim, #5a6270)"
|
|
601
|
+
textAnchor="middle"
|
|
602
|
+
>
|
|
603
|
+
{lane.metro.subtitle}
|
|
604
|
+
</text>
|
|
605
|
+
)}
|
|
606
|
+
</g>
|
|
607
|
+
</>
|
|
608
|
+
)}
|
|
609
|
+
{lane.label && lane.metro === undefined && (
|
|
418
610
|
<text
|
|
419
611
|
data-cdl-role="lane-label"
|
|
420
612
|
x={lane.x + 24}
|
|
@@ -430,8 +622,35 @@ export function CdlStage({
|
|
|
430
622
|
</g>
|
|
431
623
|
))}
|
|
432
624
|
|
|
625
|
+
{/* 補助線は札と番号の下に敷き、edge の矢頭や配置には参加させない。 */}
|
|
626
|
+
{laid.nodes.map((node) => {
|
|
627
|
+
if (node.leaderTo === undefined) return null;
|
|
628
|
+
const target = nodesById.get(node.leaderTo);
|
|
629
|
+
if (target === undefined) return null;
|
|
630
|
+
if (!isRenderedNode(node) || !isRenderedNode(target)) return null;
|
|
631
|
+
const targetIsRight = target.cx >= node.cx;
|
|
632
|
+
const x1 = node.cx + (targetIsRight ? node.w / 2 : -node.w / 2);
|
|
633
|
+
const x2 = target.cx + (targetIsRight ? -target.w / 2 : target.w / 2);
|
|
634
|
+
if (targetIsRight ? x2 <= x1 : x1 <= x2) return null;
|
|
635
|
+
return (
|
|
636
|
+
<line
|
|
637
|
+
key={`timeline-leader-${node.id}`}
|
|
638
|
+
data-cdl-role="timeline-leader"
|
|
639
|
+
data-cdl-leader-from={node.id}
|
|
640
|
+
data-cdl-leader-to={node.leaderTo}
|
|
641
|
+
x1={x1}
|
|
642
|
+
y1={node.cy}
|
|
643
|
+
x2={x2}
|
|
644
|
+
y2={target.cy}
|
|
645
|
+
stroke="var(--cdl-line-mute, #a89d8f)"
|
|
646
|
+
strokeWidth={1.5}
|
|
647
|
+
strokeDasharray="2 4"
|
|
648
|
+
strokeLinecap="round"
|
|
649
|
+
/>
|
|
650
|
+
);
|
|
651
|
+
})}
|
|
433
652
|
{/* edges (1st pass) */}
|
|
434
|
-
{laid.edges.filter(
|
|
653
|
+
{laid.edges.filter(線を描く).map((edge) => (
|
|
435
654
|
<CdlEdgeView
|
|
436
655
|
key={edge.id}
|
|
437
656
|
edge={edge}
|
|
@@ -440,6 +659,7 @@ export function CdlStage({
|
|
|
440
659
|
drawing={矢印の出番.伸ばすか(edge.id)}
|
|
441
660
|
progress={矢印の出番.伸ばすか(edge.id) ? 描く進み : progress}
|
|
442
661
|
stateValues={stateValues}
|
|
662
|
+
pending={edge.routing === "metro" && 矢印の出番.まだか(edge.id)}
|
|
443
663
|
/>
|
|
444
664
|
))}
|
|
445
665
|
|
|
@@ -449,9 +669,11 @@ export function CdlStage({
|
|
|
449
669
|
// および `tweens` は元の進みのまま = 「線はゆっくり、 値は短く」 が 1 つの phase で
|
|
450
670
|
// 両立する。 対象外にも掛けると値の移りまで速くなる
|
|
451
671
|
const 描く = drawSet.has(node.id);
|
|
672
|
+
const 段階の補足 = stageNoteNodeIds.has(node.id);
|
|
452
673
|
const view = (
|
|
453
674
|
<CdlNodeView
|
|
454
|
-
node={node}
|
|
675
|
+
node={段階の補足 ? { ...node, subtitle: undefined } : node}
|
|
676
|
+
compactTitleInset={段階の補足 ? STAGE_CARD_TEXT_INSET : undefined}
|
|
455
677
|
active={activeSet.has(node.id)}
|
|
456
678
|
// 描画側の固定の字の言語 (#847)。 `layout` が既定以外の時だけ `lang` を持たせる
|
|
457
679
|
lang={laid.lang}
|
|
@@ -463,20 +685,41 @@ export function CdlStage({
|
|
|
463
685
|
値の名前={値の名前}
|
|
464
686
|
currentPhaseId={currentPhase?.id}
|
|
465
687
|
chartLineScale={chartLineScales.get(node.id)}
|
|
688
|
+
metroDiagram={路線図}
|
|
689
|
+
metroDecision={metroDecisionIds.has(node.id)}
|
|
690
|
+
stationNameRight={stationNamesRight.has(node.id)}
|
|
466
691
|
/>
|
|
467
692
|
);
|
|
468
693
|
return <g key={node.id}>{view}</g>;
|
|
469
694
|
})}
|
|
470
695
|
|
|
696
|
+
{/* 段階列か補助線を持つ札は、既存の subtitle 入力を名前と同じ行の右端へ置く。 */}
|
|
697
|
+
{stageNoteNodes.map((node) => (
|
|
698
|
+
<text
|
|
699
|
+
key={`stage-note-${node.id}`}
|
|
700
|
+
data-cdl-role="stage-note"
|
|
701
|
+
data-cdl-stage-node={node.id}
|
|
702
|
+
x={node.cx + node.w / 2 - STAGE_CARD_TEXT_INSET}
|
|
703
|
+
y={node.cy + 1}
|
|
704
|
+
fontSize={14}
|
|
705
|
+
fontWeight={500}
|
|
706
|
+
textAnchor="end"
|
|
707
|
+
dominantBaseline="middle"
|
|
708
|
+
fill="var(--cdl-text-mute, #8a8678)"
|
|
709
|
+
>
|
|
710
|
+
{interpolate(node.subtitle, stateValues, 値の名前)}
|
|
711
|
+
</text>
|
|
712
|
+
))}
|
|
713
|
+
|
|
471
714
|
{/* 経由 node 貫通する dotted-flow edge の進行点 glow (3rd pass) */}
|
|
472
715
|
{laid.edges
|
|
473
|
-
.filter((edge) => Boolean(edge.dThrough) &&
|
|
716
|
+
.filter((edge) => Boolean(edge.dThrough) && 線を描く(edge))
|
|
474
717
|
.map((edge) => (
|
|
475
718
|
<CdlEdgeGlowView key={`glow-${edge.id}`} edge={edge} active={activeSet.has(edge.id)} />
|
|
476
719
|
))}
|
|
477
720
|
|
|
478
721
|
{/* edge labels (3rd pass) ... 全 edge の label を最前面に描画 */}
|
|
479
|
-
{laid.edges.filter(
|
|
722
|
+
{laid.edges.filter(線を描く).map((edge) => (
|
|
480
723
|
<CdlEdgeLabelView
|
|
481
724
|
key={`label-${edge.id}`}
|
|
482
725
|
edge={edge}
|
|
@@ -487,7 +730,7 @@ export function CdlStage({
|
|
|
487
730
|
|
|
488
731
|
{/* 線の端に添える字 (#821 / #825)。 札と同じ最前面の pass */}
|
|
489
732
|
{laid.edges
|
|
490
|
-
.filter((edge) => (edge.headLabel || edge.tailLabel) &&
|
|
733
|
+
.filter((edge) => (edge.headLabel || edge.tailLabel) && 線を描く(edge))
|
|
491
734
|
.flatMap((edge) =>
|
|
492
735
|
(["head", "tail"] as const).map((端) => (
|
|
493
736
|
<CdlEdgeEndLabelView
|
|
@@ -500,6 +743,8 @@ export function CdlStage({
|
|
|
500
743
|
)),
|
|
501
744
|
)}
|
|
502
745
|
|
|
746
|
+
{laid.legend && <CdlLegend items={laid.legend} />}
|
|
747
|
+
|
|
503
748
|
{/* debug overlay = engine が把握している bbox + collision を半透明で重ね描き */}
|
|
504
749
|
{debug && (
|
|
505
750
|
<g pointerEvents="none">
|