@cardenelabs/cdl 0.51.0 → 0.53.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 CHANGED
@@ -5,6 +5,33 @@ CDL (Chainome Diagram Language) の主要変更履歴。
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.53.0] - 2026-09-11
9
+
10
+ ### Fixed
11
+
12
+ - **短い縦の線で、両端の多重度が詰まって 1 つの札に見えるのを直した** (#829)
13
+
14
+ 縦に隣り合う箱をつなぐ線は、箱の間隔 (100) がそのまま線の長さになる。 両端に多重度を書くと、
15
+ 出どころの字と行き先の字が線の同じ側に縦に並び、間が 10 しか空かなかった。 2 行の札のように
16
+ 読まれ、どちらの端の数かが分からなかった。
17
+
18
+ 縦の間隔を決める計算に端の字を入れた。 両端に字がある線は、2 つの字の間が字と自分の端の間 (22) の
19
+ 2 倍以上空く長さまで間隔を広げる。 間隔は図の中で全て同じ値に揃える規則のままなので、その図の
20
+ 縦の間隔が全て広がる。 片方の端にだけ字がある線はいまの間隔で足り、見た目は変わらない。
21
+
22
+ ## [0.52.0] - 2026-09-11
23
+
24
+ ### Added
25
+
26
+ - **クラス図の関係の出どころ側の端にも多重度を出せるようにした** (#825)
27
+
28
+ UML は関係の両端に多重度を書く (例 = 管理者 `1` が注文 `1..*` を持つ)。 組み立て器は多重度を
29
+ 1 つ (`cardinality` = 行き先の端) しか受け取れず、出どころの端の数を書く口が無かった。
30
+
31
+ クラス図の組み立て器に `tailCardinality` を足した。 線に「出どころの端に添える字」 の欄 `tailLabel` を
32
+ 足し (`headLabel` と対)、置き場所は行き先の端と同じ計算で決める。 出どころの端での向きは、線の
33
+ 最初の区間を逆にたどって取る (曲線なら始点の接線)。
34
+
8
35
  ## [0.51.0] - 2026-09-11
9
36
 
10
37
  ### Changed
@@ -2435,7 +2462,9 @@ Initial OSS release.
2435
2462
  使わない = annotated tag を push しても GitHub Release は作られず、 Release を作っていない版で
2436
2463
  行き止まりになる。
2437
2464
 
2438
- [Unreleased]: https://github.com/cardene777/cdl/compare/v0.51.0...HEAD
2465
+ [Unreleased]: https://github.com/cardene777/cdl/compare/v0.53.0...HEAD
2466
+ [0.53.0]: https://github.com/cardene777/cdl/compare/v0.52.0...v0.53.0
2467
+ [0.52.0]: https://github.com/cardene777/cdl/compare/v0.51.0...v0.52.0
2439
2468
  [0.51.0]: https://github.com/cardene777/cdl/compare/v0.50.0...v0.51.0
2440
2469
  [0.50.0]: https://github.com/cardene777/cdl/compare/v0.49.0...v0.50.0
2441
2470
  [0.49.0]: https://github.com/cardene777/cdl/compare/v0.48.0...v0.49.0
package/dist/index.cjs CHANGED
@@ -9502,6 +9502,7 @@ function diagram(id, options) {
9502
9502
  guard: opts.guard,
9503
9503
  cardinality: opts.cardinality,
9504
9504
  headLabel: opts.headLabel,
9505
+ tailLabel: opts.tailLabel,
9505
9506
  labelOffsetX: opts.labelOffsetX,
9506
9507
  labelOffsetY: opts.labelOffsetY,
9507
9508
  routing: opts.routing,
@@ -9629,6 +9630,7 @@ function diagram(id, options) {
9629
9630
  guard: d.guard,
9630
9631
  cardinality: d.cardinality,
9631
9632
  headLabel: d.headLabel,
9633
+ tailLabel: d.tailLabel,
9632
9634
  labelOffsetX: d.labelOffsetX,
9633
9635
  labelOffsetY: d.labelOffsetY,
9634
9636
  routing: d.routing,
@@ -20530,6 +20532,87 @@ function requiredRowsWidth(rows, kind) {
20530
20532
  return Math.ceil(kind === "storage" ? storage : generic);
20531
20533
  }
20532
20534
 
20535
+ // src/layout/head-label.ts
20536
+ var \u7AEF\u306E\u5B57\u3092\u623B\u3059\u8DDD\u96E2 = 22;
20537
+ var \u7AEF\u306E\u5B57\u3068\u7DDA\u306E\u9699\u9593 = 8;
20538
+ var \u7AEF\u306E\u5B57\u306E\u5927\u304D\u3055 = EDGE_LABEL_TEXT.sub.fontSize;
20539
+ var \u7BB1\u306E\u9AD8\u3055 = Math.round(\u7AEF\u306E\u5B57\u306E\u5927\u304D\u3055 * 1.2);
20540
+ var BASELINE_RATIO = 0.8;
20541
+ var \u4E21\u7AEF\u306E\u5B57\u306E\u9593 = \u7AEF\u306E\u5B57\u3092\u623B\u3059\u8DDD\u96E2 * 2;
20542
+ function \u7AEF\u306E\u5B57\u306B\u8981\u308B\u7DDA\u306E\u9577\u3055(edge) {
20543
+ const \u4E00\u3064\u5206 = \u7AEF\u306E\u5B57\u3092\u623B\u3059\u8DDD\u96E2 + \u7BB1\u306E\u9AD8\u3055;
20544
+ const \u6570 = [edge.headLabel, edge.tailLabel].filter((t) => t?.trim()).length;
20545
+ if (\u6570 === 0) return 0;
20546
+ if (\u6570 === 1) return \u4E00\u3064\u5206;
20547
+ return \u4E00\u3064\u5206 * 2 + \u4E21\u7AEF\u306E\u5B57\u306E\u9593;
20548
+ }
20549
+ function \u5411\u304D\u3092\u53D6\u308B(end, \u5019\u88DC) {
20550
+ for (const p of \u5019\u88DC) {
20551
+ if (!p) continue;
20552
+ const dx = end.x - p.x;
20553
+ const dy = end.y - p.y;
20554
+ const len = Math.hypot(dx, dy);
20555
+ if (len > 1e-6) return { end, ux: dx / len, uy: dy / len };
20556
+ }
20557
+ return null;
20558
+ }
20559
+ function \u7AEF\u3067\u306E\u5411\u304D(d, \u7AEF) {
20560
+ const segs = extractPathSegments(d);
20561
+ if (segs.length === 0) return null;
20562
+ if (\u7AEF === "head") {
20563
+ const last = segs[segs.length - 1];
20564
+ const end2 = { x: last.x2, y: last.y2 };
20565
+ 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 }]);
20566
+ return \u5411\u304D\u3092\u53D6\u308B(end2, \u5019\u88DC2);
20567
+ }
20568
+ const first = segs[0];
20569
+ const end = { x: first.x1, y: first.y1 };
20570
+ const \u5019\u88DC = segs.flatMap((s) => [...s.ctrl ?? [], { x: s.x2, y: s.y2 }]);
20571
+ return \u5411\u304D\u3092\u53D6\u308B(end, \u5019\u88DC);
20572
+ }
20573
+ function \u7AEF\u306E\u5B57\u3092\u7F6E\u304F(edge, \u7AEF = "head") {
20574
+ const text = (\u7AEF === "head" ? edge.headLabel : edge.tailLabel)?.trim();
20575
+ if (!text) return null;
20576
+ const \u5411\u304D = \u7AEF\u3067\u306E\u5411\u304D(edge.d, \u7AEF);
20577
+ if (!\u5411\u304D) return null;
20578
+ const { end, ux, uy } = \u5411\u304D;
20579
+ const bx = end.x - ux * \u7AEF\u306E\u5B57\u3092\u623B\u3059\u8DDD\u96E2;
20580
+ const by = end.y - uy * \u7AEF\u306E\u5B57\u3092\u623B\u3059\u8DDD\u96E2;
20581
+ const w = measureTextWidth(text, {
20582
+ fontSize: \u7AEF\u306E\u5B57\u306E\u5927\u304D\u3055,
20583
+ fontWeight: EDGE_LABEL_TEXT.sub.fontWeight,
20584
+ fontFamily: "mono"
20585
+ });
20586
+ const h = \u7BB1\u306E\u9AD8\u3055;
20587
+ const \u672D = hasRenderedLabel(edge) && edge.labelX !== void 0 && edge.labelY !== void 0 ? { x: edge.labelX, y: edge.labelY } : null;
20588
+ let anchor;
20589
+ let box;
20590
+ if (Math.abs(ux) >= Math.abs(uy)) {
20591
+ const \u53F3\u3078\u9032\u3080 = ux > 0;
20592
+ const \u4E0A\u306B\u7F6E\u304F = \u672D !== null && \u672D.y > by;
20593
+ anchor = \u53F3\u3078\u9032\u3080 ? "end" : "start";
20594
+ box = {
20595
+ x: \u53F3\u3078\u9032\u3080 ? bx - w : bx,
20596
+ y: \u4E0A\u306B\u7F6E\u304F ? by - \u7AEF\u306E\u5B57\u3068\u7DDA\u306E\u9699\u9593 - h : by + \u7AEF\u306E\u5B57\u3068\u7DDA\u306E\u9699\u9593,
20597
+ w,
20598
+ h
20599
+ };
20600
+ } else {
20601
+ const \u4E0B\u3078\u9032\u3080 = uy > 0;
20602
+ const \u53F3\u306B\u7F6E\u304F = \u672D !== null && \u672D.x < bx;
20603
+ anchor = \u53F3\u306B\u7F6E\u304F ? "start" : "end";
20604
+ box = {
20605
+ x: \u53F3\u306B\u7F6E\u304F ? bx + \u7AEF\u306E\u5B57\u3068\u7DDA\u306E\u9699\u9593 : bx - \u7AEF\u306E\u5B57\u3068\u7DDA\u306E\u9699\u9593 - w,
20606
+ y: \u4E0B\u3078\u9032\u3080 ? by - h : by,
20607
+ w,
20608
+ h
20609
+ };
20610
+ }
20611
+ const x = anchor === "start" ? box.x : box.x + box.w;
20612
+ const y = box.y + (h - \u7AEF\u306E\u5B57\u306E\u5927\u304D\u3055) / 2 + \u7AEF\u306E\u5B57\u306E\u5927\u304D\u3055 * BASELINE_RATIO;
20613
+ return { x, y, anchor, box };
20614
+ }
20615
+
20533
20616
  // src/layout/nodes.ts
20534
20617
  function autoStorageHeight(n, defaultH) {
20535
20618
  if (n.kind !== "storage") return n.h ?? defaultH;
@@ -20551,7 +20634,7 @@ function autoRowsWidth(n, defaultW) {
20551
20634
  const required = requiredRowsWidth(n.rows, n.kind);
20552
20635
  return Math.max(defaultW, required);
20553
20636
  }
20554
- function \u7BB1\u306E\u9AD8\u3055(n, \u65E2\u5B9Ah) {
20637
+ function \u7BB1\u306E\u9AD8\u30552(n, \u65E2\u5B9Ah) {
20555
20638
  if (n.h != null) return n.h;
20556
20639
  if (n.kind === "storage") return autoStorageHeight(n, \u65E2\u5B9Ah);
20557
20640
  return \u4E2D\u8EAB\u304B\u3089\u51FA\u3059\u9AD8\u3055(n.kind, \u65E2\u5B9Ah, {
@@ -20565,8 +20648,8 @@ function requiredStackGapForEdgeLabels(diag, allStacks) {
20565
20648
  const byId = new Map(diag.nodes.map((n) => [n.id, n]));
20566
20649
  const rankOf = new Map(allStacks.map((stack, i) => [stack, i]));
20567
20650
  const pillHsByGap = /* @__PURE__ */ new Map();
20651
+ let \u7AEF\u306E\u5B57\u306E\u8981\u6C42 = 0;
20568
20652
  for (const e of diag.edges) {
20569
- if (!hasRenderedLabel(e)) continue;
20570
20653
  const from = byId.get(e.from);
20571
20654
  const to = byId.get(e.to);
20572
20655
  if (!from || !to) continue;
@@ -20575,10 +20658,12 @@ function requiredStackGapForEdgeLabels(diag, allStacks) {
20575
20658
  const toRank = rankOf.get(to.stack);
20576
20659
  if (fromRank === void 0 || toRank === void 0) continue;
20577
20660
  if (Math.abs(fromRank - toRank) !== 1) continue;
20661
+ \u7AEF\u306E\u5B57\u306E\u8981\u6C42 = Math.max(\u7AEF\u306E\u5B57\u306E\u8981\u6C42, \u7AEF\u306E\u5B57\u306B\u8981\u308B\u7DDA\u306E\u9577\u3055(e));
20662
+ if (!hasRenderedLabel(e)) continue;
20578
20663
  const key = `${from.lane}::${Math.min(fromRank, toRank)}`;
20579
20664
  pillHsByGap.set(key, [...pillHsByGap.get(key) ?? [], labelPillH(e.sub)]);
20580
20665
  }
20581
- let required = 0;
20666
+ let required = \u7AEF\u306E\u5B57\u306E\u8981\u6C42;
20582
20667
  for (const pillHs of pillHsByGap.values()) {
20583
20668
  required = Math.max(required, requiredVerticalLabelGap(pillHs));
20584
20669
  }
@@ -20590,7 +20675,7 @@ function layoutNodes(diag, lanes) {
20590
20675
  const rowH = /* @__PURE__ */ new Map();
20591
20676
  for (const stack of allStacks) {
20592
20677
  const nodesAtStack = diag.nodes.filter((n) => n.stack === stack);
20593
- const h = Math.max(...nodesAtStack.map((n) => \u7BB1\u306E\u9AD8\u3055(n, NODE_SIZE[n.kind].h)));
20678
+ const h = Math.max(...nodesAtStack.map((n) => \u7BB1\u306E\u9AD8\u30552(n, NODE_SIZE[n.kind].h)));
20594
20679
  rowH.set(stack, h);
20595
20680
  }
20596
20681
  const rowCy = /* @__PURE__ */ new Map();
@@ -20644,7 +20729,7 @@ function layoutNodes(diag, lanes) {
20644
20729
  const \u6BB5\u306E\u82AF = rowCy.get(n.stack);
20645
20730
  const \u6BB5\u306E\u9AD8\u3055 = rowH.get(n.stack);
20646
20731
  const \u81EA\u524D\u306E\u9AD8\u3055 = n.rowMarks !== void 0;
20647
- const h = n.h ?? (\u81EA\u524D\u306E\u9AD8\u3055 ? \u7BB1\u306E\u9AD8\u3055(n, size.h) : \u6BB5\u306E\u9AD8\u3055);
20732
+ const h = n.h ?? (\u81EA\u524D\u306E\u9AD8\u3055 ? \u7BB1\u306E\u9AD8\u30552(n, size.h) : \u6BB5\u306E\u9AD8\u3055);
20648
20733
  const cy = \u6BB5\u306E\u82AF;
20649
20734
  const w = autoRowsWidth(n, size.w);
20650
20735
  out.push({
@@ -22933,73 +23018,6 @@ function \u7B49\u5E45\u306E\u5B57(text, fontSize) {
22933
23018
  });
22934
23019
  return out;
22935
23020
  }
22936
-
22937
- // src/layout/head-label.ts
22938
- var \u7AEF\u306E\u5B57\u3092\u623B\u3059\u8DDD\u96E2 = 22;
22939
- var \u7AEF\u306E\u5B57\u3068\u7DDA\u306E\u9699\u9593 = 8;
22940
- var \u7AEF\u306E\u5B57\u306E\u5927\u304D\u3055 = EDGE_LABEL_TEXT.sub.fontSize;
22941
- var \u7BB1\u306E\u9AD8\u30552 = Math.round(\u7AEF\u306E\u5B57\u306E\u5927\u304D\u3055 * 1.2);
22942
- var BASELINE_RATIO = 0.8;
22943
- function \u6700\u5F8C\u306E\u5411\u304D(d) {
22944
- const segs = extractPathSegments(d);
22945
- const last = segs[segs.length - 1];
22946
- if (!last) return null;
22947
- const end = { x: last.x2, y: last.y2 };
22948
- for (let i = segs.length - 1; i >= 0; i--) {
22949
- const s = segs[i];
22950
- const \u5236\u5FA1\u70B9 = s.cmd === "Q" ? s.ctrl?.[0] : s.cmd === "C" ? s.ctrl?.[1] : void 0;
22951
- for (const p of [\u5236\u5FA1\u70B9, { x: s.x1, y: s.y1 }]) {
22952
- if (!p) continue;
22953
- const dx = end.x - p.x;
22954
- const dy = end.y - p.y;
22955
- const len = Math.hypot(dx, dy);
22956
- if (len > 1e-6) return { end, ux: dx / len, uy: dy / len };
22957
- }
22958
- }
22959
- return null;
22960
- }
22961
- function \u7AEF\u306E\u5B57\u3092\u7F6E\u304F(edge) {
22962
- const text = edge.headLabel?.trim();
22963
- if (!text) return null;
22964
- const \u5411\u304D = \u6700\u5F8C\u306E\u5411\u304D(edge.d);
22965
- if (!\u5411\u304D) return null;
22966
- const { end, ux, uy } = \u5411\u304D;
22967
- const bx = end.x - ux * \u7AEF\u306E\u5B57\u3092\u623B\u3059\u8DDD\u96E2;
22968
- const by = end.y - uy * \u7AEF\u306E\u5B57\u3092\u623B\u3059\u8DDD\u96E2;
22969
- const w = measureTextWidth(text, {
22970
- fontSize: \u7AEF\u306E\u5B57\u306E\u5927\u304D\u3055,
22971
- fontWeight: EDGE_LABEL_TEXT.sub.fontWeight,
22972
- fontFamily: "mono"
22973
- });
22974
- const h = \u7BB1\u306E\u9AD8\u30552;
22975
- const \u672D = hasRenderedLabel(edge) && edge.labelX !== void 0 && edge.labelY !== void 0 ? { x: edge.labelX, y: edge.labelY } : null;
22976
- let anchor;
22977
- let box;
22978
- if (Math.abs(ux) >= Math.abs(uy)) {
22979
- const \u53F3\u3078\u9032\u3080 = ux > 0;
22980
- const \u4E0A\u306B\u7F6E\u304F = \u672D !== null && \u672D.y > by;
22981
- anchor = \u53F3\u3078\u9032\u3080 ? "end" : "start";
22982
- box = {
22983
- x: \u53F3\u3078\u9032\u3080 ? bx - w : bx,
22984
- y: \u4E0A\u306B\u7F6E\u304F ? by - \u7AEF\u306E\u5B57\u3068\u7DDA\u306E\u9699\u9593 - h : by + \u7AEF\u306E\u5B57\u3068\u7DDA\u306E\u9699\u9593,
22985
- w,
22986
- h
22987
- };
22988
- } else {
22989
- const \u4E0B\u3078\u9032\u3080 = uy > 0;
22990
- const \u53F3\u306B\u7F6E\u304F = \u672D !== null && \u672D.x < bx;
22991
- anchor = \u53F3\u306B\u7F6E\u304F ? "start" : "end";
22992
- box = {
22993
- x: \u53F3\u306B\u7F6E\u304F ? bx + \u7AEF\u306E\u5B57\u3068\u7DDA\u306E\u9699\u9593 : bx - \u7AEF\u306E\u5B57\u3068\u7DDA\u306E\u9699\u9593 - w,
22994
- y: \u4E0B\u3078\u9032\u3080 ? by - h : by,
22995
- w,
22996
- h
22997
- };
22998
- }
22999
- const x = anchor === "start" ? box.x : box.x + box.w;
23000
- const y = box.y + (h - \u7AEF\u306E\u5B57\u306E\u5927\u304D\u3055) / 2 + \u7AEF\u306E\u5B57\u306E\u5927\u304D\u3055 * BASELINE_RATIO;
23001
- return { x, y, anchor, box };
23002
- }
23003
23021
  function defaultEdgeWidth(active) {
23004
23022
  return active ? 2.5 : 1.25;
23005
23023
  }
@@ -23262,11 +23280,12 @@ function CdlEdgeLabelView({
23262
23280
  }
23263
23281
  );
23264
23282
  }
23265
- function CdlEdgeHeadLabelView({
23283
+ function CdlEdgeEndLabelView({
23266
23284
  edge,
23267
- active
23285
+ active,
23286
+ \u7AEF
23268
23287
  }) {
23269
- const \u7F6E\u304D\u5834\u6240 = \u7AEF\u306E\u5B57\u3092\u7F6E\u304F(edge);
23288
+ const \u7F6E\u304D\u5834\u6240 = \u7AEF\u306E\u5B57\u3092\u7F6E\u304F(edge, \u7AEF);
23270
23289
  if (!\u7F6E\u304D\u5834\u6240) return null;
23271
23290
  const color = edgeColor(edge);
23272
23291
  return /* @__PURE__ */ jsxRuntime.jsx(
@@ -23274,6 +23293,7 @@ function CdlEdgeHeadLabelView({
23274
23293
  {
23275
23294
  "data-cdl-role": "edge-end-label",
23276
23295
  "data-cdl-edge-end-label-for": edge.id,
23296
+ "data-cdl-edge-end": \u7AEF,
23277
23297
  x: \u7F6E\u304D\u5834\u6240.x,
23278
23298
  y: \u7F6E\u304D\u5834\u6240.y,
23279
23299
  fontSize: \u7AEF\u306E\u5B57\u306E\u5927\u304D\u3055,
@@ -23285,7 +23305,7 @@ function CdlEdgeHeadLabelView({
23285
23305
  strokeLinejoin: "round",
23286
23306
  paintOrder: "stroke",
23287
23307
  style: { fontFamily: fontFamilyAttr("sub") },
23288
- children: \u7B49\u5E45\u306E\u5B57(edge.headLabel, \u7AEF\u306E\u5B57\u306E\u5927\u304D\u3055)
23308
+ children: \u7B49\u5E45\u306E\u5B57(\u7AEF === "head" ? edge.headLabel : edge.tailLabel, \u7AEF\u306E\u5B57\u306E\u5927\u304D\u3055)
23289
23309
  }
23290
23310
  );
23291
23311
  }
@@ -31573,14 +31593,17 @@ function CdlStage({
31573
31593
  },
31574
31594
  `label-${edge.id}`
31575
31595
  )),
31576
- laid.edges.filter((edge) => edge.headLabel && \u77E2\u5370\u306E\u51FA\u756A.\u51FA\u308B\u304B(edge.id)).map((edge) => /* @__PURE__ */ jsxRuntime.jsx(
31577
- CdlEdgeHeadLabelView,
31578
- {
31579
- edge,
31580
- active: activeSet.has(edge.id)
31581
- },
31582
- `head-label-${edge.id}`
31583
- )),
31596
+ laid.edges.filter((edge) => (edge.headLabel || edge.tailLabel) && \u77E2\u5370\u306E\u51FA\u756A.\u51FA\u308B\u304B(edge.id)).flatMap(
31597
+ (edge) => ["head", "tail"].map((\u7AEF) => /* @__PURE__ */ jsxRuntime.jsx(
31598
+ CdlEdgeEndLabelView,
31599
+ {
31600
+ edge,
31601
+ active: activeSet.has(edge.id),
31602
+ \u7AEF
31603
+ },
31604
+ `${\u7AEF}-label-${edge.id}`
31605
+ ))
31606
+ ),
31584
31607
  debug && /* @__PURE__ */ jsxRuntime.jsxs("g", { pointerEvents: "none", children: [
31585
31608
  laid.bboxes.map((bbox, idx) => {
31586
31609
  const color = bbox.kind === "node" ? "#3b82f6" : bbox.kind === "lane-label" ? "#a855f7" : bbox.kind === "edge-label" ? "#10b981" : "#f59e0b";
@@ -32628,6 +32651,7 @@ function classDiagram(preset) {
32628
32651
  // 多重度は行き先の端に添える (#821)。 札の下の行に入れると語と続けて読まれ
32629
32652
  // (英字の頃は「HAS 1..*」 が「HAS1」)、 どちらの端の数かも読めない
32630
32653
  ...r.cardinality ? { headLabel: r.cardinality } : {},
32654
+ ...r.tailCardinality ? { tailLabel: r.tailCardinality } : {},
32631
32655
  tone: r.tone ?? \u56F3\u306E\u65E2\u5B9A ?? CLASS_RELATION_TONE[r.type],
32632
32656
  // 関係の種類から線と端の形を決める (cdl#574 / #578)。 書いた値が勝つ
32633
32657
  style: r.style ?? \u898B\u305F\u76EE.style,