@cardenelabs/cdl 0.42.0 → 0.44.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 +74 -1
- package/dist/index.cjs +40 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +40 -16
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +40 -16
- package/dist/react.cjs.map +1 -1
- package/dist/react.js +40 -16
- package/dist/react.js.map +1 -1
- package/package.json +1 -1
- package/src/layout/lanes.ts +48 -11
- package/src/visual-validate.ts +54 -16
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cardenelabs/cdl",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.44.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",
|
package/src/layout/lanes.ts
CHANGED
|
@@ -219,7 +219,20 @@ export function expandLaneGapsForEdgeLabels(
|
|
|
219
219
|
// 修正 = edge が跨ぐ全ての隣接 lane 対に対して、 その edge の labelBoxW を要求する。
|
|
220
220
|
// label が span 内のどの対に置かれても収まることを保証する (routing 前なので
|
|
221
221
|
// 実際の label x は未定、 保守的に全対へ課す)。
|
|
222
|
-
|
|
222
|
+
//
|
|
223
|
+
// **要求は 2 種類に分ける** (`#789`)。 起点にできる位置が違うため。
|
|
224
|
+
//
|
|
225
|
+
// | 辺の形 | label が入りうる場所 | 起点 |
|
|
226
|
+
// |---|---|---|
|
|
227
|
+
// | 隣り合う 2 lane を発着 | 発着の 2 lane の内側と その間 | node の縁 |
|
|
228
|
+
// | 2 対以上を跨ぐ | 途中の lane の内側にも入りうる | lane の縁 |
|
|
229
|
+
//
|
|
230
|
+
// Axis 64 (`lane-border-clearance`) は **発着以外の lane** の border を貫くことだけを見る
|
|
231
|
+
// (`allowedLanes`)。 隣り合う辺の label が入るのは発着の 2 lane に閉じるので判定の対象外に
|
|
232
|
+
// なり、 node の縁を起点にしても新しい違反にならない。 跨ぐ辺は途中の lane を貫きうるので
|
|
233
|
+
// lane の縁のまま残す。
|
|
234
|
+
const requiredBoxWAdjacent = new Array<number>(Math.max(0, sortedByX.length - 1)).fill(0);
|
|
235
|
+
const requiredBoxWSpanning = new Array<number>(Math.max(0, sortedByX.length - 1)).fill(0);
|
|
223
236
|
for (const edge of edges) {
|
|
224
237
|
const fromLane = nodeById.get(edge.from)?.lane;
|
|
225
238
|
const toLane = nodeById.get(edge.to)?.lane;
|
|
@@ -231,11 +244,27 @@ export function expandLaneGapsForEdgeLabels(
|
|
|
231
244
|
if (labelBoxW === 0) continue;
|
|
232
245
|
const lo = Math.min(fi, ti);
|
|
233
246
|
const hi = Math.max(fi, ti);
|
|
247
|
+
const 要求 = hi - lo === 1 ? requiredBoxWAdjacent : requiredBoxWSpanning;
|
|
234
248
|
for (let pair = lo; pair < hi; pair++) {
|
|
235
|
-
if (labelBoxW >
|
|
249
|
+
if (labelBoxW > 要求[pair]!) 要求[pair] = labelBoxW;
|
|
236
250
|
}
|
|
237
251
|
}
|
|
238
252
|
|
|
253
|
+
/**
|
|
254
|
+
* lane の中で最も広い node の半幅。 node を 1 つも持たない lane は 0。
|
|
255
|
+
*
|
|
256
|
+
* 最大で取るのは、 一番広い node の縁から数えないと label がその node に被るため。
|
|
257
|
+
*
|
|
258
|
+
* **0 になる lane は `requiredBoxWAdjacent` の分岐に入らない**。 隣り合う要求が立つのは
|
|
259
|
+
* 発着の node が両 lane に居る edge がある時だけで、 その時どちらの lane も node を持つ。
|
|
260
|
+
* 使う側で 0 を除ける必要はない。
|
|
261
|
+
*/
|
|
262
|
+
const 箱の半幅 = new Map<string, number>();
|
|
263
|
+
for (const n of nodes) {
|
|
264
|
+
const 今 = 箱の半幅.get(n.lane) ?? 0;
|
|
265
|
+
if (n.w / 2 > 今) 箱の半幅.set(n.lane, n.w / 2);
|
|
266
|
+
}
|
|
267
|
+
|
|
239
268
|
// 各 隣接対が必要とする pitch (lane 中心間距離) を求め、 その最大値に全対を揃える。
|
|
240
269
|
//
|
|
241
270
|
// pitch を対ごとではなく全体で揃える理由 (Issue #202)。
|
|
@@ -250,26 +279,34 @@ export function expandLaneGapsForEdgeLabels(
|
|
|
250
279
|
for (let i = 0; i < sortedByX.length - 1; i++) {
|
|
251
280
|
const lane = sortedByX[i]!;
|
|
252
281
|
const rightLane = sortedByX[i + 1]!;
|
|
253
|
-
const
|
|
282
|
+
const laneHalfSum = lane.width / 2 + rightLane.width / 2;
|
|
283
|
+
const nodeHalfSum = (箱の半幅.get(lane.id) ?? 0) + (箱の半幅.get(rightLane.id) ?? 0);
|
|
254
284
|
const currentPitch = rightLane.x + rightLane.width / 2 - (lane.x + lane.width / 2);
|
|
255
|
-
const
|
|
285
|
+
const 隣 = requiredBoxWAdjacent[i]!;
|
|
286
|
+
const 跨 = requiredBoxWSpanning[i]!;
|
|
256
287
|
// edge が跨がない対は label 収納の要求がない。 下限を課さず現状の pitch を維持する。
|
|
257
288
|
// author が意図的に lane を重ねた図 (resolveOverlaps が y 軸 shift で解消する edge case) を
|
|
258
289
|
// engine が勝手に引き離さないため。
|
|
259
|
-
if (
|
|
290
|
+
if (隣 === 0 && 跨 === 0) {
|
|
260
291
|
requiredPitchByPair.push(currentPitch);
|
|
261
292
|
continue;
|
|
262
293
|
}
|
|
263
|
-
|
|
264
|
-
|
|
294
|
+
// 上乗せ (node と label の余白 + 経路の突き出し) は どちらの起点でも同じ。
|
|
295
|
+
const 上乗せ = CLEARANCE_NODE_LABEL * 2 + EDGE_STUB_OUT * 2;
|
|
296
|
+
const 隣の要求 = 隣 === 0 ? 0 : 隣 + 上乗せ + nodeHalfSum;
|
|
297
|
+
const 跨の要求 = 跨 === 0 ? 0 : 跨 + 上乗せ + laneHalfSum;
|
|
298
|
+
requiredPitchByPair.push(Math.max(currentPitch, 隣の要求, 跨の要求));
|
|
265
299
|
}
|
|
266
300
|
// 整数に切り上げる。 pitch は label 幅から決まり、 label 幅は 1 文字あたりの送り幅
|
|
267
301
|
// (1.08 em 等) × font size で出すため小数になる。 その小数がそのまま lane の x に乗り、
|
|
268
|
-
// node 中心 → edge label の x へ順に伝わって、
|
|
269
|
-
//
|
|
302
|
+
// node 中心 → edge label の x へ順に伝わって、 図全体の座標が非整数になる。
|
|
303
|
+
//
|
|
304
|
+
// 元の根拠は「非整数座標は SVG が subpixel で描くので輪郭がにじむ」 だったが、 にじみは
|
|
305
|
+
// device pixel で起きるのに world 座標を見ていたため、 それを測っていた軸 33 は `#783` で
|
|
306
|
+
// 畳んだ。 切り上げ自体は残す = 座標が整数に揃うと図全体の値が読みやすく、 外すと 444 枚
|
|
307
|
+
// すべての lane の x が動く一方で得るものが無い。
|
|
270
308
|
//
|
|
271
|
-
//
|
|
272
|
-
// label 収納に必要な幅は必ず確保される (切り捨てると label がはみ出す)。
|
|
309
|
+
// 切り上げなので label 収納に必要な幅は必ず確保される (切り捨てると label がはみ出す)。
|
|
273
310
|
const targetPitch = Math.ceil(Math.max(...requiredPitchByPair));
|
|
274
311
|
|
|
275
312
|
// 先頭 lane の x を固定し (viewBox 原点維持)、 以降を targetPitch 間隔で再配置する。
|
package/src/visual-validate.ts
CHANGED
|
@@ -3062,22 +3062,50 @@ function runAxes(
|
|
|
3062
3062
|
// 縦横比 1.4 なので 1 度も拾われず、 縦横比が最大の図 (比 8.0、 幅 1535 world) は 15.8px で
|
|
3063
3063
|
// 十分読めた。 文字が小さい上位 20 枚のうち縦横比が拾ったのは 5 枚だけ (`#785`)。
|
|
3064
3064
|
//
|
|
3065
|
-
// 幅の **下限** は見ない。
|
|
3065
|
+
// 幅の **下限** は見ない。 寸法が小さいほど倍率が上がって読みやすくなるので、
|
|
3066
3066
|
// 「幅が小さい = 識別不能」 は成り立たない。 以前 400 world 未満を warn にしていた頃に
|
|
3067
3067
|
// 発火した 17 件は全て単体部品の図 (271-390 world) で、 幅 375px の携帯では 1.0-1.38 倍に
|
|
3068
3068
|
// 拡大される最も読みやすい部類だった。
|
|
3069
3069
|
//
|
|
3070
|
+
// `#785` は幅だけを見ていた。 図は `preserveAspectRatio="xMidYMid meet"` で器に収まるよう
|
|
3071
|
+
// 中で縮むため、 **横が余っていても縦が足りなければ縦が倍率を決める**。 下流の見本帳を
|
|
3072
|
+
// 画面で測ると 28 回のうち 27 回は縦が効いており、 幅から出す見込みは縦が長い図で
|
|
3073
|
+
// 2.4-2.5 倍に外れていた (実測 = 1255×1700 の図が見込み 19.3px に対し実測 8.2px、
|
|
3074
|
+
// 875×1256 の図が 27.7px に対し 11.0px)。 順位も逆を向く = 2190×2904 の図は見込みで
|
|
3075
|
+
// 14 枚中 9 位だが実測は最下位だった。 `#793` で倍率の下限に置き換えた。
|
|
3076
|
+
//
|
|
3070
3077
|
// 描画不能な寸法 (0 / 負値 / 非有限) は別に拾う。 倍率は 0 幅で無限大になり、 負値で
|
|
3071
3078
|
// 符号が反転して比較が素通りするため、 割る前に弾く。
|
|
3072
3079
|
// ───────────────────────────────────────────────────────────
|
|
3073
3080
|
//
|
|
3074
3081
|
/**
|
|
3075
|
-
*
|
|
3082
|
+
* 判定に使う器の幅 (px)。 下流の見本帳の拡大表示を画面で測った値から導く。
|
|
3083
|
+
*
|
|
3084
|
+
* 拡大表示の枠は `min(1200px, 94vw)` で、 中身は 1px の縁と 24px の余白の内側に描かれる。
|
|
3085
|
+
* 画面が 1280px 以上なら 1200 が効くので、 そこから縁 2 枚と余白 2 枚を引く。
|
|
3086
|
+
* 得られる 1150px は画面での実測と一致する。
|
|
3076
3087
|
*
|
|
3077
|
-
*
|
|
3088
|
+
* **実際の器は呼び出し側が決める**。 engine はそれを知らないので、 判定の基準として
|
|
3078
3089
|
* 1 つ選ぶ。 選んだ値そのものが閾値の意味を決めるため、 定数として出す。
|
|
3079
3090
|
*/
|
|
3080
|
-
const
|
|
3091
|
+
const RESPONSIVE_REF_FRAME_W_PX = 1200;
|
|
3092
|
+
const RESPONSIVE_REF_FRAME_BORDER_PX = 1;
|
|
3093
|
+
const RESPONSIVE_REF_FRAME_PAD_PX = 24;
|
|
3094
|
+
const RESPONSIVE_REF_CONTAINER_W_PX =
|
|
3095
|
+
RESPONSIVE_REF_FRAME_W_PX - RESPONSIVE_REF_FRAME_BORDER_PX * 2 - RESPONSIVE_REF_FRAME_PAD_PX * 2;
|
|
3096
|
+
/**
|
|
3097
|
+
* 判定に使う器の高さ (px)。 同じ拡大表示の高さの頭打ち (`90vh − 180px`) を、
|
|
3098
|
+
* 基準の画面の高さ 900px で評価した値。
|
|
3099
|
+
*
|
|
3100
|
+
* **高さを見ないと判定にならない** (`#793`)。 図は `preserveAspectRatio="xMidYMid meet"` で
|
|
3101
|
+
* 器に収まるよう中で縮むため、 横が余っていても縦が足りなければ縦が倍率を決める。
|
|
3102
|
+
* 画面での実測 28 回のうち 27 回は縦が効いていた。
|
|
3103
|
+
*/
|
|
3104
|
+
const RESPONSIVE_REF_VIEWPORT_H_PX = 900;
|
|
3105
|
+
const RESPONSIVE_REF_FRAME_H_RATIO = 0.9;
|
|
3106
|
+
const RESPONSIVE_REF_CHROME_H_PX = 180;
|
|
3107
|
+
const RESPONSIVE_REF_CONTAINER_H_PX =
|
|
3108
|
+
RESPONSIVE_REF_VIEWPORT_H_PX * RESPONSIVE_REF_FRAME_H_RATIO - RESPONSIVE_REF_CHROME_H_PX;
|
|
3081
3109
|
/**
|
|
3082
3110
|
* 画面上で読めると見なす文字の下限 (px)。
|
|
3083
3111
|
*
|
|
@@ -3093,9 +3121,6 @@ function runAxes(
|
|
|
3093
3121
|
* ほぼ全ての図が境を割る。 最も多く出る文字で見る。
|
|
3094
3122
|
*/
|
|
3095
3123
|
const RESPONSIVE_BASE_FONT_WORLD = 22;
|
|
3096
|
-
/** 上の 3 つから導く viewBox width の上限。 数を直接書かない。 */
|
|
3097
|
-
const RESPONSIVE_MAX_VIEWBOX_W =
|
|
3098
|
-
(RESPONSIVE_BASE_FONT_WORLD * RESPONSIVE_REF_CONTAINER_PX) / RESPONSIVE_MIN_ON_SCREEN_FONT_PX;
|
|
3099
3124
|
const vbResp = laid.viewBox;
|
|
3100
3125
|
if (
|
|
3101
3126
|
!(vbResp.w > 0) ||
|
|
@@ -3108,15 +3133,28 @@ function runAxes(
|
|
|
3108
3133
|
`viewBox 寸法 ${vbResp.w}×${vbResp.h} が描画不能 (正の有限値である必要がある)`,
|
|
3109
3134
|
"warn",
|
|
3110
3135
|
);
|
|
3111
|
-
} else
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3136
|
+
} else {
|
|
3137
|
+
// 器に収めた時に効く倍率。 `xMidYMid meet` は縦横の小さい方に合わせる。
|
|
3138
|
+
const 横の倍率 = RESPONSIVE_REF_CONTAINER_W_PX / vbResp.w;
|
|
3139
|
+
const 縦の倍率 = RESPONSIVE_REF_CONTAINER_H_PX / vbResp.h;
|
|
3140
|
+
const 倍率 = Math.min(横の倍率, 縦の倍率);
|
|
3141
|
+
const 画面上の字 = RESPONSIVE_BASE_FONT_WORLD * 倍率;
|
|
3142
|
+
if (画面上の字 < RESPONSIVE_MIN_ON_SCREEN_FONT_PX) {
|
|
3143
|
+
// **効いた辺を出す**。 出さないと、 幅を削っても直らない図で書き手が幅を削り続ける
|
|
3144
|
+
// (実測 = 1255×1700 の図は幅を半分にしても縦が効いたまま)。
|
|
3145
|
+
const 効く辺 = 横の倍率 <= 縦の倍率 ? "横" : "縦";
|
|
3146
|
+
const 収まる寸法 =
|
|
3147
|
+
効く辺 === "横"
|
|
3148
|
+
? `幅 ${((RESPONSIVE_REF_CONTAINER_W_PX * RESPONSIVE_BASE_FONT_WORLD) / RESPONSIVE_MIN_ON_SCREEN_FONT_PX).toFixed(0)} 以下`
|
|
3149
|
+
: `高さ ${((RESPONSIVE_REF_CONTAINER_H_PX * RESPONSIVE_BASE_FONT_WORLD) / RESPONSIVE_MIN_ON_SCREEN_FONT_PX).toFixed(0)} 以下`;
|
|
3150
|
+
push(
|
|
3151
|
+
"responsive-viewport",
|
|
3152
|
+
`viewBox ${vbResp.w}×${vbResp.h} は器 ${RESPONSIVE_REF_CONTAINER_W_PX}×${RESPONSIVE_REF_CONTAINER_H_PX}px で ` +
|
|
3153
|
+
`${倍率.toFixed(3)} 倍に縮み、 箱の題が ${画面上の字.toFixed(1)}px になる ` +
|
|
3154
|
+
`(下限 ${RESPONSIVE_MIN_ON_SCREEN_FONT_PX}px、 倍率を決めたのは ${効く辺}、 ${収まる寸法}にすると届く)`,
|
|
3155
|
+
"warn",
|
|
3156
|
+
);
|
|
3157
|
+
}
|
|
3120
3158
|
}
|
|
3121
3159
|
|
|
3122
3160
|
// ───────────────────────────────────────────────────────────
|