@cardenelabs/cdl 0.82.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 +874 -1
- package/dist/index.cjs +882 -187
- 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 +882 -187
- 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 +24 -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
package/src/kinds/quadrant.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { JSX } from "react";
|
|
2
2
|
import type { LaidNode } from "../types";
|
|
3
3
|
import { TONE, toneFill } from "../render/tone";
|
|
4
|
-
import { measureTextWidth } from "../layout/text-width";
|
|
4
|
+
import { measureTextWidth, 収める倍率 } from "../layout/text-width";
|
|
5
5
|
import { resolveQuadrantData } from "../render/payload-binding";
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -10,6 +10,15 @@ import { resolveQuadrantData } from "../render/payload-binding";
|
|
|
10
10
|
* layout = 中央十字線で 4 象限に分割、 各象限にほんのり色分け背景 + 象限 label、
|
|
11
11
|
* item は象限内に大きめの card で配置、 軸両端に矢印 + label を配置。
|
|
12
12
|
*/
|
|
13
|
+
/**
|
|
14
|
+
* 畳んだ段に出す文 (#915)。
|
|
15
|
+
*
|
|
16
|
+
* **数を文の中に入れる**。 「ほか」 だけでは何件あるか読めず、落としたのと変わらない。
|
|
17
|
+
*/
|
|
18
|
+
export function 畳んだ文(件数: number): string {
|
|
19
|
+
return `ほか ${件数} 件`;
|
|
20
|
+
}
|
|
21
|
+
|
|
13
22
|
export function QuadrantNode({
|
|
14
23
|
node,
|
|
15
24
|
active,
|
|
@@ -39,8 +48,16 @@ export function QuadrantNode({
|
|
|
39
48
|
const ITEM_FONT = 13;
|
|
40
49
|
const ITEM_PAD_X = 12;
|
|
41
50
|
const ITEM_MIN_W = 48;
|
|
51
|
+
/** 座標で置いた項目の点の半径と、点と名前の間隔 */
|
|
52
|
+
const POINT_R = 5;
|
|
53
|
+
const POINT_GAP = 6;
|
|
54
|
+
/** 項目の補足の字。 名前より 1 段小さくして、読む順を名前から先にする (#931) */
|
|
55
|
+
const SUB_FONT = 10;
|
|
42
56
|
const axisLabelPad = (text: string): number =>
|
|
43
|
-
Math.max(
|
|
57
|
+
Math.max(
|
|
58
|
+
PAD_MIN,
|
|
59
|
+
measureTextWidth(`← ${text}`, { fontSize: AXIS_LABEL_FONT, fontWeight: 700 }) + 12,
|
|
60
|
+
);
|
|
44
61
|
// 名前が長くても図そのものは残す。 上限を置かないと、 名前が箱の幅を超えた時に
|
|
45
62
|
// 描く場所が無くなる (実測 = 幅 640 に 50 文字の名前を左右へ置くと canvasW が -143 になった)。
|
|
46
63
|
// 名前は溢れるが、 読めない図を出すよりは良い。
|
|
@@ -60,11 +77,15 @@ export function QuadrantNode({
|
|
|
60
77
|
const halfW = canvasW / 2;
|
|
61
78
|
const halfH = canvasH / 2;
|
|
62
79
|
|
|
80
|
+
// 位置の決め方は 2 通りで、項目ごとにどちらか片方だけを使う (`QuadrantItemPayload`)。
|
|
81
|
+
// 座標を書いた項目は点として置き、書かなかった項目は今までどおり象限の中に積む
|
|
82
|
+
const 点で置く = data.items.filter((i) => i.at !== undefined);
|
|
83
|
+
const 積む = data.items.filter((i) => i.at === undefined);
|
|
63
84
|
const itemsBy = {
|
|
64
|
-
topLeft:
|
|
65
|
-
topRight:
|
|
66
|
-
bottomLeft:
|
|
67
|
-
bottomRight:
|
|
85
|
+
topLeft: 積む.filter((i) => i.quadrant === "topLeft"),
|
|
86
|
+
topRight: 積む.filter((i) => i.quadrant === "topRight"),
|
|
87
|
+
bottomLeft: 積む.filter((i) => i.quadrant === "bottomLeft"),
|
|
88
|
+
bottomRight: 積む.filter((i) => i.quadrant === "bottomRight"),
|
|
68
89
|
};
|
|
69
90
|
|
|
70
91
|
// 4 象限の淡い背景。 色は共有の表から作る
|
|
@@ -95,10 +116,38 @@ export function QuadrantNode({
|
|
|
95
116
|
strokeWidth={active ? 3 : 1.25}
|
|
96
117
|
/>
|
|
97
118
|
|
|
98
|
-
<rect
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
119
|
+
<rect
|
|
120
|
+
data-cdl-role="quadrant-bg"
|
|
121
|
+
x={PAD_LEFT}
|
|
122
|
+
y={PAD_TOP}
|
|
123
|
+
width={halfW}
|
|
124
|
+
height={halfH}
|
|
125
|
+
{...quadrantColor.topLeft}
|
|
126
|
+
/>
|
|
127
|
+
<rect
|
|
128
|
+
data-cdl-role="quadrant-bg"
|
|
129
|
+
x={cx}
|
|
130
|
+
y={PAD_TOP}
|
|
131
|
+
width={halfW}
|
|
132
|
+
height={halfH}
|
|
133
|
+
{...quadrantColor.topRight}
|
|
134
|
+
/>
|
|
135
|
+
<rect
|
|
136
|
+
data-cdl-role="quadrant-bg"
|
|
137
|
+
x={PAD_LEFT}
|
|
138
|
+
y={cy}
|
|
139
|
+
width={halfW}
|
|
140
|
+
height={halfH}
|
|
141
|
+
{...quadrantColor.bottomLeft}
|
|
142
|
+
/>
|
|
143
|
+
<rect
|
|
144
|
+
data-cdl-role="quadrant-bg"
|
|
145
|
+
x={cx}
|
|
146
|
+
y={cy}
|
|
147
|
+
width={halfW}
|
|
148
|
+
height={halfH}
|
|
149
|
+
{...quadrantColor.bottomRight}
|
|
150
|
+
/>
|
|
102
151
|
|
|
103
152
|
<rect
|
|
104
153
|
data-cdl-role="quadrant-canvas"
|
|
@@ -112,32 +161,102 @@ export function QuadrantNode({
|
|
|
112
161
|
rx={8}
|
|
113
162
|
/>
|
|
114
163
|
|
|
115
|
-
<line
|
|
116
|
-
|
|
164
|
+
<line
|
|
165
|
+
x1={cx}
|
|
166
|
+
y1={PAD_TOP}
|
|
167
|
+
x2={cx}
|
|
168
|
+
y2={PAD_TOP + canvasH}
|
|
169
|
+
stroke="var(--cdl-text-mute, #8a8678)"
|
|
170
|
+
strokeWidth={1.5}
|
|
171
|
+
/>
|
|
172
|
+
<line
|
|
173
|
+
x1={PAD_LEFT}
|
|
174
|
+
y1={cy}
|
|
175
|
+
x2={PAD_LEFT + canvasW}
|
|
176
|
+
y2={cy}
|
|
177
|
+
stroke="var(--cdl-text-mute, #8a8678)"
|
|
178
|
+
strokeWidth={1.5}
|
|
179
|
+
/>
|
|
117
180
|
|
|
118
|
-
<text
|
|
181
|
+
<text
|
|
182
|
+
x={PAD_LEFT + halfW / 2}
|
|
183
|
+
y={PAD_TOP + 22}
|
|
184
|
+
fontSize={13}
|
|
185
|
+
fontWeight={700}
|
|
186
|
+
textAnchor="middle"
|
|
187
|
+
fill={quadrantAccent.topLeft}
|
|
188
|
+
>
|
|
119
189
|
{data.quadrantLabels.topLeft}
|
|
120
190
|
</text>
|
|
121
|
-
<text
|
|
191
|
+
<text
|
|
192
|
+
x={cx + halfW / 2}
|
|
193
|
+
y={PAD_TOP + 22}
|
|
194
|
+
fontSize={13}
|
|
195
|
+
fontWeight={700}
|
|
196
|
+
textAnchor="middle"
|
|
197
|
+
fill={quadrantAccent.topRight}
|
|
198
|
+
>
|
|
122
199
|
{data.quadrantLabels.topRight}
|
|
123
200
|
</text>
|
|
124
|
-
<text
|
|
201
|
+
<text
|
|
202
|
+
x={PAD_LEFT + halfW / 2}
|
|
203
|
+
y={PAD_TOP + canvasH - 12}
|
|
204
|
+
fontSize={13}
|
|
205
|
+
fontWeight={700}
|
|
206
|
+
textAnchor="middle"
|
|
207
|
+
fill={quadrantAccent.bottomLeft}
|
|
208
|
+
>
|
|
125
209
|
{data.quadrantLabels.bottomLeft}
|
|
126
210
|
</text>
|
|
127
|
-
<text
|
|
211
|
+
<text
|
|
212
|
+
x={cx + halfW / 2}
|
|
213
|
+
y={PAD_TOP + canvasH - 12}
|
|
214
|
+
fontSize={13}
|
|
215
|
+
fontWeight={700}
|
|
216
|
+
textAnchor="middle"
|
|
217
|
+
fill={quadrantAccent.bottomRight}
|
|
218
|
+
>
|
|
128
219
|
{data.quadrantLabels.bottomRight}
|
|
129
220
|
</text>
|
|
130
221
|
|
|
131
|
-
<text
|
|
222
|
+
<text
|
|
223
|
+
x={cx}
|
|
224
|
+
y={PAD_TOP - 12}
|
|
225
|
+
fontSize={12}
|
|
226
|
+
fontWeight={700}
|
|
227
|
+
textAnchor="middle"
|
|
228
|
+
fill="var(--cdl-text-dim, #5a6270)"
|
|
229
|
+
>
|
|
132
230
|
↑ {data.yAxis.top}
|
|
133
231
|
</text>
|
|
134
|
-
<text
|
|
232
|
+
<text
|
|
233
|
+
x={cx}
|
|
234
|
+
y={PAD_TOP + canvasH + 24}
|
|
235
|
+
fontSize={12}
|
|
236
|
+
fontWeight={700}
|
|
237
|
+
textAnchor="middle"
|
|
238
|
+
fill="var(--cdl-text-dim, #5a6270)"
|
|
239
|
+
>
|
|
135
240
|
↓ {data.yAxis.bottom}
|
|
136
241
|
</text>
|
|
137
|
-
<text
|
|
242
|
+
<text
|
|
243
|
+
x={PAD_LEFT - 12}
|
|
244
|
+
y={cy + 4}
|
|
245
|
+
fontSize={AXIS_LABEL_FONT}
|
|
246
|
+
fontWeight={700}
|
|
247
|
+
textAnchor="end"
|
|
248
|
+
fill="var(--cdl-text-dim, #5a6270)"
|
|
249
|
+
>
|
|
138
250
|
← {data.xAxis.left}
|
|
139
251
|
</text>
|
|
140
|
-
<text
|
|
252
|
+
<text
|
|
253
|
+
x={PAD_LEFT + canvasW + 12}
|
|
254
|
+
y={cy + 4}
|
|
255
|
+
fontSize={AXIS_LABEL_FONT}
|
|
256
|
+
fontWeight={700}
|
|
257
|
+
textAnchor="start"
|
|
258
|
+
fill="var(--cdl-text-dim, #5a6270)"
|
|
259
|
+
>
|
|
141
260
|
{data.xAxis.right} →
|
|
142
261
|
</text>
|
|
143
262
|
|
|
@@ -164,39 +283,193 @@ export function QuadrantNode({
|
|
|
164
283
|
// 象限の外へ置くより、 何も描かない方が図として正しい。
|
|
165
284
|
const 枠の余地 = qHalfW;
|
|
166
285
|
if (枠の余地 <= 0) return null;
|
|
167
|
-
|
|
286
|
+
/*
|
|
287
|
+
枠の幅は **載せる行の広い方** で決める (#931)。 名前だけで決めると、名前より長い
|
|
288
|
+
補足が枠から出る。 行ごとに字の大きさと太さが違うので、文字列ではなく行の並びで受ける。
|
|
289
|
+
*/
|
|
290
|
+
const 枠の幅 = (行ら: readonly { text: string; size: number; weight: number }[]) =>
|
|
168
291
|
Math.min(
|
|
169
292
|
枠の余地,
|
|
170
|
-
Math.max(
|
|
293
|
+
Math.max(
|
|
294
|
+
ITEM_MIN_W,
|
|
295
|
+
Math.max(
|
|
296
|
+
...行ら.map((r) =>
|
|
297
|
+
measureTextWidth(r.text, { fontSize: r.size, fontWeight: r.weight }),
|
|
298
|
+
),
|
|
299
|
+
) +
|
|
300
|
+
ITEM_PAD_X * 2,
|
|
301
|
+
),
|
|
171
302
|
);
|
|
303
|
+
/** その項目が枠に載せる行。 補足が空なら名前だけ */
|
|
304
|
+
const 行ら = (it: (typeof items)[number]) => {
|
|
305
|
+
const 補足 = (it.subtitle ?? "").trim();
|
|
306
|
+
return [
|
|
307
|
+
{ text: it.title, size: ITEM_FONT, weight: 600 },
|
|
308
|
+
...(補足 === "" ? [] : [{ text: 補足, size: SUB_FONT, weight: 500 }]),
|
|
309
|
+
];
|
|
310
|
+
};
|
|
172
311
|
const qHalfH = halfH - 60;
|
|
173
312
|
const gap = 10;
|
|
174
313
|
const rowH = 34;
|
|
175
314
|
const maxRows = Math.max(1, Math.floor(qHalfH / (rowH + gap)));
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
315
|
+
// 入りきらない件は **落とさずに畳む** (#915)。 落とすと、書いた項目が図に無い理由が
|
|
316
|
+
// 読めず、区画の厚みも実際より薄く出る。 半円ゲージが「その他」 に畳むのと同じ形。
|
|
317
|
+
//
|
|
318
|
+
// 名前を出すのは「置ける段数 - 1」 まで。 置ける段数が 1 の区画では名前を 1 件も
|
|
319
|
+
// 出さず、畳んだ段だけを出す = 名前 1 件と引き換えに残りが消えるより、件数が
|
|
320
|
+
// 正しいほうを採る。
|
|
321
|
+
const 畳む = items.length > maxRows;
|
|
322
|
+
const 名前を出す = 畳む ? items.slice(0, maxRows - 1) : items;
|
|
323
|
+
const 畳んだ件数 = items.length - 名前を出す.length;
|
|
324
|
+
return (
|
|
325
|
+
<g key={`q-${q}`}>
|
|
326
|
+
{名前を出す.map((it, idx) => {
|
|
327
|
+
const py = qyStart + idx * (rowH + gap);
|
|
328
|
+
const 幅 = 枠の幅(行ら(it));
|
|
329
|
+
const 補足 = (it.subtitle ?? "").trim();
|
|
330
|
+
// 枠は象限に収まる幅で頭打ちになるため、長い補足は枠に入らないことがある。
|
|
331
|
+
// 枠から出さずに字を縮める = 隣の項目や軸の名前に重なると両方読めなくなる
|
|
332
|
+
const 補足の字 =
|
|
333
|
+
SUB_FONT *
|
|
334
|
+
収める倍率(
|
|
335
|
+
補足,
|
|
336
|
+
{ fontSize: SUB_FONT, fontWeight: 500 },
|
|
337
|
+
Math.max(0, 幅 - ITEM_PAD_X * 2),
|
|
338
|
+
);
|
|
339
|
+
return (
|
|
340
|
+
<g key={`item-${it.id}`}>
|
|
341
|
+
<rect
|
|
342
|
+
data-cdl-role="quadrant-item"
|
|
343
|
+
data-cdl-unresolved={it.unresolved ? "true" : undefined}
|
|
344
|
+
x={qxStart}
|
|
345
|
+
y={py}
|
|
346
|
+
width={幅}
|
|
347
|
+
height={rowH}
|
|
348
|
+
rx={6}
|
|
349
|
+
fill="var(--cdl-node-fill, #ffffff)"
|
|
350
|
+
stroke={quadrantAccent[q]}
|
|
351
|
+
strokeWidth={1.5}
|
|
352
|
+
/>
|
|
353
|
+
{/* 字間を 0 に固定する。 枠の幅は字幅の見積り (letterSpacing 0 前提) で決めるため、
|
|
354
|
+
外から字間を継承すると見積りより名前が広がって枠から出る (#457 Round 4) */}
|
|
355
|
+
<text
|
|
356
|
+
x={qxStart + ITEM_PAD_X}
|
|
357
|
+
y={補足 === "" ? py + rowH / 2 + 5 : py + rowH / 2 - 2}
|
|
358
|
+
fontSize={ITEM_FONT}
|
|
359
|
+
fontWeight={600}
|
|
360
|
+
letterSpacing={0}
|
|
361
|
+
fill="var(--cdl-text, #1a1f2a)"
|
|
362
|
+
>
|
|
363
|
+
{it.title}
|
|
364
|
+
</text>
|
|
365
|
+
{補足 !== "" && (
|
|
366
|
+
<text
|
|
367
|
+
data-cdl-role="quadrant-item-subtitle"
|
|
368
|
+
x={qxStart + ITEM_PAD_X}
|
|
369
|
+
y={py + rowH / 2 + 11}
|
|
370
|
+
fontSize={補足の字}
|
|
371
|
+
fontWeight={500}
|
|
372
|
+
letterSpacing={0}
|
|
373
|
+
fill="var(--cdl-text-dim, #5a6270)"
|
|
374
|
+
>
|
|
375
|
+
{補足}
|
|
376
|
+
</text>
|
|
377
|
+
)}
|
|
378
|
+
</g>
|
|
379
|
+
);
|
|
380
|
+
})}
|
|
381
|
+
{畳む ? (
|
|
382
|
+
/*
|
|
383
|
+
畳んだ段は名前の段と見た目を分ける。 同じにすると「ほか 3 件」 という名前の
|
|
384
|
+
項目があるように読める。 枠を持たず、点線の下線と薄い字で「続きがある」 ことを表す。
|
|
385
|
+
*/
|
|
386
|
+
<g key={`more-${q}`}>
|
|
387
|
+
<line
|
|
388
|
+
data-cdl-role="quadrant-more-rule"
|
|
389
|
+
x1={qxStart}
|
|
390
|
+
y1={qyStart + 名前を出す.length * (rowH + gap) + rowH / 2 + 9}
|
|
391
|
+
x2={qxStart + 枠の幅([{ text: 畳んだ文(畳んだ件数), size: ITEM_FONT, weight: 600 }])}
|
|
392
|
+
y2={qyStart + 名前を出す.length * (rowH + gap) + rowH / 2 + 9}
|
|
393
|
+
stroke={quadrantAccent[q]}
|
|
394
|
+
strokeWidth={1}
|
|
395
|
+
strokeDasharray="3 3"
|
|
396
|
+
strokeOpacity={0.6}
|
|
397
|
+
/>
|
|
398
|
+
<text
|
|
399
|
+
data-cdl-role="quadrant-more"
|
|
400
|
+
x={qxStart + ITEM_PAD_X}
|
|
401
|
+
y={qyStart + 名前を出す.length * (rowH + gap) + rowH / 2 + 5}
|
|
402
|
+
fontSize={ITEM_FONT}
|
|
403
|
+
fontWeight={600}
|
|
404
|
+
letterSpacing={0}
|
|
405
|
+
fill={quadrantAccent[q]}
|
|
406
|
+
>
|
|
407
|
+
{畳んだ文(畳んだ件数)}
|
|
408
|
+
</text>
|
|
409
|
+
</g>
|
|
410
|
+
) : null}
|
|
411
|
+
</g>
|
|
412
|
+
);
|
|
413
|
+
})}
|
|
414
|
+
|
|
415
|
+
{/*
|
|
416
|
+
座標を書いた項目。 点と名前で描く。
|
|
417
|
+
|
|
418
|
+
名前は点の右に置くが、右半分の項目は左に置く = 右に置くと描画域の外へ出る。
|
|
419
|
+
名前の位置を点との間隔だけで決めると、右端の項目が枠を越える (軸の名前と同じ理屈)。
|
|
420
|
+
*/}
|
|
421
|
+
{点で置く.map((it) => {
|
|
422
|
+
const at = it.at!;
|
|
423
|
+
const px = PAD_LEFT + at.x * canvasW;
|
|
424
|
+
// y は上向き。 SVG は下向きなので 1 から引く (`QuadrantAt`)
|
|
425
|
+
const py = PAD_TOP + (1 - at.y) * canvasH;
|
|
426
|
+
const 右寄り = at.x >= 0.5;
|
|
427
|
+
const 名前のx = px + (右寄り ? -(POINT_R + POINT_GAP) : POINT_R + POINT_GAP);
|
|
428
|
+
const 寄せ = 右寄り ? "end" : "start";
|
|
429
|
+
const 補足 = (it.subtitle ?? "").trim();
|
|
430
|
+
return (
|
|
431
|
+
<g key={`at-${it.id}`} data-cdl-unresolved={it.unresolved ? "true" : undefined}>
|
|
432
|
+
<circle
|
|
433
|
+
data-cdl-role="quadrant-point"
|
|
434
|
+
cx={px}
|
|
435
|
+
cy={py}
|
|
436
|
+
r={POINT_R}
|
|
437
|
+
fill={quadrantAccent[it.quadrant]}
|
|
438
|
+
/>
|
|
439
|
+
<text
|
|
440
|
+
data-cdl-role="quadrant-point-label"
|
|
441
|
+
x={名前のx}
|
|
442
|
+
y={補足 === "" ? py + 4 : py - 1}
|
|
443
|
+
fontSize={ITEM_FONT}
|
|
444
|
+
fontWeight={600}
|
|
445
|
+
letterSpacing={0}
|
|
446
|
+
textAnchor={寄せ}
|
|
447
|
+
fill="var(--cdl-text, #1a1f2a)"
|
|
448
|
+
>
|
|
449
|
+
{it.title}
|
|
450
|
+
</text>
|
|
451
|
+
{/*
|
|
452
|
+
補足は名前の下に置く (#931)。 x と寄せ方を名前と揃えて 2 行を縦に並べる。
|
|
453
|
+
|
|
454
|
+
幅は測らない。 点の横は箱を持たないので収める先が無く、名前と同じ扱いにする
|
|
455
|
+
(名前は溢れても図を残す = この kind が軸の名前について採っている判断と同じ)。
|
|
456
|
+
*/}
|
|
457
|
+
{補足 !== "" && (
|
|
458
|
+
<text
|
|
459
|
+
data-cdl-role="quadrant-point-subtitle"
|
|
460
|
+
x={名前のx}
|
|
461
|
+
y={py + 11}
|
|
462
|
+
fontSize={SUB_FONT}
|
|
463
|
+
fontWeight={500}
|
|
464
|
+
letterSpacing={0}
|
|
465
|
+
textAnchor={寄せ}
|
|
466
|
+
fill="var(--cdl-text-dim, #5a6270)"
|
|
467
|
+
>
|
|
468
|
+
{補足}
|
|
196
469
|
</text>
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
470
|
+
)}
|
|
471
|
+
</g>
|
|
472
|
+
);
|
|
200
473
|
})}
|
|
201
474
|
</g>
|
|
202
475
|
);
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
fitArt,
|
|
5
5
|
labelPlan,
|
|
6
6
|
shapeRegion,
|
|
7
|
-
|
|
7
|
+
幅の倍率,
|
|
8
8
|
打ち消しの倍率,
|
|
9
9
|
絵の中の字の幅,
|
|
10
10
|
type ArtExtent,
|
|
@@ -39,6 +39,8 @@ const BLOCK_W = 240;
|
|
|
39
39
|
const BLOCK_H = 220;
|
|
40
40
|
/** `shape-blockchain-block` の字の指定 (#871 で打ち消しの計算に使うため定数へ出した) */
|
|
41
41
|
const 見出しの字 = 13;
|
|
42
|
+
/** 見出しの帯の右に置く肩書の字 (#941)。 題より小さくして、題の読みを主に保つ */
|
|
43
|
+
const 肩書の字 = 10;
|
|
42
44
|
const 名の字 = 11;
|
|
43
45
|
const 値の字 = 11;
|
|
44
46
|
/** 5 行を並べる間隔と、見出しが乗る帯の高さ。 打ち消しの上限をここから出す (#871) */
|
|
@@ -129,21 +131,6 @@ function blockchainNodeExtent(strokeWidth: number): ArtExtent {
|
|
|
129
131
|
return { 上: 縦, 下: 縦, 左: 横, 右: 横 };
|
|
130
132
|
}
|
|
131
133
|
|
|
132
|
-
/**
|
|
133
|
-
* 名前の行を箱の幅に収める倍率。
|
|
134
|
-
*
|
|
135
|
-
* 字間は字の大きさに追随しないため、 大きさだけを縮めても幅は `字間 x 字数` より狭く
|
|
136
|
-
* ならない。 大きさと字間を同じ倍率で縮めると、 両項が揃って縮むので幅が倍率に比例する。
|
|
137
|
-
*
|
|
138
|
-
* 収まっている間は 1 を返す = 箱が広い時の見え方を変えない。
|
|
139
|
-
*/
|
|
140
|
-
function 幅の倍率(text: string, size: number, letterSpacing: number, 幅: number): number {
|
|
141
|
-
if (!(幅 > 0) || !(size > 0)) return 1;
|
|
142
|
-
// 階級表を二重に持つと、 片方を直した時にもう片方だけ古くなる。
|
|
143
|
-
const 実幅 = textWidth(text, size, letterSpacing);
|
|
144
|
-
return 実幅 > 幅 ? 幅 / 実幅 : 1;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
134
|
/**
|
|
148
135
|
* 名前の塊を描く。 位置と大きさは `labelPlan` が決める (箱の下端から積み上げる、 #433)。
|
|
149
136
|
*/
|
|
@@ -373,8 +360,26 @@ export function ShapeBlockchainBlockNode({ node, active, lang }: ShapeProps): JS
|
|
|
373
360
|
};
|
|
374
361
|
});
|
|
375
362
|
|
|
363
|
+
/*
|
|
364
|
+
* 見出しの帯の右に肩書を置く (#941)。 書かなければ 1 文字も描かない = 帯は題と丸だけの
|
|
365
|
+
* 今までの見え方に戻る。 課題の札と違って控えを持たないのは、連なりの塊の肩書が
|
|
366
|
+
* 「その塊が何か」 を言う語で、控えを置くと書いてもいない分類が図に出るため。
|
|
367
|
+
*
|
|
368
|
+
* **題の使える幅から肩書の分を引く**。 引かないと長い題と肩書が帯の中で重なる。
|
|
369
|
+
* 肩書の幅は帯の残りの 3 分の 1 を上限にする = 上限を置かないと、長い肩書が帯を占めて
|
|
370
|
+
* 題が 1 文字も読めなくなる。
|
|
371
|
+
*/
|
|
372
|
+
const 肩書 = 渡された(node.eyebrow);
|
|
376
373
|
// 見出しは右上の丸 (半径 5.5、 中心が bw - 16) に掛からないところまで
|
|
377
|
-
const
|
|
374
|
+
const 帯の残り = bw - 14 - 30;
|
|
375
|
+
const 肩書の隙間 = 8;
|
|
376
|
+
// 上限は帯の残りの半分。 長い肩書は **切らずに字を縮める** = 分類を表す語なので、
|
|
377
|
+
// 末尾を落とすと何の分類か読めなくなる (`台帳…`)。 縮めた後の幅は上限ちょうどになる
|
|
378
|
+
const 肩書の上限 = (帯の残り - 肩書の隙間) / 2;
|
|
379
|
+
const 肩書の倍率 = 肩書 === "" ? 1 : 幅の倍率(肩書, 肩書の字, 0, 肩書の上限);
|
|
380
|
+
const 肩書の指定 = 肩書の字 * 肩書の倍率;
|
|
381
|
+
const 肩書の幅 = 肩書 === "" ? 0 : 字の幅(肩書, 肩書の指定);
|
|
382
|
+
const 見出しの幅 = 帯の残り - (肩書 === "" ? 0 : 肩書の幅 + 肩書の隙間);
|
|
378
383
|
/*
|
|
379
384
|
* 絵を **倍率を受け取ってから** 組む (#871)。 縮んだ分を字の指定で打ち消す。
|
|
380
385
|
*
|
|
@@ -392,6 +397,10 @@ export function ShapeBlockchainBlockNode({ node, active, lang }: ShapeProps): JS
|
|
|
392
397
|
*/
|
|
393
398
|
const 打ち消す字ら: 打ち消す字[] = [
|
|
394
399
|
{ text: 見出し, size: 見出しの字, 帯: { 幅: 見出しの幅, 高さ: 見出しの帯 } },
|
|
400
|
+
// 肩書も帯に入れる = 入れないと打ち消しの倍率が肩書を見ずに決まり、帯から横へ出る
|
|
401
|
+
...(肩書 === ""
|
|
402
|
+
? []
|
|
403
|
+
: [{ text: 肩書, size: 肩書の指定, 帯: { 幅: 肩書の幅, 高さ: 見出しの帯 } }]),
|
|
395
404
|
...fields.flatMap((f): 打ち消す字[] => [
|
|
396
405
|
{ text: f.label, size: 名の字, 帯: { 幅: bw - 28, 高さ: 行の間隔 } },
|
|
397
406
|
{
|
|
@@ -404,6 +413,7 @@ export function ShapeBlockchainBlockNode({ node, active, lang }: ShapeProps): JS
|
|
|
404
413
|
const art = (倍率: number): JSX.Element => {
|
|
405
414
|
const 倍 = 打ち消しの倍率(倍率, 打ち消す字ら);
|
|
406
415
|
const 題の大きさ = 見出しの字 * 倍;
|
|
416
|
+
const 肩書の大きさ = 肩書の指定 * 倍;
|
|
407
417
|
const 名の大きさ = 名の字 * 倍;
|
|
408
418
|
const 値の大きさ = 値の字 * 倍;
|
|
409
419
|
return (
|
|
@@ -416,6 +426,22 @@ export function ShapeBlockchainBlockNode({ node, active, lang }: ShapeProps): JS
|
|
|
416
426
|
<text data-cdl-role="blockchain-block-title" data-cdl-counter-scaled="1" x={x + 14} y={y + 22} fontSize={題の大きさ} fontWeight={700} fontFamily="ui-monospace, SFMono-Regular, Menlo, monospace" fill="#ffffff">
|
|
417
427
|
{幅で切る(見出し, 題の大きさ, 見出しの幅, 字の幅)}
|
|
418
428
|
</text>
|
|
429
|
+
{肩書 !== "" && (
|
|
430
|
+
<text
|
|
431
|
+
data-cdl-role="blockchain-block-eyebrow"
|
|
432
|
+
data-cdl-counter-scaled="1"
|
|
433
|
+
x={x + bw - 30}
|
|
434
|
+
y={y + 21}
|
|
435
|
+
fontSize={肩書の大きさ}
|
|
436
|
+
fontWeight={600}
|
|
437
|
+
textAnchor="end"
|
|
438
|
+
fontFamily="ui-monospace, SFMono-Regular, Menlo, monospace"
|
|
439
|
+
fill="#ffffff"
|
|
440
|
+
fillOpacity={0.78}
|
|
441
|
+
>
|
|
442
|
+
{肩書}
|
|
443
|
+
</text>
|
|
444
|
+
)}
|
|
419
445
|
<circle cx={x + bw - 16} cy={y + 16} r={5.5} fill="#7cd88f" stroke="#ffffff" strokeWidth={1.5} />
|
|
420
446
|
{/* fields */}
|
|
421
447
|
{fields.map((f, i) => (
|
|
@@ -499,7 +525,13 @@ export function ShapeBlockchainNode({ node, active }: ShapeProps): JSX.Element {
|
|
|
499
525
|
<text data-cdl-counter-scaled="0" x={bx + blockW / 2} y={cy + 8} fontSize={14} fontWeight={700} textAnchor="middle" fill={isCurrent ? "#ffffff" : "var(--cdl-tone-accent, #2d6a8f)"} fontFamily="Georgia, serif">
|
|
500
526
|
#
|
|
501
527
|
</text>
|
|
502
|
-
|
|
528
|
+
{/*
|
|
529
|
+
末尾の `…` は **書いてあるもの** で、engine が幅に入らず切った印ではない (#917)。
|
|
530
|
+
描いた結果を読む側からは両者が同じ形に見えるため、書いてある方に印を付けて
|
|
531
|
+
見分けられるようにする。 印を付け忘れると「切れた名前」 として報告されるので、
|
|
532
|
+
間違えた時は調べれば分かる側に倒れる。
|
|
533
|
+
*/}
|
|
534
|
+
<text data-cdl-ellipsis="written" data-cdl-counter-scaled="1" x={bx + blockW / 2} y={cy + 22} fontSize={CHAIN_HASH_SIZE * 倍} fontFamily="ui-monospace, SFMono-Regular, Menlo, monospace" textAnchor="middle" fill={isCurrent ? "#ffffff" : "var(--cdl-text-dim, #5a6270)"} opacity={0.9}>
|
|
503
535
|
0x…
|
|
504
536
|
</text>
|
|
505
537
|
</g>
|
|
@@ -349,13 +349,36 @@ export function ShapeWarehouseNode({ node, active, lang }: ShapeProps): JSX.Elem
|
|
|
349
349
|
}
|
|
350
350
|
|
|
351
351
|
/** shape-online-shop = O-A 大 shopping cart (縦長 260x320、 中身なし + badge なし = カート単体 + 車輪 + ハンドル + 籠 grid) */
|
|
352
|
+
/**
|
|
353
|
+
* 買い物かごの絵の、中心から一番外まで出る距離 (#921)。
|
|
354
|
+
*
|
|
355
|
+
* 絵の中と、絵を中心へ置く計算の両方がこの値を読む。 別々に持つと片方だけ古くなり、
|
|
356
|
+
* ハンドルを伸ばした時に絵だけがずれる。
|
|
357
|
+
*/
|
|
358
|
+
/** ハンドルの握り (丸の中心) が中心から左へ出る距離 */
|
|
359
|
+
const HANDLE_GRIP_X = 118;
|
|
360
|
+
/** ハンドルの握りの丸の半径 */
|
|
361
|
+
const HANDLE_GRIP_R = 4;
|
|
362
|
+
/** 影の楕円が中心から左右へ出る距離。 絵の中で一番右まで来る部品 */
|
|
363
|
+
const CART_SHADOW_RX = 80;
|
|
364
|
+
|
|
352
365
|
export function ShapeOnlineShopNode({ node, active }: ShapeProps): JSX.Element {
|
|
353
366
|
const frame = commonFrame(active);
|
|
354
367
|
const r = shapeRegion(node);
|
|
355
|
-
const cx = r.cx;
|
|
356
368
|
const cy = r.cy + 10;
|
|
357
369
|
const scale = Math.min(r.availW / 260, r.availH / 320);
|
|
358
370
|
const s = scale * 0.9;
|
|
371
|
+
/*
|
|
372
|
+
* 絵の横の広がりは左右で違う (#921)。 ハンドルは左へ 122 出るのに、一番右まで来る影は
|
|
373
|
+
* 80 しか出ない。 `r.cx` をそのまま絵の中心に使うと、描いたものの中心が
|
|
374
|
+
* `(122 - 80) / 2 = 21` だけ左へずれる (実測 = 節の中心 220 に対し絵の中心 205)。
|
|
375
|
+
*
|
|
376
|
+
* **数を直に書かず、絵が使っている値から導く**。 ハンドルや影の大きさを変えた時に、
|
|
377
|
+
* 中心の計算だけが古くなるのを防ぐ。
|
|
378
|
+
*/
|
|
379
|
+
const 左へ出る = HANDLE_GRIP_X + HANDLE_GRIP_R;
|
|
380
|
+
const 右へ出る = CART_SHADOW_RX;
|
|
381
|
+
const cx = r.cx + ((左へ出る - 右へ出る) / 2) * s;
|
|
359
382
|
const accent = "var(--cdl-tone-accent, #2d6a8f)";
|
|
360
383
|
const accentDark = "#1a4a68";
|
|
361
384
|
const accentDarker = "#0f3450";
|
|
@@ -372,11 +395,11 @@ export function ShapeOnlineShopNode({ node, active }: ShapeProps): JSX.Element {
|
|
|
372
395
|
return (
|
|
373
396
|
<g>
|
|
374
397
|
{/* drop shadow */}
|
|
375
|
-
<ellipse cx={cx} cy={wheelY + 22 * s} rx={
|
|
398
|
+
<ellipse cx={cx} cy={wheelY + 22 * s} rx={CART_SHADOW_RX * s} ry={5 * s} fill={frame.stroke} opacity={0.25} stroke="none" />
|
|
376
399
|
{/* ハンドル (左上から下降) */}
|
|
377
|
-
<path d={`M ${cx -
|
|
400
|
+
<path d={`M ${cx - HANDLE_GRIP_X * s} ${basketTopY - 60 * s} L ${cx - 90 * s} ${basketTopY - 60 * s} L ${cx - 80 * s} ${basketTopY - 30 * s} L ${basketTopL} ${basketTopY}`} fill="none" stroke={accent} strokeWidth={6 * s} strokeLinecap="round" strokeLinejoin="round" />
|
|
378
401
|
{/* ハンドル grip 端 (円) */}
|
|
379
|
-
<circle cx={cx -
|
|
402
|
+
<circle cx={cx - HANDLE_GRIP_X * s} cy={basketTopY - 60 * s} r={HANDLE_GRIP_R * s} fill={accent} stroke={accentDark} strokeWidth={1.5} />
|
|
380
403
|
{/* 籠本体 (台形) */}
|
|
381
404
|
<path
|
|
382
405
|
data-cdl-role="node-body"
|