compasso 0.4.1 → 0.5.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 +86 -5
- package/dist/{chunk-WJYYBGZW.js → chunk-2NDET6O5.js} +3 -3
- package/dist/{chunk-WJYYBGZW.js.map → chunk-2NDET6O5.js.map} +1 -1
- package/dist/{chunk-LR7BXUWM.js → chunk-3RGYLVTN.js} +3 -3
- package/dist/{chunk-LR7BXUWM.js.map → chunk-3RGYLVTN.js.map} +1 -1
- package/dist/chunk-BM7UJBK5.js +680 -0
- package/dist/chunk-BM7UJBK5.js.map +1 -0
- package/dist/chunk-DVLWT565.js +372 -0
- package/dist/chunk-DVLWT565.js.map +1 -0
- package/dist/{chunk-IPE7JZO5.js → chunk-JBDA7E2O.js} +3 -3
- package/dist/{chunk-IPE7JZO5.js.map → chunk-JBDA7E2O.js.map} +1 -1
- package/dist/chunk-MIJTBYX2.js +982 -0
- package/dist/chunk-MIJTBYX2.js.map +1 -0
- package/dist/{chunk-PGUMLTIM.js → chunk-PJHLWSGD.js} +3 -3
- package/dist/{chunk-PGUMLTIM.js.map → chunk-PJHLWSGD.js.map} +1 -1
- package/dist/{chunk-M4WA6ME7.js → chunk-RDH4XHA2.js} +3 -3
- package/dist/{chunk-M4WA6ME7.js.map → chunk-RDH4XHA2.js.map} +1 -1
- package/dist/{chunk-TAE2UB7D.js → chunk-WEHUSHVI.js} +4 -40
- package/dist/chunk-WEHUSHVI.js.map +1 -0
- package/dist/{chunk-FYBABYC7.js → chunk-Z66YUOUM.js} +3 -3
- package/dist/{chunk-FYBABYC7.js.map → chunk-Z66YUOUM.js.map} +1 -1
- package/dist/core/index.cjs +217 -0
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +79 -3
- package/dist/core/index.d.ts +79 -3
- package/dist/core/index.js +1 -1
- package/dist/ecomap/index.js +2 -2
- package/dist/fault-tree/index.d.cts +2 -2
- package/dist/fault-tree/index.d.ts +2 -2
- package/dist/fault-tree/index.js +2 -2
- package/dist/fishbone/index.js +2 -2
- package/dist/genogram/index.d.cts +2 -2
- package/dist/genogram/index.d.ts +2 -2
- package/dist/genogram/index.js +2 -2
- package/dist/geometry-P-XGqGe7.d.cts +8 -0
- package/dist/geometry-P-XGqGe7.d.ts +8 -0
- package/dist/grid-BMgUSly1.d.cts +79 -0
- package/dist/grid-BMgUSly1.d.ts +79 -0
- package/dist/index.cjs +2263 -380
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -3
- package/dist/index.d.ts +10 -3
- package/dist/index.js +10 -8
- package/dist/labels-Br8yjc3C.d.cts +29 -0
- package/dist/labels-Br8yjc3C.d.ts +29 -0
- package/dist/labels-D1v1RWZd.d.cts +97 -0
- package/dist/labels-D1v1RWZd.d.ts +97 -0
- package/dist/layered-DmZluAqe.d.cts +72 -0
- package/dist/layered-DmZluAqe.d.ts +72 -0
- package/dist/locales/pt-br.cjs +53 -0
- package/dist/locales/pt-br.cjs.map +1 -1
- package/dist/locales/pt-br.d.cts +7 -1
- package/dist/locales/pt-br.d.ts +7 -1
- package/dist/locales/pt-br.js +50 -1
- package/dist/locales/pt-br.js.map +1 -1
- package/dist/org-chart/index.cjs +38 -36
- package/dist/org-chart/index.cjs.map +1 -1
- package/dist/org-chart/index.d.cts +5 -31
- package/dist/org-chart/index.d.ts +5 -31
- package/dist/org-chart/index.js +2 -2
- package/dist/pedigree/index.d.cts +2 -2
- package/dist/pedigree/index.d.ts +2 -2
- package/dist/pedigree/index.js +2 -2
- package/dist/phylo/index.d.cts +2 -2
- package/dist/phylo/index.d.ts +2 -2
- package/dist/phylo/index.js +2 -2
- package/dist/prisma/index.cjs +882 -0
- package/dist/prisma/index.cjs.map +1 -0
- package/dist/prisma/index.d.cts +174 -0
- package/dist/prisma/index.d.ts +174 -0
- package/dist/prisma/index.js +4 -0
- package/dist/prisma/index.js.map +1 -0
- package/dist/{text-DuO_PwYw.d.cts → text-DDVzpwPZ.d.cts} +1 -8
- package/dist/{text-DuO_PwYw.d.ts → text-DDVzpwPZ.d.ts} +1 -8
- package/dist/uml/index.cjs +1214 -0
- package/dist/uml/index.cjs.map +1 -0
- package/dist/uml/index.d.cts +189 -0
- package/dist/uml/index.d.ts +189 -0
- package/dist/uml/index.js +4 -0
- package/dist/uml/index.js.map +1 -0
- package/package.json +28 -2
- package/dist/chunk-SD4NTRBM.js +0 -171
- package/dist/chunk-SD4NTRBM.js.map +0 -1
- package/dist/chunk-TAE2UB7D.js.map +0 -1
|
@@ -0,0 +1,882 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/prisma/types.ts
|
|
4
|
+
var PRISMA_PHASES = ["identification", "screening", "included"];
|
|
5
|
+
var PRISMA_BOX_KINDS = ["flow", "exclusion"];
|
|
6
|
+
var PRISMA_COLUMNS = ["main", "new", "previous"];
|
|
7
|
+
|
|
8
|
+
// src/prisma/validate.ts
|
|
9
|
+
var PrismaValidationError = class extends Error {
|
|
10
|
+
issues;
|
|
11
|
+
constructor(issues) {
|
|
12
|
+
super(`invalid PRISMA diagram: ${issues.map((i) => i.message).join("; ")}`);
|
|
13
|
+
this.name = "PrismaValidationError";
|
|
14
|
+
this.issues = issues;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
function sortIssues(issues) {
|
|
18
|
+
const unique = /* @__PURE__ */ new Map();
|
|
19
|
+
for (const issue of issues) unique.set(`${issue.code} ${issue.message}`, issue);
|
|
20
|
+
return [...unique.values()].sort(
|
|
21
|
+
(a, b) => a.code !== b.code ? a.code < b.code ? -1 : 1 : a.message < b.message ? -1 : a.message > b.message ? 1 : 0
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
function prismaIssues(input) {
|
|
25
|
+
if (input.boxes.length === 0 && input.arrows.length === 0) return [];
|
|
26
|
+
const issues = [];
|
|
27
|
+
const push = (code, message) => {
|
|
28
|
+
issues.push({ code, message });
|
|
29
|
+
};
|
|
30
|
+
const boxById = /* @__PURE__ */ new Map();
|
|
31
|
+
const dupBoxIds = /* @__PURE__ */ new Set();
|
|
32
|
+
for (const b of input.boxes) {
|
|
33
|
+
if (boxById.has(b.id)) dupBoxIds.add(b.id);
|
|
34
|
+
else boxById.set(b.id, b);
|
|
35
|
+
}
|
|
36
|
+
for (const id of [...dupBoxIds].sort((a, b) => a - b)) {
|
|
37
|
+
push("duplicate-id", `duplicate box id ${id}`);
|
|
38
|
+
}
|
|
39
|
+
const arrowById = /* @__PURE__ */ new Map();
|
|
40
|
+
const dupArrowIds = /* @__PURE__ */ new Set();
|
|
41
|
+
for (const a of input.arrows) {
|
|
42
|
+
if (arrowById.has(a.id)) dupArrowIds.add(a.id);
|
|
43
|
+
else arrowById.set(a.id, a);
|
|
44
|
+
}
|
|
45
|
+
for (const id of [...dupArrowIds].sort((a, b) => a - b)) {
|
|
46
|
+
push("duplicate-id", `duplicate arrow id ${id}`);
|
|
47
|
+
}
|
|
48
|
+
if (issues.length > 0) return sortIssues(issues);
|
|
49
|
+
for (const arrow of [...arrowById.values()].sort((a, b) => a.id - b.id)) {
|
|
50
|
+
if (!boxById.has(arrow.fromId)) {
|
|
51
|
+
push("unknown-arrow-endpoint", `arrow ${arrow.id} fromId ${arrow.fromId} is not a declared box`);
|
|
52
|
+
}
|
|
53
|
+
if (!boxById.has(arrow.toId)) {
|
|
54
|
+
push("unknown-arrow-endpoint", `arrow ${arrow.id} toId ${arrow.toId} is not a declared box`);
|
|
55
|
+
}
|
|
56
|
+
if (arrow.fromId === arrow.toId) {
|
|
57
|
+
push("self-arrow", `arrow ${arrow.id} has fromId === toId (${arrow.fromId})`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
for (const box of [...boxById.values()].sort((a, b) => a.id - b.id)) {
|
|
61
|
+
for (const count of box.counts) {
|
|
62
|
+
if (count.n !== null && !Number.isFinite(count.n)) {
|
|
63
|
+
push("non-finite-count", `box ${box.id} count "${count.label}" has non-finite n: ${String(count.n)}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (input.variant !== "flow-with-prior") {
|
|
68
|
+
for (const box of [...boxById.values()].sort((a, b) => a.id - b.id)) {
|
|
69
|
+
if (box.column === "previous") {
|
|
70
|
+
push(
|
|
71
|
+
"invalid-variant-column",
|
|
72
|
+
`box ${box.id} has column "previous" but variant is "${input.variant}"`
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const GRAPH_BLOCKING = /* @__PURE__ */ new Set([
|
|
78
|
+
"duplicate-id",
|
|
79
|
+
"unknown-arrow-endpoint"
|
|
80
|
+
]);
|
|
81
|
+
if (!issues.some((i) => GRAPH_BLOCKING.has(i.code))) {
|
|
82
|
+
const phaseIndex = /* @__PURE__ */ new Map();
|
|
83
|
+
for (let i = 0; i < PRISMA_PHASES.length; i++) phaseIndex.set(PRISMA_PHASES[i], i);
|
|
84
|
+
for (const arrow of [...arrowById.values()].sort((a, b) => a.id - b.id)) {
|
|
85
|
+
const fromBox = boxById.get(arrow.fromId);
|
|
86
|
+
const toBox = boxById.get(arrow.toId);
|
|
87
|
+
const fromPhaseIdx = phaseIndex.get(fromBox.phase);
|
|
88
|
+
const toPhaseIdx = phaseIndex.get(toBox.phase);
|
|
89
|
+
if (fromBox.kind === "flow" && toBox.kind === "exclusion" && fromBox.phase === toBox.phase) {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (toPhaseIdx < fromPhaseIdx) {
|
|
93
|
+
push(
|
|
94
|
+
"arrow-skips-backward",
|
|
95
|
+
`arrow ${arrow.id} goes backward: from phase "${fromBox.phase}" to "${toBox.phase}"`
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return sortIssues(issues);
|
|
101
|
+
}
|
|
102
|
+
function validatePrisma(input) {
|
|
103
|
+
const issues = prismaIssues(input);
|
|
104
|
+
if (issues.length > 0) throw new PrismaValidationError(issues);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// src/prisma/labels.ts
|
|
108
|
+
var PRISMA_TITLE_LABELS_EN = {
|
|
109
|
+
phases: {
|
|
110
|
+
identification: "Identification",
|
|
111
|
+
screening: "Screening",
|
|
112
|
+
included: "Included"
|
|
113
|
+
},
|
|
114
|
+
nullCount: "n = \u2014",
|
|
115
|
+
arrowKinds: {
|
|
116
|
+
flow: "Flow",
|
|
117
|
+
exclusion: "Exclusion",
|
|
118
|
+
merge: "Merge"
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
var PRISMA_SVG_LABELS_EN = {
|
|
122
|
+
legend: {
|
|
123
|
+
flow: "Main flow",
|
|
124
|
+
exclusion: "Excluded"
|
|
125
|
+
},
|
|
126
|
+
ariaLabel: "PRISMA 2020 flow diagram"
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// src/core/xml.ts
|
|
130
|
+
function xmlEscape(text) {
|
|
131
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// src/core/geometry.ts
|
|
135
|
+
function pathData(points) {
|
|
136
|
+
return points.map((p, i) => `${i === 0 ? "M" : "L"} ${p.x} ${p.y}`).join(" ");
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// src/core/text.ts
|
|
140
|
+
var CHAR_W = 0.6;
|
|
141
|
+
function estimateTextWidth(text, fontPx) {
|
|
142
|
+
return text.length * fontPx * CHAR_W;
|
|
143
|
+
}
|
|
144
|
+
function wrapLabel(label, perLine, maxLines = 2) {
|
|
145
|
+
if (label.length <= perLine) return [label];
|
|
146
|
+
const cap = (s) => s.length > perLine ? s.slice(0, Math.max(1, perLine - 1)) + "\u2026" : s;
|
|
147
|
+
const lines = [""];
|
|
148
|
+
for (const word of label.split(/\s+/)) {
|
|
149
|
+
const last = lines.length - 1;
|
|
150
|
+
const current = lines[last];
|
|
151
|
+
if (current === "" || (current + " " + word).length <= perLine) {
|
|
152
|
+
lines[last] = current === "" ? word : `${current} ${word}`;
|
|
153
|
+
} else if (lines.length < maxLines) {
|
|
154
|
+
lines.push(word);
|
|
155
|
+
} else {
|
|
156
|
+
lines[last] = `${current} ${word}`;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (lines.length > 1 && lines[lines.length - 1] === "") lines.pop();
|
|
160
|
+
return lines.map(cap);
|
|
161
|
+
}
|
|
162
|
+
function wrapLabelBalanced(label, maxLines) {
|
|
163
|
+
const perLine = Math.min(26, Math.max(14, Math.ceil(label.length / 2) + 2));
|
|
164
|
+
return wrapLabel(label, perLine, maxLines);
|
|
165
|
+
}
|
|
166
|
+
var FONT_FAMILY = "Helvetica, Arial, sans-serif";
|
|
167
|
+
|
|
168
|
+
// src/core/legend.ts
|
|
169
|
+
var LEGEND_ROW_H = 18;
|
|
170
|
+
var LEGEND_PAD = 16;
|
|
171
|
+
var LEGEND_SWATCH_W = 22;
|
|
172
|
+
var LEGEND_GAP = 14;
|
|
173
|
+
var LEGEND_FONT = 11;
|
|
174
|
+
var LEGEND_TEXT_FILL = "#52525b";
|
|
175
|
+
function legendBlock(entries, startY) {
|
|
176
|
+
if (entries.length === 0) return { svg: "", width: 0, height: startY };
|
|
177
|
+
const rows = entries.map((entry, i) => {
|
|
178
|
+
const rowCenterY = startY + i * LEGEND_ROW_H + LEGEND_ROW_H / 2;
|
|
179
|
+
const textX = LEGEND_PAD + LEGEND_SWATCH_W + LEGEND_GAP;
|
|
180
|
+
return entry.swatch(LEGEND_PAD, rowCenterY) + `<text x="${textX}" y="${rowCenterY + LEGEND_FONT * 0.32}" font-family="${FONT_FAMILY}" font-size="${LEGEND_FONT}" fill="${LEGEND_TEXT_FILL}">${xmlEscape(entry.label)}</text>`;
|
|
181
|
+
});
|
|
182
|
+
const widestLabel = entries.reduce((m, e) => Math.max(m, estimateTextWidth(e.label, LEGEND_FONT)), 0);
|
|
183
|
+
return {
|
|
184
|
+
svg: `<g data-compasso-legend="true">${rows.join("")}</g>`,
|
|
185
|
+
width: LEGEND_PAD + LEGEND_SWATCH_W + LEGEND_GAP + widestLabel + LEGEND_PAD,
|
|
186
|
+
height: startY + entries.length * LEGEND_ROW_H + LEGEND_PAD / 2
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// src/core/layered.ts
|
|
191
|
+
function packSubtree(node, gaps) {
|
|
192
|
+
const { ownHalfL, ownHalfR, children } = node;
|
|
193
|
+
if (children.length === 0) {
|
|
194
|
+
return { halfL: ownHalfL, halfR: ownHalfR, offsets: [] };
|
|
195
|
+
}
|
|
196
|
+
const xs = [0];
|
|
197
|
+
for (let i = 1; i < children.length; i++) {
|
|
198
|
+
xs.push(xs[i - 1] + children[i - 1].halfR + gaps.siblingGap + children[i].halfL);
|
|
199
|
+
}
|
|
200
|
+
const first = children[0];
|
|
201
|
+
const last = children[children.length - 1];
|
|
202
|
+
const axis = (xs[0] + xs[xs.length - 1]) / 2;
|
|
203
|
+
const offsets = xs.map((x) => x - axis);
|
|
204
|
+
const halfL = Math.max(ownHalfL, axis - (xs[0] - first.halfL));
|
|
205
|
+
const halfR = Math.max(ownHalfR, xs[xs.length - 1] + last.halfR - axis);
|
|
206
|
+
return { halfL, halfR, offsets };
|
|
207
|
+
}
|
|
208
|
+
function bandStack(rows, opts) {
|
|
209
|
+
const { padding, corridor } = opts;
|
|
210
|
+
if (rows.length === 0) {
|
|
211
|
+
return { rowTop: [], busY: () => 0, height: padding * 2 };
|
|
212
|
+
}
|
|
213
|
+
const rowTop = [padding];
|
|
214
|
+
for (let d = 0; d < rows.length - 1; d++) {
|
|
215
|
+
rowTop.push(rowTop[d] + rows[d].rowH + rows[d].zoneH + corridor);
|
|
216
|
+
}
|
|
217
|
+
const last = rows.length - 1;
|
|
218
|
+
const busY = (d) => rowTop[d + 1] - corridor / 2;
|
|
219
|
+
const height = rowTop[last] + rows[last].rowH + rows[last].zoneH + padding;
|
|
220
|
+
return { rowTop, busY, height };
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// src/core/glyph.ts
|
|
224
|
+
function dirVec(dir) {
|
|
225
|
+
switch (dir) {
|
|
226
|
+
case "right":
|
|
227
|
+
return [1, 0];
|
|
228
|
+
case "left":
|
|
229
|
+
return [-1, 0];
|
|
230
|
+
case "down":
|
|
231
|
+
return [0, 1];
|
|
232
|
+
case "up":
|
|
233
|
+
return [0, -1];
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
function r(n) {
|
|
237
|
+
return Math.round(n * 100) / 100;
|
|
238
|
+
}
|
|
239
|
+
function pt(x, y) {
|
|
240
|
+
return `${r(x)},${r(y)}`;
|
|
241
|
+
}
|
|
242
|
+
var GLYPH_ARROW_LEN = 10;
|
|
243
|
+
var GLYPH_ARROW_HALF = 5;
|
|
244
|
+
var GLYPH_TRI_LEN = 14;
|
|
245
|
+
var GLYPH_DIAMOND_LEN = 16;
|
|
246
|
+
function arrowFilledPoints(tip, dir, len = GLYPH_ARROW_LEN, half = GLYPH_ARROW_HALF) {
|
|
247
|
+
return _arrowPoints(tip, dir, len, half);
|
|
248
|
+
}
|
|
249
|
+
function _arrowPoints(tip, dir, len, half) {
|
|
250
|
+
const [ux, uy] = dirVec(dir);
|
|
251
|
+
const tx = tip.x;
|
|
252
|
+
const ty = tip.y;
|
|
253
|
+
const bx = tx - ux * len;
|
|
254
|
+
const by = ty - uy * len;
|
|
255
|
+
const px = -uy;
|
|
256
|
+
const py = ux;
|
|
257
|
+
return [
|
|
258
|
+
pt(tx, ty),
|
|
259
|
+
pt(bx + px * half, by + py * half),
|
|
260
|
+
pt(bx - px * half, by - py * half)
|
|
261
|
+
].join(" ");
|
|
262
|
+
}
|
|
263
|
+
function glyphInset(_dir, kind) {
|
|
264
|
+
switch (kind) {
|
|
265
|
+
case "arrow":
|
|
266
|
+
return GLYPH_ARROW_LEN;
|
|
267
|
+
case "triangle":
|
|
268
|
+
return GLYPH_TRI_LEN;
|
|
269
|
+
case "diamond":
|
|
270
|
+
return GLYPH_DIAMOND_LEN;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// src/core/compartment.ts
|
|
275
|
+
function round(n) {
|
|
276
|
+
return Math.round(n * 100) / 100;
|
|
277
|
+
}
|
|
278
|
+
function measureCompartmentBox(compartments, opts) {
|
|
279
|
+
const { padX, padY, lineH, minW } = opts;
|
|
280
|
+
let maxLineW = 0;
|
|
281
|
+
for (const c of compartments) {
|
|
282
|
+
for (const line of c.lines) {
|
|
283
|
+
const w = estimateTextWidth(line, c.font);
|
|
284
|
+
if (w > maxLineW) maxLineW = w;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
const boxW = round(Math.max(minW, maxLineW + 2 * padX));
|
|
288
|
+
const rows = [];
|
|
289
|
+
const dividerYs = [];
|
|
290
|
+
let y = 0;
|
|
291
|
+
for (let i = 0; i < compartments.length; i++) {
|
|
292
|
+
const c = compartments[i];
|
|
293
|
+
const lineCount = c.lines.length > 0 ? c.lines.length : 1;
|
|
294
|
+
const compartmentH = padY + lineCount * lineH + padY;
|
|
295
|
+
rows.push({
|
|
296
|
+
lines: c.lines,
|
|
297
|
+
top: round(y + padY),
|
|
298
|
+
font: c.font
|
|
299
|
+
});
|
|
300
|
+
y += compartmentH;
|
|
301
|
+
if (i < compartments.length - 1) {
|
|
302
|
+
dividerYs.push(round(y));
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
const boxH = round(y);
|
|
306
|
+
return { boxW, boxH, rows, dividerYs };
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// src/prisma/layout.ts
|
|
310
|
+
var PRISMA_PADDING = 32;
|
|
311
|
+
var PRISMA_BAND_LABEL_W = 40;
|
|
312
|
+
var PRISMA_COL_GAP = 56;
|
|
313
|
+
var PRISMA_CORRIDOR = 30;
|
|
314
|
+
var PRISMA_BOX_PAD_X = 12;
|
|
315
|
+
var PRISMA_BOX_PAD_Y = 10;
|
|
316
|
+
var PRISMA_LINE_H = 14;
|
|
317
|
+
var PRISMA_MIN_BOX_W = 200;
|
|
318
|
+
var PRISMA_EXCL_GAP = 48;
|
|
319
|
+
var PRISMA_HEADING_FONT = 12;
|
|
320
|
+
var PRISMA_COUNT_FONT = 10;
|
|
321
|
+
var PRISMA_ROUTE_MARGIN = 10;
|
|
322
|
+
var PRISMA_ROUTE_PITCH = 10;
|
|
323
|
+
var PRISMA_FLOW_ARROW_BASE = 1e6;
|
|
324
|
+
var PRISMA_EXCL_ARROW_BASE = 2e6;
|
|
325
|
+
var PRISMA_MERGE_ARROW_BASE = 3e6;
|
|
326
|
+
function round2(n) {
|
|
327
|
+
return Math.round(n * 100) / 100;
|
|
328
|
+
}
|
|
329
|
+
function wrapHeading(h) {
|
|
330
|
+
if (h === null || h === "") return [];
|
|
331
|
+
return wrapLabelBalanced(h);
|
|
332
|
+
}
|
|
333
|
+
function countLine(label, n, nullToken) {
|
|
334
|
+
const nStr = n === null ? nullToken : String(n);
|
|
335
|
+
return `${label}: n = ${nStr}`;
|
|
336
|
+
}
|
|
337
|
+
function buildCountLines(counts, nullToken) {
|
|
338
|
+
return counts.map((c) => countLine(c.label, c.n, nullToken));
|
|
339
|
+
}
|
|
340
|
+
function boxTitle(box, nullToken) {
|
|
341
|
+
if (box.title !== void 0) return box.title;
|
|
342
|
+
const parts = [];
|
|
343
|
+
if (box.heading !== null && box.heading !== "") parts.push(box.heading);
|
|
344
|
+
for (const c of box.counts) parts.push(countLine(c.label, c.n, nullToken));
|
|
345
|
+
return parts.join("; ") || "(box)";
|
|
346
|
+
}
|
|
347
|
+
function computePrismaLayout(input, opts = {}) {
|
|
348
|
+
if (opts.validate !== false) validatePrisma(input);
|
|
349
|
+
const labels = opts.labels ?? PRISMA_TITLE_LABELS_EN;
|
|
350
|
+
const nullToken = labels.nullCount;
|
|
351
|
+
const COMP_OPTS = {
|
|
352
|
+
padX: PRISMA_BOX_PAD_X,
|
|
353
|
+
padY: PRISMA_BOX_PAD_Y,
|
|
354
|
+
lineH: PRISMA_LINE_H,
|
|
355
|
+
minW: PRISMA_MIN_BOX_W};
|
|
356
|
+
const boxMetrics = /* @__PURE__ */ new Map();
|
|
357
|
+
for (const b of input.boxes) {
|
|
358
|
+
const headingLines = wrapHeading(b.heading);
|
|
359
|
+
const cLines = buildCountLines(b.counts, nullToken);
|
|
360
|
+
const metrics = measureCompartmentBox(
|
|
361
|
+
[
|
|
362
|
+
{ lines: headingLines, font: PRISMA_HEADING_FONT },
|
|
363
|
+
{ lines: cLines, font: PRISMA_COUNT_FONT }
|
|
364
|
+
],
|
|
365
|
+
COMP_OPTS
|
|
366
|
+
);
|
|
367
|
+
boxMetrics.set(b.id, {
|
|
368
|
+
boxW: metrics.boxW,
|
|
369
|
+
boxH: metrics.boxH,
|
|
370
|
+
rows: metrics.rows,
|
|
371
|
+
dividerYs: metrics.dividerYs,
|
|
372
|
+
headingLines,
|
|
373
|
+
countLines: cLines
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
const boxesByKey = /* @__PURE__ */ new Map();
|
|
377
|
+
for (const b of input.boxes) {
|
|
378
|
+
const key = `${b.phase}:${b.column}:${b.kind}`;
|
|
379
|
+
const arr = boxesByKey.get(key) ?? [];
|
|
380
|
+
arr.push(b);
|
|
381
|
+
boxesByKey.set(key, arr);
|
|
382
|
+
}
|
|
383
|
+
for (const arr of boxesByKey.values()) {
|
|
384
|
+
arr.sort((a, b) => a.rank !== b.rank ? a.rank - b.rank : a.id - b.id);
|
|
385
|
+
}
|
|
386
|
+
const groupOf = (phase, col, kind) => boxesByKey.get(`${phase}:${col}:${kind}`) ?? [];
|
|
387
|
+
const flowCols = input.variant === "flow-with-prior" ? ["new", "previous"] : input.variant === "flow-other-methods" ? ["main", "new"] : ["main"];
|
|
388
|
+
const contentLeft = PRISMA_PADDING + PRISMA_BAND_LABEL_W;
|
|
389
|
+
function maxColW(col, kind) {
|
|
390
|
+
let w = 0;
|
|
391
|
+
for (const phase of PRISMA_PHASES) {
|
|
392
|
+
for (const b of groupOf(phase, col, kind)) {
|
|
393
|
+
const bw = boxMetrics.get(b.id).boxW;
|
|
394
|
+
if (bw > w) w = bw;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
return w;
|
|
398
|
+
}
|
|
399
|
+
const colHasFlow = (col) => PRISMA_PHASES.some((phase) => groupOf(phase, col, "flow").length > 0);
|
|
400
|
+
const colHasExcl = (col) => PRISMA_PHASES.some((phase) => groupOf(phase, col, "exclusion").length > 0);
|
|
401
|
+
const exclOnlyCols = ["main", "new", "previous"].filter(
|
|
402
|
+
(col) => !flowCols.includes(col) && colHasExcl(col)
|
|
403
|
+
);
|
|
404
|
+
const placedCols = [...flowCols, ...exclOnlyCols];
|
|
405
|
+
const colGeom = /* @__PURE__ */ new Map();
|
|
406
|
+
for (const col of placedCols) {
|
|
407
|
+
const flowW = colHasFlow(col) ? Math.max(PRISMA_MIN_BOX_W, maxColW(col, "flow")) : 0;
|
|
408
|
+
const exclW = maxColW(col, "exclusion");
|
|
409
|
+
const blockW = flowW > 0 && exclW > 0 ? flowW + PRISMA_EXCL_GAP + exclW : flowW > 0 ? flowW : exclW;
|
|
410
|
+
colGeom.set(col, { flowW, exclW, blockW, flowCx: 0, exclCx: 0 });
|
|
411
|
+
}
|
|
412
|
+
function placeBlock(col, blockLeft) {
|
|
413
|
+
const g = colGeom.get(col);
|
|
414
|
+
if (g.flowW > 0) g.flowCx = round2(blockLeft + g.flowW / 2);
|
|
415
|
+
if (g.exclW > 0) {
|
|
416
|
+
const exclLeft = g.flowW > 0 ? blockLeft + g.flowW + PRISMA_EXCL_GAP : blockLeft;
|
|
417
|
+
g.exclCx = round2(exclLeft + g.exclW / 2);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
if (placedCols.length === 1) {
|
|
421
|
+
placeBlock(placedCols[0], contentLeft);
|
|
422
|
+
} else {
|
|
423
|
+
const packed = packSubtree(
|
|
424
|
+
{
|
|
425
|
+
ownHalfL: 0,
|
|
426
|
+
ownHalfR: 0,
|
|
427
|
+
children: placedCols.map((col) => {
|
|
428
|
+
const half = colGeom.get(col).blockW / 2;
|
|
429
|
+
return { halfL: half, halfR: half };
|
|
430
|
+
})
|
|
431
|
+
},
|
|
432
|
+
{ siblingGap: PRISMA_COL_GAP }
|
|
433
|
+
);
|
|
434
|
+
const totalW = packed.halfL + packed.halfR;
|
|
435
|
+
const blockCx = round2(contentLeft + totalW / 2);
|
|
436
|
+
placedCols.forEach((col, i) => {
|
|
437
|
+
const w = colGeom.get(col).blockW;
|
|
438
|
+
placeBlock(col, round2(blockCx + packed.offsets[i] - w / 2));
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
let totalRight = contentLeft;
|
|
442
|
+
for (const col of placedCols) {
|
|
443
|
+
const g = colGeom.get(col);
|
|
444
|
+
const r2 = g.exclW > 0 ? round2(g.exclCx + g.exclW / 2) : round2(g.flowCx + g.flowW / 2);
|
|
445
|
+
if (r2 > totalRight) totalRight = r2;
|
|
446
|
+
}
|
|
447
|
+
const rowsByCol = /* @__PURE__ */ new Map();
|
|
448
|
+
const rowsKey = (phase, col) => `${phase}:${col}`;
|
|
449
|
+
function buildRankRows(phase, col) {
|
|
450
|
+
const byRank = /* @__PURE__ */ new Map();
|
|
451
|
+
for (const kind of ["flow", "exclusion"]) {
|
|
452
|
+
for (const b of groupOf(phase, col, kind)) {
|
|
453
|
+
const h = boxMetrics.get(b.id).boxH;
|
|
454
|
+
const prev = byRank.get(b.rank) ?? 0;
|
|
455
|
+
if (h > prev) byRank.set(b.rank, h);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
const ranks = [...byRank.keys()].sort((a, b) => a - b);
|
|
459
|
+
return ranks.map((rank) => ({ rank, rowH: byRank.get(rank), top: 0 }));
|
|
460
|
+
}
|
|
461
|
+
for (const phase of PRISMA_PHASES) {
|
|
462
|
+
for (const col of placedCols) {
|
|
463
|
+
rowsByCol.set(rowsKey(phase, col), buildRankRows(phase, col));
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
function colStackH(rows) {
|
|
467
|
+
if (rows.length === 0) return 0;
|
|
468
|
+
let h = 0;
|
|
469
|
+
for (const r2 of rows) h += r2.rowH + PRISMA_BOX_PAD_Y;
|
|
470
|
+
return h - PRISMA_BOX_PAD_Y;
|
|
471
|
+
}
|
|
472
|
+
function bandRowH(phase) {
|
|
473
|
+
let maxH = 0;
|
|
474
|
+
let any = false;
|
|
475
|
+
for (const col of placedCols) {
|
|
476
|
+
const rows = rowsByCol.get(rowsKey(phase, col));
|
|
477
|
+
if (rows.length > 0) any = true;
|
|
478
|
+
const h = colStackH(rows);
|
|
479
|
+
if (h > maxH) maxH = h;
|
|
480
|
+
}
|
|
481
|
+
if (!any) return PRISMA_LINE_H * 2 + PRISMA_BOX_PAD_Y * 4;
|
|
482
|
+
return maxH;
|
|
483
|
+
}
|
|
484
|
+
const bandRowHs = PRISMA_PHASES.map((phase) => ({
|
|
485
|
+
rowH: bandRowH(phase),
|
|
486
|
+
zoneH: 0
|
|
487
|
+
}));
|
|
488
|
+
const stacked = bandStack(bandRowHs, { padding: PRISMA_PADDING, corridor: PRISMA_CORRIDOR });
|
|
489
|
+
const layoutBoxes = [];
|
|
490
|
+
const lboxById = /* @__PURE__ */ new Map();
|
|
491
|
+
function placeBox(b, phase, kind, cx, top) {
|
|
492
|
+
const m = boxMetrics.get(b.id);
|
|
493
|
+
const t = round2(top);
|
|
494
|
+
const title = boxTitle(b, nullToken);
|
|
495
|
+
const lbox = {
|
|
496
|
+
id: b.id,
|
|
497
|
+
phase,
|
|
498
|
+
kind,
|
|
499
|
+
cx: round2(cx),
|
|
500
|
+
top: t,
|
|
501
|
+
boxW: m.boxW,
|
|
502
|
+
boxH: m.boxH,
|
|
503
|
+
rows: m.rows.map((r2) => ({ ...r2, top: round2(t + r2.top) })),
|
|
504
|
+
dividerYs: m.dividerYs.map((d) => round2(t + d)),
|
|
505
|
+
heading: b.heading,
|
|
506
|
+
countLines: m.countLines,
|
|
507
|
+
title
|
|
508
|
+
};
|
|
509
|
+
layoutBoxes.push(lbox);
|
|
510
|
+
lboxById.set(b.id, lbox);
|
|
511
|
+
}
|
|
512
|
+
for (let phaseIdx = 0; phaseIdx < PRISMA_PHASES.length; phaseIdx++) {
|
|
513
|
+
const phase = PRISMA_PHASES[phaseIdx];
|
|
514
|
+
const bandTop = stacked.rowTop[phaseIdx];
|
|
515
|
+
for (const col of placedCols) {
|
|
516
|
+
const g = colGeom.get(col);
|
|
517
|
+
const rows = rowsByCol.get(rowsKey(phase, col));
|
|
518
|
+
const topByRank = /* @__PURE__ */ new Map();
|
|
519
|
+
let y = bandTop;
|
|
520
|
+
for (const r2 of rows) {
|
|
521
|
+
r2.top = round2(y);
|
|
522
|
+
topByRank.set(r2.rank, r2.top);
|
|
523
|
+
y = round2(y + r2.rowH + PRISMA_BOX_PAD_Y);
|
|
524
|
+
}
|
|
525
|
+
if (g.flowW > 0) {
|
|
526
|
+
for (const b of groupOf(phase, col, "flow")) {
|
|
527
|
+
placeBox(b, phase, "flow", g.flowCx, topByRank.get(b.rank));
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
if (g.exclW > 0) {
|
|
531
|
+
for (const b of groupOf(phase, col, "exclusion")) {
|
|
532
|
+
placeBox(b, phase, "exclusion", g.exclCx, topByRank.get(b.rank));
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
const elements = [];
|
|
538
|
+
const boxById = new Map(input.boxes.map((b) => [b.id, b]));
|
|
539
|
+
const phaseIndexMap = new Map(
|
|
540
|
+
PRISMA_PHASES.map((p, i) => [p, i])
|
|
541
|
+
);
|
|
542
|
+
function boxBottom(lb) {
|
|
543
|
+
return round2(lb.top + lb.boxH);
|
|
544
|
+
}
|
|
545
|
+
function bandBottom(phase) {
|
|
546
|
+
const idx = PRISMA_PHASES.indexOf(phase);
|
|
547
|
+
return round2(stacked.rowTop[idx] + bandRowHs[idx].rowH);
|
|
548
|
+
}
|
|
549
|
+
const arrowsToBox = /* @__PURE__ */ new Map();
|
|
550
|
+
for (const arrow of input.arrows) {
|
|
551
|
+
const toBox = boxById.get(arrow.toId);
|
|
552
|
+
const fromBox = boxById.get(arrow.fromId);
|
|
553
|
+
if (toBox === void 0 || fromBox === void 0) continue;
|
|
554
|
+
if (fromBox.kind === "flow" && toBox.kind === "flow") {
|
|
555
|
+
const arr = arrowsToBox.get(arrow.toId) ?? [];
|
|
556
|
+
arr.push(arrow.id);
|
|
557
|
+
arrowsToBox.set(arrow.toId, arr);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
const arrowToBoxArrivalIdx = /* @__PURE__ */ new Map();
|
|
561
|
+
const exclArrowIndexBySource = /* @__PURE__ */ new Map();
|
|
562
|
+
for (const arrow of input.arrows) {
|
|
563
|
+
const fromLBox = lboxById.get(arrow.fromId);
|
|
564
|
+
const toLBox = lboxById.get(arrow.toId);
|
|
565
|
+
if (fromLBox === void 0 || toLBox === void 0) continue;
|
|
566
|
+
const fromBox = boxById.get(arrow.fromId);
|
|
567
|
+
const toBox = boxById.get(arrow.toId);
|
|
568
|
+
const isExclusion = fromBox.kind === "flow" && toBox.kind === "exclusion";
|
|
569
|
+
const isFlowToFlow = fromBox.kind === "flow" && toBox.kind === "flow";
|
|
570
|
+
if (isExclusion) {
|
|
571
|
+
const idx = exclArrowIndexBySource.get(arrow.fromId) ?? 0;
|
|
572
|
+
exclArrowIndexBySource.set(arrow.fromId, idx + 1);
|
|
573
|
+
const srcRight = round2(fromLBox.cx + fromLBox.boxW / 2);
|
|
574
|
+
const exclMidY = round2(toLBox.top + toLBox.boxH / 2);
|
|
575
|
+
const eLeft = round2(toLBox.cx - toLBox.boxW / 2);
|
|
576
|
+
const sameColumn = fromBox.column === toBox.column;
|
|
577
|
+
let points;
|
|
578
|
+
if (sameColumn) {
|
|
579
|
+
points = [
|
|
580
|
+
{ x: round2(srcRight), y: round2(exclMidY) },
|
|
581
|
+
{ x: round2(eLeft), y: round2(exclMidY) }
|
|
582
|
+
];
|
|
583
|
+
} else {
|
|
584
|
+
const colGapStep = round2(PRISMA_COL_GAP / 8);
|
|
585
|
+
const exclGapStep = round2(PRISMA_EXCL_GAP / 8);
|
|
586
|
+
const fenceX = round2(srcRight + (idx + 1) * colGapStep);
|
|
587
|
+
const exclFenceX = round2(eLeft - (idx + 1) * exclGapStep);
|
|
588
|
+
const bBotY = bandBottom(fromBox.phase);
|
|
589
|
+
const corridorY = round2(bBotY + PRISMA_ROUTE_MARGIN + idx * PRISMA_ROUTE_PITCH);
|
|
590
|
+
points = [
|
|
591
|
+
{ x: round2(srcRight), y: round2(exclMidY) },
|
|
592
|
+
// exit source right at excl center y
|
|
593
|
+
{ x: round2(fenceX), y: round2(exclMidY) },
|
|
594
|
+
// horizontal into inter-col gap (unique y)
|
|
595
|
+
{ x: round2(fenceX), y: round2(corridorY) },
|
|
596
|
+
// vertical at unique x in inter-col gap
|
|
597
|
+
{ x: round2(exclFenceX), y: round2(corridorY) },
|
|
598
|
+
// horizontal in corridor (unique y)
|
|
599
|
+
{ x: round2(exclFenceX), y: round2(exclMidY) },
|
|
600
|
+
// vertical at unique x left of excl box
|
|
601
|
+
{ x: round2(eLeft), y: round2(exclMidY) }
|
|
602
|
+
// horizontal into excl box (unique y)
|
|
603
|
+
];
|
|
604
|
+
}
|
|
605
|
+
elements.push({
|
|
606
|
+
edgeId: PRISMA_EXCL_ARROW_BASE + arrow.id,
|
|
607
|
+
kind: "exclusion",
|
|
608
|
+
points,
|
|
609
|
+
title: `${labels.arrowKinds.exclusion}: ${fromLBox.title} \u2192 ${toLBox.title}`,
|
|
610
|
+
headDir: "right"
|
|
611
|
+
});
|
|
612
|
+
} else if (isFlowToFlow) {
|
|
613
|
+
const fromPhaseIdx = phaseIndexMap.get(fromBox.phase);
|
|
614
|
+
const toPhaseIdx = phaseIndexMap.get(toBox.phase);
|
|
615
|
+
const isSamePhase = fromBox.phase === toBox.phase;
|
|
616
|
+
const isSameCol = fromBox.column === toBox.column;
|
|
617
|
+
const isForward = toPhaseIdx >= fromPhaseIdx;
|
|
618
|
+
const arrivals = arrowsToBox.get(arrow.toId) ?? [];
|
|
619
|
+
let arrivalIdx = arrowToBoxArrivalIdx.get(arrow.id) ?? 0;
|
|
620
|
+
if (!arrowToBoxArrivalIdx.has(arrow.id)) {
|
|
621
|
+
arrivalIdx = arrivals.indexOf(arrow.id);
|
|
622
|
+
arrowToBoxArrivalIdx.set(arrow.id, arrivalIdx);
|
|
623
|
+
}
|
|
624
|
+
const nArrivals = arrivals.length;
|
|
625
|
+
const APPROACH_SPREAD = round2(toLBox.boxW / 4);
|
|
626
|
+
const spreadStep = nArrivals > 1 ? round2(APPROACH_SPREAD / (nArrivals - 1)) : 0;
|
|
627
|
+
const approachX = round2(toLBox.cx - APPROACH_SPREAD / 2 + arrivalIdx * spreadStep);
|
|
628
|
+
const toTop = toLBox.top;
|
|
629
|
+
if (isSamePhase && isSameCol) {
|
|
630
|
+
const fromBottom = boxBottom(fromLBox);
|
|
631
|
+
const fromCx = round2(fromLBox.cx);
|
|
632
|
+
const toCx = round2(toLBox.cx);
|
|
633
|
+
const dropX = fromCx === toCx ? fromCx : toCx;
|
|
634
|
+
elements.push({
|
|
635
|
+
edgeId: PRISMA_FLOW_ARROW_BASE + arrow.id,
|
|
636
|
+
kind: "flow",
|
|
637
|
+
points: [
|
|
638
|
+
{ x: fromCx, y: round2(fromBottom) },
|
|
639
|
+
{ x: dropX, y: round2(toTop) }
|
|
640
|
+
],
|
|
641
|
+
title: `${labels.arrowKinds.flow}: ${fromLBox.title} \u2192 ${toLBox.title}`,
|
|
642
|
+
headDir: "down"
|
|
643
|
+
});
|
|
644
|
+
} else if (isSameCol && isForward) {
|
|
645
|
+
const fromBottom = boxBottom(fromLBox);
|
|
646
|
+
if (nArrivals <= 1 && round2(fromLBox.cx) === round2(toLBox.cx)) {
|
|
647
|
+
elements.push({
|
|
648
|
+
edgeId: PRISMA_FLOW_ARROW_BASE + arrow.id,
|
|
649
|
+
kind: "flow",
|
|
650
|
+
points: [
|
|
651
|
+
{ x: round2(fromLBox.cx), y: round2(fromBottom) },
|
|
652
|
+
{ x: round2(toLBox.cx), y: round2(toTop) }
|
|
653
|
+
],
|
|
654
|
+
title: `${labels.arrowKinds.flow}: ${fromLBox.title} \u2192 ${toLBox.title}`,
|
|
655
|
+
headDir: "down"
|
|
656
|
+
});
|
|
657
|
+
} else {
|
|
658
|
+
const phIdx = phaseIndexMap.get(fromBox.phase);
|
|
659
|
+
const elbowY = round2(stacked.rowTop[phIdx] + bandRowHs[phIdx].rowH + PRISMA_CORRIDOR / 2);
|
|
660
|
+
elements.push({
|
|
661
|
+
edgeId: PRISMA_FLOW_ARROW_BASE + arrow.id,
|
|
662
|
+
kind: "flow",
|
|
663
|
+
points: [
|
|
664
|
+
{ x: round2(fromLBox.cx), y: round2(fromBottom) },
|
|
665
|
+
{ x: round2(fromLBox.cx), y: round2(elbowY) },
|
|
666
|
+
{ x: round2(approachX), y: round2(elbowY) },
|
|
667
|
+
{ x: round2(approachX), y: round2(toTop) }
|
|
668
|
+
],
|
|
669
|
+
title: `${labels.arrowKinds.flow}: ${fromLBox.title} \u2192 ${toLBox.title}`,
|
|
670
|
+
headDir: "down"
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
} else {
|
|
674
|
+
const fromBottom = boxBottom(fromLBox);
|
|
675
|
+
const phIdx = phaseIndexMap.get(fromBox.phase);
|
|
676
|
+
const elbowY = round2(stacked.rowTop[phIdx] + bandRowHs[phIdx].rowH + PRISMA_CORRIDOR / 2);
|
|
677
|
+
const fromCx = round2(fromLBox.cx);
|
|
678
|
+
if (round2(fromCx) === round2(approachX)) {
|
|
679
|
+
elements.push({
|
|
680
|
+
edgeId: PRISMA_MERGE_ARROW_BASE + arrow.id,
|
|
681
|
+
kind: "merge",
|
|
682
|
+
points: [
|
|
683
|
+
{ x: fromCx, y: round2(fromBottom) },
|
|
684
|
+
{ x: round2(approachX), y: round2(toTop) }
|
|
685
|
+
],
|
|
686
|
+
title: `${labels.arrowKinds.merge}: ${fromLBox.title} \u2192 ${toLBox.title}`,
|
|
687
|
+
headDir: "down"
|
|
688
|
+
});
|
|
689
|
+
} else {
|
|
690
|
+
elements.push({
|
|
691
|
+
edgeId: PRISMA_MERGE_ARROW_BASE + arrow.id,
|
|
692
|
+
kind: "merge",
|
|
693
|
+
points: [
|
|
694
|
+
{ x: fromCx, y: round2(fromBottom) },
|
|
695
|
+
{ x: fromCx, y: round2(elbowY) },
|
|
696
|
+
{ x: round2(approachX), y: round2(elbowY) },
|
|
697
|
+
{ x: round2(approachX), y: round2(toTop) }
|
|
698
|
+
],
|
|
699
|
+
title: `${labels.arrowKinds.merge}: ${fromLBox.title} \u2192 ${toLBox.title}`,
|
|
700
|
+
headDir: "down"
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
const canvasH = stacked.height;
|
|
707
|
+
const canvasW = round2(totalRight + PRISMA_PADDING);
|
|
708
|
+
const bands = PRISMA_PHASES.map((phase, i) => {
|
|
709
|
+
const rowTop = stacked.rowTop[i];
|
|
710
|
+
const rowH = bandRowHs[i].rowH;
|
|
711
|
+
return {
|
|
712
|
+
phase,
|
|
713
|
+
rowTop,
|
|
714
|
+
rowH,
|
|
715
|
+
labelX: PRISMA_PADDING,
|
|
716
|
+
centerY: round2(rowTop + rowH / 2)
|
|
717
|
+
};
|
|
718
|
+
});
|
|
719
|
+
return {
|
|
720
|
+
width: canvasW,
|
|
721
|
+
height: canvasH,
|
|
722
|
+
boxes: layoutBoxes,
|
|
723
|
+
elements,
|
|
724
|
+
bands,
|
|
725
|
+
variant: input.variant
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
// src/prisma/svg.ts
|
|
730
|
+
var BOX_STROKE = "#52525b";
|
|
731
|
+
var BOX_STROKE_W = 2;
|
|
732
|
+
var BOX_FILL = "#fff";
|
|
733
|
+
var BOX_RX = 2;
|
|
734
|
+
var HEADING_FILL = "#3f3f46";
|
|
735
|
+
var COUNT_FILL = "#52525b";
|
|
736
|
+
var ARROW_STROKE = "#71717a";
|
|
737
|
+
var ARROW_STROKE_W = 1.5;
|
|
738
|
+
var ARROW_FILL = "#71717a";
|
|
739
|
+
var BAND_LABEL_FILL = "#52525b";
|
|
740
|
+
var BAND_LABEL_FONT = 11;
|
|
741
|
+
function round3(n) {
|
|
742
|
+
return Math.round(n * 100) / 100;
|
|
743
|
+
}
|
|
744
|
+
function emitBox(b) {
|
|
745
|
+
const x = round3(b.cx - b.boxW / 2);
|
|
746
|
+
const y = b.top;
|
|
747
|
+
const parts = [];
|
|
748
|
+
parts.push(
|
|
749
|
+
`<rect x="${x}" y="${y}" width="${b.boxW}" height="${b.boxH}" rx="${BOX_RX}" fill="${BOX_FILL}" stroke="${BOX_STROKE}" stroke-width="${BOX_STROKE_W}"/>`
|
|
750
|
+
);
|
|
751
|
+
for (const dy of b.dividerYs) {
|
|
752
|
+
parts.push(
|
|
753
|
+
`<line x1="${x}" y1="${dy}" x2="${round3(x + b.boxW)}" y2="${dy}" stroke="${BOX_STROKE}" stroke-width="1"/>`
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
for (let ri = 0; ri < b.rows.length; ri++) {
|
|
757
|
+
const row = b.rows[ri];
|
|
758
|
+
const isHeading = ri === 0;
|
|
759
|
+
const font = row.font;
|
|
760
|
+
const fill = isHeading ? HEADING_FILL : COUNT_FILL;
|
|
761
|
+
const textX = isHeading ? round3(b.cx) : round3(x + 8);
|
|
762
|
+
for (let li = 0; li < row.lines.length; li++) {
|
|
763
|
+
const line = row.lines[li];
|
|
764
|
+
const textY = round3(row.top + li * 14 + font);
|
|
765
|
+
const anchorAttr = isHeading ? ` text-anchor="middle"` : "";
|
|
766
|
+
const weightAttr = isHeading ? ` font-weight="bold"` : "";
|
|
767
|
+
parts.push(
|
|
768
|
+
`<text x="${textX}" y="${textY}" font-family="${FONT_FAMILY}" font-size="${font}" fill="${fill}"${anchorAttr}${weightAttr}>${xmlEscape(line)}</text>`
|
|
769
|
+
);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
return parts.join("");
|
|
773
|
+
}
|
|
774
|
+
function emitArrow(el) {
|
|
775
|
+
const points = el.points;
|
|
776
|
+
if (points.length < 2) return "";
|
|
777
|
+
const inset = glyphInset(el.headDir, "arrow");
|
|
778
|
+
const last = points[points.length - 1];
|
|
779
|
+
let lineEndX = last.x;
|
|
780
|
+
let lineEndY = last.y;
|
|
781
|
+
if (el.headDir === "down") lineEndY = round3(last.y - inset);
|
|
782
|
+
else if (el.headDir === "right") lineEndX = round3(last.x - inset);
|
|
783
|
+
else if (el.headDir === "left") lineEndX = round3(last.x + inset);
|
|
784
|
+
else lineEndY = round3(last.y + inset);
|
|
785
|
+
const linePoints = [
|
|
786
|
+
...points.slice(0, points.length - 1).map((p) => ({ x: p.x, y: p.y })),
|
|
787
|
+
{ x: lineEndX, y: lineEndY }
|
|
788
|
+
];
|
|
789
|
+
const pathStr = pathData(linePoints);
|
|
790
|
+
const arrowPts = arrowFilledPoints({ x: last.x, y: last.y }, el.headDir);
|
|
791
|
+
return `<title>${xmlEscape(el.title)}</title><path d="${pathStr}" stroke="${ARROW_STROKE}" stroke-width="${ARROW_STROKE_W}" fill="none"/><polygon points="${arrowPts}" fill="${ARROW_FILL}" stroke="none"/>`;
|
|
792
|
+
}
|
|
793
|
+
function emitBandLabel(phase, labelX, centerY, bandTitleLabels) {
|
|
794
|
+
const text = bandTitleLabels.phases[phase];
|
|
795
|
+
const gutterCx = round3(labelX + PRISMA_BAND_LABEL_W / 2);
|
|
796
|
+
return `<text x="${gutterCx}" y="${round3(centerY)}" font-family="${FONT_FAMILY}" font-size="${BAND_LABEL_FONT}" fill="${BAND_LABEL_FILL}" text-anchor="middle" transform="rotate(-90,${gutterCx},${round3(centerY)})">${xmlEscape(text)}</text>`;
|
|
797
|
+
}
|
|
798
|
+
function buildLegendEntries(layout, svgLabels) {
|
|
799
|
+
const hasFlow = layout.boxes.some((b) => b.kind === "flow");
|
|
800
|
+
const hasExcl = layout.boxes.some((b) => b.kind === "exclusion");
|
|
801
|
+
const entries = [];
|
|
802
|
+
if (hasFlow) {
|
|
803
|
+
entries.push({
|
|
804
|
+
label: svgLabels.legend.flow,
|
|
805
|
+
swatch: (x, cy) => `<rect x="${x}" y="${round3(cy - 6)}" width="22" height="12" rx="2" fill="${BOX_FILL}" stroke="${BOX_STROKE}" stroke-width="2"/>`
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
if (hasExcl) {
|
|
809
|
+
entries.push({
|
|
810
|
+
label: svgLabels.legend.exclusion,
|
|
811
|
+
swatch: (x, cy) => `<rect x="${x}" y="${round3(cy - 6)}" width="22" height="12" rx="2" fill="${BOX_FILL}" stroke="${BOX_STROKE}" stroke-width="2" stroke-dasharray="4,2"/>`
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
return entries;
|
|
815
|
+
}
|
|
816
|
+
function prismaLayoutSvg(layout, opts = {}) {
|
|
817
|
+
const svgLabels = opts.svgLabels ?? PRISMA_SVG_LABELS_EN;
|
|
818
|
+
const bandTitleLabels = opts.titleLabels ?? PRISMA_TITLE_LABELS_EN;
|
|
819
|
+
const doLegend = opts.legend !== false;
|
|
820
|
+
const parts = [];
|
|
821
|
+
for (const band of layout.bands) {
|
|
822
|
+
parts.push(emitBandLabel(band.phase, band.labelX, band.centerY, bandTitleLabels));
|
|
823
|
+
}
|
|
824
|
+
for (const b of layout.boxes) {
|
|
825
|
+
const boxSvg = emitBox(b);
|
|
826
|
+
parts.push(`<g data-node-id="b${b.id}"><title>${xmlEscape(b.title)}</title>${boxSvg}</g>`);
|
|
827
|
+
}
|
|
828
|
+
for (const el of layout.elements) {
|
|
829
|
+
const arrowSvg = emitArrow(el);
|
|
830
|
+
parts.push(`<g data-edge-id="${el.edgeId}">${arrowSvg}</g>`);
|
|
831
|
+
}
|
|
832
|
+
let w = layout.width;
|
|
833
|
+
let h = layout.height;
|
|
834
|
+
let legendSvg = "";
|
|
835
|
+
if (doLegend) {
|
|
836
|
+
const entries = buildLegendEntries(layout, svgLabels);
|
|
837
|
+
const lb = legendBlock(entries, h);
|
|
838
|
+
legendSvg = lb.svg;
|
|
839
|
+
if (entries.length > 0) {
|
|
840
|
+
w = Math.max(w, lb.width + 32 * 2);
|
|
841
|
+
h = lb.height;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
w = round3(w);
|
|
845
|
+
h = round3(h);
|
|
846
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}" role="img" aria-label="${xmlEscape(svgLabels.ariaLabel)}">` + parts.join("") + legendSvg + `</svg>`;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
// src/prisma/render.ts
|
|
850
|
+
function prismaSvg(input, opts = {}) {
|
|
851
|
+
const layout = computePrismaLayout(input, opts);
|
|
852
|
+
const svg = prismaLayoutSvg(layout, opts);
|
|
853
|
+
return { svg, layout };
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
exports.PRISMA_BAND_LABEL_W = PRISMA_BAND_LABEL_W;
|
|
857
|
+
exports.PRISMA_BOX_KINDS = PRISMA_BOX_KINDS;
|
|
858
|
+
exports.PRISMA_BOX_PAD_X = PRISMA_BOX_PAD_X;
|
|
859
|
+
exports.PRISMA_BOX_PAD_Y = PRISMA_BOX_PAD_Y;
|
|
860
|
+
exports.PRISMA_COLUMNS = PRISMA_COLUMNS;
|
|
861
|
+
exports.PRISMA_COL_GAP = PRISMA_COL_GAP;
|
|
862
|
+
exports.PRISMA_CORRIDOR = PRISMA_CORRIDOR;
|
|
863
|
+
exports.PRISMA_COUNT_FONT = PRISMA_COUNT_FONT;
|
|
864
|
+
exports.PRISMA_EXCL_ARROW_BASE = PRISMA_EXCL_ARROW_BASE;
|
|
865
|
+
exports.PRISMA_EXCL_GAP = PRISMA_EXCL_GAP;
|
|
866
|
+
exports.PRISMA_FLOW_ARROW_BASE = PRISMA_FLOW_ARROW_BASE;
|
|
867
|
+
exports.PRISMA_HEADING_FONT = PRISMA_HEADING_FONT;
|
|
868
|
+
exports.PRISMA_LINE_H = PRISMA_LINE_H;
|
|
869
|
+
exports.PRISMA_MERGE_ARROW_BASE = PRISMA_MERGE_ARROW_BASE;
|
|
870
|
+
exports.PRISMA_MIN_BOX_W = PRISMA_MIN_BOX_W;
|
|
871
|
+
exports.PRISMA_PADDING = PRISMA_PADDING;
|
|
872
|
+
exports.PRISMA_PHASES = PRISMA_PHASES;
|
|
873
|
+
exports.PRISMA_SVG_LABELS_EN = PRISMA_SVG_LABELS_EN;
|
|
874
|
+
exports.PRISMA_TITLE_LABELS_EN = PRISMA_TITLE_LABELS_EN;
|
|
875
|
+
exports.PrismaValidationError = PrismaValidationError;
|
|
876
|
+
exports.computePrismaLayout = computePrismaLayout;
|
|
877
|
+
exports.prismaIssues = prismaIssues;
|
|
878
|
+
exports.prismaLayoutSvg = prismaLayoutSvg;
|
|
879
|
+
exports.prismaSvg = prismaSvg;
|
|
880
|
+
exports.validatePrisma = validatePrisma;
|
|
881
|
+
//# sourceMappingURL=index.cjs.map
|
|
882
|
+
//# sourceMappingURL=index.cjs.map
|