@cardenelabs/cdl 0.121.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/dist/react.cjs CHANGED
@@ -988,6 +988,7 @@ var KINDS_DRAWING_TITLE_OUTSIDE = /* @__PURE__ */ new Set([
988
988
  "dyn-arc",
989
989
  "dyn-wave",
990
990
  "dyn-polygon",
991
+ "station",
991
992
  ...KINDS_DRAWING_OWN_PAYLOAD
992
993
  ]);
993
994
  function drawsTitleOutsideBox(kind) {
@@ -995,7 +996,7 @@ function drawsTitleOutsideBox(kind) {
995
996
  }
996
997
  var TITLE_FONT_SIZE_DEFAULT = 18;
997
998
  var TITLE_FONT_SIZE = new Map([
998
- ...["dyn-rect", "dyn-circle", "dyn-arc", "dyn-wave", "dyn-polygon"].map(
999
+ ...["dyn-rect", "dyn-circle", "dyn-arc", "dyn-wave", "dyn-polygon", "station"].map(
999
1000
  (k) => [k, 13]
1000
1001
  ),
1001
1002
  ...[
@@ -1046,6 +1047,10 @@ var TITLE_FONT_SIZE = new Map([
1046
1047
  function titleFontSize(kind) {
1047
1048
  return TITLE_FONT_SIZE.get(kind ?? "") ?? TITLE_FONT_SIZE_DEFAULT;
1048
1049
  }
1050
+ var KINDS_WITH_TITLE_WIDTH_OUTSIDE_NODE = /* @__PURE__ */ new Set(["station"]);
1051
+ function skipsNodeTitleWidthCheck(kind) {
1052
+ return KINDS_WITH_TITLE_WIDTH_OUTSIDE_NODE.has(kind ?? "");
1053
+ }
1049
1054
  var TITLE_BOX_PADDING = 52;
1050
1055
  var KINDS_SCALING_TITLE_WHEN_LOW = /* @__PURE__ */ new Set(["actor", "event", "function", "storage"]);
1051
1056
  var KINDS_SCALING_TITLE_ALWAYS = /* @__PURE__ */ new Set([
@@ -1128,7 +1133,8 @@ var KINDS_DRAWING_ONLY_SHAPE = [
1128
1133
  "dyn-circle",
1129
1134
  "dyn-arc",
1130
1135
  "dyn-wave",
1131
- "dyn-polygon"
1136
+ "dyn-polygon",
1137
+ "station"
1132
1138
  ];
1133
1139
  var KINDS_WITHOUT_EYEBROW_TEXT = /* @__PURE__ */ new Set([
1134
1140
  ...KINDS_DRAWING_ONLY_SHAPE,
@@ -1831,13 +1837,13 @@ function detectCollisions(bboxes, edges) {
1831
1837
  }
1832
1838
  function extractPathSegments(d) {
1833
1839
  const segs = [];
1834
- const commandRegex = /([MLCQZmlcqz])([^MLCQZmlcqz]*)/g;
1840
+ const commandRegex = /([MLCQAZmlcqaz])([^MLCQAZmlcqaz]*)/g;
1835
1841
  let cur = { x: 0, y: 0 };
1836
1842
  let m;
1837
1843
  while ((m = commandRegex.exec(d)) !== null) {
1838
1844
  const cmd = m[1];
1839
1845
  const args = (m[2] ?? "").trim().split(/[\s,]+/).filter(Boolean).map(parseFloat);
1840
- const need = cmd === "M" || cmd === "L" ? 2 : cmd === "Q" ? 4 : cmd === "C" ? 6 : 0;
1846
+ const need = cmd === "M" || cmd === "L" ? 2 : cmd === "Q" ? 4 : cmd === "C" ? 6 : cmd === "A" ? 7 : 0;
1841
1847
  if (need > 0 && (args.length < need || !args.slice(0, need).every(Number.isFinite))) continue;
1842
1848
  if (cmd === "M") {
1843
1849
  cur = { x: args[0], y: args[1] };
@@ -1867,6 +1873,10 @@ function extractPathSegments(d) {
1867
1873
  ctrl: [{ x: args[0], y: args[1] }, { x: args[2], y: args[3] }]
1868
1874
  });
1869
1875
  cur = next;
1876
+ } else if (cmd === "A") {
1877
+ const next = { x: args[5], y: args[6] };
1878
+ segs.push({ x1: cur.x, y1: cur.y, x2: next.x, y2: next.y, cmd: "A" });
1879
+ cur = next;
1870
1880
  }
1871
1881
  }
1872
1882
  return segs;
@@ -2973,6 +2983,232 @@ function applyEdgeLabelAndPathShifts(edges, labelShiftMap, edgePathShiftMap) {
2973
2983
  });
2974
2984
  }
2975
2985
 
2986
+ // src/layout/curve-path.ts
2987
+ var SAME_HEIGHT_THRESHOLD = 1;
2988
+ function curvePath(from, to, edge, labelMarginExtra = 0) {
2989
+ const fromLeft = from.cx - from.w / 2;
2990
+ const fromRight = from.cx + from.w / 2;
2991
+ const toLeft = to.cx - to.w / 2;
2992
+ const toRight = to.cx + to.w / 2;
2993
+ let fromSide;
2994
+ let toSide;
2995
+ let x1;
2996
+ let x2;
2997
+ if (toLeft >= fromRight) {
2998
+ fromSide = "right";
2999
+ toSide = "left";
3000
+ x1 = fromRight;
3001
+ x2 = toLeft;
3002
+ } else if (toRight <= fromLeft) {
3003
+ fromSide = "left";
3004
+ toSide = "right";
3005
+ x1 = fromLeft;
3006
+ x2 = toRight;
3007
+ } else {
3008
+ fromSide = "right";
3009
+ toSide = "right";
3010
+ x1 = fromRight;
3011
+ x2 = toRight;
3012
+ }
3013
+ const y1 = from.cy;
3014
+ const y2 = to.cy;
3015
+ const sameHeight = Math.abs(y2 - y1) < SAME_HEIGHT_THRESHOLD;
3016
+ let d;
3017
+ let midX;
3018
+ let midY;
3019
+ let tangentX;
3020
+ let tangentY;
3021
+ if (sameHeight) {
3022
+ d = `M ${x1} ${y1} L ${x2} ${y2}`;
3023
+ midX = (x1 + x2) / 2;
3024
+ midY = (y1 + y2) / 2;
3025
+ tangentX = x2 - x1;
3026
+ tangentY = y2 - y1;
3027
+ } else {
3028
+ let c1x;
3029
+ let c2x;
3030
+ if (fromSide !== toSide) {
3031
+ const direction = x2 > x1 ? 1 : -1;
3032
+ const bulge = Math.max(Math.abs(x2 - x1) / 2, EDGE_STUB_OUT);
3033
+ c1x = x1 + direction * bulge;
3034
+ c2x = x2 - direction * bulge;
3035
+ } else {
3036
+ const bulgeX = Math.max(x1, x2) + EDGE_STUB_OUT;
3037
+ c1x = bulgeX;
3038
+ c2x = bulgeX;
3039
+ }
3040
+ d = `M ${x1} ${y1} C ${c1x} ${y1}, ${c2x} ${y2}, ${x2} ${y2}`;
3041
+ midX = (x1 + 3 * c1x + 3 * c2x + x2) / 8;
3042
+ midY = (y1 + 3 * y1 + 3 * y2 + y2) / 8;
3043
+ tangentX = x2 + c2x - c1x - x1;
3044
+ tangentY = y2 + y2 - y1 - y1;
3045
+ }
3046
+ if (edge?.overlay) {
3047
+ return { d, fromSide, toSide, labelX: midX, labelY: midY, labelAnchor: "middle" };
3048
+ }
3049
+ const tangentLength = Math.hypot(tangentX, tangentY);
3050
+ let normalX = tangentY / tangentLength;
3051
+ let normalY = -tangentX / tangentLength;
3052
+ if (normalY > Number.EPSILON || Math.abs(normalY) <= Number.EPSILON && normalX < 0) {
3053
+ normalX *= -1;
3054
+ normalY *= -1;
3055
+ }
3056
+ const labelW = computeLabelBoxW(edge?.label, edge?.sub);
3057
+ const labelH = labelPillH(edge?.sub);
3058
+ const distance = labelPathClearanceHorizontal(edge) + (Math.abs(normalX) * labelW + Math.abs(normalY) * labelH) / 2 + labelMarginExtra;
3059
+ return {
3060
+ d,
3061
+ fromSide,
3062
+ toSide,
3063
+ labelX: midX + normalX * distance,
3064
+ labelY: midY + normalY * distance,
3065
+ labelAnchor: "middle"
3066
+ };
3067
+ }
3068
+
3069
+ // src/layout/metro-path.ts
3070
+ var METRO_CORNER_RADIUS = 24;
3071
+ var T = Math.SQRT2 - 1;
3072
+ function round2(v) {
3073
+ return Math.round(v * 100) / 100;
3074
+ }
3075
+ function point(p) {
3076
+ return `${round2(p.x)} ${round2(p.y)}`;
3077
+ }
3078
+ function add(p, v, scale) {
3079
+ return { x: p.x + v.x * scale, y: p.y + v.y * scale };
3080
+ }
3081
+ function polyline(points) {
3082
+ const distinct = points.filter((p, i) => {
3083
+ const previous = points[i - 1];
3084
+ return previous === void 0 || p.x !== previous.x || p.y !== previous.y;
3085
+ });
3086
+ return distinct.map((p, i) => `${i === 0 ? "M" : "L"} ${point(p)}`).join(" ");
3087
+ }
3088
+ function metroPath(from, to, edge, labelMarginExtra = 0) {
3089
+ const fromLeft = from.cx - from.w / 2;
3090
+ const fromRight = from.cx + from.w / 2;
3091
+ const toLeft = to.cx - to.w / 2;
3092
+ const toRight = to.cx + to.w / 2;
3093
+ let fromSide;
3094
+ let toSide;
3095
+ let x1;
3096
+ let x2;
3097
+ let dir;
3098
+ if (toLeft >= fromRight) {
3099
+ fromSide = "right";
3100
+ toSide = "left";
3101
+ x1 = fromRight;
3102
+ x2 = toLeft;
3103
+ dir = 1;
3104
+ } else if (toRight <= fromLeft) {
3105
+ fromSide = "left";
3106
+ toSide = "right";
3107
+ x1 = fromLeft;
3108
+ x2 = toRight;
3109
+ dir = -1;
3110
+ } else {
3111
+ fromSide = "right";
3112
+ toSide = "right";
3113
+ x1 = fromRight;
3114
+ x2 = toRight;
3115
+ dir = 1;
3116
+ }
3117
+ const y1 = from.cy;
3118
+ const y2 = to.cy;
3119
+ const dy = y2 - y1;
3120
+ const sgn = Math.sign(dy);
3121
+ let d;
3122
+ let mid;
3123
+ let tangent;
3124
+ if (Math.abs(dy) < SAME_HEIGHT_THRESHOLD) {
3125
+ d = `M ${point({ x: x1, y: y1 })} L ${point({ x: x2, y: y2 })}`;
3126
+ mid = { x: (x1 + x2) / 2, y: (y1 + y2) / 2 };
3127
+ tangent = { x: x2 - x1, y: y2 - y1 };
3128
+ } else if (fromSide !== toSide && Math.abs(x2 - x1) >= Math.abs(dy)) {
3129
+ const horizontal = Math.abs(x2 - x1);
3130
+ const s = (horizontal - Math.abs(dy)) / 2;
3131
+ const p0 = { x: x1, y: y1 };
3132
+ const p1 = { x: x1 + dir * s, y: y1 };
3133
+ const p2 = { x: x2 - dir * s, y: y2 };
3134
+ const p3 = { x: x2, y: y2 };
3135
+ const u = { x: dir, y: 0 };
3136
+ const w = { x: dir / Math.SQRT2, y: sgn / Math.SQRT2 };
3137
+ const r = Math.min(
3138
+ METRO_CORNER_RADIUS,
3139
+ s / T,
3140
+ Math.SQRT2 * Math.abs(dy) / (2 * T)
3141
+ );
3142
+ const t = r * T;
3143
+ if (r >= 0.5) {
3144
+ const f1 = dir * sgn > 0 ? 1 : 0;
3145
+ d = [
3146
+ `M ${point(p0)}`,
3147
+ `L ${point(add(p1, u, -t))}`,
3148
+ `A ${round2(r)} ${round2(r)} 0 0 ${f1} ${point(add(p1, w, t))}`,
3149
+ `L ${point(add(p2, w, -t))}`,
3150
+ `A ${round2(r)} ${round2(r)} 0 0 ${1 - f1} ${point(add(p2, u, t))}`,
3151
+ `L ${point(p3)}`
3152
+ ].join(" ");
3153
+ } else {
3154
+ d = polyline([p0, p1, p2, p3]);
3155
+ }
3156
+ mid = { x: (x1 + x2) / 2, y: (y1 + y2) / 2 };
3157
+ tangent = w;
3158
+ } else {
3159
+ fromSide = "right";
3160
+ toSide = "right";
3161
+ x1 = fromRight;
3162
+ x2 = toRight;
3163
+ const xa = Math.max(x1, x2) + EDGE_STUB_OUT;
3164
+ const h = Math.abs(dy) / 2;
3165
+ const p0 = { x: x1, y: y1 };
3166
+ const p1 = { x: xa, y: y1 };
3167
+ const p2 = { x: xa + h, y: y1 + sgn * h };
3168
+ const p3 = { x: xa, y: y2 };
3169
+ const p4 = { x: x2, y: y2 };
3170
+ const u = { x: 1, y: 0 };
3171
+ const w1 = { x: 1 / Math.SQRT2, y: sgn / Math.SQRT2 };
3172
+ const w2 = { x: -1 / Math.SQRT2, y: sgn / Math.SQRT2 };
3173
+ const r = Math.min(METRO_CORNER_RADIUS, h);
3174
+ const t = r * T;
3175
+ const sweep = sgn > 0 ? 1 : 0;
3176
+ d = [
3177
+ `M ${point(p0)}`,
3178
+ `L ${point(add(p1, u, -t))}`,
3179
+ `A ${round2(r)} ${round2(r)} 0 0 ${sweep} ${point(add(p1, w1, t))}`,
3180
+ `L ${point(add(p2, w1, -r))}`,
3181
+ `A ${round2(r)} ${round2(r)} 0 0 ${sweep} ${point(add(p2, w2, r))}`,
3182
+ `L ${point(add(p3, w2, -t))}`,
3183
+ `A ${round2(r)} ${round2(r)} 0 0 ${sweep} ${point(add(p3, u, -t))}`,
3184
+ `L ${point(p4)}`
3185
+ ].join(" ");
3186
+ mid = { x: p2.x - T * r, y: p2.y };
3187
+ tangent = { x: 0, y: sgn };
3188
+ }
3189
+ if (edge?.overlay) {
3190
+ return { d, fromSide, toSide, labelX: mid.x, labelY: mid.y, labelAnchor: "middle" };
3191
+ }
3192
+ const tangentLength = Math.hypot(tangent.x, tangent.y);
3193
+ let normalX = tangent.y / tangentLength;
3194
+ let normalY = -tangent.x / tangentLength;
3195
+ if (normalY > Number.EPSILON || Math.abs(normalY) <= Number.EPSILON && normalX < 0) {
3196
+ normalX *= -1;
3197
+ normalY *= -1;
3198
+ }
3199
+ const labelW = computeLabelBoxW(edge?.label, edge?.sub);
3200
+ const labelH = labelPillH(edge?.sub);
3201
+ const distance = labelPathClearanceHorizontal(edge) + (Math.abs(normalX) * labelW + Math.abs(normalY) * labelH) / 2 + labelMarginExtra;
3202
+ return {
3203
+ d,
3204
+ fromSide,
3205
+ toSide,
3206
+ labelX: mid.x + normalX * distance,
3207
+ labelY: mid.y + normalY * distance,
3208
+ labelAnchor: "middle"
3209
+ };
3210
+ }
3211
+
2976
3212
  // src/types.ts
2977
3213
  var TONES = ["accent", "teal", "success", "error", "warning", "info"];
2978
3214
  var NODE_KINDS = [
@@ -3100,6 +3336,8 @@ var NODE_KINDS = [
3100
3336
  // 状態遷移図が使う。 印そのものが意味を持つので文字は描かない
3101
3337
  "mark-start",
3102
3338
  "mark-end",
3339
+ // 路線図の駅 (#996) ... 線路上の点を小さな丸で示し、名前と説明は丸の外に置く
3340
+ "station",
3103
3341
  // 時系列のやり取り (#578) ... 1 つの箱が図を丸ごと描く。 縦が時間で、参加者は箱を持たない
3104
3342
  "sequence-board"
3105
3343
  ];
@@ -6259,6 +6497,7 @@ function runAxes(laid, diag, violations, counts, push, profile, skippedAxes, \u5
6259
6497
  const LIFELINE_W_THRESHOLD = 8;
6260
6498
  for (const n of laid.nodes) {
6261
6499
  if (n.w < LIFELINE_W_THRESHOLD) continue;
6500
+ if (n.kind === "station") continue;
6262
6501
  if (n.w < MIN_NODE_W || n.h < MIN_NODE_H) {
6263
6502
  push(
6264
6503
  "node-visibility",
@@ -6280,6 +6519,7 @@ function runAxes(laid, diag, violations, counts, push, profile, skippedAxes, \u5
6280
6519
  if (n.id.endsWith("-spacer")) continue;
6281
6520
  if (!rendersTitleText(n.kind)) continue;
6282
6521
  if (fitsOwnTitleWidth(n.kind)) continue;
6522
+ if (skipsNodeTitleWidthCheck(n.kind)) continue;
6283
6523
  if (!isRenderedNode(n)) continue;
6284
6524
  const titleTrimmed = n.title?.trim() ?? "";
6285
6525
  if (!titleTrimmed) continue;
@@ -6884,6 +7124,9 @@ function runAxes(laid, diag, violations, counts, push, profile, skippedAxes, \u5
6884
7124
  for (let i = 0; i + 1 < sorted.length; i++) {
6885
7125
  const upper = sorted[i];
6886
7126
  const lower = sorted[i + 1];
7127
+ if (upper.cx + upper.w / 2 <= lower.cx - lower.w / 2 || lower.cx + lower.w / 2 <= upper.cx - upper.w / 2) {
7128
+ continue;
7129
+ }
6887
7130
  const upperBottom = upper.cy + upper.h / 2;
6888
7131
  const lowerTop = lower.cy - lower.h / 2;
6889
7132
  const gap = lowerTop - upperBottom;
@@ -7515,11 +7758,18 @@ function runAxes(laid, diag, violations, counts, push, profile, skippedAxes, \u5
7515
7758
  {
7516
7759
  const crestSpans = (segs, peakY) => {
7517
7760
  const out = [];
7761
+ let \u5186\u5F27\u306E\u59CB\u70B9 = null;
7518
7762
  for (const s of segs) {
7519
- if (Math.abs(s.y1 - s.y2) > 1) continue;
7520
- if (Math.abs(s.y1 - peakY) > 1) continue;
7763
+ if (s.cmd === "A") {
7764
+ \u5186\u5F27\u306E\u59CB\u70B9 ??= { x: s.x1, y: s.y1 };
7765
+ continue;
7766
+ }
7767
+ const \u59CB\u70B9 = \u5186\u5F27\u306E\u59CB\u70B9 ?? { x: s.x1, y: s.y1 };
7768
+ \u5186\u5F27\u306E\u59CB\u70B9 = null;
7769
+ if (Math.abs(\u59CB\u70B9.y - s.y2) > 1) continue;
7770
+ if (Math.abs(\u59CB\u70B9.y - peakY) > 1) continue;
7521
7771
  if (s.cmd !== "L" && !(s.ctrl ?? []).every((c) => Math.abs(c.y - peakY) <= 1)) continue;
7522
- out.push({ minX: Math.min(s.x1, s.x2), maxX: Math.max(s.x1, s.x2) });
7772
+ out.push({ minX: Math.min(\u59CB\u70B9.x, s.x2), maxX: Math.max(\u59CB\u70B9.x, s.x2) });
7523
7773
  }
7524
7774
  return out;
7525
7775
  };
@@ -7956,6 +8206,8 @@ var TOKENS = {
7956
8206
  // 箱としての最小 (80x40) より大きく、名前を持つ箱 (card 320x150) よりは明らかに小さい
7957
8207
  "mark-start": { w: 96, h: 96 },
7958
8208
  "mark-end": { w: 96, h: 96 },
8209
+ // 路線図の駅 (#996) ... 正方形にして小さな丸が縦横どちらにも歪まないようにする。
8210
+ station: { w: 24, h: 24 },
7959
8211
  // 時系列のやり取り (#578)。 実寸は中身から決まる (`sequenceBoardMetrics`) ので、
7960
8212
  // ここは書かなかった時の受け皿
7961
8213
  "sequence-board": { w: 960, h: 640 }
@@ -7980,6 +8232,7 @@ var LANE_PAD_MIN = 25;
7980
8232
  // src/layout/edges.ts
7981
8233
  var VERTICAL_DETOUR_GAP = 50;
7982
8234
  var VERTICAL_DETOUR_CORNER = 14;
8235
+ var OVERLAY_LABEL_ON_PATH_TOLERANCE = 1;
7983
8236
  function verticalCornerDir(from, to) {
7984
8237
  return to < from ? -1 : 1;
7985
8238
  }
@@ -8063,6 +8316,36 @@ function layoutEdges(diag, nodes, lanes = []) {
8063
8316
  });
8064
8317
  } else if (isSelfLoop(e)) {
8065
8318
  selfLoopEdges.push(e);
8319
+ } else if (e.routing === "curve") {
8320
+ const from = nmap.get(e.from);
8321
+ const to = nmap.get(e.to);
8322
+ if (!from) throw new Error(`cdl layout: edge "${e.id}" \u306E from "${e.from}" \u304C node \u306B\u7121\u3044`);
8323
+ if (!to) throw new Error(`cdl layout: edge "${e.id}" \u306E to "${e.to}" \u304C node \u306B\u7121\u3044`);
8324
+ const c = curvePath(from, to, e, labelMarginExtra);
8325
+ positionedEdges.push({
8326
+ ...e,
8327
+ d: c.d,
8328
+ fromSide: c.fromSide,
8329
+ toSide: c.toSide,
8330
+ labelX: c.labelX + (e.labelOffsetX ?? 0),
8331
+ labelY: c.labelY + (e.labelOffsetY ?? 0),
8332
+ labelAnchor: c.labelAnchor
8333
+ });
8334
+ } else if (e.routing === "metro") {
8335
+ const from = nmap.get(e.from);
8336
+ const to = nmap.get(e.to);
8337
+ if (!from) throw new Error(`cdl layout: edge "${e.id}" \u306E from "${e.from}" \u304C node \u306B\u7121\u3044`);
8338
+ if (!to) throw new Error(`cdl layout: edge "${e.id}" \u306E to "${e.to}" \u304C node \u306B\u7121\u3044`);
8339
+ const m = metroPath(from, to, e, labelMarginExtra);
8340
+ positionedEdges.push({
8341
+ ...e,
8342
+ d: m.d,
8343
+ fromSide: m.fromSide,
8344
+ toSide: m.toSide,
8345
+ labelX: m.labelX + (e.labelOffsetX ?? 0),
8346
+ labelY: m.labelY + (e.labelOffsetY ?? 0),
8347
+ labelAnchor: m.labelAnchor
8348
+ });
8066
8349
  } else {
8067
8350
  autoEdges.push(e);
8068
8351
  }
@@ -9567,9 +9850,11 @@ function hopEdgeCrossings(edges) {
9567
9850
  ends.set(e.id, pathEnds(segs));
9568
9851
  }
9569
9852
  for (const e of edges) {
9853
+ if (e.routing === "metro") continue;
9570
9854
  if ((e.d.match(/M/g) ?? []).length !== 1) continue;
9571
9855
  const own = extractPathSegments(e.d);
9572
9856
  if (own.length === 0) continue;
9857
+ if (own.some((s) => s.cmd === "A")) continue;
9573
9858
  const myEnds = ends.get(e.id) ?? [];
9574
9859
  const hops = /* @__PURE__ */ new Map();
9575
9860
  for (let i = 0; i < own.length; i++) {
@@ -9657,7 +9942,7 @@ function pathVertices(d) {
9657
9942
  const out = [{ x: first.x1, y: first.y1 }];
9658
9943
  for (let i = 0; i < segs.length; i++) {
9659
9944
  const s = segs[i];
9660
- if (s.cmd === "C") return null;
9945
+ if (s.cmd === "C" || s.cmd === "A") return null;
9661
9946
  if (s.cmd === "Q") {
9662
9947
  const c = s.ctrl?.[0];
9663
9948
  if (!c) return null;
@@ -9720,7 +10005,7 @@ function separateSharedStraightRuns(edges, nodes) {
9720
10005
  const v = pathVertices(e.d);
9721
10006
  if (v && v.length >= 2) verts.set(e.id, v);
9722
10007
  }
9723
- if (verts.size < 2) return;
10008
+ if (verts.size === 0) return;
9724
10009
  const moved = /* @__PURE__ */ new Set();
9725
10010
  const fits = (e, pts, k, ax, delta) => {
9726
10011
  const \u8A66\u3057 = pts.map(
@@ -9738,33 +10023,40 @@ function separateSharedStraightRuns(edges, nodes) {
9738
10023
  for (let j = i + 1; j < edges.length; j++) {
9739
10024
  const \u524D = edges[i];
9740
10025
  const \u5F8C = edges[j];
9741
- const A = verts.get(\u524D.id);
9742
- const B = verts.get(\u5F8C.id);
9743
- if (!A || !B) continue;
9744
- if (\u524D.from === \u5F8C.from) continue;
10026
+ if (\u524D.routing === "metro" && \u5F8C.routing === "metro") continue;
10027
+ const \u56FA\u5B9A = \u5F8C.routing === "metro" ? \u5F8C : \u524D;
10028
+ const \u52D5\u304B\u3059 = \u5F8C.routing === "metro" ? \u524D : \u5F8C;
10029
+ const B = verts.get(\u52D5\u304B\u3059.id);
10030
+ if (!B) continue;
10031
+ const \u56FA\u5B9A\u306E\u89D2 = verts.get(\u56FA\u5B9A.id);
10032
+ const \u56FA\u5B9A\u306E\u7D4C\u8DEF = \u56FA\u5B9A\u306E\u89D2 ? [\u56FA\u5B9A\u306E\u89D2] : \u56FA\u5B9A.routing === "metro" ? extractPathSegments(\u56FA\u5B9A.d).filter((s) => s.cmd === "L").map((s) => [{ x: s.x1, y: s.y1 }, { x: s.x2, y: s.y2 }]) : [];
10033
+ if (\u56FA\u5B9A\u306E\u7D4C\u8DEF.length === 0) continue;
10034
+ if (\u56FA\u5B9A.from === \u52D5\u304B\u3059.from) continue;
9745
10035
  for (const ax of [0, 1]) {
9746
- for (let ka = 0; ka + 1 < A.length; ka++) {
9747
- if (!runIsAligned(A, ka, ax)) continue;
9748
- for (let kb = 0; kb + 1 < B.length; kb++) {
9749
- if (!runIsAligned(B, kb, ax)) continue;
9750
- if (kb === 0 || kb + 1 === B.length - 1) continue;
9751
- const \u5EA7\u6A19A = ax === 0 ? A[ka].x : A[ka].y;
9752
- const \u5EA7\u6A19B = ax === 0 ? B[kb].x : B[kb].y;
9753
- if (Math.abs(\u5EA7\u6A19A - \u5EA7\u6A19B) > SHARED_RUN_EPS) continue;
9754
- const sa = runSpan(A, ka, ax);
9755
- const sb = runSpan(B, kb, ax);
9756
- if (Math.min(sa.hi, sb.hi) - Math.max(sa.lo, sb.lo) < SHARED_RUN_MIN) continue;
9757
- for (const delta of [SHARED_RUN_SEPARATION, -SHARED_RUN_SEPARATION]) {
9758
- if (!fits(\u5F8C, B, kb, ax, delta)) continue;
9759
- if (ax === 0) {
9760
- B[kb].x += delta;
9761
- B[kb + 1].x += delta;
9762
- } else {
9763
- B[kb].y += delta;
9764
- B[kb + 1].y += delta;
10036
+ for (const A of \u56FA\u5B9A\u306E\u7D4C\u8DEF) {
10037
+ for (let ka = 0; ka + 1 < A.length; ka++) {
10038
+ if (!runIsAligned(A, ka, ax)) continue;
10039
+ for (let kb = 0; kb + 1 < B.length; kb++) {
10040
+ if (!runIsAligned(B, kb, ax)) continue;
10041
+ if (kb === 0 || kb + 1 === B.length - 1) continue;
10042
+ const \u5EA7\u6A19A = ax === 0 ? A[ka].x : A[ka].y;
10043
+ const \u5EA7\u6A19B = ax === 0 ? B[kb].x : B[kb].y;
10044
+ if (Math.abs(\u5EA7\u6A19A - \u5EA7\u6A19B) > SHARED_RUN_EPS) continue;
10045
+ const sa = runSpan(A, ka, ax);
10046
+ const sb = runSpan(B, kb, ax);
10047
+ if (Math.min(sa.hi, sb.hi) - Math.max(sa.lo, sb.lo) < SHARED_RUN_MIN) continue;
10048
+ for (const delta of [SHARED_RUN_SEPARATION, -SHARED_RUN_SEPARATION]) {
10049
+ if (!fits(\u52D5\u304B\u3059, B, kb, ax, delta)) continue;
10050
+ if (ax === 0) {
10051
+ B[kb].x += delta;
10052
+ B[kb + 1].x += delta;
10053
+ } else {
10054
+ B[kb].y += delta;
10055
+ B[kb + 1].y += delta;
10056
+ }
10057
+ moved.add(\u52D5\u304B\u3059.id);
10058
+ break;
9765
10059
  }
9766
- moved.add(\u5F8C.id);
9767
- break;
9768
10060
  }
9769
10061
  }
9770
10062
  }
@@ -9791,12 +10083,19 @@ function pullOutgoingFirstCorner(edges, nodes) {
9791
10083
  let \u672D\u3092\u65E7\u7D4C\u8DEF\u3078\u8FFD\u5F93\u6E08\u307F = false;
9792
10084
  for (const list of \u675F.values()) {
9793
10085
  if (list.length < 2) continue;
9794
- const \u4E26\u3073 = list.flatMap((e) => {
10086
+ const \u4E26\u3073 = list.map((e) => {
9795
10087
  const pts = pathVertices(e.d);
9796
- if (!pts || pts.length < 2) return [];
9797
- return [{ e, pts, \u6700\u521D\u306E\u9577\u3055: Math.hypot(pts[1].x - pts[0].x, pts[1].y - pts[0].y) }];
9798
- }).sort((a, b) => b.\u6700\u521D\u306E\u9577\u3055 - a.\u6700\u521D\u306E\u9577\u3055);
10088
+ if (pts && pts.length >= 2) {
10089
+ return { e, pts, \u6700\u521D\u306E\u9577\u3055: Math.hypot(pts[1].x - pts[0].x, pts[1].y - pts[0].y) };
10090
+ }
10091
+ if (e.routing !== "metro") return null;
10092
+ const first = extractPathSegments(e.d)[0];
10093
+ if (!first) return null;
10094
+ return { e, pts: null, \u6700\u521D\u306E\u9577\u3055: Math.hypot(first.x2 - first.x1, first.y2 - first.y1) };
10095
+ }).filter((x) => x !== null).sort((a, b) => b.\u6700\u521D\u306E\u9577\u3055 - a.\u6700\u521D\u306E\u9577\u3055);
9799
10096
  for (const { e, pts, \u6700\u521D\u306E\u9577\u3055 } of \u4E26\u3073.slice(1)) {
10097
+ if (e.routing === "metro") continue;
10098
+ if (!pts) continue;
9800
10099
  if ((\u540C\u3058\u7D44\u306E\u6570.get(`${e.from}->${e.to}`) ?? 0) > 1) continue;
9801
10100
  if (pts.length < 3 || \u6700\u521D\u306E\u9577\u3055 <= EDGE_STUB_OUT + VERTICAL_DETOUR_CORNER) continue;
9802
10101
  const \u6A2A = e.fromSide === "left" || e.fromSide === "right";
@@ -9850,7 +10149,8 @@ var ENDPOINT_FAN_MIN = 20;
9850
10149
  var ENDPOINT_FAN_RUNUP = 40;
9851
10150
  var MIXED_ROOT_OVERLAP_MIN = 24;
9852
10151
  function fanIncomingEndpoints(edges, nodes) {
9853
- if (edges.length < 2) return;
10152
+ const \u7D42\u70B9\u306E\u79FB\u52D5 = /* @__PURE__ */ new Map();
10153
+ if (edges.length < 2) return \u7D42\u70B9\u306E\u79FB\u52D5;
9854
10154
  const nmap = new Map(nodes.map((n) => [n.id, n]));
9855
10155
  const \u5E73\u5766\u306A\u533A\u9593 = new Map(edges.map((e) => [e.id, flattenPathSegments(extractPathSegments(e.d))]));
9856
10156
  const \u8EF8\u306B\u5E73\u884C\u306A\u91CD\u306A\u308A\u9577\u3055 = (\u4E00\u65B9, \u4ED6\u65B9) => {
@@ -9920,6 +10220,7 @@ function fanIncomingEndpoints(edges, nodes) {
9920
10220
  const d = \u4E2D\u592E\u304C\u585E\u304C\u3063\u3066\u3044\u308B ? \u4E26\u3073[k].\u6DF1\u3055 < \u8FBA\u306E\u4E2D\u5FC3 ? -(++\u8CA0\u5074\u306E\u5E2D * \u9593\u9694) : ++\u6B63\u5074\u306E\u5E2D * \u9593\u9694 : k * \u9593\u9694 - \u4E2D\u5FC3;
9921
10221
  if (Math.abs(d) < 1e-3) continue;
9922
10222
  const e = \u4E26\u3073[k].e;
10223
+ if (e.routing === "metro") continue;
9923
10224
  const \u524D = e.d;
9924
10225
  const \u524D\u306E\u4EA4\u5DEE = countEdgeCrossings(edges).count;
9925
10226
  const \u9577\u3044\u52A9\u8D70 = \u4E26\u3073[k].\u9577\u3044\u52A9\u8D70;
@@ -9931,6 +10232,68 @@ function fanIncomingEndpoints(edges, nodes) {
9931
10232
  if (countEdgeCrossings(edges).count <= \u524D\u306E\u4EA4\u5DEE) break;
9932
10233
  e.d = \u524D;
9933
10234
  }
10235
+ if (e.d !== \u524D) \u7D42\u70B9\u306E\u79FB\u52D5.set(e.id, \u6A2A ? { dx: 0, dy: d } : { dx: d, dy: 0 });
10236
+ }
10237
+ }
10238
+ return \u7D42\u70B9\u306E\u79FB\u52D5;
10239
+ }
10240
+ function followLabelsToFannedTails(edges, obstacles, shifts) {
10241
+ if (shifts.size === 0) return;
10242
+ const \u6E2C\u308A\u65B9 = \u7DDA\u306E\u6E2C\u308A\u65B9\u3092\u4F5C\u308B();
10243
+ const labels = edges.filter(hasRenderedLabel).map((e) => ({
10244
+ kind: "edge-label",
10245
+ id: e.id,
10246
+ ...computeLabelBBoxWorld(e)
10247
+ }));
10248
+ const labelIndexByEdgeId = new Map(labels.map((label, index) => [label.id, index]));
10249
+ const edgeById = new Map(edges.map((e) => [e.id, e]));
10250
+ const \u5468\u308A = {
10251
+ \u7BB1\u305F\u3061: obstacles.filter((o) => o.kind === "node" || o.kind === "lane-label"),
10252
+ labels,
10253
+ edges,
10254
+ edgeById,
10255
+ \u6E2C\u308A\u65B9
10256
+ };
10257
+ for (const e of edges) {
10258
+ const \u79FB\u52D5 = shifts.get(e.id);
10259
+ if (!\u79FB\u52D5 || !hasRenderedLabel(e)) continue;
10260
+ const labelIndex = labelIndexByEdgeId.get(e.id);
10261
+ if (labelIndex === void 0) continue;
10262
+ const \u5143\u306E\u672D = labels[labelIndex];
10263
+ const \u81EA\u5206\u306E\u533A\u9593 = \u6E2C\u308A\u65B9.\u533A\u9593(e.id, e.d);
10264
+ if (e.overlay && measurePathLabelGapSegs(\u81EA\u5206\u306E\u533A\u9593, {
10265
+ x: \u5143\u306E\u672D.x + \u5143\u306E\u672D.w / 2,
10266
+ y: \u5143\u306E\u672D.y + \u5143\u306E\u672D.h / 2,
10267
+ w: 0,
10268
+ h: 0
10269
+ }) <= OVERLAY_LABEL_ON_PATH_TOLERANCE) continue;
10270
+ const \u6700\u5F8C = \u81EA\u5206\u306E\u533A\u9593.at(-1);
10271
+ if (!\u6700\u5F8C) continue;
10272
+ if (!segmentsCrossRect([\u6700\u5F8C], \u5143\u306E\u672D)) continue;
10273
+ const \u5019\u88DCa = {
10274
+ labelX: e.labelX + \u79FB\u52D5.dx,
10275
+ labelY: e.labelY + \u79FB\u52D5.dy,
10276
+ labelAnchor: e.labelAnchor
10277
+ };
10278
+ const \u5019\u88DCa\u306E\u7BB1 = computeLabelBBoxWorld({ ...e, ...\u5019\u88DCa });
10279
+ const \u6700\u5F8C\u306F\u7E26 = Math.abs(\u6700\u5F8C.x2 - \u6700\u5F8C.x1) <= Math.abs(\u6700\u5F8C.y2 - \u6700\u5F8C.y1);
10280
+ const \u5019\u88DC = [
10281
+ \u5019\u88DCa,
10282
+ {
10283
+ labelX: \u6700\u5F8C\u306F\u7E26 ? \u6700\u5F8C.x1 * 2 - (\u5019\u88DCa\u306E\u7BB1.x + \u5019\u88DCa\u306E\u7BB1.w / 2) : \u5019\u88DCa\u306E\u7BB1.x + \u5019\u88DCa\u306E\u7BB1.w / 2,
10284
+ labelY: \u6700\u5F8C\u306F\u7E26 ? \u5019\u88DCa\u306E\u7BB1.y + \u5019\u88DCa\u306E\u7BB1.h / 2 : \u6700\u5F8C.y1 * 2 - (\u5019\u88DCa\u306E\u7BB1.y + \u5019\u88DCa\u306E\u7BB1.h / 2),
10285
+ labelAnchor: "middle"
10286
+ }
10287
+ ];
10288
+ for (const c of \u5019\u88DC) {
10289
+ const \u672D = { kind: "edge-label", id: e.id, ...computeLabelBBoxWorld({ ...e, ...c }) };
10290
+ if (segmentsCrossRect([\u6700\u5F8C], \u672D)) continue;
10291
+ if (\u672D\u306E\u79FB\u52D5\u3092\u585E\u3050\u76F8\u624B\u3092\u63A2\u3059(\u5468\u308A, \u5143\u306E\u672D, \u672D) !== null) continue;
10292
+ e.labelX = c.labelX;
10293
+ e.labelY = c.labelY;
10294
+ e.labelAnchor = c.labelAnchor;
10295
+ labels[labelIndex] = \u672D;
10296
+ break;
9934
10297
  }
9935
10298
  }
9936
10299
  }
@@ -9947,7 +10310,7 @@ function \u672B\u5C3E\u3092\u8FBA\u306B\u6CBF\u3063\u3066\u305A\u3089\u3059(e, \
9947
10310
  if ((e.d.match(/M/g) ?? []).length !== 1) return null;
9948
10311
  const segs = extractPathSegments(e.d);
9949
10312
  if (segs.length === 0) return null;
9950
- if (segs.some((s) => s.cmd === "C")) return null;
10313
+ if (segs.some((s) => s.cmd === "C" || s.cmd === "A")) return null;
9951
10314
  const last = segs[segs.length - 1];
9952
10315
  if (last.cmd !== "L") return null;
9953
10316
  const \u5782\u76F4\u306B\u5165\u308B = \u6A2A ? Math.abs(last.y2 - last.y1) < 0.5 && Math.abs(last.x2 - last.x1) > 0.5 : Math.abs(last.x2 - last.x1) < 0.5 && Math.abs(last.y2 - last.y1) > 0.5;
@@ -10238,13 +10601,16 @@ function \u5411\u304D\u3092\u53D6\u308B(end, \u5019\u88DC) {
10238
10601
  }
10239
10602
  return null;
10240
10603
  }
10241
- function \u7AEF\u3067\u306E\u5411\u304D(d, \u7AEF) {
10604
+ function \u7DDA\u306E\u7AEF\u306E\u5411\u304D(d, \u7AEF) {
10242
10605
  const segs = extractPathSegments(d);
10243
10606
  if (segs.length === 0) return null;
10244
10607
  if (\u7AEF === "head") {
10245
10608
  const last = segs[segs.length - 1];
10246
10609
  const end2 = { x: last.x2, y: last.y2 };
10247
- const \u5019\u88DC2 = segs.slice().reverse().flatMap((s) => [s.cmd === "Q" ? s.ctrl?.[0] : s.cmd === "C" ? s.ctrl?.[1] : void 0, { x: s.x1, y: s.y1 }]);
10610
+ const \u5019\u88DC2 = segs.slice().reverse().flatMap((s) => {
10611
+ const \u5236\u5FA1\u70B9 = s.cmd === "C" ? [s.ctrl?.[1], s.ctrl?.[0]] : s.cmd === "Q" ? [s.ctrl?.[0]] : [];
10612
+ return [...\u5236\u5FA1\u70B9, { x: s.x1, y: s.y1 }];
10613
+ });
10248
10614
  return \u5411\u304D\u3092\u53D6\u308B(end2, \u5019\u88DC2);
10249
10615
  }
10250
10616
  const first = segs[0];
@@ -10255,7 +10621,7 @@ function \u7AEF\u3067\u306E\u5411\u304D(d, \u7AEF) {
10255
10621
  function \u7AEF\u306E\u5B57\u3092\u7F6E\u304F(edge, \u7AEF = "head") {
10256
10622
  const text = (\u7AEF === "head" ? edge.headLabel : edge.tailLabel)?.trim();
10257
10623
  if (!text) return null;
10258
- const \u5411\u304D = \u7AEF\u3067\u306E\u5411\u304D(edge.d, \u7AEF);
10624
+ const \u5411\u304D = \u7DDA\u306E\u7AEF\u306E\u5411\u304D(edge.d, \u7AEF);
10259
10625
  if (!\u5411\u304D) return null;
10260
10626
  const { end, ux, uy } = \u5411\u304D;
10261
10627
  const bx = end.x - ux * \u7AEF\u306E\u5B57\u3092\u623B\u3059\u8DDD\u96E2;
@@ -10583,22 +10949,23 @@ function layout(diag) {
10583
10949
  }
10584
10950
  }
10585
10951
  const positionedEdgeIds = new Set(
10586
- diag.edges.filter((e) => e.posX1 !== void 0 && e.posY1 !== void 0 && e.posX2 !== void 0 && e.posY2 !== void 0).map((e) => e.id)
10952
+ diag.edges.filter(
10953
+ (e) => e.routing === "curve" || e.routing === "metro" || e.posX1 !== void 0 && e.posY1 !== void 0 && e.posX2 !== void 0 && e.posY2 !== void 0
10954
+ ).map((e) => e.id)
10587
10955
  );
10588
10956
  bowSharedStraightPaths(edges, nodesFinal, positionedEdgeIds);
10589
10957
  pullOutgoingFirstCorner(edges, nodesFinal);
10590
- fanIncomingEndpoints(edges, nodesFinal);
10958
+ const fannedTailShifts = fanIncomingEndpoints(edges, nodesFinal);
10591
10959
  separateSharedStraightRuns(edges, nodesFinal);
10592
10960
  hopEdgeCrossings(edges);
10961
+ const labelClearanceObstacles = collectBBoxes(containedLanesFinal, nodesFinal, []).filter(
10962
+ (b) => b.kind === "node" || b.kind === "lane-label"
10963
+ );
10964
+ followLabelsToFannedTails(edges, labelClearanceObstacles, fannedTailShifts);
10593
10965
  repositionParallelLabels(edges, nodesFinal);
10594
10966
  slideLabelsAlongOwnPath(edges, nodesFinal);
10595
10967
  if (!isChainEdgeLabelDisabled()) {
10596
- edges = resolveLabelClearanceAfterPathPasses(
10597
- edges,
10598
- collectBBoxes(containedLanesFinal, nodesFinal, []).filter(
10599
- (b) => b.kind === "node" || b.kind === "lane-label"
10600
- )
10601
- );
10968
+ edges = resolveLabelClearanceAfterPathPasses(edges, labelClearanceObstacles);
10602
10969
  }
10603
10970
  const bboxes = collectBBoxes(containedLanesFinal, nodesFinal, edges);
10604
10971
  const collisions = detectCollisions(bboxes, edges);
@@ -11536,6 +11903,7 @@ function CdlEdgeView({
11536
11903
  "g",
11537
11904
  {
11538
11905
  "data-cdl-edge": edge.id,
11906
+ "data-cdl-routing": edge.routing === "metro" ? "metro" : void 0,
11539
11907
  "data-cdl-from": edge.from,
11540
11908
  "data-cdl-to": edge.to,
11541
11909
  "data-cdl-active": active ? "true" : "false",
@@ -12964,17 +13332,22 @@ function DynShapeNode({
12964
13332
  return renderPolygon(shape, cx, cy, stateValues, fillColor, strokeColor, activeOpacity);
12965
13333
  }
12966
13334
  })();
13335
+ const \u63C3\u3048 = \u7BB1\u3044\u3063\u3071\u3044\u306B\u63CF\u304F2(shape.kind) ? "start" : "middle";
13336
+ return renderShapeWithOutsideLabels(\u5F62, node, \u63C3\u3048);
13337
+ }
13338
+ function renderShapeWithOutsideLabels(\u5F62, node, \u63C3\u3048) {
13339
+ const cx = node.cx;
13340
+ const y = node.cy - node.h / 2;
12967
13341
  const \u540D\u524D = (node.title ?? "").trim();
12968
13342
  const \u5024 = (node.subtitle ?? "").trim();
12969
13343
  if (\u540D\u524D === "" && \u5024 === "") return \u5F62;
12970
- const \u63C3\u3048 = \u7BB1\u3044\u3063\u3071\u3044\u306B\u63CF\u304F2(shape.kind) ? "start" : "middle";
12971
13344
  return /* @__PURE__ */ jsxRuntime.jsxs("g", { children: [
12972
13345
  \u5F62,
12973
13346
  \u540D\u524D !== "" && /* @__PURE__ */ jsxRuntime.jsx(
12974
13347
  "text",
12975
13348
  {
12976
13349
  "data-cdl-role": "shape-name",
12977
- x: \u63C3\u3048 === "start" ? x : cx,
13350
+ x: \u63C3\u3048 === "start" ? node.cx - node.w / 2 : cx,
12978
13351
  y: y - NAME_GAP,
12979
13352
  textAnchor: \u63C3\u3048,
12980
13353
  fontSize: 13,
@@ -12988,7 +13361,7 @@ function DynShapeNode({
12988
13361
  {
12989
13362
  "data-cdl-role": "shape-value",
12990
13363
  x: cx,
12991
- y: y + h + VALUE_GAP,
13364
+ y: y + node.h + VALUE_GAP,
12992
13365
  textAnchor: "middle",
12993
13366
  fontSize: 12,
12994
13367
  fill: "var(--cdl-text-dim, #5a6270)",
@@ -13238,6 +13611,27 @@ function interpolateColor(template, stateValues, fallback) {
13238
13611
  if (!template) return fallback;
13239
13612
  return interpolate(template, stateValues);
13240
13613
  }
13614
+ var STATION_STROKE_ACTIVE = 4;
13615
+ var STATION_STROKE_IDLE = 3;
13616
+ var STATION_DEFAULT = "var(--cdl-tone-accent, #2d6a8f)";
13617
+ function StationNode({ node, active }) {
13618
+ const strokeWidth = active ? STATION_STROKE_ACTIVE : STATION_STROKE_IDLE;
13619
+ const radius = Math.max(0, Math.min(node.w, node.h) / 2 - strokeWidth / 2);
13620
+ const \u5F62 = /* @__PURE__ */ jsxRuntime.jsx(
13621
+ "circle",
13622
+ {
13623
+ "data-cdl-role": "node-body",
13624
+ "data-cdl-mark": "station",
13625
+ cx: node.cx,
13626
+ cy: node.cy,
13627
+ r: radius,
13628
+ fill: "var(--cdl-node-fill, #ffffff)",
13629
+ stroke: nodeAccent(node, STATION_DEFAULT),
13630
+ strokeWidth
13631
+ }
13632
+ );
13633
+ return renderShapeWithOutsideLabels(\u5F62, node, "middle");
13634
+ }
13241
13635
 
13242
13636
  // src/kinds/label-stack.ts
13243
13637
  function \u540D\u672D\u306E\u6BB5\u3092\u307B\u3069\u304F(\u771F\u306Ey, \u9593\u9694, \u4E0A\u9650, \u4E0B\u9650) {
@@ -20466,6 +20860,8 @@ function CdlNodeView({
20466
20860
  return /* @__PURE__ */ jsxRuntime.jsx(MarkStartNode, { node: resolvedNode, active });
20467
20861
  case "mark-end":
20468
20862
  return /* @__PURE__ */ jsxRuntime.jsx(MarkEndNode, { node: resolvedNode, active });
20863
+ case "station":
20864
+ return /* @__PURE__ */ jsxRuntime.jsx(StationNode, { node: resolvedNode, active });
20469
20865
  default:
20470
20866
  return /* @__PURE__ */ jsxRuntime.jsx(GenericNode, { node: resolvedNode, active, value: node.value, stateValues, \u5024\u306E\u540D\u524D });
20471
20867
  }
@@ -20698,15 +21094,15 @@ function CdlStage({
20698
21094
  }, [laid]);
20699
21095
  const edgeHeadMarkers = react.useMemo(() => {
20700
21096
  const referenced = /* @__PURE__ */ new Set();
20701
- const add = (tone, head, fill) => {
21097
+ const add2 = (tone, head, fill) => {
20702
21098
  for (const small of [false, true]) {
20703
21099
  const id = edgeHeadMarkerName(tone, head, small, fill);
20704
21100
  if (id !== void 0) referenced.add(id);
20705
21101
  }
20706
21102
  };
20707
21103
  for (const edge of laid.edges) {
20708
- add(edge.tone, edge.head, edge.headFill);
20709
- if (edge.tailHead !== void 0) add(edge.tone, edge.tailHead, edge.tailHeadFill);
21104
+ add2(edge.tone, edge.head, edge.headFill);
21105
+ if (edge.tailHead !== void 0) add2(edge.tone, edge.tailHead, edge.tailHeadFill);
20710
21106
  }
20711
21107
  return Object.entries(TONE).flatMap(
20712
21108
  ([tone, color]) => [