@cardenelabs/cdl 0.36.0 → 0.38.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.
@@ -424,18 +424,29 @@ type CdlNode = {
424
424
  */
425
425
  chartPieForm?: ChartPieForm;
426
426
  /**
427
- * chart-slope の見せ方 (cdl#702)。 右の列に何の数を出すかを決める。
427
+ * chart-slope の見せ方 (cdl#702 / cdl#707)。
428
428
  *
429
- * - `values` ... 今の値
430
- * - `delta` ... 増減 (`+40` / `-90`)
429
+ * | 値 | 縦の位置 | 左の列 | 右の列 |
430
+ * |---|---|---|---|
431
+ * | `values` | 値 | 前の値 | 今の値 |
432
+ * | `delta` | 値 | 前の値 | 増減 (`+40` / `-90`) |
433
+ * | `rank` | 順位の段 (等間隔) | 前の値 | 今の値 |
431
434
  *
432
- * 左の列は どちらでも前の値。 `前 + 増減 = 今` なので、 `delta` でも今の値は足せば戻る。
435
+ * `values` と `delta` は縦の位置が同じで、右の列の字だけが違う。
436
+ * `前 + 増減 = 今` なので、 `delta` でも今の値は足せば戻る。
433
437
  * 3 つ目の数を足さないので、 名札の高さも線を引く幅も変わらない。
434
438
  *
439
+ * `rank` は **縦の位置** を変える (cdl#707)。 段を等間隔に置くので、線の交差が
440
+ * 順位の入れ替わりと 1 対 1 になり、水平線が「順位が動かなかった」 を意味する。
441
+ * 字は値のままにして、量が図から消えないようにしている。
442
+ *
443
+ * `rank` では「線の傾きが動いた量」 という読み方が成立しない (`+40` と `+30` が
444
+ * どちらも水平線になる)。 書いた図だけに効く割り切りで、量は字を読む。
445
+ *
435
446
  * 型の外の値は `values` として描く = 見せ方が決まらないより、既定の形が出る方がよい。
436
447
  *
437
- * 前の時点を持たない図 (`previous` が 1 件も無い) は右の列そのものが無いため、
438
- * この欄を書いても絵は変わらない。
448
+ * 前の時点を持たない図 (`previous` が 1 件も無い) は軸が 1 本で並べ替える相手が
449
+ * いないため、 この欄を書いても絵は変わらない。
439
450
  */
440
451
  chartSlopeForm?: ChartSlopeForm;
441
452
  /**
@@ -493,8 +504,8 @@ type BoundEnum<T extends string> = T | (string & {});
493
504
  /** Chart 系 kind の 1 datum payload。 label + value + optional tone (color) */
494
505
  /** chart-pie の見せ方 (cdl#684)。 SSOT は `kinds/chart-pie.tsx` */
495
506
  type ChartPieForm = "ring" | "arcs" | "table";
496
- /** chart-slope の見せ方 (cdl#702)。 SSOT は `kinds/chart-slope.tsx` */
497
- type ChartSlopeForm = "values" | "delta";
507
+ /** chart-slope の見せ方 (cdl#702 / cdl#707)。 SSOT は `kinds/chart-slope.tsx` */
508
+ type ChartSlopeForm = "values" | "delta" | "rank";
498
509
  type ChartDatumPayload = {
499
510
  label: string;
500
511
  /** 棒 / 折れ線 / 扇の大きさ。 `{signal}` で状態に追随する */
@@ -424,18 +424,29 @@ type CdlNode = {
424
424
  */
425
425
  chartPieForm?: ChartPieForm;
426
426
  /**
427
- * chart-slope の見せ方 (cdl#702)。 右の列に何の数を出すかを決める。
427
+ * chart-slope の見せ方 (cdl#702 / cdl#707)。
428
428
  *
429
- * - `values` ... 今の値
430
- * - `delta` ... 増減 (`+40` / `-90`)
429
+ * | 値 | 縦の位置 | 左の列 | 右の列 |
430
+ * |---|---|---|---|
431
+ * | `values` | 値 | 前の値 | 今の値 |
432
+ * | `delta` | 値 | 前の値 | 増減 (`+40` / `-90`) |
433
+ * | `rank` | 順位の段 (等間隔) | 前の値 | 今の値 |
431
434
  *
432
- * 左の列は どちらでも前の値。 `前 + 増減 = 今` なので、 `delta` でも今の値は足せば戻る。
435
+ * `values` と `delta` は縦の位置が同じで、右の列の字だけが違う。
436
+ * `前 + 増減 = 今` なので、 `delta` でも今の値は足せば戻る。
433
437
  * 3 つ目の数を足さないので、 名札の高さも線を引く幅も変わらない。
434
438
  *
439
+ * `rank` は **縦の位置** を変える (cdl#707)。 段を等間隔に置くので、線の交差が
440
+ * 順位の入れ替わりと 1 対 1 になり、水平線が「順位が動かなかった」 を意味する。
441
+ * 字は値のままにして、量が図から消えないようにしている。
442
+ *
443
+ * `rank` では「線の傾きが動いた量」 という読み方が成立しない (`+40` と `+30` が
444
+ * どちらも水平線になる)。 書いた図だけに効く割り切りで、量は字を読む。
445
+ *
435
446
  * 型の外の値は `values` として描く = 見せ方が決まらないより、既定の形が出る方がよい。
436
447
  *
437
- * 前の時点を持たない図 (`previous` が 1 件も無い) は右の列そのものが無いため、
438
- * この欄を書いても絵は変わらない。
448
+ * 前の時点を持たない図 (`previous` が 1 件も無い) は軸が 1 本で並べ替える相手が
449
+ * いないため、 この欄を書いても絵は変わらない。
439
450
  */
440
451
  chartSlopeForm?: ChartSlopeForm;
441
452
  /**
@@ -493,8 +504,8 @@ type BoundEnum<T extends string> = T | (string & {});
493
504
  /** Chart 系 kind の 1 datum payload。 label + value + optional tone (color) */
494
505
  /** chart-pie の見せ方 (cdl#684)。 SSOT は `kinds/chart-pie.tsx` */
495
506
  type ChartPieForm = "ring" | "arcs" | "table";
496
- /** chart-slope の見せ方 (cdl#702)。 SSOT は `kinds/chart-slope.tsx` */
497
- type ChartSlopeForm = "values" | "delta";
507
+ /** chart-slope の見せ方 (cdl#702 / cdl#707)。 SSOT は `kinds/chart-slope.tsx` */
508
+ type ChartSlopeForm = "values" | "delta" | "rank";
498
509
  type ChartDatumPayload = {
499
510
  label: string;
500
511
  /** 棒 / 折れ線 / 扇の大きさ。 `{signal}` で状態に追随する */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cardenelabs/cdl",
3
- "version": "0.36.0",
3
+ "version": "0.38.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",
@@ -60,6 +60,8 @@ export class Timeline {
60
60
  private rafHandle: number | null = null;
61
61
  private phaseIndex = 0;
62
62
  private phaseProgress = 0;
63
+ /** 曲線を通す前の進み (cdl#711)。 経過時間そのものの割合で、 描く動きが読む */
64
+ private phaseLinearProgress = 0;
63
65
  private phaseStartedAt = 0;
64
66
  private status: TimelineStatus = "idle";
65
67
  /**
@@ -75,7 +77,7 @@ export class Timeline {
75
77
  */
76
78
  private readonly initialState: {
77
79
  phaseIndex: number;
78
- phaseProgress: number;
80
+ phaseLinearProgress: number;
79
81
  status: TimelineStatus;
80
82
  };
81
83
 
@@ -94,12 +96,12 @@ export class Timeline {
94
96
  const initial = options.initialPhaseIndex;
95
97
  if (initial != null && initial > 0 && initial < phases.length) {
96
98
  this.phaseIndex = initial;
97
- this.phaseProgress = 1;
99
+ this.setProgress(1);
98
100
  this.status = initial === phases.length - 1 ? "settled" : "paused";
99
101
  }
100
102
  this.initialState = {
101
103
  phaseIndex: this.phaseIndex,
102
- phaseProgress: this.phaseProgress,
104
+ phaseLinearProgress: this.phaseLinearProgress,
103
105
  status: this.status,
104
106
  };
105
107
  }
@@ -133,6 +135,7 @@ export class Timeline {
133
135
  cached.status === this.status &&
134
136
  cached.phaseIndex === this.phaseIndex &&
135
137
  cached.phaseProgress === this.phaseProgress &&
138
+ cached.phaseLinearProgress === this.phaseLinearProgress &&
136
139
  cached.totalPhases === this.phases.length
137
140
  ) {
138
141
  return cached;
@@ -141,6 +144,7 @@ export class Timeline {
141
144
  status: this.status,
142
145
  phaseIndex: this.phaseIndex,
143
146
  phaseProgress: this.phaseProgress,
147
+ phaseLinearProgress: this.phaseLinearProgress,
144
148
  totalPhases: this.phases.length,
145
149
  };
146
150
  this.cachedSnapshot = next;
@@ -168,7 +172,7 @@ export class Timeline {
168
172
  const clamped = Math.max(0, Math.min(this.phases.length - 1, index));
169
173
  this.cancelRaf();
170
174
  this.phaseIndex = clamped;
171
- this.phaseProgress = 1;
175
+ this.setProgress(1);
172
176
  this.status = clamped === this.phases.length - 1 ? "settled" : "paused";
173
177
  this.emit();
174
178
  }
@@ -212,7 +216,7 @@ export class Timeline {
212
216
  if (this.phaseIndex <= 0) return;
213
217
  this.cancelRaf();
214
218
  this.phaseIndex -= 1;
215
- this.phaseProgress = 1;
219
+ this.setProgress(1);
216
220
  this.status = "paused";
217
221
  this.emit();
218
222
  }
@@ -220,7 +224,7 @@ export class Timeline {
220
224
  reset(): void {
221
225
  this.cancelRaf();
222
226
  this.phaseIndex = 0;
223
- this.phaseProgress = 0;
227
+ this.setProgress(0);
224
228
  this.status = "idle";
225
229
  this.emit();
226
230
  }
@@ -242,7 +246,7 @@ export class Timeline {
242
246
  this.subscribers.clear();
243
247
  this.status = this.initialState.status;
244
248
  this.phaseIndex = this.initialState.phaseIndex;
245
- this.phaseProgress = this.initialState.phaseProgress;
249
+ this.setProgress(this.initialState.phaseLinearProgress);
246
250
  }
247
251
 
248
252
  // ─── internals ───────────────────────────────────────────────
@@ -284,7 +288,7 @@ export class Timeline {
284
288
  }
285
289
 
286
290
  private beginPhaseTransition(): void {
287
- this.phaseProgress = 0;
291
+ this.setProgress(0);
288
292
  this.phaseStartedAt = performance.now();
289
293
  this.cancelRaf();
290
294
  const step = (now: number) => {
@@ -292,13 +296,13 @@ export class Timeline {
292
296
  this.autoAdvanceMsOverride ?? this.getPhase().durationMs ?? 600;
293
297
  const elapsed = now - this.phaseStartedAt;
294
298
  const linearT = clamp01(elapsed / duration);
295
- this.phaseProgress = this.easing(linearT);
299
+ this.setProgress(linearT);
296
300
  this.emit();
297
301
  if (linearT < 1) {
298
302
  this.rafHandle = requestAnimationFrame(step);
299
303
  return;
300
304
  }
301
- this.phaseProgress = 1;
305
+ this.setProgress(1);
302
306
  this.emit();
303
307
  if (this.status === "playing") {
304
308
  // 自動進行中は次 Phase へ
@@ -315,6 +319,20 @@ export class Timeline {
315
319
  this.rafHandle = requestAnimationFrame(step);
316
320
  }
317
321
 
322
+ /**
323
+ * 段の進みを 2 つ同時に置く (cdl#711)。
324
+ *
325
+ * **代入する場所を 1 つに閉じる**。 曲げた進みと曲げる前の進みを別々に書くと、
326
+ * どちらかを更新し忘れた経路が「前の段の値のまま」 で残る。
327
+ *
328
+ * 両端 (0 と 1) は曲線を通さず、 そのまま置く。 曲線は差し替えられる欄なので、
329
+ * `easing(1)` が 1 を返す保証がない。 段の始まりと終わりの値は曲線に委ねない。
330
+ */
331
+ private setProgress(linear: number): void {
332
+ this.phaseLinearProgress = linear;
333
+ this.phaseProgress = linear <= 0 ? 0 : linear >= 1 ? 1 : this.easing(linear);
334
+ }
335
+
318
336
  private cancelRaf(): void {
319
337
  if (this.rafHandle != null) {
320
338
  cancelAnimationFrame(this.rafHandle);
@@ -37,6 +37,18 @@ export type TimelineState = {
37
37
  phaseIndex: number;
38
38
  /** 現在 Phase 内の進行度 (0..1)、 視覚要素の補間に使う */
39
39
  phaseProgress: number;
40
+ /**
41
+ * 曲線を通す前の進行度 (0..1、 cdl#711)。 経過時間そのものの割合。
42
+ *
43
+ * `phaseProgress` は既定で `easeOutQuint` を通るため前半に動きが寄る。
44
+ * **値が落ち着く動きには合うが、 線を引く動きには合わない** =
45
+ * `1.2s` の段で半分に達するのが 158 ms になり、 折れ線の 5 点のうち 4 点が
46
+ * 292 ms の間に出てしまう (実測)。
47
+ *
48
+ * 描く動き (`draw` の node と伸ばす edge) はこちらを読む。
49
+ * 両端 (0 と 1) は 2 つとも同じ値になる。
50
+ */
51
+ phaseLinearProgress: number;
40
52
  /** 全 Phase 数 */
41
53
  totalPhases: number;
42
54
  };
@@ -50,6 +50,24 @@ import { 進みを畳む, 伸ばすdash } from "./draw-progress";
50
50
  *
51
51
  * 色を持たせるのは **名前だけ**。 値は太字の濃い色のままにして、数の読み比べを保つ。
52
52
  * identity は色が、量は字が担う。
53
+ *
54
+ * ## 順位で見せる形は縦の位置を変える (cdl#707)
55
+ *
56
+ * この図は「線の交差が順位の入れ替わり」 を読ませるが、縦の位置は値なので **交差点の
57
+ * 位置が順位と噛み合わない**。 どの交差が本当に順位を変えた交差なのかは、値を読み比べ
58
+ * ないと決まらない。
59
+ *
60
+ * `rank` は段を等間隔に置く。 交差が入れ替わりと 1 対 1 になり、水平線が「順位が
61
+ * 動かなかった」 を意味する。
62
+ *
63
+ * **字は値のままにする**。 字も順位にすると `380 → 420` と `60 → 90` がどちらも
64
+ * 同じ水平線になり、量が図から完全に消える。 位置が順位を、字が量を担う。
65
+ *
66
+ * 順位と値を **両方** 出す案は落とした = 3 つ目の数を足す形で、実物に当てると値の列が
67
+ * 伸びて名前と重なった (`delta` で落としたのと同じ形、cdl#702)。
68
+ *
69
+ * **この形では「線の傾きが動いた量」 が成立しない**。 `+40` と `+30` がどちらも水平線に
70
+ * なる。 書いた図だけに効く割り切りで、量は字を読む。
53
71
  */
54
72
 
55
73
  /** 線を引くのに最低限要る横幅。 名札が長い図でもこれだけは残す */
@@ -113,14 +131,39 @@ export function ChartSlopeNode({
113
131
  尺の幅 > 0 ? 下端 - ((v - 最小) / 尺の幅) * (下端 - 上端) : (上端 + 下端) / 2;
114
132
 
115
133
  /**
116
- * 見せ方 (cdl#702)。 **型の外の値は `values` に落とす** = 見せ方が決まらないより、
117
- * 既定の形が出る方がよい (円グラフと同じ判断)。
134
+ * 見せ方 (cdl#702 / cdl#707)。 **型の外の値は `values` に落とす** = 見せ方が
135
+ * 決まらないより、既定の形が出る方がよい (円グラフと同じ判断)。
136
+ *
137
+ * **前の時点を持たない図では `values` に落とす**。 `delta` は引く相手がいないので、
138
+ * 増減を出すと「0 から増えた」 という書いていない主張になる (前を持たない件を 0 から
139
+ * 立ち上げるのは線を引くための扱いで、前が 0 だったという意味ではない)。
140
+ * `rank` は軸が 1 本で並べ替える相手がいない。
141
+ */
142
+ const 書いた形 = node.chartSlopeForm;
143
+ const 形: ChartSlopeForm =
144
+ 前あり && (書いた形 === "delta" || 書いた形 === "rank") ? 書いた形 : "values";
145
+
146
+ /**
147
+ * 順位。 **自分より大きい値の件数 + 1** で決める。
148
+ *
149
+ * 同じ値なら同じ順位になり、段も重なる。 無理に 1 つずつずらすと、書いていない差を
150
+ * 絵が主張することになる。
151
+ */
152
+ const 順位を取る = (v: number, 全部: number[]): number =>
153
+ 全部.filter((x) => x > v).length + 1;
154
+
155
+ const 前の順位 = 系列たち.map((s) => 順位を取る(s.前, 系列たち.map((x) => x.前)));
156
+ const 今の順位 = 系列たち.map((s) => 順位を取る(s.今, 系列たち.map((x) => x.今)));
157
+
158
+ /**
159
+ * 段の y。 1 位を上端、系列の数を下端にして等間隔に置く。
118
160
  *
119
- * **前の時点を持たない図では `values` に落とす**。 引く相手がいないので、増減を出すと
120
- * 「0 から増えた」 という書いていない主張になる (前を持たない件を 0 から立ち上げるのは
121
- * 線を引くための扱いで、前が 0 だったという意味ではない)。
161
+ * **系列が 1 本の図では割り算ができない**ので縦の中央に置く (尺の幅が 0 の時と同じ扱い)。
122
162
  */
123
- const 形: ChartSlopeForm = 前あり && node.chartSlopeForm === "delta" ? "delta" : "values";
163
+ const 段のyを取る = (順位: number): number =>
164
+ 系列たち.length > 1
165
+ ? 上端 + ((順位 - 1) / (系列たち.length - 1)) * (下端 - 上端)
166
+ : (上端 + 下端) / 2;
124
167
 
125
168
  /**
126
169
  * 増減の表記。 符号を前に付ける。
@@ -233,8 +276,9 @@ export function ChartSlopeNode({
233
276
  )}
234
277
 
235
278
  {系列たち.map((s, i) => {
236
- const 左y = yを取る(s.前);
237
- const 右y = yを取る(s.今);
279
+ // `rank` は縦の位置を順位の段にする (cdl#707)。 値の尺は使わない
280
+ const 左y = 形 === "rank" ? 段のyを取る(前の順位[i]!) : yを取る(s.前);
281
+ const 右y = 形 === "rank" ? 段のyを取る(今の順位[i]!) : yを取る(s.今);
238
282
  const 未解決 = s.未解決 ? "true" : undefined;
239
283
  return (
240
284
  <g key={`slope-${i}`}>
@@ -28,6 +28,7 @@ export function CdlStage({
28
28
  laid,
29
29
  currentPhase,
30
30
  progress,
31
+ drawProgress,
31
32
  stateValues,
32
33
  debug,
33
34
  compact = false,
@@ -36,6 +37,13 @@ export function CdlStage({
36
37
  laid: LaidDiagram;
37
38
  currentPhase: CdlPhase | undefined;
38
39
  progress: number;
40
+ /**
41
+ * 描く動きが読む進み (cdl#711)。 曲線を通す前の値で、 経過時間そのものの割合。
42
+ *
43
+ * 書かない呼出は `progress` と同じ値になる = 曲線を通した進みで描く従来の形。
44
+ * 検査から直接呼ぶ経路が既に多数あり、 そこでは 2 つを分ける意味がない。
45
+ */
46
+ drawProgress?: number;
39
47
  stateValues: Record<string, string>;
40
48
  debug: boolean;
41
49
  compact?: boolean;
@@ -112,9 +120,15 @@ export function CdlStage({
112
120
  }
113
121
  return scales;
114
122
  }, [laid]);
115
- // 描く対象から見た進み (cdl#570)。 `drawRatio` を書かない phase では `progress` と同値で、
123
+ /**
124
+ * 描く動きの素の進み (cdl#711)。 渡されなければ従来どおり曲線を通した進みを使う。
125
+ *
126
+ * **`??` で受ける** = 0 は正しい進み (段の始まり) なので、 偽としては落とせない。
127
+ */
128
+ const 描く素の進み = drawProgress ?? progress;
129
+ // 描く対象から見た進み (cdl#570)。 `drawRatio` を書かない phase では `描く素の進み` と同値で、
116
130
  // 描く速さを phase の長さから切り離すためだけに使う
117
- const 描く進み = 描く進みに直す(progress, currentPhase?.drawRatio);
131
+ const 描く進み = 描く進みに直す(描く素の進み, currentPhase?.drawRatio);
118
132
  // 縦線の終点は全 lane で 1 つに揃える (等長保証)。 値の決め方と、 1 lane の異常が図全体から
119
133
  // 縦線を消していた形の対処は `uniformLifelineEndY` の説明。
120
134
  //
@@ -345,7 +359,7 @@ export function CdlStage({
345
359
  edge={edge}
346
360
  active={activeSet.has(edge.id)}
347
361
  drawing={矢印の出番.伸ばすか(edge.id)}
348
- progress={progress}
362
+ progress={矢印の出番.伸ばすか(edge.id) ? 描く進み : progress}
349
363
  stateValues={stateValues}
350
364
  />
351
365
  ))}
package/src/render.tsx CHANGED
@@ -133,6 +133,13 @@ export function CdlDiagramView({ diagram, laid: laidProp, hideHeader = false, hi
133
133
  : state.phaseIndex;
134
134
  const currentPhase = laid.phases[Math.max(0, phaseIdx)] ?? laid.phases[0];
135
135
  const progress = reduced ? 1 : state.phaseProgress;
136
+ /**
137
+ * 描く動きが読む進み (cdl#711)。 曲線を通さない = 経過時間そのものの割合。
138
+ *
139
+ * 線を引く動きに `easeOutQuint` を当てると前半に寄り、 折れ線の 5 点のうち 4 点が
140
+ * 292 ms の間に出る。 ペンの速さは一定であるほど「1 つずつ現れる」 が読める。
141
+ */
142
+ const drawProgress = reduced ? 1 : state.phaseLinearProgress;
136
143
 
137
144
  // state 値を計算 (現在 phase + これまでの phase の最終 tween 値を集約)
138
145
  const baseStateValues = computeStateValues(laid, phaseIdx, progress);
@@ -184,7 +191,7 @@ export function CdlDiagramView({ diagram, laid: laidProp, hideHeader = false, hi
184
191
  {/* interactive panel = input widget / formula readout / scroll readout (SVG の上に配置)、
185
192
  diagram に interactive primitive がなければ null (SSR / render 影響なし) */}
186
193
  {isInteractive && interactive.panel}
187
- <CdlStage laid={laid} currentPhase={currentPhase} progress={progress} stateValues={stateValues} debug={debug} compact={hideHeader} svgRef={isInteractive ? interactive.svgRef : undefined} />
194
+ <CdlStage laid={laid} currentPhase={currentPhase} progress={progress} drawProgress={drawProgress} stateValues={stateValues} debug={debug} compact={hideHeader} svgRef={isInteractive ? interactive.svgRef : undefined} />
188
195
  {/* hideHeader + multi-phase 時、 phase indicator が消えると動きが読めないため
189
196
  mini indicator (dot 列 + 現在 phase title + badge) を bottom に追加 */}
190
197
  {hideHeader && !hideMiniPhaseIndicator && currentPhase && laid.phases.length > 1 && (
package/src/types.ts CHANGED
@@ -483,18 +483,29 @@ export type CdlNode = {
483
483
  */
484
484
  chartPieForm?: ChartPieForm;
485
485
  /**
486
- * chart-slope の見せ方 (cdl#702)。 右の列に何の数を出すかを決める。
486
+ * chart-slope の見せ方 (cdl#702 / cdl#707)。
487
487
  *
488
- * - `values` ... 今の値
489
- * - `delta` ... 増減 (`+40` / `-90`)
488
+ * | 値 | 縦の位置 | 左の列 | 右の列 |
489
+ * |---|---|---|---|
490
+ * | `values` | 値 | 前の値 | 今の値 |
491
+ * | `delta` | 値 | 前の値 | 増減 (`+40` / `-90`) |
492
+ * | `rank` | 順位の段 (等間隔) | 前の値 | 今の値 |
490
493
  *
491
- * 左の列は どちらでも前の値。 `前 + 増減 = 今` なので、 `delta` でも今の値は足せば戻る。
494
+ * `values` と `delta` は縦の位置が同じで、右の列の字だけが違う。
495
+ * `前 + 増減 = 今` なので、 `delta` でも今の値は足せば戻る。
492
496
  * 3 つ目の数を足さないので、 名札の高さも線を引く幅も変わらない。
493
497
  *
498
+ * `rank` は **縦の位置** を変える (cdl#707)。 段を等間隔に置くので、線の交差が
499
+ * 順位の入れ替わりと 1 対 1 になり、水平線が「順位が動かなかった」 を意味する。
500
+ * 字は値のままにして、量が図から消えないようにしている。
501
+ *
502
+ * `rank` では「線の傾きが動いた量」 という読み方が成立しない (`+40` と `+30` が
503
+ * どちらも水平線になる)。 書いた図だけに効く割り切りで、量は字を読む。
504
+ *
494
505
  * 型の外の値は `values` として描く = 見せ方が決まらないより、既定の形が出る方がよい。
495
506
  *
496
- * 前の時点を持たない図 (`previous` が 1 件も無い) は右の列そのものが無いため、
497
- * この欄を書いても絵は変わらない。
507
+ * 前の時点を持たない図 (`previous` が 1 件も無い) は軸が 1 本で並べ替える相手が
508
+ * いないため、 この欄を書いても絵は変わらない。
498
509
  */
499
510
  chartSlopeForm?: ChartSlopeForm;
500
511
  /**
@@ -556,8 +567,8 @@ export type BoundEnum<T extends string> = T | (string & {});
556
567
  /** chart-pie の見せ方 (cdl#684)。 SSOT は `kinds/chart-pie.tsx` */
557
568
  export type ChartPieForm = "ring" | "arcs" | "table";
558
569
 
559
- /** chart-slope の見せ方 (cdl#702)。 SSOT は `kinds/chart-slope.tsx` */
560
- export type ChartSlopeForm = "values" | "delta";
570
+ /** chart-slope の見せ方 (cdl#702 / cdl#707)。 SSOT は `kinds/chart-slope.tsx` */
571
+ export type ChartSlopeForm = "values" | "delta" | "rank";
561
572
 
562
573
  export type ChartDatumPayload = {
563
574
  label: string;