@cardenelabs/cdl 0.65.0 → 0.67.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 +84 -1
- package/dist/index.cjs +64 -50
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +64 -50
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +64 -50
- package/dist/react.cjs.map +1 -1
- package/dist/react.js +64 -50
- package/dist/react.js.map +1 -1
- package/package.json +1 -1
- package/src/kinds/gantt.tsx +57 -24
- package/src/layout/edges.ts +60 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cardenelabs/cdl",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.67.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/kinds/gantt.tsx
CHANGED
|
@@ -2,7 +2,7 @@ import type { JSX } from "react";
|
|
|
2
2
|
import type { LaidNode, Tone } from "../types";
|
|
3
3
|
import { TONE } from "../render/tone";
|
|
4
4
|
import { resolveGanttData } from "../render/payload-binding";
|
|
5
|
-
import {
|
|
5
|
+
import { 進みを畳む, 段の伸び } from "./draw-progress";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* gantt-timeline kind ... 1 node に task 配列 (ganttData) を保持し、 帯状 timeline を SVG 描画する。
|
|
@@ -181,6 +181,21 @@ export function GanttNode({
|
|
|
181
181
|
*/
|
|
182
182
|
const 伸び = 進みを畳む(drawing, progress);
|
|
183
183
|
|
|
184
|
+
/**
|
|
185
|
+
* その行の帯がどこまで伸びたか (0..1、 cdl#873)。
|
|
186
|
+
*
|
|
187
|
+
* **上の行から順に 1 本ずつ伸ばす**。 全部を同じ倍率で伸ばすと、「前の工程が終わってから
|
|
188
|
+
* 次が始まる」 を示す図なのに 4 本が横並びで一斉に伸びる。 依存の向きは上から下なので、
|
|
189
|
+
* 行番号がそのまま順序になる。
|
|
190
|
+
*
|
|
191
|
+
* 区切り方は発想の図と木の図が使う `段の伸び` と同じ。 進みを行数で等分し、その区間に
|
|
192
|
+
* 入ってから出るまでを 0→1 で返す。 **行が増えるほど 1 本あたりは速くなる** = 図全体の
|
|
193
|
+
* 時間は段の `duration` が決めるので、本数で割る以外に配りようがない。
|
|
194
|
+
*
|
|
195
|
+
* 描く指定が無い時は `伸び` が 1 なので、どの行も 1 を返す (今までと同じ)。
|
|
196
|
+
*/
|
|
197
|
+
const 行の伸び = (row: number): number => 段の伸び(伸び, row + 1, rowCount);
|
|
198
|
+
|
|
184
199
|
const taskById = new Map(整えた.map((t) => [t.id, t]));
|
|
185
200
|
const rowOf = new Map(整えた.map((t, i) => [t.id, i]));
|
|
186
201
|
|
|
@@ -257,30 +272,44 @@ export function GanttNode({
|
|
|
257
272
|
// 目盛りの幅の半分以上になる = **この分岐に届く入力を今は作れない** (変異試験で確認)。
|
|
258
273
|
// 余白の決め方を変えた時に負の幅を出さないために残す
|
|
259
274
|
const bw = Math.max(0, 帯の右(t.endIdx) - bx);
|
|
275
|
+
const この行 = 行の伸び(row);
|
|
260
276
|
return (
|
|
261
|
-
<g
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
<
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
277
|
+
<g key={`bar-${t.id}`}>
|
|
278
|
+
{/*
|
|
279
|
+
倍率は **帯の四角だけ** に掛ける (cdl#873)。 以前は担当者の字も同じ包みに
|
|
280
|
+
入れていたため、伸びている間ずっと字が横に潰れていた (実測 = 進み 0.6 で
|
|
281
|
+
字の横幅が 6 割)。 字は倍率のかからない外側に置く。
|
|
282
|
+
*/}
|
|
283
|
+
<g
|
|
284
|
+
{...(drawing
|
|
285
|
+
? { transform: `translate(${bx} 0) scale(${この行} 1) translate(${-bx} 0)` }
|
|
286
|
+
: {})}
|
|
287
|
+
>
|
|
288
|
+
<rect
|
|
289
|
+
data-cdl-role="gantt-bar"
|
|
290
|
+
data-cdl-unresolved={t.unresolved ? "true" : undefined}
|
|
291
|
+
data-cdl-clipped={"clipped" in t && t.clipped ? "true" : undefined}
|
|
292
|
+
x={bx}
|
|
293
|
+
y={by}
|
|
294
|
+
width={bw}
|
|
295
|
+
height={rowH}
|
|
296
|
+
rx={4}
|
|
297
|
+
fill={toneColor(t.tone)}
|
|
298
|
+
fillOpacity={0.9}
|
|
299
|
+
/>
|
|
300
|
+
</g>
|
|
301
|
+
{/*
|
|
302
|
+
担当者の字は帯の真ん中に置き、**その帯が伸び切ってから出す**。
|
|
303
|
+
薄く出しながら伸ばす案は採らない = 中央は伸び切った後の位置なので、
|
|
304
|
+
伸びている間は帯の無い所に字が浮く。
|
|
305
|
+
*/}
|
|
306
|
+
{t.owner && rowH >= 22 && この行 >= 1 && (
|
|
280
307
|
<text
|
|
281
|
-
|
|
308
|
+
data-cdl-role="gantt-owner"
|
|
309
|
+
x={bx + bw / 2}
|
|
282
310
|
y={by + rowH / 2 + 4}
|
|
283
311
|
fontSize={11}
|
|
312
|
+
textAnchor="middle"
|
|
284
313
|
fill="var(--cdl-node-fill, #ffffff)"
|
|
285
314
|
fontWeight={600}
|
|
286
315
|
>
|
|
@@ -323,10 +352,14 @@ export function GanttNode({
|
|
|
323
352
|
` L ${startX + elbowGap} ${(parentMidY + tipY) / 2}` +
|
|
324
353
|
` L ${enterX} ${(parentMidY + tipY) / 2} L ${enterX} ${tipY} L ${tipX} ${tipY}`;
|
|
325
354
|
const arrowHeadPath = `M ${tipX + arrowHeadSize} ${tipY} L ${tipX} ${tipY - arrowHeadSize / 2} L ${tipX} ${tipY + arrowHeadSize / 2} Z`;
|
|
355
|
+
// 矢印は両端の帯が出てから描く。 帯が無い場所へ矢印だけが伸びると、
|
|
356
|
+
// 指す先の無い線になる (cdl#521)。
|
|
357
|
+
//
|
|
358
|
+
// **見るのは両端の行の伸びで、図全体の進みではない** (cdl#873)。 行ごとに順へ
|
|
359
|
+
// 変えた後も全体の進みで見ると、帯が 4 本とも並んでから矢印がまとめて出る。
|
|
360
|
+
const 両端が出た = 行の伸び(parentRow) >= 1 && 行の伸び(childRow) >= 1;
|
|
326
361
|
return (
|
|
327
|
-
|
|
328
|
-
// 指す先の無い線になる (cdl#521)
|
|
329
|
-
(drawing && 伸び < 1) ? null : (
|
|
362
|
+
(drawing && !両端が出た) ? null : (
|
|
330
363
|
<g key={`dep-${parent.id}-${t.id}`} data-cdl-role="gantt-arrow">
|
|
331
364
|
<path
|
|
332
365
|
d={path}
|
package/src/layout/edges.ts
CHANGED
|
@@ -67,6 +67,25 @@ export function verticalCornerDir(from: number, to: number): 1 | -1 {
|
|
|
67
67
|
export function cornerRadiusFor(半径: number, ...走り: readonly number[]): number {
|
|
68
68
|
return Math.min(半径, ...走り.map((v) => Math.abs(v)));
|
|
69
69
|
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* 横に走る線が箱の中を通るか (`#867`)。
|
|
73
|
+
*
|
|
74
|
+
* 判定は箱そのもので行い、余白を足さない = 絵の検査 (`edge-node-cross`) が箱の矩形で
|
|
75
|
+
* 貫通を数えるため、余白を足すと「検査は通るのに経路だけ動く」 図が出る。
|
|
76
|
+
*
|
|
77
|
+
* 端がちょうど箱の縁に載る形は通さない (不等号は全て開いた側)。 箱の縁から出る線は必ず
|
|
78
|
+
* 縁に載るので、閉じた側にすると自分の出入口を障害物と読む。
|
|
79
|
+
*/
|
|
80
|
+
export function runCrossesBox(
|
|
81
|
+
走り: { readonly y: number; readonly x1: number; readonly x2: number },
|
|
82
|
+
箱: { readonly cx: number; readonly cy: number; readonly w: number; readonly h: number },
|
|
83
|
+
): boolean {
|
|
84
|
+
if (走り.y <= 箱.cy - 箱.h / 2 || 走り.y >= 箱.cy + 箱.h / 2) return false;
|
|
85
|
+
return (
|
|
86
|
+
箱.cx + 箱.w / 2 > Math.min(走り.x1, 走り.x2) && 箱.cx - 箱.w / 2 < Math.max(走り.x1, 走り.x2)
|
|
87
|
+
);
|
|
88
|
+
}
|
|
70
89
|
/** 縦 detour が source から曲がるまで直進する距離 (world unit)。 EDGE_STUB_OUT を上回る。 */
|
|
71
90
|
const VERTICAL_DETOUR_EXIT = 45;
|
|
72
91
|
|
|
@@ -1931,11 +1950,35 @@ function routePath(
|
|
|
1931
1950
|
const labelIntersect = oTop < labelBottom && oBottom > labelTop;
|
|
1932
1951
|
return segIntersect || labelIntersect;
|
|
1933
1952
|
};
|
|
1953
|
+
// 折れる位置を決めると、横の走りは A.x → midX と midX → B.x の 2 本になる。 この 2 本が
|
|
1954
|
+
// 箱の中を通るかを見る (`#867`)。
|
|
1955
|
+
//
|
|
1956
|
+
// **退いた側で横の走りが伸びる**。 縦の走りだけを見て箱の向こう側へ退くと、避けたはずの
|
|
1957
|
+
// 同じ箱を横の走りが横切る = 実測で「右へ 228 退く」 と「左へ 228 退く」 が並び、同点を
|
|
1958
|
+
// 右に倒す規則が塞がる側を選んでいた (dragon#2011)。
|
|
1959
|
+
const 横の走りが塞がる = (mx: number): boolean =>
|
|
1960
|
+
labelObstacles.some((o) => {
|
|
1961
|
+
if (o.id === from.id || o.id === to.id) return false;
|
|
1962
|
+
return (
|
|
1963
|
+
runCrossesBox({ y: A.y, x1: A.x, x2: mx }, o) ||
|
|
1964
|
+
runCrossesBox({ y: B.y, x1: mx, x2: B.x }, o)
|
|
1965
|
+
);
|
|
1966
|
+
});
|
|
1934
1967
|
// xDir 方向に obstacle を避けて midX を retreat する。
|
|
1935
1968
|
let iter = 0;
|
|
1936
1969
|
while (iter < 12) {
|
|
1937
1970
|
const band = labelBand(midX);
|
|
1938
|
-
|
|
1971
|
+
// 縦の走りと札の帯に当たる箱を先に探し、当たらなければ横の走りが通る箱を探す。
|
|
1972
|
+
// 後者を見ないと、初期の midX で既に横の走りが箱を横切っている形は 1 度も退かない。
|
|
1973
|
+
const hit =
|
|
1974
|
+
labelObstacles.find((o) => intersects(o, band)) ??
|
|
1975
|
+
labelObstacles.find(
|
|
1976
|
+
(o) =>
|
|
1977
|
+
o.id !== from.id &&
|
|
1978
|
+
o.id !== to.id &&
|
|
1979
|
+
(runCrossesBox({ y: A.y, x1: A.x, x2: midX }, o) ||
|
|
1980
|
+
runCrossesBox({ y: B.y, x1: midX, x2: B.x }, o)),
|
|
1981
|
+
);
|
|
1939
1982
|
if (!hit) break;
|
|
1940
1983
|
// hit を xDir 方向に回避、 obstacle right/left から labelHalfW + clearance を確保
|
|
1941
1984
|
// xDir=1 (右向き) なら hit.right + labelHalfW + clearance より右へ、
|
|
@@ -1945,10 +1988,24 @@ function routePath(
|
|
|
1945
1988
|
const hitRight = hit.cx + hit.w / 2;
|
|
1946
1989
|
const retreatRight = hitRight + labelHalfW + CLEARANCE_NODE_LABEL + 4;
|
|
1947
1990
|
const retreatLeft = hitLeft - labelHalfW - CLEARANCE_NODE_LABEL - 4;
|
|
1948
|
-
//
|
|
1991
|
+
// 退く向きは「横の走りが箱を横切らない側」 を先に見る (`#867`)。 片方だけが塞がるなら
|
|
1992
|
+
// 塞がらない側へ退く。 どちらも同じ (両方塞がる / 両方通る) 時だけ、今までどおり
|
|
1993
|
+
// midX を大きく動かさない側を選ぶ。
|
|
1994
|
+
const 右が塞がる = 横の走りが塞がる(retreatRight);
|
|
1995
|
+
const 左が塞がる = 横の走りが塞がる(retreatLeft);
|
|
1949
1996
|
const dRight = Math.abs(retreatRight - midX);
|
|
1950
1997
|
const dLeft = Math.abs(retreatLeft - midX);
|
|
1951
|
-
|
|
1998
|
+
const 次 =
|
|
1999
|
+
右が塞がる === 左が塞がる
|
|
2000
|
+
? dRight <= dLeft
|
|
2001
|
+
? retreatRight
|
|
2002
|
+
: retreatLeft
|
|
2003
|
+
: 右が塞がる
|
|
2004
|
+
? retreatLeft
|
|
2005
|
+
: retreatRight;
|
|
2006
|
+
// 同じ位置へ退こうとしたら止める。 進まない繰り返しは上限まで回すだけで形が変わらない。
|
|
2007
|
+
if (次 === midX) break;
|
|
2008
|
+
midX = 次;
|
|
1952
2009
|
iter++;
|
|
1953
2010
|
}
|
|
1954
2011
|
}
|