@cardenelabs/cdl 0.83.0 → 0.102.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 +851 -1
- package/dist/index.cjs +878 -186
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -3
- package/dist/index.d.ts +17 -3
- package/dist/index.js +878 -186
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +869 -178
- 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 +869 -178
- package/dist/react.js.map +1 -1
- package/dist/{render-DfXCs3Eh.d.cts → render-oEmwYN-q.d.cts} +21 -3
- package/dist/{render-DfXCs3Eh.d.ts → render-oEmwYN-q.d.ts} +21 -3
- package/package.json +1 -1
- package/src/kinds/chart-gauge.tsx +20 -3
- package/src/kinds/chart-legend-number.tsx +33 -0
- package/src/kinds/chart-number.ts +33 -0
- package/src/kinds/chart-radial.tsx +24 -7
- package/src/kinds/chart-stat.tsx +35 -5
- package/src/kinds/chart-waffle.tsx +25 -7
- package/src/kinds/dyn-shape.tsx +72 -12
- package/src/kinds/funnel.tsx +50 -2
- package/src/kinds/mind-map.tsx +206 -64
- package/src/kinds/quadrant.tsx +318 -45
- package/src/kinds/shape-blockchain.tsx +50 -18
- package/src/kinds/shape-commerce.tsx +27 -4
- package/src/kinds/shape-region.tsx +42 -1
- package/src/kinds/shape-software.tsx +51 -12
- package/src/kinds/storage.tsx +16 -8
- package/src/kinds/tree.tsx +46 -10
- package/src/lang.ts +5 -5
- package/src/layout/nodes.ts +3 -1
- package/src/layout/spec.ts +154 -25
- package/src/layout/text-width.ts +25 -0
- package/src/layout/viewbox.ts +32 -0
- package/src/presets.ts +21 -7
- package/src/render/nodes.tsx +77 -0
- package/src/render/payload-binding.ts +108 -25
- package/src/render/stage.tsx +24 -2
- package/src/types.ts +117 -38
- package/src/visual-validate.ts +61 -1
|
@@ -27,6 +27,40 @@ import { textWidth } from "./text-width";
|
|
|
27
27
|
// 既存の呼出 (検査を含む) がここから取っているため、 そのまま出し直す。
|
|
28
28
|
export { textWidth } from "./text-width";
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* 書いた値があればそれを、無ければ控えの固定語を返す (#927)。
|
|
32
|
+
*
|
|
33
|
+
* 絵の中に文字を置く種別は、見本として固定語を描いている。 書いた値をそこへ通さないと、
|
|
34
|
+
* 同じ種別がどの図でも 1 文字も変わらない絵になる (見本帳の 486 枚で 8 件が黙って消えていた)。
|
|
35
|
+
*
|
|
36
|
+
* **空白だけの値は書いていない扱いにする**。 通すと絵の中の字の口が空欄になり、
|
|
37
|
+
* 控えを描く場合より読めなくなる。
|
|
38
|
+
*/
|
|
39
|
+
export function 書いた値か控え(書いた: string | undefined, 控え: string): string {
|
|
40
|
+
const v = (書いた ?? "").trim();
|
|
41
|
+
return v === "" ? 控え : v;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 字の行を決めた幅に収める倍率 (#927 で `shape-blockchain.tsx` から移した)。
|
|
46
|
+
*
|
|
47
|
+
* 字間は字の大きさに追随しないため、 大きさだけを縮めても幅は `字間 x 字数` より狭く
|
|
48
|
+
* ならない。 大きさと字間を同じ倍率で縮めると、 両項が揃って縮むので幅が倍率に比例する。
|
|
49
|
+
*
|
|
50
|
+
* 収まっている間は 1 を返す = 箱が広い時の見え方を変えない。
|
|
51
|
+
*/
|
|
52
|
+
export function 幅の倍率(
|
|
53
|
+
text: string,
|
|
54
|
+
size: number,
|
|
55
|
+
letterSpacing: number,
|
|
56
|
+
幅: number,
|
|
57
|
+
): number {
|
|
58
|
+
if (!(幅 > 0) || !(size > 0)) return 1;
|
|
59
|
+
// 階級表を二重に持つと、 片方を直した時にもう片方だけ古くなる。
|
|
60
|
+
const 実幅 = textWidth(text, size, letterSpacing);
|
|
61
|
+
return 実幅 > 幅 ? 幅 / 実幅 : 1;
|
|
62
|
+
}
|
|
63
|
+
|
|
30
64
|
/** 名前の字の大きさの上限。 大きい箱ではここに当たり、 今までの見え方を保つ。 */
|
|
31
65
|
const TITLE_MAX = 18;
|
|
32
66
|
|
|
@@ -115,7 +149,10 @@ const 落としても描く種別: Readonly<Record<DroppedLabelLine, ReadonlySet
|
|
|
115
149
|
"shape-code-block",
|
|
116
150
|
"shape-message-bubble",
|
|
117
151
|
"shape-gear",
|
|
118
|
-
//
|
|
152
|
+
// 絵の中の字の口に肩書を通す 2 種別 (#941)。 状態の札 / 見出しの帯の右 に描く
|
|
153
|
+
"shape-kanban-card",
|
|
154
|
+
"shape-blockchain-block",
|
|
155
|
+
// hardware / blockchain 系のうち固有 layout を持つもの
|
|
119
156
|
"shape-iot-sensor",
|
|
120
157
|
"shape-robot-arm",
|
|
121
158
|
]),
|
|
@@ -132,6 +169,10 @@ const 落としても描く種別: Readonly<Record<DroppedLabelLine, ReadonlySet
|
|
|
132
169
|
"shape-window",
|
|
133
170
|
"shape-message-bubble",
|
|
134
171
|
"shape-gear",
|
|
172
|
+
// 絵の中の字の口に説明を通す 3 種別 (#927)。 札の file 名 / 出力の行 / 課題の 1 行 に描く
|
|
173
|
+
"shape-terminal",
|
|
174
|
+
"shape-code-block",
|
|
175
|
+
"shape-kanban-card",
|
|
135
176
|
// hardware / blockchain 系のうち固有 layout を持つもの
|
|
136
177
|
"shape-iot-sensor",
|
|
137
178
|
"shape-robot-arm",
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import type { JSX } from "react";
|
|
2
2
|
import type { LaidNode } from "../types";
|
|
3
3
|
import { 字引きを作る, type CdlLang } from "../lang";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
textWidth,
|
|
6
|
+
打ち消しの倍率,
|
|
7
|
+
幅の倍率,
|
|
8
|
+
書いた値か控え,
|
|
9
|
+
type 打ち消す字,
|
|
10
|
+
} from "./shape-region";
|
|
5
11
|
|
|
6
12
|
/**
|
|
7
13
|
* Shape-driven software 6 kinds (CAR-1111 Phase 2-B)。
|
|
@@ -224,12 +230,14 @@ export function ShapeTerminalNode({ node, active, lang }: ShapeProps): JSX.Eleme
|
|
|
224
230
|
const 先頭 = 26;
|
|
225
231
|
const 行間 = 22;
|
|
226
232
|
const カーソル = { 左: 30, 幅: 9, 高: 14, 上: 11, 下: 3 };
|
|
233
|
+
// 出力の行に書いた説明を通す (#927)。 通さないと、どの端末も同じ 1 行を出す絵になる
|
|
234
|
+
const 出力 = 書いた値か控え(node.subtitle, 字引("terminal.output"));
|
|
227
235
|
const 行 = [
|
|
228
236
|
[
|
|
229
237
|
{ 左: 16, 字: 12.5, 色: "#7cd88f", 文: "$" },
|
|
230
238
|
{ 左: 30, 字: 12.5, 色: "#e6edf3", 文: "pnpm build" },
|
|
231
239
|
],
|
|
232
|
-
[{ 左: 16, 字: 12.5, 色: "#8b95a5", 文:
|
|
240
|
+
[{ 左: 16, 字: 12.5, 色: "#8b95a5", 文: 出力 }],
|
|
233
241
|
[{ 左: 16, 字: 12.5, 色: "#7cd88f", 文: "$" }],
|
|
234
242
|
];
|
|
235
243
|
// 中身は自分の座標で描くため、 本体に入る倍率を出せるよう最長行から自然幅を求める。
|
|
@@ -347,7 +355,16 @@ export function ShapeCodeBlockNode({ node, active, lang }: ShapeProps): JSX.Elem
|
|
|
347
355
|
// 42 は従来どおり名前の行に空け、 低い箱では負の本体を作らないための取り分。
|
|
348
356
|
const bodyH = Math.max(0, node.h - 42 - 線 * 2);
|
|
349
357
|
const lineFont = "ui-monospace, SFMono-Regular, Menlo, monospace";
|
|
350
|
-
|
|
358
|
+
// 札の file 名に書いた説明を通す (#927)。 通さないと、どの code 欄も同じ札を出す絵になる
|
|
359
|
+
const 札 = 書いた値か控え(node.subtitle, 字引("codeBlock.fileName"));
|
|
360
|
+
const 札の字 = 11;
|
|
361
|
+
/*
|
|
362
|
+
* 札の幅は書いた値に合わせて広げる。
|
|
363
|
+
*
|
|
364
|
+
* 固定のままだと、控えより長い値を渡した図で字が札からはみ出す。 本体の幅を上限に置くのは、
|
|
365
|
+
* 札が本体より広くなると `path` で描く角丸が本体の右端を越えるため。
|
|
366
|
+
*/
|
|
367
|
+
const タブ幅 = Math.min(Math.max(100, textWidth(札, 札の字, 0) + 24), Math.max(0, w));
|
|
351
368
|
const タブ高 = 24;
|
|
352
369
|
const 角 = 10;
|
|
353
370
|
const 溝 = 32;
|
|
@@ -402,7 +419,7 @@ export function ShapeCodeBlockNode({ node, active, lang }: ShapeProps): JSX.Elem
|
|
|
402
419
|
* 高さ `行間`)。 絵に 1 つの倍率なので、狭い方が全体の上限になる。
|
|
403
420
|
*/
|
|
404
421
|
const 打ち消す字ら: 打ち消す字[] = [
|
|
405
|
-
{ text:
|
|
422
|
+
{ text: 札, size: 札の字, 帯: { 幅: タブ幅, 高さ: タブ高 } },
|
|
406
423
|
{ text: "1", size: 行番号の字, 帯: { 幅: 溝 - 持ち上げ, 高さ: 行間 } },
|
|
407
424
|
];
|
|
408
425
|
const 倍返し = 打ち消しの倍率(倍, 打ち消す字ら);
|
|
@@ -439,12 +456,12 @@ export function ShapeCodeBlockNode({ node, active, lang }: ShapeProps): JSX.Elem
|
|
|
439
456
|
data-cdl-counter-scaled="1"
|
|
440
457
|
x={x + tabW / 2}
|
|
441
458
|
y={y + tabH / 2 + 4 * 倍}
|
|
442
|
-
fontSize={
|
|
459
|
+
fontSize={札の字 * 倍 * 倍返し * 幅の倍率(札, 札の字 * 倍 * 倍返し, 0, tabW)}
|
|
443
460
|
fontFamily={lineFont}
|
|
444
461
|
textAnchor="middle"
|
|
445
462
|
fill="#e6edf3"
|
|
446
463
|
>
|
|
447
|
-
{
|
|
464
|
+
{札}
|
|
448
465
|
</text>
|
|
449
466
|
<circle cx={x + tabW - 丸} cy={y + tabH / 2} r={3 * 倍} fill="#7cd88f" stroke="none" />
|
|
450
467
|
<path
|
|
@@ -558,6 +575,14 @@ export function ShapeKanbanCardNode({ node, active, lang }: ShapeProps): JSX.Ele
|
|
|
558
575
|
* 収まらない。 その 8 は上段の自然幅に乗り、120x110 の箱で全部の字を 0.4 world 下げる。
|
|
559
576
|
*/
|
|
560
577
|
const PILL字間 = 0;
|
|
578
|
+
/*
|
|
579
|
+
* 状態の札に入れてよい字の幅 (#941)。 **札そのものは広げない** = 札の自然幅が絵全体の
|
|
580
|
+
* 縮尺を決めるので (#891)、広げると札の中の字だけでなく全部の字が小さくなる。
|
|
581
|
+
*
|
|
582
|
+
* 丸い端に触れないよう札の幅から 6 引く。 控えの `IN PROG` は指定 11 で 55.4 なので
|
|
583
|
+
* この予算に収まり、書かない箱の見え方は変わらない (#888 で札を 62 にした時の実測)。
|
|
584
|
+
*/
|
|
585
|
+
const PILL字幅 = PILL幅 - 6;
|
|
561
586
|
const FOOTER下 = 22;
|
|
562
587
|
const AVATAR中心Y = 4;
|
|
563
588
|
const AVATAR半径 = 9;
|
|
@@ -594,6 +619,19 @@ export function ShapeKanbanCardNode({ node, active, lang }: ShapeProps): JSX.Ele
|
|
|
594
619
|
下: size * 説明基準比 + size * 説明字比 * DESCENT,
|
|
595
620
|
}),
|
|
596
621
|
);
|
|
622
|
+
// 課題の 1 行に書いた説明を通す (#927)。 通さないと、どの付箋も同じ 1 行を出す絵になる
|
|
623
|
+
const 説明 = 書いた値か控え(node.subtitle, 字引("kanbanCard.task"));
|
|
624
|
+
/*
|
|
625
|
+
* 状態の札に書いた肩書を通す (#941)。 説明と同じ形で、書かなければ控えを出す。
|
|
626
|
+
*
|
|
627
|
+
* 通していなかったため、書き手が「看板」 と書いた箱でも札は控えの `IN PROG` を出していた
|
|
628
|
+
* = 書いてもいない語が図の一部として読める状態だった (見本帳で 3 件)。
|
|
629
|
+
*/
|
|
630
|
+
const 状態 = 書いた値か控え(node.eyebrow, 字引("kanbanCard.status"));
|
|
631
|
+
const 状態の倍率 = 幅の倍率(状態, PILL字, PILL字間, PILL字幅);
|
|
632
|
+
const 説明の字 = 名前.size * 説明字比;
|
|
633
|
+
// 札の内側の幅。 左右の余白は札と同じ倍率で縮むので、倍率を掛けてから引く
|
|
634
|
+
const 使える幅 = Math.max(0, w - 左右余白 * 倍 * 2);
|
|
597
635
|
return (
|
|
598
636
|
<g>
|
|
599
637
|
{/* shadow */}
|
|
@@ -641,16 +679,17 @@ export function ShapeKanbanCardNode({ node, active, lang }: ShapeProps): JSX.Ele
|
|
|
641
679
|
strokeWidth={1 * 倍}
|
|
642
680
|
/>
|
|
643
681
|
<text
|
|
682
|
+
data-cdl-role="kanban-status"
|
|
644
683
|
x={x + w - (左右余白 + PILL幅 / 2) * 倍}
|
|
645
684
|
y={pillY + 12 * 倍}
|
|
646
|
-
fontSize={PILL字 * 倍}
|
|
685
|
+
fontSize={PILL字 * 倍 * 状態の倍率}
|
|
647
686
|
fontWeight={700}
|
|
648
|
-
letterSpacing={PILL字間 * 倍}
|
|
687
|
+
letterSpacing={PILL字間 * 倍 * 状態の倍率}
|
|
649
688
|
textAnchor="middle"
|
|
650
689
|
fill="#5a4a1a"
|
|
651
690
|
fontFamily="ui-monospace, SFMono-Regular, Menlo, monospace"
|
|
652
691
|
>
|
|
653
|
-
{
|
|
692
|
+
{状態}
|
|
654
693
|
</text>
|
|
655
694
|
{/* タスク名 = title (中央上、 大きく太字) */}
|
|
656
695
|
<text
|
|
@@ -664,15 +703,15 @@ export function ShapeKanbanCardNode({ node, active, lang }: ShapeProps): JSX.Ele
|
|
|
664
703
|
>
|
|
665
704
|
{node.title}
|
|
666
705
|
</text>
|
|
667
|
-
{/* タスク説明 (中央下、 小さめ dim) */}
|
|
706
|
+
{/* タスク説明 (中央下、 小さめ dim)。 書いた説明をここへ通す (#927) */}
|
|
668
707
|
<text
|
|
669
708
|
x={x + w / 2}
|
|
670
709
|
y={名前.y + 名前.size * 説明基準比}
|
|
671
|
-
fontSize={
|
|
710
|
+
fontSize={説明の字 * 幅の倍率(説明, 説明の字, 0, 使える幅)}
|
|
672
711
|
textAnchor="middle"
|
|
673
712
|
fill="var(--cdl-text-dim, #5a6270)"
|
|
674
713
|
>
|
|
675
|
-
{
|
|
714
|
+
{説明}
|
|
676
715
|
</text>
|
|
677
716
|
{/* footer = 2 avatars 左 + due date 右 */}
|
|
678
717
|
<g transform={`translate(${x + 左右余白 * 倍}, ${footY})`}>
|
package/src/kinds/storage.tsx
CHANGED
|
@@ -151,8 +151,17 @@ export function StorageNode({
|
|
|
151
151
|
* 従来の書き方に落ちて呼び名が消える (実測)。
|
|
152
152
|
*/
|
|
153
153
|
const 印で分ける = node.rowMarks !== undefined;
|
|
154
|
-
|
|
155
|
-
|
|
154
|
+
/*
|
|
155
|
+
呼び名を出す箱では行の群を下げる。 高さの計算 (`autoStorageHeight`) も同じ値を足す。
|
|
156
|
+
|
|
157
|
+
**印の有無を見ない** (#937)。 従来の箱で呼び名を落としていたのは「題の下に区切り線が
|
|
158
|
+
来るので置き場が無い」 ためだったが、置き場が無いのではなく **下げていなかった**。
|
|
159
|
+
区切り線も同じ分だけ下げれば、印を書いた箱と同じ並びになる。
|
|
160
|
+
|
|
161
|
+
見本 374 枚で 36 件が消えていた。 ER の図は表の名前を識別子 (`users` / `order_items`)
|
|
162
|
+
で書くため、日本語の呼び名が唯一の読み口になる。
|
|
163
|
+
*/
|
|
164
|
+
const 行の下げ = node.subtitle ? SUBTITLE_ROW_SHIFT : 0;
|
|
156
165
|
const lastRowBottom = Math.min(
|
|
157
166
|
ROW_TOP_STORAGE + (rows.length - 1) * ROW_PITCH_STORAGE + (rowGlyphDepth("storage") ?? 0),
|
|
158
167
|
node.h - ROW_PAD_X_PX,
|
|
@@ -211,9 +220,8 @@ export function StorageNode({
|
|
|
211
220
|
{node.eyebrow}
|
|
212
221
|
</text>
|
|
213
222
|
)}
|
|
214
|
-
{
|
|
215
|
-
//
|
|
216
|
-
// 置き場が無い
|
|
223
|
+
{node.subtitle && (
|
|
224
|
+
// 呼び名は書き方によらず出す (#937)。 区切り線を同じ分だけ下げて置き場を作る
|
|
217
225
|
<text
|
|
218
226
|
data-cdl-role="node-subtitle"
|
|
219
227
|
x={ROW_PAD_X_PX}
|
|
@@ -263,9 +271,9 @@ export function StorageNode({
|
|
|
263
271
|
<line
|
|
264
272
|
data-cdl-role="node-row-divider"
|
|
265
273
|
x1={26}
|
|
266
|
-
y1={STORAGE_ROW_DIVIDER_Y}
|
|
274
|
+
y1={STORAGE_ROW_DIVIDER_Y + 行の下げ}
|
|
267
275
|
x2={node.w - 26}
|
|
268
|
-
y2={STORAGE_ROW_DIVIDER_Y}
|
|
276
|
+
y2={STORAGE_ROW_DIVIDER_Y + 行の下げ}
|
|
269
277
|
stroke="var(--cdl-row-divider, #7e848e)"
|
|
270
278
|
strokeWidth={1.25}
|
|
271
279
|
/>
|
|
@@ -274,7 +282,7 @@ export function StorageNode({
|
|
|
274
282
|
<line
|
|
275
283
|
data-cdl-role="node-column-divider"
|
|
276
284
|
x1={columnDividerX}
|
|
277
|
-
y1={STORAGE_ROW_DIVIDER_Y}
|
|
285
|
+
y1={STORAGE_ROW_DIVIDER_Y + 行の下げ}
|
|
278
286
|
x2={columnDividerX}
|
|
279
287
|
y2={lastRowBottom}
|
|
280
288
|
stroke="var(--cdl-row-divider, #7e848e)"
|
package/src/kinds/tree.tsx
CHANGED
|
@@ -3,6 +3,7 @@ import type { LaidNode, TreeNodePayload } from "../types";
|
|
|
3
3
|
import { TONE } from "../render/tone";
|
|
4
4
|
import { resolveTreeData } from "../render/payload-binding";
|
|
5
5
|
import { 使える幅, 幅に収める, 箱に積む, type 積む行, type 積んだ行 } from "../kinds/box-lines";
|
|
6
|
+
import { measureTextWidth } from "../layout/text-width";
|
|
6
7
|
import { 進みを畳む, 伸ばすdash, 段の伸び } from "./draw-progress";
|
|
7
8
|
|
|
8
9
|
/**
|
|
@@ -61,6 +62,16 @@ const PAD_RIGHT = 24;
|
|
|
61
62
|
const PAD_BOTTOM = 40;
|
|
62
63
|
const PAD_LEFT = 24;
|
|
63
64
|
|
|
65
|
+
/**
|
|
66
|
+
* 枝の太さ。 根に近いほど太く、葉へ行くほど細い (#909)。
|
|
67
|
+
*
|
|
68
|
+
* 木は根から葉へ辿る図なので、太さが一様だと流れが絵に出ない。
|
|
69
|
+
*
|
|
70
|
+
* **下限を置く**。 段数に上限が無いため、引き算だけだと深い図で 0 を割り、線が消える。
|
|
71
|
+
* 消えた線は「繋がっていない」 と読まれるので、細くても残す方を採る (放射と同じ判断)。
|
|
72
|
+
*/
|
|
73
|
+
export const 木の枝の太さ = (段: number): number => Math.max(1.1, 2.6 - 段 * 0.45);
|
|
74
|
+
|
|
64
75
|
/**
|
|
65
76
|
* 木の中の箱と枝を並べる (#538)。
|
|
66
77
|
*
|
|
@@ -73,11 +84,7 @@ export function 木の中身(
|
|
|
73
84
|
): { nodes: LayoutNode[]; edges: LayoutEdge[] } {
|
|
74
85
|
// 名前は状態を読める (#467)。 解決できない名前は書いたまま残して印を付ける
|
|
75
86
|
const treeNodes = resolveTreeData(node.treeData ?? [], stateValues);
|
|
76
|
-
return computeTreeLayout(
|
|
77
|
-
treeNodes,
|
|
78
|
-
node.w - PAD_LEFT - PAD_RIGHT,
|
|
79
|
-
node.h - PAD_TOP - PAD_BOTTOM,
|
|
80
|
-
);
|
|
87
|
+
return computeTreeLayout(treeNodes, node.w - PAD_LEFT - PAD_RIGHT, node.h - PAD_TOP - PAD_BOTTOM);
|
|
81
88
|
}
|
|
82
89
|
|
|
83
90
|
/**
|
|
@@ -159,7 +166,9 @@ export function TreeNode({
|
|
|
159
166
|
const path = `M ${x1} ${y1} L ${x1} ${midY} L ${x2} ${midY} L ${x2} ${y2}`;
|
|
160
167
|
// 循環を閉じる枝は根から外へ向かう枝ではないため、全 node が出てから表示する
|
|
161
168
|
const 枝の伸び = e.deferUntilComplete
|
|
162
|
-
?
|
|
169
|
+
? 伸び >= 1
|
|
170
|
+
? 1
|
|
171
|
+
: 0
|
|
163
172
|
: 段の伸び(伸び, e.depth, 最大深さ);
|
|
164
173
|
return (
|
|
165
174
|
<g key={`edge-${i}`}>
|
|
@@ -169,7 +178,7 @@ export function TreeNode({
|
|
|
169
178
|
fill="none"
|
|
170
179
|
stroke={TONE.accent}
|
|
171
180
|
strokeOpacity={0.55}
|
|
172
|
-
strokeWidth={
|
|
181
|
+
strokeWidth={木の枝の太さ(e.depth)}
|
|
173
182
|
strokeLinejoin="round"
|
|
174
183
|
{...伸ばすdash(drawing, 枝の伸び)}
|
|
175
184
|
/>
|
|
@@ -352,7 +361,8 @@ export function computeTreeLayout(
|
|
|
352
361
|
if (kids.length === 0) return 0;
|
|
353
362
|
return 1 + Math.max(...kids.map((k) => depthBelow(k.id, seen)));
|
|
354
363
|
};
|
|
355
|
-
const maxDepth =
|
|
364
|
+
const maxDepth =
|
|
365
|
+
roots.length === 0 ? 0 : Math.max(...roots.map((r) => depthBelow(r.id, new Set())));
|
|
356
366
|
|
|
357
367
|
// 葉の数で横幅を配分する。 親は自分の子の中央に置く。
|
|
358
368
|
// 深さごとに canvas 全幅へ均等配置すると、 親と無関係な位置に子が並んで系統が読めない。
|
|
@@ -363,7 +373,10 @@ export function computeTreeLayout(
|
|
|
363
373
|
if (kids.length === 0) return 1;
|
|
364
374
|
return kids.reduce((sum, k) => sum + leafCount(k.id, seen), 0);
|
|
365
375
|
};
|
|
366
|
-
const totalLeaves = Math.max(
|
|
376
|
+
const totalLeaves = Math.max(
|
|
377
|
+
roots.reduce((sum, r) => sum + leafCount(r.id, new Set()), 0),
|
|
378
|
+
1,
|
|
379
|
+
);
|
|
367
380
|
|
|
368
381
|
const depthGap = canvasH / Math.max(maxDepth + 1, 1);
|
|
369
382
|
const slotW = canvasW / totalLeaves;
|
|
@@ -373,6 +386,29 @@ export function computeTreeLayout(
|
|
|
373
386
|
const nodeH = Math.min(38, depthGap * 0.7);
|
|
374
387
|
const nodeW = Math.min(140, slotW * 0.85);
|
|
375
388
|
|
|
389
|
+
/**
|
|
390
|
+
* 箱の幅を名前から出す (#909)。
|
|
391
|
+
*
|
|
392
|
+
* 全ての箱を同じ幅にすると、長い名前は縮められるか切られ、短い名前は間延びする。
|
|
393
|
+
* 木は「何がどこに属するか」 を読む図なので、名前が読めないと図の役目が立たない。
|
|
394
|
+
*
|
|
395
|
+
* **下限は今までの幅**。 短い名前の箱を狭めると、既存の図の見た目が名前の長さで
|
|
396
|
+
* 変わってしまう。 広がる向きだけを足す。
|
|
397
|
+
*
|
|
398
|
+
* **頭打ちは区画の 92%**。 これを超えると隣の箱とぶつかる。 超える名前は今までどおり
|
|
399
|
+
* 縮めて収める (`幅に収める` が描く側で効く)。
|
|
400
|
+
*/
|
|
401
|
+
const 箱の幅 = (n: TreeNodePayload, depth: number): number => {
|
|
402
|
+
// 字の大きさは段で変わる (`木の行`)。 段を渡さないと根だけ見積りがずれる
|
|
403
|
+
const 材料 = 木の行({ ...n, x: 0, y: 0, w: 0, h: 0, depth } as LayoutNode);
|
|
404
|
+
const 名前 = 材料[0]!;
|
|
405
|
+
// `使える幅` が引く左右の余白を足し戻す = 余白の値をここに書き写さない
|
|
406
|
+
const 要る =
|
|
407
|
+
measureTextWidth(名前.text, { fontSize: 名前.fontSize, fontWeight: 600 }) +
|
|
408
|
+
(nodeW - 使える幅(nodeW));
|
|
409
|
+
return Math.min(Math.max(要る, nodeW), slotW * 0.92);
|
|
410
|
+
};
|
|
411
|
+
|
|
376
412
|
const posOf = new Map<string, { x: number; y: number }>();
|
|
377
413
|
const nodes: LayoutNode[] = [];
|
|
378
414
|
const placed = new Set<string>();
|
|
@@ -398,7 +434,7 @@ export function computeTreeLayout(
|
|
|
398
434
|
...(n.subtitle !== undefined ? { subtitle: n.subtitle } : {}),
|
|
399
435
|
x,
|
|
400
436
|
y,
|
|
401
|
-
w:
|
|
437
|
+
w: 箱の幅(n, depth),
|
|
402
438
|
h: nodeH,
|
|
403
439
|
depth,
|
|
404
440
|
...((n as { unresolved?: true }).unresolved ? { unresolved: true as const } : {}),
|
package/src/lang.ts
CHANGED
|
@@ -32,22 +32,22 @@ export function resolveLang(value: unknown): CdlLang {
|
|
|
32
32
|
* 鍵は `形.役割` の形にする。 同じ字でも役割が違えば別の鍵にする (訳が分かれうるため)。
|
|
33
33
|
*/
|
|
34
34
|
const en = {
|
|
35
|
-
/** `shape-terminal` の 2 行目 (命令の出力) */
|
|
36
35
|
/*
|
|
37
|
-
* `shape-terminal`
|
|
36
|
+
* `shape-terminal` の出力の行を **書かない時** の控え (#927)。
|
|
38
37
|
*
|
|
39
38
|
* **いちばん長い行が絵の倍率を決める** (#891)。 端末の中身は自分の座標で組んでから箱へ
|
|
40
39
|
* 収めるので、この行が長いほど全部の字が小さくなる。 120x110 の箱では 20 字の版で
|
|
41
|
-
* 7.71 world まで落ちていた。
|
|
40
|
+
* 7.71 world まで落ちていた。 書いた説明を通すようになったので、長い説明を渡した図でも
|
|
41
|
+
* 同じことが起きる。
|
|
42
42
|
*/
|
|
43
43
|
"terminal.output": "→ 12 chunks",
|
|
44
|
-
/** `shape-code-block` の札の file
|
|
44
|
+
/** `shape-code-block` の札の file 名を **書かない時** の控え (#927) */
|
|
45
45
|
"codeBlock.fileName": "utils.ts",
|
|
46
46
|
/** `shape-kanban-card` の課題の番号 */
|
|
47
47
|
"kanbanCard.id": "CAR-1111",
|
|
48
48
|
/** `shape-kanban-card` の状態の札 */
|
|
49
49
|
"kanbanCard.status": "IN PROG",
|
|
50
|
-
/** `shape-kanban-card`
|
|
50
|
+
/** `shape-kanban-card` の題の下の説明を **書かない時** の控え (#927) */
|
|
51
51
|
"kanbanCard.task": "shape-driven kind 実装",
|
|
52
52
|
/** `shape-kanban-card` の期日 */
|
|
53
53
|
"kanbanCard.due": "Jul 10",
|
package/src/layout/nodes.ts
CHANGED
|
@@ -42,7 +42,9 @@ function autoStorageHeight(
|
|
|
42
42
|
// 上下の余白を揃える (#586)。 実測で 150 のとき上 21 / 下 55.9 と下だけ 35 空いていた。
|
|
43
43
|
// 字の高さ 73.2 に上の余白 21 を上下ぶん足して 115 にする。
|
|
44
44
|
const 印あり = n.rowMarks !== undefined;
|
|
45
|
-
|
|
45
|
+
// 呼び名の分は **印の有無を見ない** (#937)。 描き手 (`kinds/storage.tsx`) が行と区切り線を
|
|
46
|
+
// 下げる条件と同じにする = 片方だけ条件が違うと、下げた分が箱に入らず下端をまたぐ
|
|
47
|
+
const 呼び名の分 = n.subtitle ? SUBTITLE_ROW_SHIFT : 0;
|
|
46
48
|
// 小見出しを書かない形では字を上へ繰り上げるので、その分だけ箱も低くする (#589)
|
|
47
49
|
const 繰り上げ = n.eyebrow ? 0 : 目印なしの繰り上げ("storage");
|
|
48
50
|
if (!n.rows || n.rows.length === 0) return 115 + 呼び名の分 - 繰り上げ;
|
package/src/layout/spec.ts
CHANGED
|
@@ -60,28 +60,18 @@ export function hasRenderedLabel(e: { label?: string; sub?: string }): boolean {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
/**
|
|
63
|
-
*
|
|
63
|
+
* 図表を自前で描く node の種別 (#929)。
|
|
64
64
|
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
* 10 種と時系列のやり取り 1 種は、 描くのが payload 側の label で `title` は使わない。 流れの始まりと終わりの印 2 種は
|
|
68
|
-
* 印そのものが意味を持ち、 丸の中に名前を入れても読めない (#560)。
|
|
65
|
+
* 1 つの箱が図を丸ごと描く群で、箱の中は図表の中身で埋まっている。
|
|
66
|
+
* `compile.ts` は図の主題をこの箱の題として渡すため、**書いた題 = 図の名前** になる。
|
|
69
67
|
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
68
|
+
* 中に題の場所が無いので、題は **箱の上** に描く (`render/nodes.tsx`)。
|
|
69
|
+
* 図表の題は元から図の上に置くものなので、位置としても自然になる。
|
|
70
|
+
*
|
|
71
|
+
* 一覧を 2 つ持たないよう、`KINDS_FITTING_OWN_TITLE` はこの集合を取り込む。
|
|
72
|
+
* 図表の種別を足す人が片方だけ直すと、幅の近似が当たらない軸が誤報する。
|
|
73
73
|
*/
|
|
74
|
-
const
|
|
75
|
-
// 時系列のやり取り (#578)。 図を丸ごと描く種別で、描くのは面々の名前と言づての語。
|
|
76
|
-
// 箱の `title` は使わない
|
|
77
|
-
"sequence-board",
|
|
78
|
-
// 形だけを描く (DynShapeNode)
|
|
79
|
-
"dyn-rect",
|
|
80
|
-
"dyn-circle",
|
|
81
|
-
"dyn-arc",
|
|
82
|
-
"dyn-wave",
|
|
83
|
-
"dyn-polygon",
|
|
84
|
-
// payload を自前 layout して描く (label は payload 側が持つ)
|
|
74
|
+
const KINDS_DRAWING_OWN_PAYLOAD: ReadonlySet<string> = new Set([
|
|
85
75
|
"chart-pie",
|
|
86
76
|
"chart-line",
|
|
87
77
|
"chart-bar",
|
|
@@ -97,7 +87,30 @@ const KINDS_WITHOUT_TITLE_TEXT: ReadonlySet<string> = new Set([
|
|
|
97
87
|
"quadrant-matrix",
|
|
98
88
|
"tree-hierarchy",
|
|
99
89
|
"journey-map",
|
|
100
|
-
|
|
90
|
+
]);
|
|
91
|
+
|
|
92
|
+
/** その node が図の名前を箱の上に描くか (SSOT) */
|
|
93
|
+
export function drawsFigureTitle(kind: string | undefined): boolean {
|
|
94
|
+
return KINDS_DRAWING_OWN_PAYLOAD.has(kind ?? "");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* `title` を文字として描かない node の種別。
|
|
99
|
+
*
|
|
100
|
+
* 2 群ある。 時系列のやり取りは図を丸ごと描くが、描くのは面々の名前と言づての語で、
|
|
101
|
+
* 箱の題は別の役割を持つ (#578)。 流れの始まりと終わりの印は印そのものが意味を持ち、
|
|
102
|
+
* 丸の中に名前を入れても読めない (#560)。
|
|
103
|
+
*
|
|
104
|
+
* ここから外れた 2 群がある。 形だけを描く `dyn-*` 5 種は #925 で名前を描くようになり、
|
|
105
|
+
* 図表を自前で描く 15 種は #929 で題を箱の上に描くようになった。
|
|
106
|
+
* どちらも「書いた値が黙って捨てられる」 ことが実害で、見本帳の実測は 264 件と 27 件だった。
|
|
107
|
+
*
|
|
108
|
+
* 一覧は手で持つと描画側とずれるため、 `test/node-title-render.test.tsx` が `NODE_KINDS` を
|
|
109
|
+
* **全件 render** して「title の文字が出るか」 と本述語の返り値の一致を検査する。 新しい種別を
|
|
110
|
+
* 足すと `NODE_KINDS` に載った時点で検査対象に入るので、 追記を忘れれば必ず落ちる。
|
|
111
|
+
*/
|
|
112
|
+
const KINDS_WITHOUT_TITLE_TEXT: ReadonlySet<string> = new Set([
|
|
113
|
+
"sequence-board",
|
|
101
114
|
"mark-start",
|
|
102
115
|
"mark-end",
|
|
103
116
|
]);
|
|
@@ -128,8 +141,15 @@ export function rendersTitleText(kind: string | undefined): boolean {
|
|
|
128
141
|
*
|
|
129
142
|
* **`rendersTitleText` とは別の述語にする**。 あちらは「文字として描くか」 で、 RTL / CJK / bidi の
|
|
130
143
|
* 判定はこの種別にも当てたい。 ここで見るのは「幅の近似が当たるか」 だけになる。
|
|
144
|
+
*
|
|
145
|
+
* 図表を自前で描く 15 種も同じ理由で入る (#929)。 題を 13 で描き、箱の幅に収まる倍率を
|
|
146
|
+
* 自分で掛けるので、超えて切れることが起きない。 **集合を書き写さず取り込む** = 種別を
|
|
147
|
+
* 足した人が片方だけ直すと、幅の近似が当たらない軸が誤報する。
|
|
131
148
|
*/
|
|
132
|
-
const KINDS_FITTING_OWN_TITLE: ReadonlySet<string> = new Set([
|
|
149
|
+
const KINDS_FITTING_OWN_TITLE: ReadonlySet<string> = new Set([
|
|
150
|
+
"shape-blockchain-block",
|
|
151
|
+
...KINDS_DRAWING_OWN_PAYLOAD,
|
|
152
|
+
]);
|
|
133
153
|
|
|
134
154
|
/** その node が `title` を自分で幅に合わせて切るか (SSOT) */
|
|
135
155
|
export function fitsOwnTitleWidth(kind: string | undefined): boolean {
|
|
@@ -150,10 +170,13 @@ export function fitsOwnTitleWidth(kind: string | undefined): boolean {
|
|
|
150
170
|
* **全件 render** して「行の文字が出るか」 と本述語の返り値の一致を検査する。 新しい種別を足すと
|
|
151
171
|
* `NODE_KINDS` に載った時点で検査対象に入るので、 追記を忘れれば必ず落ちる。
|
|
152
172
|
*/
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
173
|
+
/**
|
|
174
|
+
* 共通の箱の描き手 (`GenericNode`) に回る種別。
|
|
175
|
+
*
|
|
176
|
+
* 行 (`rows`) と値 (`value`) の採用側がどちらもこの 21 種を含むため、一覧を 1 つにする。
|
|
177
|
+
* 2 か所に書き写すと、種別を足した人が片方だけ直してずれる。
|
|
178
|
+
*/
|
|
179
|
+
const KINDS_DRAWN_BY_GENERIC: readonly string[] = [
|
|
157
180
|
"person",
|
|
158
181
|
"user-group",
|
|
159
182
|
"admin",
|
|
@@ -175,6 +198,12 @@ const KINDS_WITH_ROW_TEXT: ReadonlySet<string> = new Set([
|
|
|
175
198
|
"oracle",
|
|
176
199
|
"merkle-tree",
|
|
177
200
|
"decision",
|
|
201
|
+
];
|
|
202
|
+
|
|
203
|
+
const KINDS_WITH_ROW_TEXT: ReadonlySet<string> = new Set([
|
|
204
|
+
// 専用の表組み (StorageNode)
|
|
205
|
+
"storage",
|
|
206
|
+
...KINDS_DRAWN_BY_GENERIC,
|
|
178
207
|
]);
|
|
179
208
|
|
|
180
209
|
/**
|
|
@@ -188,6 +217,106 @@ export function rendersRows(kind: string | undefined): boolean {
|
|
|
188
217
|
return KINDS_WITH_ROW_TEXT.has(kind ?? "");
|
|
189
218
|
}
|
|
190
219
|
|
|
220
|
+
/**
|
|
221
|
+
* 形だけを描く種別 (#925 で名前を描くようになった)。
|
|
222
|
+
*
|
|
223
|
+
* 語は **箱の外** に置く。 名前を形の上、説明を形の下に出す (`kinds/dyn-shape.tsx`)。
|
|
224
|
+
* 中に置くと、内側の塗りが値で動くたびに字と重なる。
|
|
225
|
+
*
|
|
226
|
+
* 描かないのは肩書と値の 2 欄。 箱の外に置ける行は 2 本で、名前と説明が埋めている。
|
|
227
|
+
*/
|
|
228
|
+
const KINDS_DRAWING_ONLY_SHAPE: readonly string[] = [
|
|
229
|
+
"dyn-rect",
|
|
230
|
+
"dyn-circle",
|
|
231
|
+
"dyn-arc",
|
|
232
|
+
"dyn-wave",
|
|
233
|
+
"dyn-polygon",
|
|
234
|
+
];
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* 肩書 (`eyebrow`) を描かない種別。
|
|
238
|
+
*
|
|
239
|
+
* 2 群ある。 形だけを描く種別は箱の外に置ける行を名前と説明で埋めている。 題そのものを
|
|
240
|
+
* 描かない種別は語の置き場所が無い。
|
|
241
|
+
*
|
|
242
|
+
* **図表を自前で描く 15 種は #935 で描く側へ移した**。 題を箱の上に出すようにした (#929) ので、
|
|
243
|
+
* その上にもう 1 行置ける。 呼び出し側 (`dragon` の `図の小見出し`) は元から 15 種へ肩書を
|
|
244
|
+
* 渡しており、出る先が無いまま捨てられていた。
|
|
245
|
+
*
|
|
246
|
+
* **課題の札と連なりの塊の 2 種も #941 で描く側へ移した**。 どちらも絵の中に固有の並びを
|
|
247
|
+
* 持つが、置き場はあった = 課題の札は状態の札に控えの語を出しており、連なりの塊は見出しの
|
|
248
|
+
* 帯の右が空いていた。
|
|
249
|
+
*
|
|
250
|
+
* 除外側を列挙するのは、描く種別が描かない種別より多く、新しい種別が増えた時に既定で
|
|
251
|
+
* 「描く」 側に入るのが正しいため (件数は実装が SSOT で、内訳は
|
|
252
|
+
* `test/node-field-render.test.tsx` の母数の検査が持つ)。 描かないようにしたら明示的に
|
|
253
|
+
* ここへ足す。
|
|
254
|
+
*
|
|
255
|
+
* 一覧は手で持つと描画側とずれるため、`test/node-field-render.test.tsx` が `NODE_KINDS` を
|
|
256
|
+
* **全件 render** して「肩書の文字が出るか」 と本述語の返り値の一致を検査する。
|
|
257
|
+
*/
|
|
258
|
+
const KINDS_WITHOUT_EYEBROW_TEXT: ReadonlySet<string> = new Set([
|
|
259
|
+
...KINDS_DRAWING_ONLY_SHAPE,
|
|
260
|
+
...KINDS_WITHOUT_TITLE_TEXT,
|
|
261
|
+
]);
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* その node が `eyebrow` を文字として描くか (SSOT)。
|
|
265
|
+
*
|
|
266
|
+
* 描かない種別に `eyebrow` が書かれていること自体は「書いた語が画面に出ない」 破綻で、
|
|
267
|
+
* `node-field-unsupported` (軸 70) が見る。 `shape-label-dropped` (軸 67) は別の形
|
|
268
|
+
* (箱が狭くて落ちた行) を見ており、種別が元から描かない欄は範囲外だと自分で書いている。
|
|
269
|
+
*/
|
|
270
|
+
export function rendersEyebrow(kind: string | undefined): boolean {
|
|
271
|
+
return !KINDS_WITHOUT_EYEBROW_TEXT.has(kind ?? "");
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* 説明 (`subtitle`) を描かない種別。
|
|
276
|
+
*
|
|
277
|
+
* 肩書の一覧との差は 2 つ。
|
|
278
|
+
*
|
|
279
|
+
* 形だけを描く 5 種は **描く側に居る** = 説明を形の下に出す (`KINDS_DRAWING_ONLY_SHAPE`)。
|
|
280
|
+
* 課題の札と連なりの塊は #927 と #929 で絵の中の字の口に説明を通すようになった。
|
|
281
|
+
*
|
|
282
|
+
* **`storage` も描く側に居る** (#937)。 印を書いた箱でだけ呼び名を出していたため、
|
|
283
|
+
* 種別だけで判定する本述語は「描かない」 を返し、印を書いた箱で軸 70 が誤検知していた。
|
|
284
|
+
* どちらの書き方でも描くようにしたので、種別だけで真と言える
|
|
285
|
+
* (条件が箱ごとに決まる形は、種別ごとの述語では表せない)。
|
|
286
|
+
*/
|
|
287
|
+
const KINDS_WITHOUT_SUBTITLE_TEXT: ReadonlySet<string> = new Set([
|
|
288
|
+
...KINDS_DRAWING_OWN_PAYLOAD,
|
|
289
|
+
...KINDS_WITHOUT_TITLE_TEXT,
|
|
290
|
+
]);
|
|
291
|
+
|
|
292
|
+
/** その node が `subtitle` を文字として描くか (SSOT)。 破綻の扱いは `rendersEyebrow` と同じ */
|
|
293
|
+
export function rendersSubtitle(kind: string | undefined): boolean {
|
|
294
|
+
return !KINDS_WITHOUT_SUBTITLE_TEXT.has(kind ?? "");
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* 値 (`value`) を文字として描く種別。
|
|
299
|
+
*
|
|
300
|
+
* 共通の箱の描き手 (`GenericNode`) に回る 21 種と、面々の絵 (`ActorNode`) が描く。
|
|
301
|
+
* 自前の描き手を持つ残りの種別は 1 つも描かない。
|
|
302
|
+
*
|
|
303
|
+
* 採用側を列挙するのは、描く種別が少数 (22 対 76) で、新しい種別が増えた時に既定で
|
|
304
|
+
* 「描かない」 側に入るのが正しいため (`KINDS_WITH_ROW_TEXT` と同じ考え方)。
|
|
305
|
+
*
|
|
306
|
+
* 行 (`rows`) の採用側とは **1 種ずつ違う**。 `storage` は行を描いて値を描かず、
|
|
307
|
+
* `actor` は値を描いて行を描かない。 片方の一覧を写して使うと両方でずれる。
|
|
308
|
+
*/
|
|
309
|
+
const KINDS_WITH_VALUE_TEXT: ReadonlySet<string> = new Set([
|
|
310
|
+
// 面々の絵 (ActorNode)
|
|
311
|
+
"actor",
|
|
312
|
+
...KINDS_DRAWN_BY_GENERIC,
|
|
313
|
+
]);
|
|
314
|
+
|
|
315
|
+
/** その node が `value` を文字として描くか (SSOT)。 破綻の扱いは `rendersEyebrow` と同じ */
|
|
316
|
+
export function rendersValue(kind: string | undefined): boolean {
|
|
317
|
+
return KINDS_WITH_VALUE_TEXT.has(kind ?? "");
|
|
318
|
+
}
|
|
319
|
+
|
|
191
320
|
/**
|
|
192
321
|
* `rows[i]` の baseline が node 上端から何 world 下に来るか (SSOT)。
|
|
193
322
|
*
|