@cardenelabs/dragon 0.28.0 → 0.32.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/README.md +391 -16
- package/dist/index.cjs +13479 -7403
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2360 -208
- package/dist/index.d.ts +2360 -208
- package/dist/index.js +13462 -7405
- package/dist/index.js.map +1 -1
- package/package.json +12 -13
- package/src/compile/actor-ids.ts +297 -0
- package/src/compile/actor-option-notice.ts +379 -0
- package/src/compile/actors.ts +103 -0
- package/src/compile/c4.ts +74 -0
- package/src/compile/chain.ts +93 -0
- package/src/compile/chart-fields.ts +193 -0
- package/src/compile/deep-rewrite.ts +30 -0
- package/src/compile/derived-source.ts +18 -0
- package/src/compile/direction.ts +102 -0
- package/src/compile/edge-option-notice.ts +112 -0
- package/src/compile/er-relation.ts +200 -0
- package/src/compile/flow.ts +54 -0
- package/src/compile/flowchart.ts +344 -0
- package/src/compile/funnel.ts +87 -0
- package/src/compile/gantt.ts +337 -0
- package/src/compile/generic.ts +696 -0
- package/src/compile/hierarchy.ts +92 -0
- package/src/compile/journey.ts +93 -0
- package/src/compile/kinds.ts +148 -0
- package/src/compile/lanes.ts +138 -0
- package/src/compile/metro.ts +271 -0
- package/src/compile/mind.ts +393 -0
- package/src/compile/node-title.ts +24 -0
- package/src/compile/notice.ts +141 -0
- package/src/compile/order.ts +62 -0
- package/src/compile/parts-lookup.ts +17 -0
- package/src/compile/parts.ts +2709 -0
- package/src/compile/placement-apply.ts +689 -0
- package/src/compile/placement.ts +15 -0
- package/src/compile/quadrant.ts +265 -0
- package/src/compile/record.ts +75 -0
- package/src/compile/row-marks.ts +128 -0
- package/src/compile/rows.ts +40 -0
- package/src/compile/sequence.ts +305 -0
- package/src/compile/slug.ts +37 -0
- package/src/compile/stages.ts +247 -0
- package/src/compile/subtitle.ts +35 -0
- package/src/compile/swimlane-marks.ts +87 -0
- package/src/compile/swimlane.ts +82 -0
- package/src/compile/timeline.ts +213 -0
- package/src/compile/topology.ts +44 -0
- package/src/compile/tree.ts +69 -0
- package/src/compile/value-chart.ts +337 -0
- package/src/compile/values.ts +432 -0
- package/src/compile/word-state.ts +82 -0
- package/src/compile.ts +1806 -6971
- package/src/focus.ts +2 -2
- package/src/index.ts +42 -9
- package/src/json-line-map.ts +30 -0
- package/src/json-parser.ts +1760 -168
- package/src/keywords.ts +251 -17
- package/src/near-name.ts +42 -0
- package/src/notation-lint.ts +41 -64
- package/src/relative-pos.ts +71 -2
- package/src/schema.ts +1 -1
- package/src/schemas/diagram.json +680 -23
- package/src/types.ts +337 -36
- package/src/v05/parser.ts +2553 -354
|
@@ -0,0 +1,689 @@
|
|
|
1
|
+
import type { CdlDiagram, LaidDiagram } from "@cardenelabs/cdl";
|
|
2
|
+
import {
|
|
3
|
+
orderByDependency,
|
|
4
|
+
resolveRelativePos,
|
|
5
|
+
type AnchorBox,
|
|
6
|
+
type RelativeDirection,
|
|
7
|
+
} from "../relative-pos";
|
|
8
|
+
import type { DslDocument } from "../types";
|
|
9
|
+
import type { CompileNotice } from "./notice";
|
|
10
|
+
import { 箱の題 } from "./node-title";
|
|
11
|
+
import { partBoxes, partSizes, 測るために配置する } from "./parts";
|
|
12
|
+
import { PLACEMENT_TOLERANCE } from "./placement";
|
|
13
|
+
import { slugify } from "./slug";
|
|
14
|
+
import { truncateForMessage } from "./subtitle";
|
|
15
|
+
/**
|
|
16
|
+
* 書いた位置を図に当てる (#2036 で `compile.ts` から移した)。
|
|
17
|
+
*
|
|
18
|
+
* 相対で書いた位置を絶対座標に直し、置けたかを確かめ、置けていなければずらして置き直す。
|
|
19
|
+
* 色を当てるのと、図枠の起点から座標を伝えるのも ここが持つ。
|
|
20
|
+
*
|
|
21
|
+
* 外へ出す口は 4 つ。 いずれも入口が順に呼ぶ (`resolveRelativeDoc` →
|
|
22
|
+
* `applyCanvasPivotPositions` → `applyLayoutOffsets` → `applyNodeTones`)。
|
|
23
|
+
*
|
|
24
|
+
* ほかに `measureActorBoxes` を持つ。 画面が同じ物差しで箱を測るため `index.ts` が読む。
|
|
25
|
+
* `compile.ts` が再輸出するので、取り込む側の書き方は変わらない。
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 相対で書かれた位置 (`位置: Web の右 200`) を絶対座標に直した doc を返す。
|
|
30
|
+
*
|
|
31
|
+
* 基準の実座標は配置を 1 度計算しないと分からない。 cdl の `layout` を呼んで測り、
|
|
32
|
+
* 基準の縁から間隔を空けた位置を求める。 元の doc は書き換えず、 座標を入れた複製を返す。
|
|
33
|
+
*
|
|
34
|
+
* 相対指定が 1 件も無ければ何もしない。 配置計算は 1 回 1ms 前後かかるので、 使っていない
|
|
35
|
+
* 図に負担をかけない。
|
|
36
|
+
*/
|
|
37
|
+
export function resolveRelativeDoc(
|
|
38
|
+
diagram: CdlDiagram,
|
|
39
|
+
doc: DslDocument,
|
|
40
|
+
onNotice?: (notice: CompileNotice) => void,
|
|
41
|
+
partsCatalog?: Record<string, CdlDiagram>,
|
|
42
|
+
): DslDocument {
|
|
43
|
+
if (!doc.actors.some((a) => a.posRel !== undefined)) return doc;
|
|
44
|
+
|
|
45
|
+
// 1. 座標を書いた分を先に反映してから測る。 基準がどこに居るかはここで分かる。
|
|
46
|
+
//
|
|
47
|
+
// 自動配置のまま測ると、 座標で固定した箱を基準にした指定が壊れる。 基準の自動配置位置
|
|
48
|
+
// から狙いを作るため、 実際の位置と食い違い、 最後の確認で「効きません」 と捨てられる
|
|
49
|
+
// (実測 = `Web @1000,500` の右に置くはずの箱が 200 に出て、 そのまま落とされた)。
|
|
50
|
+
const measured = measureActorBoxes(withPositions(diagram, doc, new Map()));
|
|
51
|
+
// パーツの箱は catalog から作る。 組み立て前の図に残っている仮の箱を測ると、 実際に
|
|
52
|
+
// 描かれる大きさと違う値で間隔を計算することになる
|
|
53
|
+
const baseBoxes = new Map(measured);
|
|
54
|
+
if (partsCatalog) {
|
|
55
|
+
for (const [name, box] of partBoxes(diagram, doc, partsCatalog)) baseBoxes.set(name, box);
|
|
56
|
+
}
|
|
57
|
+
const sizeOverride = partsCatalog
|
|
58
|
+
? partSizes(doc, partsCatalog)
|
|
59
|
+
: new Map<string, { w: number; h: number; dx: number; dy: number }>();
|
|
60
|
+
const want = desiredCenters(doc, baseBoxes, sizeOverride);
|
|
61
|
+
if (want.size === 0) return doc;
|
|
62
|
+
|
|
63
|
+
// 2. 狙った中心をそのまま座標として仮に置く。
|
|
64
|
+
// パーツは渡す座標が段の中心なので、 矩形の中心とのずれを引く
|
|
65
|
+
const naive = new Map(
|
|
66
|
+
[...want].map(([name, c]) => {
|
|
67
|
+
const off = sizeOverride.get(name);
|
|
68
|
+
return [name, { posX: c.cx - (off?.dx ?? 0), posY: c.cy - (off?.dy ?? 0) }] as const;
|
|
69
|
+
}),
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
// 3. 測り直して、 狙いとの差を足す。
|
|
73
|
+
//
|
|
74
|
+
// 座標を書いた時に中心がどこに来るかは図種で違う。 順序図の座標は縦列の左端を動かすので、
|
|
75
|
+
// 中心を狙って書くと縦列の幅の半分だけ右にずれる (実測 = 200 空けたいのに 370 空いた)。
|
|
76
|
+
// 図種ごとの規則を書き写すと cdl 側の変更で黙って壊れるため、 実際に置いた結果との差を
|
|
77
|
+
// 使って直す。 差は図種ごとに一定なので 1 度で合う (実測 = 8 図種すべてで狙い通り)。
|
|
78
|
+
const isPart = new Set(doc.actors.filter((a) => a.partId !== undefined).map((a) => a.name));
|
|
79
|
+
// 確かめる時も、 基準になるパーツは catalog 由来の箱で見る。 この時点の図には仮の箱しか
|
|
80
|
+
// 無いため、 測ると解決側と違う基準で期待を作ることになる (実測 = 正しく置いた箱が
|
|
81
|
+
// 「効きません」 と落とされた)
|
|
82
|
+
const partOverride = new Map<string, AnchorBox>();
|
|
83
|
+
for (const [name, box] of baseBoxes) {
|
|
84
|
+
if (isPart.has(name)) partOverride.set(name, box);
|
|
85
|
+
}
|
|
86
|
+
for (const [name, c] of want) {
|
|
87
|
+
if (!isPart.has(name)) continue;
|
|
88
|
+
partOverride.set(name, c);
|
|
89
|
+
}
|
|
90
|
+
const placedBoxes = measureActorBoxes(withPositions(diagram, doc, naive));
|
|
91
|
+
const fixed = new Map<string, { posX: number; posY: number }>();
|
|
92
|
+
for (const [name, pos] of naive) {
|
|
93
|
+
// パーツは補正しない。 merge が座標を中心としてそのまま使うので狙いがそのまま効く。
|
|
94
|
+
// 一方この時点の図にはパーツの仮の箱しか無く、 動いていない位置を測って差を足すと
|
|
95
|
+
// ずれが二重になる (実測 = 狙い 760 に対して 1320 に飛んだ)
|
|
96
|
+
if (isPart.has(name)) {
|
|
97
|
+
fixed.set(name, pos);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const got = placedBoxes.get(name);
|
|
101
|
+
const target = want.get(name)!;
|
|
102
|
+
if (!got) {
|
|
103
|
+
fixed.set(name, pos);
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
fixed.set(name, {
|
|
107
|
+
posX: pos.posX + (target.cx - got.cx),
|
|
108
|
+
posY: pos.posY + (target.cy - got.cy),
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// 4. 効いたかを確かめ、 効かなかった分は自動配置に戻す。
|
|
113
|
+
//
|
|
114
|
+
// 座標がどの向きにも効く保証は無い。 順序図の縦位置がその例で、 縦列は横に並ぶものなので
|
|
115
|
+
// 下に動かせない。 そのまま出すと基準の上に重なった図が出る (実測)。 動かなかった時は
|
|
116
|
+
// 書かなかった時と同じ配置に戻し、 何が効かなかったかを呼出側に伝える。
|
|
117
|
+
return withDocPositions(doc, verifyPlacement(diagram, doc, fixed, partOverride, onNotice));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* 置いた結果が書いた通りかを確かめ、 外れた分を落とす。
|
|
122
|
+
*
|
|
123
|
+
* 確かめるのは最後の配置での「基準との位置関係」 で、 手順 1 で測った狙いではない。
|
|
124
|
+
* 誰かを固定すると周りの自動配置が動くため、 狙いと突き合わせると基準がずれた分を
|
|
125
|
+
* 見逃す。 書いた言葉 (`Web の右 200`) が最後の図でも成り立つかを見る。
|
|
126
|
+
*
|
|
127
|
+
* **落とす側 (`relative-position-ignored` を出す枝) に届く入力は組めていない** (#2041 の実測)。
|
|
128
|
+
* 試した形は 56 通り = 22 図種 × 右と下、値を持つ `pie` / `bar`、板になる `sequence` と `tree`、
|
|
129
|
+
* 同じ場所に 2 つ置く / 間隔 0 で隣に置く / 縦列をまたいで下に置く / 囲いの外へ 3000 出す。
|
|
130
|
+
* どれも知らせが 0 件だった。
|
|
131
|
+
*
|
|
132
|
+
* 座標が効かない図種は、そもそも面ごとの箱を持たない (`sequence` の板)。
|
|
133
|
+
* その場合は上の `boxes.get(actor.name)` が空になって手前で抜けるため、この枝には来ない。
|
|
134
|
+
* 箱を持つ図種では座標がそのまま効く。 描画側が置いた位置を動かす規則を持った時に備えて残す。
|
|
135
|
+
*
|
|
136
|
+
* **この枝を消さない理由は、知らせが要るかどうかが描画側の都合で変わるから**。 消すと、
|
|
137
|
+
* 動かす規則が入った日に「書いたのに効かない」 が黙って起きる (#2041 で実際に起きた形と同じ)。
|
|
138
|
+
*/
|
|
139
|
+
function verifyPlacement(
|
|
140
|
+
diagram: CdlDiagram,
|
|
141
|
+
doc: DslDocument,
|
|
142
|
+
assign: ReadonlyMap<string, { posX: number; posY: number }>,
|
|
143
|
+
partOverride: ReadonlyMap<string, AnchorBox>,
|
|
144
|
+
onNotice?: (notice: CompileNotice) => void,
|
|
145
|
+
): Map<string, { posX: number; posY: number }> {
|
|
146
|
+
const boxes = measureActorBoxes(withPositions(diagram, doc, assign));
|
|
147
|
+
// パーツは merge 前なので、 図には実寸と違う仮の箱しか無い。 解決側と同じ箱に差し替える。
|
|
148
|
+
//
|
|
149
|
+
// 差し替えないと 2 通りに壊れる。 パーツを基準にした箱は仮の箱から期待を作って落とされ
|
|
150
|
+
// (実測 = 正しく置いた箱が「効きません」 になった)、 パーツ自身も仮の箱の位置と
|
|
151
|
+
// 突き合わせて落とされる。
|
|
152
|
+
for (const [name, box] of partOverride) boxes.set(name, box);
|
|
153
|
+
const kept = new Map(assign);
|
|
154
|
+
for (const actor of doc.actors) {
|
|
155
|
+
const rel = actor.posRel;
|
|
156
|
+
const pos = assign.get(actor.name);
|
|
157
|
+
if (!rel || !pos) continue;
|
|
158
|
+
const self = boxes.get(actor.name);
|
|
159
|
+
const anchor = boxes.get(rel.anchor);
|
|
160
|
+
if (!self || !anchor) continue;
|
|
161
|
+
const expect = resolveRelativePos(rel, anchor, self);
|
|
162
|
+
const offX = Math.abs(expect.posX - self.cx);
|
|
163
|
+
const offY = Math.abs(expect.posY - self.cy);
|
|
164
|
+
if (offX <= PLACEMENT_TOLERANCE && offY <= PLACEMENT_TOLERANCE) continue;
|
|
165
|
+
kept.delete(actor.name);
|
|
166
|
+
onNotice?.({
|
|
167
|
+
kind: "relative-position-ignored",
|
|
168
|
+
actor: actor.name,
|
|
169
|
+
line: actor.pos.line,
|
|
170
|
+
message: `"${actor.name}" の位置 (${rel.anchor} の${DIRECTION_LABEL[rel.dir]}) は${doc.type}図では効きません`,
|
|
171
|
+
hint: "座標 (`位置: 300,200`) で置くか、 自動配置に任せる",
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
return kept;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** 向きの表示名。 効かなかった時の知らせで、 書いた言葉に近い形で返すために持つ。 */
|
|
178
|
+
const DIRECTION_LABEL: Readonly<Record<RelativeDirection, string>> = {
|
|
179
|
+
right: "右",
|
|
180
|
+
left: "左",
|
|
181
|
+
above: "上",
|
|
182
|
+
below: "下",
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* 相対で書かれた分について、 中心をどこに置きたいかを求める。
|
|
187
|
+
*
|
|
188
|
+
* 基準がまた相対で書かれていることがある (`C は B の右`、 `B は A の右`) ため、 依存の浅い順に
|
|
189
|
+
* 解く。 解けた中心は基準として次に使う。
|
|
190
|
+
*/
|
|
191
|
+
function desiredCenters(
|
|
192
|
+
doc: DslDocument,
|
|
193
|
+
boxes: ReadonlyMap<string, AnchorBox>,
|
|
194
|
+
sizeOverride: ReadonlyMap<string, { w: number; h: number }> = new Map(),
|
|
195
|
+
): Map<string, AnchorBox> {
|
|
196
|
+
// 大きさだけを見る。 中心のずれは呼ぶ側が座標に直す時に引く
|
|
197
|
+
const byName = new Map(doc.actors.map((a) => [a.name, a] as const));
|
|
198
|
+
const { order } = orderByDependency(doc.actors.map((a) => ({ name: a.name, rel: a.posRel })));
|
|
199
|
+
// 基準に使う中心。 相対で書かれていない分は測った位置をそのまま使う
|
|
200
|
+
const centers = new Map<string, AnchorBox>(boxes);
|
|
201
|
+
const out = new Map<string, AnchorBox>();
|
|
202
|
+
|
|
203
|
+
for (const name of order) {
|
|
204
|
+
const actor = byName.get(name);
|
|
205
|
+
// 自分の大きさ。 パーツは catalog の値を使う (図に残る仮の箱は実寸と違う)
|
|
206
|
+
const override = sizeOverride.get(name);
|
|
207
|
+
const measuredSelf = boxes.get(name);
|
|
208
|
+
const self = override
|
|
209
|
+
? { cx: measuredSelf?.cx ?? 0, cy: measuredSelf?.cy ?? 0, w: override.w, h: override.h }
|
|
210
|
+
: measuredSelf;
|
|
211
|
+
if (!actor?.posRel || !self) continue;
|
|
212
|
+
const anchor = centers.get(actor.posRel.anchor);
|
|
213
|
+
// 測れない相手を基準にした分は自動配置のまま残す。 相手が居ることは parser が確かめて
|
|
214
|
+
// いるので、 ここに来るのは図に箱として現れない相手 (catalog に無いパーツ等) を指した場合
|
|
215
|
+
if (!anchor) continue;
|
|
216
|
+
const p = resolveRelativePos(actor.posRel, anchor, self);
|
|
217
|
+
const center: AnchorBox = { cx: p.posX, cy: p.posY, w: self.w, h: self.h };
|
|
218
|
+
centers.set(name, center);
|
|
219
|
+
out.set(name, center);
|
|
220
|
+
}
|
|
221
|
+
return out;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* 登場人物ごとの、 図の上での中心と大きさを測る。
|
|
226
|
+
*
|
|
227
|
+
* 対応付けは箱に表示される名前で行う。 id を使わない理由は `applyNodeTones` と同じで、
|
|
228
|
+
* slug の作り方が dragon と cdl で違うため記号を含む名前で一致しない。
|
|
229
|
+
*
|
|
230
|
+
* 1 人が複数の箱に分かれる図種 (順序図の上端 / 下端) では、 全部を囲む矩形を返す。
|
|
231
|
+
* 箱として現れない登場人物は縦列の矩形で代用する。
|
|
232
|
+
*/
|
|
233
|
+
export function measureActorBoxes(
|
|
234
|
+
diagram: CdlDiagram,
|
|
235
|
+
/**
|
|
236
|
+
* 配置まで済ませた図。 渡すとここでは測り直さない (#1006)。
|
|
237
|
+
*
|
|
238
|
+
* 呼出側が既に組み立てているなら、 ここで `layout` を呼ぶと同じ図の配置を 2 度計算する。
|
|
239
|
+
* 図の規模に比例して重く、 辺 500 本で約 300ms (実測)。
|
|
240
|
+
* 渡す時は `diagram` と対にする = 別の図の配置を渡すと、 測る位置がずれる。
|
|
241
|
+
*/
|
|
242
|
+
laidHint?: LaidDiagram,
|
|
243
|
+
): Map<string, AnchorBox> {
|
|
244
|
+
const laid = laidHint ?? 測るために配置する(diagram);
|
|
245
|
+
const bounds = new Map<string, { x0: number; y0: number; x1: number; y1: number }>();
|
|
246
|
+
for (const n of laid.nodes) {
|
|
247
|
+
const title = n.title;
|
|
248
|
+
if (!title) continue;
|
|
249
|
+
const x0 = n.cx - n.w / 2;
|
|
250
|
+
const y0 = n.cy - n.h / 2;
|
|
251
|
+
const x1 = n.cx + n.w / 2;
|
|
252
|
+
const y1 = n.cy + n.h / 2;
|
|
253
|
+
const cur = bounds.get(title);
|
|
254
|
+
if (cur) {
|
|
255
|
+
cur.x0 = Math.min(cur.x0, x0);
|
|
256
|
+
cur.y0 = Math.min(cur.y0, y0);
|
|
257
|
+
cur.x1 = Math.max(cur.x1, x1);
|
|
258
|
+
cur.y1 = Math.max(cur.y1, y1);
|
|
259
|
+
} else {
|
|
260
|
+
bounds.set(title, { x0, y0, x1, y1 });
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
const out = new Map<string, AnchorBox>();
|
|
264
|
+
for (const [name, b] of bounds) {
|
|
265
|
+
out.set(name, { cx: (b.x0 + b.x1) / 2, cy: (b.y0 + b.y1) / 2, w: b.x1 - b.x0, h: b.y1 - b.y0 });
|
|
266
|
+
}
|
|
267
|
+
for (const lane of laid.lanes) {
|
|
268
|
+
const label = lane.label;
|
|
269
|
+
if (!label || out.has(label)) continue;
|
|
270
|
+
const y = lane.y ?? 0;
|
|
271
|
+
const h = lane.height ?? 0;
|
|
272
|
+
out.set(label, { cx: (lane.x ?? 0) + lane.width / 2, cy: y + h / 2, w: lane.width, h });
|
|
273
|
+
}
|
|
274
|
+
return out;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** doc の複製に、 決まった座標を入れる。 元の doc は書き換えない。 */
|
|
278
|
+
function withDocPositions(
|
|
279
|
+
doc: DslDocument,
|
|
280
|
+
assign: ReadonlyMap<string, { posX: number; posY: number }>,
|
|
281
|
+
): DslDocument {
|
|
282
|
+
if (assign.size === 0) return doc;
|
|
283
|
+
return {
|
|
284
|
+
...doc,
|
|
285
|
+
actors: doc.actors.map((a) => {
|
|
286
|
+
const p = assign.get(a.name);
|
|
287
|
+
return p ? { ...a, posX: p.posX, posY: p.posY } : a;
|
|
288
|
+
}),
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/** 決まった座標を反映した図の複製を作る。 測り直す時だけ使う捨て図。 */
|
|
293
|
+
function withPositions(
|
|
294
|
+
diagram: CdlDiagram,
|
|
295
|
+
doc: DslDocument,
|
|
296
|
+
assign: ReadonlyMap<string, { posX: number; posY: number }>,
|
|
297
|
+
): CdlDiagram {
|
|
298
|
+
const probe: CdlDiagram = {
|
|
299
|
+
...diagram,
|
|
300
|
+
lanes: diagram.lanes.map((l) => ({ ...l })),
|
|
301
|
+
nodes: diagram.nodes.map((n) => ({ ...n })),
|
|
302
|
+
};
|
|
303
|
+
applyCanvasPivotPositions(probe, withDocPositions(doc, assign));
|
|
304
|
+
return probe;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/** ずらしを当てる相手 1 つ。 狙いは配置後の位置 (箱は中心、縦列は左上) */
|
|
308
|
+
type ずらしの相手 = {
|
|
309
|
+
名前: string;
|
|
310
|
+
line: number;
|
|
311
|
+
箱: { id: string; 狙いX: number; 狙いY: number }[];
|
|
312
|
+
縦列: { id: string; 狙いX: number; 狙いY: number }[];
|
|
313
|
+
/** 縦列に書いたずらしの相手なら、その縦列の id */
|
|
314
|
+
ずらした縦列?: string;
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* 書いた位置のずらし (JSON の `pos` / 記法の `offsetX` / `offsetY`) を、配置後の位置に足して置き直す (#1971)。
|
|
319
|
+
*
|
|
320
|
+
* **最後の図の上で置き直す**。 配置は縦列の間隔や視点の間隔を足した後でないと決まらず、途中の
|
|
321
|
+
* 図で測ると、後から足される間隔の分だけ狙いがずれる。
|
|
322
|
+
*
|
|
323
|
+
* 手順は 3 つ。
|
|
324
|
+
*
|
|
325
|
+
* 1. 1 度配置して、ずらす箱の中心と縦列の左上を測る
|
|
326
|
+
* 2. 狙い (測った位置 + ずらし) を `posX` / `posY` に書いてもう 1 度配置する
|
|
327
|
+
* 3. 狙いからずれた分を足して直し (最大 2 回)、それでも 1 を超えてずれる相手は固定を戻して知らせる
|
|
328
|
+
*
|
|
329
|
+
* **縦列は 1 本でもずらすなら全ての縦列を固定する**。 1 本だけ固定すると、残りの縦列が左端から
|
|
330
|
+
* 詰め直されて崩れる (実測 = 3 本のうち最初を動かすと 2 本目が 0 に来た)。 固定する位置と大きさは
|
|
331
|
+
* 配置後の値なので、ずらさない縦列の配置は変わらない (実測 = 6 図種で一致)。
|
|
332
|
+
*
|
|
333
|
+
* **ずらした縦列の箱も同じ量で固定し、他の箱は動いたら元の位置に留める**。 縦列を固定しただけでは、
|
|
334
|
+
* 中の箱は縦列の並ぶ向きにしか付いて来ず (`topology` / `c4` は縦列自身も縦に 32 ずれた)、最初の
|
|
335
|
+
* 縦列を縦にずらすと他の縦列の箱まで動く (実測)。 動いた箱を留めるのは手順 3 の直し。
|
|
336
|
+
*
|
|
337
|
+
* **近すぎる箱を押し下げるのは止めない**。 ずらした箱に同じ縦列の箱が近づくと、描画側が間隔を保つよう
|
|
338
|
+
* そちらを動かす (実測 = 静止した flow で下の箱が 100 下がった)。 これは描画側の規則で、ずらした箱
|
|
339
|
+
* 自身は狙いに置かれる。
|
|
340
|
+
*
|
|
341
|
+
* ずらしを持つ相手が 1 つも無い図は何もしない = 配置を 1 度も計算しない。
|
|
342
|
+
*/
|
|
343
|
+
export function applyLayoutOffsets(
|
|
344
|
+
diagram: CdlDiagram,
|
|
345
|
+
doc: DslDocument,
|
|
346
|
+
onNotice?: (notice: CompileNotice) => void,
|
|
347
|
+
): void {
|
|
348
|
+
// `mind` は放射に描けない欄として「配置のずらし」 を既に知らせる (`放射で描けない欄の名前`)。 2 度知らせない
|
|
349
|
+
const ずらす箱 =
|
|
350
|
+
doc.type === "mind" ? [] : doc.actors.filter((a) => a.layoutPos !== undefined);
|
|
351
|
+
const ずらす縦列 = Object.entries(doc.lanes ?? {}).filter(([, l]) => l.layoutPos !== undefined);
|
|
352
|
+
if (ずらす箱.length === 0 && ずらす縦列.length === 0) return;
|
|
353
|
+
// 順序図の板は面ごとの箱を持たない。 効かないことは `reportActorKindNotHonored` が伝える
|
|
354
|
+
if (doc.type === "sequence") return;
|
|
355
|
+
|
|
356
|
+
const 前 = 測るために配置する(diagram);
|
|
357
|
+
const 箱の配置 = new Map(前.nodes.map((n) => [n.id, n] as const));
|
|
358
|
+
const 縦列の配置 = new Map(前.lanes.map((l) => [l.id, l] as const));
|
|
359
|
+
const 相手たち: ずらしの相手[] = [];
|
|
360
|
+
const 縦列のずらし = new Map<string, { x: number; y: number }>();
|
|
361
|
+
/** 箱のずらしだけの狙い (縦列のずらしを足さない)。 縦列をずらす図で、ずらさない箱の基準を取るのに使う */
|
|
362
|
+
const 箱だけの狙い = new Map<string, { x: number; y: number }>();
|
|
363
|
+
|
|
364
|
+
for (const [id, lane] of ずらす縦列) {
|
|
365
|
+
const laid = 縦列の配置.get(id);
|
|
366
|
+
const d = lane.layoutPos!;
|
|
367
|
+
if (!laid) {
|
|
368
|
+
onNotice?.({
|
|
369
|
+
kind: "position-offset-ignored",
|
|
370
|
+
actor: id,
|
|
371
|
+
line: lane.pos?.line ?? 0,
|
|
372
|
+
message: `縦列 "${id}" の位置のずらしを載せる縦列が図にありません (type: ${doc.type})`,
|
|
373
|
+
hint: "縦列を作る図種で書くか、ずらしを外す",
|
|
374
|
+
});
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
縦列のずらし.set(id, d);
|
|
378
|
+
相手たち.push({
|
|
379
|
+
名前: id,
|
|
380
|
+
line: lane.pos?.line ?? 0,
|
|
381
|
+
// 縦列を固定しただけでは、中の箱は縦列の並ぶ向きにしか付いて来ない (実測 = flow の縦列を
|
|
382
|
+
// 右へ 120、下へ 40 ずらすと、見出しは両方動き箱は右へ 120 だけ動いた)。 箱も同じ量で固定する
|
|
383
|
+
箱: diagram.nodes.flatMap((n) => {
|
|
384
|
+
const 箱laid = n.lane === id ? 箱の配置.get(n.id) : undefined;
|
|
385
|
+
return 箱laid ? [{ id: n.id, 狙いX: 箱laid.cx + d.x, 狙いY: 箱laid.cy + d.y }] : [];
|
|
386
|
+
}),
|
|
387
|
+
縦列: [{ id, 狙いX: (laid.x ?? 0) + d.x, 狙いY: (laid.y ?? 0) + d.y }],
|
|
388
|
+
ずらした縦列: id,
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
for (const a of ずらす箱) {
|
|
393
|
+
const d = a.layoutPos!;
|
|
394
|
+
// 見本は `{名前}__{元の id}` で重なる。 箱と縦列を見本 1 つ分まとめて動かす
|
|
395
|
+
const 前置き = `${a.name}__`;
|
|
396
|
+
const 箱 =
|
|
397
|
+
a.partId !== undefined
|
|
398
|
+
? diagram.nodes.filter((n) => n.id.startsWith(前置き))
|
|
399
|
+
: diagram.nodes.filter((n) => n.id === slugify(a.name));
|
|
400
|
+
const 縦列 = a.partId !== undefined ? diagram.lanes.filter((l) => l.id.startsWith(前置き)) : [];
|
|
401
|
+
if (箱.length === 0) {
|
|
402
|
+
onNotice?.({
|
|
403
|
+
kind: "position-offset-ignored",
|
|
404
|
+
actor: a.name,
|
|
405
|
+
line: a.pos?.line ?? 0,
|
|
406
|
+
message: `"${truncateForMessage(a.name)}" の位置のずらしを載せる箱が図にありません (type: ${doc.type})`,
|
|
407
|
+
hint: "箱を描く図種で書くか、ずらしを外す",
|
|
408
|
+
});
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
相手たち.push({
|
|
412
|
+
名前: a.name,
|
|
413
|
+
line: a.pos?.line ?? 0,
|
|
414
|
+
箱: 箱.map((n) => {
|
|
415
|
+
const laid = 箱の配置.get(n.id)!;
|
|
416
|
+
箱だけの狙い.set(n.id, { x: laid.cx + d.x, y: laid.cy + d.y });
|
|
417
|
+
// 箱を固定すると縦列を動かしても付いて来ないので、縦列のずらしも狙いに足す
|
|
418
|
+
const 列 = 縦列のずらし.get(n.lane);
|
|
419
|
+
return { id: n.id, 狙いX: laid.cx + d.x + (列?.x ?? 0), 狙いY: laid.cy + d.y + (列?.y ?? 0) };
|
|
420
|
+
}),
|
|
421
|
+
縦列: 縦列.map((l) => {
|
|
422
|
+
const laid = 縦列の配置.get(l.id)!;
|
|
423
|
+
return { id: l.id, 狙いX: (laid.x ?? 0) + d.x, 狙いY: (laid.y ?? 0) + d.y };
|
|
424
|
+
}),
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
if (相手たち.length === 0) return;
|
|
428
|
+
|
|
429
|
+
// 固定を戻せるよう、書き換える前の欄を控える
|
|
430
|
+
const 元の箱 = new Map(diagram.nodes.map((n) => [n.id, { posX: n.posX, posY: n.posY }] as const));
|
|
431
|
+
const 元の縦列 = new Map(
|
|
432
|
+
diagram.lanes.map((l) => [l.id, { posX: l.posX, posY: l.posY, posW: l.posW, posH: l.posH }] as const),
|
|
433
|
+
);
|
|
434
|
+
const 箱の狙い = new Map<string, { x: number; y: number }>();
|
|
435
|
+
const 縦列の狙い = new Map<string, { x: number; y: number }>();
|
|
436
|
+
for (const t of 相手たち) {
|
|
437
|
+
for (const b of t.箱) 箱の狙い.set(b.id, { x: b.狙いX, y: b.狙いY });
|
|
438
|
+
for (const l of t.縦列) 縦列の狙い.set(l.id, { x: l.狙いX, y: l.狙いY });
|
|
439
|
+
}
|
|
440
|
+
const 縦列を固定する = 縦列の狙い.size > 0;
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* 縦列をずらす図で、ずらさない箱を留める位置。
|
|
444
|
+
*
|
|
445
|
+
* 描画側は箱を並べ始める高さを最初の縦列に合わせるので、最初の縦列を縦にずらすと固定しない箱が
|
|
446
|
+
* 全て一緒に動く (実測 = 3 本のうち最初の縦列を 40 下げると他の縦列の箱も 40 下がり、2 本目と
|
|
447
|
+
* 3 本目を下げても動かない)。 箱のずらしだけを当てた配置を基準に取り、そこから動いた箱をその位置に
|
|
448
|
+
* 留める。 基準に箱のずらしを含めるのは、近すぎる箱の押し下げを残すため。
|
|
449
|
+
*/
|
|
450
|
+
const 留める位置 = new Map<string, { x: number; y: number }>();
|
|
451
|
+
if (縦列を固定する) {
|
|
452
|
+
let 基準 = 前;
|
|
453
|
+
if (箱だけの狙い.size > 0) {
|
|
454
|
+
for (const node of diagram.nodes) {
|
|
455
|
+
const 狙い = 箱だけの狙い.get(node.id);
|
|
456
|
+
if (!狙い) continue;
|
|
457
|
+
node.posX = 狙い.x;
|
|
458
|
+
node.posY = 狙い.y;
|
|
459
|
+
}
|
|
460
|
+
基準 = 測るために配置する(diagram);
|
|
461
|
+
for (const node of diagram.nodes) {
|
|
462
|
+
if (!箱だけの狙い.has(node.id)) continue;
|
|
463
|
+
const 元 = 元の箱.get(node.id)!;
|
|
464
|
+
node.posX = 元.posX;
|
|
465
|
+
node.posY = 元.posY;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
const 基準の箱 = new Map(基準.nodes.map((n) => [n.id, n] as const));
|
|
469
|
+
// 縦列と一緒に動かす箱は、押し下げを含む基準の位置から縦列のずらしだけ動かす
|
|
470
|
+
for (const t of 相手たち) {
|
|
471
|
+
const d = t.ずらした縦列 === undefined ? undefined : 縦列のずらし.get(t.ずらした縦列);
|
|
472
|
+
if (!d) continue;
|
|
473
|
+
for (const b of t.箱) {
|
|
474
|
+
const n = 基準の箱.get(b.id);
|
|
475
|
+
if (!n || 箱だけの狙い.has(b.id)) continue;
|
|
476
|
+
箱の狙い.set(b.id, { x: n.cx + d.x, y: n.cy + d.y });
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
for (const n of 基準.nodes) {
|
|
480
|
+
if (!箱の狙い.has(n.id)) 留める位置.set(n.id, { x: n.cx, y: n.cy });
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
/** 基準から動いたので固定した、ずらさない箱 */
|
|
484
|
+
const 留めた箱 = new Set<string>();
|
|
485
|
+
|
|
486
|
+
// 手順 2。 縦列を固定する時は、ずらさない縦列も配置後の位置と大きさで固定する
|
|
487
|
+
const 書く = (直し: ReadonlyMap<string, { x: number; y: number }>): void => {
|
|
488
|
+
if (縦列を固定する) {
|
|
489
|
+
for (const lane of diagram.lanes) {
|
|
490
|
+
const laid = 縦列の配置.get(lane.id);
|
|
491
|
+
if (!laid) continue;
|
|
492
|
+
const 狙い = 縦列の狙い.get(lane.id) ?? { x: laid.x ?? 0, y: laid.y ?? 0 };
|
|
493
|
+
const 差 = 直し.get(`lane:${lane.id}`) ?? { x: 0, y: 0 };
|
|
494
|
+
lane.posX = 狙い.x - 差.x;
|
|
495
|
+
lane.posY = 狙い.y - 差.y;
|
|
496
|
+
lane.posW = laid.width;
|
|
497
|
+
lane.posH = laid.height;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
for (const node of diagram.nodes) {
|
|
501
|
+
const 狙い = 箱の狙い.get(node.id);
|
|
502
|
+
const 留める = 留めた箱.has(node.id) ? 留める位置.get(node.id) : undefined;
|
|
503
|
+
if (!狙い && !留める) continue;
|
|
504
|
+
const 差 = 直し.get(狙い ? `node:${node.id}` : `keep:${node.id}`) ?? { x: 0, y: 0 };
|
|
505
|
+
node.posX = (狙い ?? 留める)!.x - 差.x;
|
|
506
|
+
node.posY = (狙い ?? 留める)!.y - 差.y;
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
/** 狙いと実際の差。 縦列を固定する時は、ずらさない縦列と箱も元の位置に居るかを見る */
|
|
510
|
+
const 測る = (): Map<string, { x: number; y: number }> => {
|
|
511
|
+
const 後 = 測るために配置する(diagram);
|
|
512
|
+
const 差 = new Map<string, { x: number; y: number }>();
|
|
513
|
+
for (const n of 後.nodes) {
|
|
514
|
+
const 狙い = 箱の狙い.get(n.id);
|
|
515
|
+
if (狙い) 差.set(`node:${n.id}`, { x: n.cx - 狙い.x, y: n.cy - 狙い.y });
|
|
516
|
+
const 留める = 留める位置.get(n.id);
|
|
517
|
+
if (留める) 差.set(`keep:${n.id}`, { x: n.cx - 留める.x, y: n.cy - 留める.y });
|
|
518
|
+
}
|
|
519
|
+
if (縦列を固定する) {
|
|
520
|
+
for (const l of 後.lanes) {
|
|
521
|
+
const laid = 縦列の配置.get(l.id);
|
|
522
|
+
if (!laid) continue;
|
|
523
|
+
const 狙い = 縦列の狙い.get(l.id) ?? { x: laid.x ?? 0, y: laid.y ?? 0 };
|
|
524
|
+
差.set(`lane:${l.id}`, { x: (l.x ?? 0) - 狙い.x, y: (l.y ?? 0) - 狙い.y });
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
return 差;
|
|
528
|
+
};
|
|
529
|
+
const ずれた = (差: { x: number; y: number } | undefined): boolean =>
|
|
530
|
+
差 !== undefined && (Math.abs(差.x) > PLACEMENT_TOLERANCE || Math.abs(差.y) > PLACEMENT_TOLERANCE);
|
|
531
|
+
|
|
532
|
+
const 直し = new Map<string, { x: number; y: number }>();
|
|
533
|
+
書く(直し);
|
|
534
|
+
let 差 = 測る();
|
|
535
|
+
// 手順 3。 狙いからの差を書いた位置から引いて置き直す。 固定した相手の差は図種ごとに一定なので
|
|
536
|
+
// 1 度で合う。 基準から動いた箱はその回に初めて固定し、固定で出る差をもう 1 度で直す。
|
|
537
|
+
//
|
|
538
|
+
// **ずらす箱と縦列そのものの差 (`node:` / `lane:`) を直す入力は組めていない** (#1971 の実測)。
|
|
539
|
+
// 縦列だけを固定していた形では `topology` / `c4` の縦列が縦に 32 ずれたが、中の箱も固定する今の形
|
|
540
|
+
// ではずれない。 試した形 = 7 図種の箱 / 全ての縦列を 1 本ずつ / 箱の無い縦列。 実際に直すのは
|
|
541
|
+
// 留める箱 (`keep:`) で、最初の縦列を縦にずらした図が通る
|
|
542
|
+
for (let 回 = 0; 回 < 2 && [...差.values()].some(ずれた); 回++) {
|
|
543
|
+
for (const [k, v] of 差) {
|
|
544
|
+
if (!ずれた(v)) continue;
|
|
545
|
+
const id = k.slice(k.indexOf(":") + 1);
|
|
546
|
+
if (k.startsWith("keep:") && !留めた箱.has(id)) {
|
|
547
|
+
留めた箱.add(id);
|
|
548
|
+
continue;
|
|
549
|
+
}
|
|
550
|
+
const 前の直し = 直し.get(k) ?? { x: 0, y: 0 };
|
|
551
|
+
直し.set(k, { x: 前の直し.x + v.x, y: 前の直し.y + v.y });
|
|
552
|
+
}
|
|
553
|
+
書く(直し);
|
|
554
|
+
差 = 測る();
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
const 戻す箱 = (t: ずらしの相手): void => {
|
|
558
|
+
for (const b of t.箱) {
|
|
559
|
+
const node = diagram.nodes.find((n) => n.id === b.id)!;
|
|
560
|
+
const 元 = 元の箱.get(b.id)!;
|
|
561
|
+
node.posX = 元.posX;
|
|
562
|
+
node.posY = 元.posY;
|
|
563
|
+
}
|
|
564
|
+
};
|
|
565
|
+
const 知らせる = (t: ずらしの相手): void =>
|
|
566
|
+
onNotice?.({
|
|
567
|
+
kind: "position-offset-ignored",
|
|
568
|
+
actor: t.名前,
|
|
569
|
+
line: t.line,
|
|
570
|
+
message: `"${truncateForMessage(t.名前)}" の位置のずらしは type: ${doc.type} の図では書いた量だけ動かせません`,
|
|
571
|
+
hint: "ずらしを外すか、座標 (`位置: 300,200`) で置く",
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
// **ここから下の「戻して知らせる」 経路に届く入力は組めていない** (#1971 の実測)。 描画側は
|
|
575
|
+
// `posX` / `posY` を書いた箱と縦列をそのまま置き、囲いの縦列のずれも 1 度の直しで合う。 試した
|
|
576
|
+
// 形 = 大きく正と負にずらす / 図の広さを固定してその外へ出す / 7 図種の箱 / 囲いを持つ 2 図種と
|
|
577
|
+
// クラス図と横長の図の縦列。 描画側が置いた位置を動かす規則を持った時に備えて残す
|
|
578
|
+
//
|
|
579
|
+
// 縦列が 1 本でも狙いに置けないか、ずらさない箱を留められなければ、全ての固定を戻して全ての相手を
|
|
580
|
+
// 知らせる。 縦列を 1 本だけ残すと他が詰め直され、箱の狙いは縦列のずらしを含むので、縦列を戻すと
|
|
581
|
+
// 箱の狙いも崩れる
|
|
582
|
+
if ([...差].some(([k, v]) => (k.startsWith("lane:") || k.startsWith("keep:")) && ずれた(v))) {
|
|
583
|
+
for (const lane of diagram.lanes) {
|
|
584
|
+
const 元 = 元の縦列.get(lane.id);
|
|
585
|
+
if (!元) continue;
|
|
586
|
+
lane.posX = 元.posX;
|
|
587
|
+
lane.posY = 元.posY;
|
|
588
|
+
lane.posW = 元.posW;
|
|
589
|
+
lane.posH = 元.posH;
|
|
590
|
+
}
|
|
591
|
+
for (const node of diagram.nodes) {
|
|
592
|
+
if (!留めた箱.has(node.id)) continue;
|
|
593
|
+
const 元 = 元の箱.get(node.id)!;
|
|
594
|
+
node.posX = 元.posX;
|
|
595
|
+
node.posY = 元.posY;
|
|
596
|
+
}
|
|
597
|
+
for (const t of 相手たち) {
|
|
598
|
+
戻す箱(t);
|
|
599
|
+
知らせる(t);
|
|
600
|
+
}
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
for (const t of 相手たち) {
|
|
604
|
+
if (!t.箱.some((b) => ずれた(差.get(`node:${b.id}`)))) continue;
|
|
605
|
+
戻す箱(t);
|
|
606
|
+
知らせる(t);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
/**
|
|
611
|
+
* 全図種共通の後処理で、 登場人物に書かれた色を対応する箱に載せる。
|
|
612
|
+
*
|
|
613
|
+
* 箱を作る経路は図種ごとに違い、 cdl の preset を経由する図種 (フロー / ER / 状態遷移 / 構成図)
|
|
614
|
+
* では preset の入力型が色の項目を持たない。 箱が出来上がった後に id で対応付けることで、
|
|
615
|
+
* どの図種でも同じ書き方が効く。 座標を伝播する `applyCanvasPivotPositions` と同じ経路。
|
|
616
|
+
*
|
|
617
|
+
* 対応付けは箱に表示される名前との一致で行う。 id は使わない。
|
|
618
|
+
*
|
|
619
|
+
* **表示される名前は `箱の題` が決める** (#2333)。 `actor.name` と直接比べていた間、
|
|
620
|
+
* **題 (`title`) を書いた箱の色が黙って消えていた** = 題を書くと箱に出るのは題なので、
|
|
621
|
+
* 名前と比べた照合が外れる。 実測で箱になる 7 図種のうち 5 図種 (フロー / 構成図 /
|
|
622
|
+
* 状態遷移 / クラス図 / ER 図) が落ちており、知らせも出ないため「書いたのに色が付かない」
|
|
623
|
+
* だけが残った。
|
|
624
|
+
*
|
|
625
|
+
* **呼ぶ位置も合わせて決まる**。 題が箱に入る時点は図種で 2 つに割れ、帯図と c4 は登場人物の
|
|
626
|
+
* 名前で箱を作って `applyV05Extensions` が後から題へ書き換える。 そのため本関数は書き換えの
|
|
627
|
+
* 後に呼ぶ (呼出側の説明が SSOT)。 前に呼んだまま照合だけを題へ寄せると、今度はその 2 図種で
|
|
628
|
+
* 色が落ちる (実測)。
|
|
629
|
+
*
|
|
630
|
+
* 題を持たない箱 (始まりと終わりの印、順序図の間隔用) は `箱の題` が空を返す。
|
|
631
|
+
* 空のまま照合すると同じく空の別の箱を巻き込むので、空は対象から外す。
|
|
632
|
+
*
|
|
633
|
+
* id での対応付けは 2 通りに壊れる。 id は名前を slug に変換して作るが、 その変換規則が
|
|
634
|
+
* dragon と cdl で違い、 記号を含む名前では一致しない (実測 = `A_B` が dragon 側で `a_b`、
|
|
635
|
+
* cdl 側で `a-b`)。 逆に、 生成した id (`{slug}-header`) をそのまま名前に持つ登場人物が
|
|
636
|
+
* 居ると、 別人の箱を巻き込む。
|
|
637
|
+
*
|
|
638
|
+
* 表示名は変換を経ないので前者が起きず、 別人と一致しないので後者も起きない。 順序図で
|
|
639
|
+
* 1 人が分かれる複数の箱のうち、 間隔用と手順ごとの anchor は表示名が空なので自然に対象外に
|
|
640
|
+
* なる (色を持っても幅 2 で見えない)。
|
|
641
|
+
*
|
|
642
|
+
* parts は対象外。 parts の `tone` は色ではなく状態の上書きとして parser が扱うため、
|
|
643
|
+
* ここに色として渡ってこない。
|
|
644
|
+
*/
|
|
645
|
+
export function applyNodeTones(diagram: CdlDiagram, doc: DslDocument): void {
|
|
646
|
+
for (const actor of doc.actors) {
|
|
647
|
+
if (actor.tone === undefined) continue;
|
|
648
|
+
const 題 = 箱の題(actor);
|
|
649
|
+
if (題 === "") continue;
|
|
650
|
+
for (const node of diagram.nodes) {
|
|
651
|
+
if (node.title === 題) node.tone = actor.tone;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* canvas pivot 新 spec = 全 preset 共通の post-process で actor.posX/Y/W/H を CDL 側 lane / node に伝播。
|
|
658
|
+
* preset builder が生成した diagram に対して、 doc.actors の 4 field を絶対座標として反映する。
|
|
659
|
+
* slugify で actor 名 → lane id / node id の逆引き、 posX/Y set 済 actor に対応する lane / node に
|
|
660
|
+
* 座標を書込む。 未指定 actor は従来 auto layout 経路そのまま。
|
|
661
|
+
*
|
|
662
|
+
* **知らせは出さない**。 かつて `sub-node-not-found` を出していたが、 その知らせが見ていた
|
|
663
|
+
* `nodes` の欄を #1976 で外した時に出し所が消えた。 知らせる相手が戻った時に引数を足す。
|
|
664
|
+
*/
|
|
665
|
+
export function applyCanvasPivotPositions(diagram: CdlDiagram, doc: DslDocument): void {
|
|
666
|
+
for (const actor of doc.actors) {
|
|
667
|
+
if (actor.partId !== undefined) continue; // parts actor は別経路 (mergePartsFromActors) で処理
|
|
668
|
+
const aliasSlug = slugify(actor.name);
|
|
669
|
+
// actor 全体 posX/Y = lane と単一 node に一括反映 (従来経路)
|
|
670
|
+
if (actor.posX !== undefined && actor.posY !== undefined) {
|
|
671
|
+
for (const lane of diagram.lanes) {
|
|
672
|
+
if (lane.id === aliasSlug || lane.id === actor.name) {
|
|
673
|
+
lane.posX = actor.posX;
|
|
674
|
+
lane.posY = actor.posY;
|
|
675
|
+
if (actor.posW !== undefined) lane.posW = actor.posW;
|
|
676
|
+
if (actor.posH !== undefined) lane.posH = actor.posH;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
for (const node of diagram.nodes) {
|
|
680
|
+
if (node.id === aliasSlug || node.id === actor.name) {
|
|
681
|
+
node.posX = actor.posX;
|
|
682
|
+
node.posY = actor.posY;
|
|
683
|
+
if (actor.posW !== undefined) node.posW = actor.posW;
|
|
684
|
+
if (actor.posH !== undefined) node.posH = actor.posH;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
}
|