compasso 0.1.0 → 0.3.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 +126 -8
- package/dist/chunk-F47C6ZEB.js +1041 -0
- package/dist/chunk-F47C6ZEB.js.map +1 -0
- package/dist/chunk-JP4N42AY.js +497 -0
- package/dist/chunk-JP4N42AY.js.map +1 -0
- package/dist/chunk-LRHHUJFZ.js +703 -0
- package/dist/chunk-LRHHUJFZ.js.map +1 -0
- package/dist/{chunk-E456YKAJ.js → chunk-O3BT2O42.js} +69 -10
- package/dist/chunk-O3BT2O42.js.map +1 -0
- package/dist/{chunk-L5CYESBI.js → chunk-Q6DVTCXD.js} +9 -24
- package/dist/chunk-Q6DVTCXD.js.map +1 -0
- package/dist/{chunk-5RRRE2GF.js → chunk-RWPGGWO5.js} +9 -28
- package/dist/chunk-RWPGGWO5.js.map +1 -0
- package/dist/chunk-ZBDABVIO.js +252 -0
- package/dist/chunk-ZBDABVIO.js.map +1 -0
- package/dist/core/index.cjs +74 -7
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +33 -29
- package/dist/core/index.d.ts +33 -29
- package/dist/core/index.js +1 -1
- package/dist/ecomap/index.cjs +43 -28
- package/dist/ecomap/index.cjs.map +1 -1
- package/dist/ecomap/index.js +2 -2
- package/dist/fault-tree/index.cjs +782 -0
- package/dist/fault-tree/index.cjs.map +1 -0
- package/dist/fault-tree/index.d.cts +148 -0
- package/dist/fault-tree/index.d.ts +148 -0
- package/dist/fault-tree/index.js +4 -0
- package/dist/fault-tree/index.js.map +1 -0
- package/dist/fishbone/index.cjs +314 -0
- package/dist/fishbone/index.cjs.map +1 -0
- package/dist/fishbone/index.d.cts +91 -0
- package/dist/fishbone/index.d.ts +91 -0
- package/dist/fishbone/index.js +4 -0
- package/dist/fishbone/index.js.map +1 -0
- package/dist/genogram/index.cjs +47 -32
- package/dist/genogram/index.cjs.map +1 -1
- package/dist/genogram/index.d.cts +7 -4
- package/dist/genogram/index.d.ts +7 -4
- package/dist/genogram/index.js +2 -2
- package/dist/index.cjs +2622 -55
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -2
- package/dist/index.d.ts +12 -2
- package/dist/index.js +7 -3
- package/dist/kinship-DqEklrDN.d.ts +51 -0
- package/dist/kinship-Dy_ijjJV.d.cts +51 -0
- package/dist/labels-CBQ_3Ec9.d.cts +123 -0
- package/dist/labels-CYbM5XV7.d.cts +83 -0
- package/dist/labels-CYbM5XV7.d.ts +83 -0
- package/dist/labels-DNqRkWuI.d.ts +123 -0
- package/dist/labels-iZjijjtK.d.cts +64 -0
- package/dist/labels-iZjijjtK.d.ts +64 -0
- package/dist/locales/pt-br.cjs +94 -0
- package/dist/locales/pt-br.cjs.map +1 -1
- package/dist/locales/pt-br.d.cts +14 -2
- package/dist/locales/pt-br.d.ts +14 -2
- package/dist/locales/pt-br.js +88 -1
- package/dist/locales/pt-br.js.map +1 -1
- package/dist/pedigree/index.cjs +1151 -0
- package/dist/pedigree/index.cjs.map +1 -0
- package/dist/pedigree/index.d.cts +155 -0
- package/dist/pedigree/index.d.ts +155 -0
- package/dist/pedigree/index.js +4 -0
- package/dist/pedigree/index.js.map +1 -0
- package/dist/phylo/index.cjs +553 -0
- package/dist/phylo/index.cjs.map +1 -0
- package/dist/phylo/index.d.cts +158 -0
- package/dist/phylo/index.d.ts +158 -0
- package/dist/phylo/index.js +4 -0
- package/dist/phylo/index.js.map +1 -0
- package/dist/text-DuO_PwYw.d.cts +45 -0
- package/dist/text-DuO_PwYw.d.ts +45 -0
- package/dist/types-BnMG7TCd.d.cts +66 -0
- package/dist/types-BnMG7TCd.d.ts +66 -0
- package/dist/xml-DDae1eUr.d.cts +4 -0
- package/dist/xml-DDae1eUr.d.ts +4 -0
- package/package.json +100 -26
- package/dist/chunk-5RRRE2GF.js.map +0 -1
- package/dist/chunk-E456YKAJ.js.map +0 -1
- package/dist/chunk-L5CYESBI.js.map +0 -1
- package/dist/kinship-BARO5-qz.d.cts +0 -115
- package/dist/kinship-Bkf87Jhu.d.ts +0 -115
|
@@ -0,0 +1,703 @@
|
|
|
1
|
+
import { legendBlock, xmlEscape, FONT_FAMILY, LEGEND_SWATCH_W, wrapLabelBalanced, clampLabel, estimateTextWidth, wrapLabel, CHAR_W } from './chunk-O3BT2O42.js';
|
|
2
|
+
|
|
3
|
+
// src/fault-tree/types.ts
|
|
4
|
+
var FAULT_TREE_EVENT_KINDS = [
|
|
5
|
+
"intermediate",
|
|
6
|
+
"basic",
|
|
7
|
+
"undeveloped",
|
|
8
|
+
"house",
|
|
9
|
+
"conditioning",
|
|
10
|
+
"transfer"
|
|
11
|
+
];
|
|
12
|
+
var GATE_TYPES = ["and", "or", "xor", "inhibit", "vote"];
|
|
13
|
+
|
|
14
|
+
// src/fault-tree/labels.ts
|
|
15
|
+
var FAULT_TREE_TITLE_LABELS_EN = {
|
|
16
|
+
gates: {
|
|
17
|
+
and: "AND gate",
|
|
18
|
+
or: "OR gate",
|
|
19
|
+
xor: "Exclusive-OR gate",
|
|
20
|
+
inhibit: "Inhibit gate",
|
|
21
|
+
vote: "Voting gate"
|
|
22
|
+
},
|
|
23
|
+
condition: "condition"
|
|
24
|
+
};
|
|
25
|
+
var FAULT_TREE_SVG_LABELS_EN = {
|
|
26
|
+
events: {
|
|
27
|
+
intermediate: "Intermediate event",
|
|
28
|
+
basic: "Basic event",
|
|
29
|
+
undeveloped: "Undeveloped event",
|
|
30
|
+
house: "External event (normally occurs)",
|
|
31
|
+
conditioning: "Conditioning event",
|
|
32
|
+
transfer: "Transfer (developed elsewhere)"
|
|
33
|
+
},
|
|
34
|
+
gates: {
|
|
35
|
+
and: "AND gate (all inputs)",
|
|
36
|
+
or: "OR gate (any input)",
|
|
37
|
+
xor: "Exclusive-OR gate (exactly one input)",
|
|
38
|
+
inhibit: "INHIBIT gate (input under condition)",
|
|
39
|
+
vote: "Voting gate (k of n)"
|
|
40
|
+
},
|
|
41
|
+
ariaLabel: "Fault tree"
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// src/fault-tree/validate.ts
|
|
45
|
+
var FaultTreeValidationError = class extends Error {
|
|
46
|
+
issues;
|
|
47
|
+
constructor(issues) {
|
|
48
|
+
super(`invalid fault tree: ${issues.map((i) => i.message).join("; ")}`);
|
|
49
|
+
this.name = "FaultTreeValidationError";
|
|
50
|
+
this.issues = issues;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
function listIds(ids) {
|
|
54
|
+
if (ids.length === 1) return String(ids[0]);
|
|
55
|
+
return `${ids.slice(0, -1).join(", ")} and ${ids[ids.length - 1]}`;
|
|
56
|
+
}
|
|
57
|
+
function sortIssues(issues) {
|
|
58
|
+
const unique = /* @__PURE__ */ new Map();
|
|
59
|
+
for (const issue of issues) unique.set(`${issue.code} ${issue.message}`, issue);
|
|
60
|
+
return [...unique.values()].sort(
|
|
61
|
+
(a, b) => a.code !== b.code ? a.code < b.code ? -1 : 1 : a.message < b.message ? -1 : a.message > b.message ? 1 : 0
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
function faultTreeIssues(input) {
|
|
65
|
+
if (input.events.length === 0 && input.gates.length === 0) return [];
|
|
66
|
+
const issues = [];
|
|
67
|
+
const push = (code, message) => {
|
|
68
|
+
issues.push({ code, message });
|
|
69
|
+
};
|
|
70
|
+
const eventById = /* @__PURE__ */ new Map();
|
|
71
|
+
const dupEventIds = /* @__PURE__ */ new Set();
|
|
72
|
+
for (const e of input.events) {
|
|
73
|
+
if (eventById.has(e.id)) dupEventIds.add(e.id);
|
|
74
|
+
else eventById.set(e.id, e);
|
|
75
|
+
}
|
|
76
|
+
for (const id of [...dupEventIds].sort((a, b) => a - b)) {
|
|
77
|
+
push("duplicate-id", `duplicate event id ${id}`);
|
|
78
|
+
}
|
|
79
|
+
const gateById = /* @__PURE__ */ new Map();
|
|
80
|
+
const dupGateIds = /* @__PURE__ */ new Set();
|
|
81
|
+
for (const g of input.gates) {
|
|
82
|
+
if (gateById.has(g.id)) dupGateIds.add(g.id);
|
|
83
|
+
else gateById.set(g.id, g);
|
|
84
|
+
}
|
|
85
|
+
for (const id of [...dupGateIds].sort((a, b) => a - b)) {
|
|
86
|
+
push("duplicate-id", `duplicate gate id ${id}`);
|
|
87
|
+
}
|
|
88
|
+
if (issues.length > 0) {
|
|
89
|
+
return sortIssues(issues);
|
|
90
|
+
}
|
|
91
|
+
const gates = [...gateById.values()].sort((a, b) => a.id - b.id);
|
|
92
|
+
const top = eventById.get(input.topId);
|
|
93
|
+
if (top === void 0 || top.kind !== "intermediate") {
|
|
94
|
+
push("top-not-intermediate", `topId ${input.topId} is not an intermediate event`);
|
|
95
|
+
}
|
|
96
|
+
const conditioningAsInput = /* @__PURE__ */ new Set();
|
|
97
|
+
for (const g of gates) {
|
|
98
|
+
const out = eventById.get(g.eventId);
|
|
99
|
+
if (out !== void 0 && out.kind === "transfer") {
|
|
100
|
+
push("transfer-with-gate", `transfer event ${g.eventId} cannot have a gate`);
|
|
101
|
+
} else if (out === void 0 || out.kind !== "intermediate") {
|
|
102
|
+
push("gate-on-non-intermediate", `gate ${g.id} resolves non-intermediate event ${g.eventId}`);
|
|
103
|
+
}
|
|
104
|
+
if (g.type === "inhibit") {
|
|
105
|
+
if (g.inputIds.length !== 1) push("gate-arity", `inhibit gate ${g.id} needs exactly 1 input`);
|
|
106
|
+
} else if (g.inputIds.length < 2) {
|
|
107
|
+
push("gate-arity", `${g.type} gate ${g.id} needs \u22652 inputs`);
|
|
108
|
+
}
|
|
109
|
+
if (g.type === "vote" && (!Number.isInteger(g.k) || g.k < 1 || g.k > g.inputIds.length)) {
|
|
110
|
+
push("vote-threshold", `vote gate ${g.id}: k=${g.k} of ${g.inputIds.length}`);
|
|
111
|
+
}
|
|
112
|
+
if (g.type === "inhibit") {
|
|
113
|
+
const cond = eventById.get(g.conditionId);
|
|
114
|
+
if (cond === void 0 || cond.kind !== "conditioning") {
|
|
115
|
+
push("inhibit-condition", `inhibit gate ${g.id} conditionId ${g.conditionId} is not a conditioning event`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
for (const inputId of g.inputIds) {
|
|
119
|
+
const child = eventById.get(inputId);
|
|
120
|
+
if (child === void 0) {
|
|
121
|
+
push("unknown-input", `gate ${g.id} input ${inputId} is not a declared event`);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (inputId === input.topId) push("top-as-input", `top event ${input.topId} is used as a gate input`);
|
|
125
|
+
if (child.kind === "conditioning") conditioningAsInput.add(inputId);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
for (const id of [...conditioningAsInput].sort((a, b) => a - b)) {
|
|
129
|
+
push("conditioning-as-input", `conditioning event ${id} used as a gate input`);
|
|
130
|
+
}
|
|
131
|
+
const gatesByEvent = /* @__PURE__ */ new Map();
|
|
132
|
+
for (const g of gates) {
|
|
133
|
+
const arr = gatesByEvent.get(g.eventId) ?? [];
|
|
134
|
+
arr.push(g.id);
|
|
135
|
+
gatesByEvent.set(g.eventId, arr);
|
|
136
|
+
}
|
|
137
|
+
for (const [eventId, gateIds] of [...gatesByEvent.entries()].sort((a, b) => a[0] - b[0])) {
|
|
138
|
+
if (gateIds.length > 1) {
|
|
139
|
+
push("event-with-two-gates", `event ${eventId} is resolved by gates ${listIds(gateIds.sort((a, b) => a - b))}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
for (const e of [...eventById.values()].sort((a, b) => a.id - b.id)) {
|
|
143
|
+
if (e.kind === "intermediate" && !gatesByEvent.has(e.id)) {
|
|
144
|
+
push("event-without-gate", `intermediate event ${e.id} has no gate \u2014 declare it kind "undeveloped" instead`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
const intermediateRefs = /* @__PURE__ */ new Map();
|
|
148
|
+
for (const g of gates) {
|
|
149
|
+
for (const inputId of g.inputIds) {
|
|
150
|
+
if (eventById.get(inputId)?.kind !== "intermediate") continue;
|
|
151
|
+
const arr = intermediateRefs.get(inputId) ?? [];
|
|
152
|
+
arr.push(g.id);
|
|
153
|
+
intermediateRefs.set(inputId, arr);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
for (const [eventId, gateIds] of [...intermediateRefs.entries()].sort((a, b) => a[0] - b[0])) {
|
|
157
|
+
if (gateIds.length > 1) {
|
|
158
|
+
push(
|
|
159
|
+
"intermediate-reused",
|
|
160
|
+
`intermediate event ${eventId} feeds gates ${listIds(gateIds.sort((a, b) => a - b))} \u2014 repeat it via a "transfer" event`
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
const GRAPH_BLOCKING = /* @__PURE__ */ new Set([
|
|
165
|
+
"duplicate-id",
|
|
166
|
+
"unknown-input",
|
|
167
|
+
"gate-on-non-intermediate",
|
|
168
|
+
"transfer-with-gate",
|
|
169
|
+
"event-with-two-gates",
|
|
170
|
+
"inhibit-condition",
|
|
171
|
+
"top-not-intermediate"
|
|
172
|
+
]);
|
|
173
|
+
if (!issues.some((i) => GRAPH_BLOCKING.has(i.code))) {
|
|
174
|
+
const gateOf = (eventId) => gates.find((g) => g.eventId === eventId);
|
|
175
|
+
const reachable = /* @__PURE__ */ new Set();
|
|
176
|
+
const queue = [input.topId];
|
|
177
|
+
while (queue.length > 0) {
|
|
178
|
+
const id = queue.shift();
|
|
179
|
+
if (reachable.has(id)) continue;
|
|
180
|
+
reachable.add(id);
|
|
181
|
+
const g = gateOf(id);
|
|
182
|
+
if (g === void 0) continue;
|
|
183
|
+
queue.push(...g.inputIds);
|
|
184
|
+
if (g.type === "inhibit") queue.push(g.conditionId);
|
|
185
|
+
}
|
|
186
|
+
for (const e of [...eventById.values()].sort((a, b) => a.id - b.id)) {
|
|
187
|
+
if (!reachable.has(e.id)) push("unreachable-event", `event ${e.id} is declared but unreachable from the top`);
|
|
188
|
+
}
|
|
189
|
+
const color = /* @__PURE__ */ new Map();
|
|
190
|
+
const seenCycles = /* @__PURE__ */ new Set();
|
|
191
|
+
const dfs = (start) => {
|
|
192
|
+
const stack = [{ id: start, nextChild: 0 }];
|
|
193
|
+
color.set(start, 1);
|
|
194
|
+
while (stack.length > 0) {
|
|
195
|
+
const frame = stack[stack.length - 1];
|
|
196
|
+
const children = gateOf(frame.id)?.inputIds ?? [];
|
|
197
|
+
if (frame.nextChild >= children.length) {
|
|
198
|
+
color.set(frame.id, 2);
|
|
199
|
+
stack.pop();
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
const child = children[frame.nextChild];
|
|
203
|
+
frame.nextChild += 1;
|
|
204
|
+
const c = color.get(child) ?? 0;
|
|
205
|
+
if (c === 1) {
|
|
206
|
+
const from = stack.findIndex((f) => f.id === child);
|
|
207
|
+
const cycle = stack.slice(from).map((f) => f.id);
|
|
208
|
+
const minIdx = cycle.indexOf(Math.min(...cycle));
|
|
209
|
+
const rotated = [...cycle.slice(minIdx), ...cycle.slice(0, minIdx)];
|
|
210
|
+
const key = rotated.join(">");
|
|
211
|
+
if (!seenCycles.has(key)) {
|
|
212
|
+
seenCycles.add(key);
|
|
213
|
+
push("cycle", `cycle: ${[...rotated, rotated[0]].join(" \u2192 ")}`);
|
|
214
|
+
}
|
|
215
|
+
} else if (c === 0) {
|
|
216
|
+
color.set(child, 1);
|
|
217
|
+
stack.push({ id: child, nextChild: 0 });
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
for (const e of [...eventById.values()].sort((a, b) => a.id - b.id)) {
|
|
222
|
+
if ((color.get(e.id) ?? 0) === 0) dfs(e.id);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return sortIssues(issues);
|
|
226
|
+
}
|
|
227
|
+
function validateFaultTree(input) {
|
|
228
|
+
const issues = faultTreeIssues(input);
|
|
229
|
+
if (issues.length > 0) throw new FaultTreeValidationError(issues);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// src/fault-tree/layout.ts
|
|
233
|
+
var FT_LABEL_FONT = 12;
|
|
234
|
+
var FT_LABEL_LINE_H = 14;
|
|
235
|
+
var CODE_FONT = 10;
|
|
236
|
+
var LABEL_GAP = 6;
|
|
237
|
+
var PADDING = 32;
|
|
238
|
+
var H_GAP = 28;
|
|
239
|
+
var CORRIDOR = 30;
|
|
240
|
+
var STEM = 10;
|
|
241
|
+
var COND_GAP = 14;
|
|
242
|
+
var GATE_W = 44;
|
|
243
|
+
var GATE_H = 36;
|
|
244
|
+
var XOR_H = GATE_H + 5;
|
|
245
|
+
var INHIBIT_W = 36;
|
|
246
|
+
var INHIBIT_H = 46;
|
|
247
|
+
var INHIBIT_CY = 23;
|
|
248
|
+
var FT_STEM_ID_BASE = 1e6;
|
|
249
|
+
var FT_DROP_ID_BASE = 2e6;
|
|
250
|
+
var FT_BUS_ID_BASE = 3e6;
|
|
251
|
+
var FT_RISER_ID_BASE = 4e6;
|
|
252
|
+
var FT_CONDITION_ID_BASE = 5e6;
|
|
253
|
+
var round = (n) => Math.round(n * 100) / 100;
|
|
254
|
+
function wrapLeafLabel(displayLabel) {
|
|
255
|
+
const perLine = Math.min(20, Math.max(12, Math.ceil(displayLabel.length / 2) + 2));
|
|
256
|
+
return wrapLabel(displayLabel, perLine);
|
|
257
|
+
}
|
|
258
|
+
var CODE_REAL_HEADROOM = 1.15;
|
|
259
|
+
var CODE_MARGIN = 2;
|
|
260
|
+
function codeGlyphHalfAt(kind, y) {
|
|
261
|
+
if (kind === "basic") {
|
|
262
|
+
const dy = y - 22;
|
|
263
|
+
return Math.sqrt(Math.max(0, 22 * 22 - dy * dy));
|
|
264
|
+
}
|
|
265
|
+
if (kind === "undeveloped") {
|
|
266
|
+
return 24 * Math.max(0, 1 - Math.abs(y - 24) / 24);
|
|
267
|
+
}
|
|
268
|
+
if (kind === "house") {
|
|
269
|
+
return y >= 16 ? 22 : 22 * y / 16;
|
|
270
|
+
}
|
|
271
|
+
return 22 * Math.max(0, Math.min(35, y)) / 35;
|
|
272
|
+
}
|
|
273
|
+
function codeBaselineHalfWidth(kind, glyphH) {
|
|
274
|
+
return codeGlyphHalfAt(kind, glyphH / 2 + CODE_FONT * 0.32);
|
|
275
|
+
}
|
|
276
|
+
function maxCodeChars(kind, glyphH) {
|
|
277
|
+
const half = codeBaselineHalfWidth(kind, glyphH);
|
|
278
|
+
const perChar = CODE_FONT * CHAR_W * CODE_REAL_HEADROOM / 2;
|
|
279
|
+
return Math.max(1, Math.floor((half - CODE_MARGIN) / perChar));
|
|
280
|
+
}
|
|
281
|
+
function measureEvent(event, maxLabelChars) {
|
|
282
|
+
if (event.kind === "intermediate") {
|
|
283
|
+
const lines2 = event.label === "" ? [] : wrapLabelBalanced(clampLabel(event.label, maxLabelChars), 3);
|
|
284
|
+
const maxLineW = lines2.reduce((m, l) => Math.max(m, estimateTextWidth(l, FT_LABEL_FONT)), 0);
|
|
285
|
+
const w = Math.max(96, maxLineW + 24);
|
|
286
|
+
const h = lines2.length * FT_LABEL_LINE_H + 18;
|
|
287
|
+
return { nodeW: w, nodeH: h, glyphW: w, glyphH: h, labelLines: lines2, code: null };
|
|
288
|
+
}
|
|
289
|
+
const rawCode = event.code === null || event.code === "" ? null : event.code;
|
|
290
|
+
const lines = event.label === "" ? [] : wrapLeafLabel(clampLabel(event.label, maxLabelChars));
|
|
291
|
+
const labelW = lines.reduce((m, l) => Math.max(m, estimateTextWidth(l, FT_LABEL_FONT)), 0);
|
|
292
|
+
let glyphW;
|
|
293
|
+
let glyphH;
|
|
294
|
+
let code = rawCode;
|
|
295
|
+
if (event.kind === "basic") {
|
|
296
|
+
glyphW = 44;
|
|
297
|
+
glyphH = 44;
|
|
298
|
+
code = rawCode === null ? null : clampLabel(rawCode, maxCodeChars("basic", glyphH));
|
|
299
|
+
} else if (event.kind === "undeveloped") {
|
|
300
|
+
glyphW = 48;
|
|
301
|
+
glyphH = 48;
|
|
302
|
+
code = rawCode === null ? null : clampLabel(rawCode, maxCodeChars("undeveloped", glyphH));
|
|
303
|
+
} else if (event.kind === "house") {
|
|
304
|
+
glyphW = 44;
|
|
305
|
+
glyphH = 40;
|
|
306
|
+
code = rawCode === null ? null : clampLabel(rawCode, maxCodeChars("house", glyphH));
|
|
307
|
+
} else if (event.kind === "conditioning") {
|
|
308
|
+
code = rawCode === null ? null : clampLabel(rawCode, 6);
|
|
309
|
+
const rx = Math.max(30, estimateTextWidth(code ?? "", CODE_FONT) / 2 + 8);
|
|
310
|
+
glyphW = rx * 2;
|
|
311
|
+
glyphH = 32;
|
|
312
|
+
} else {
|
|
313
|
+
glyphW = 44;
|
|
314
|
+
glyphH = 35;
|
|
315
|
+
code = rawCode === null ? null : clampLabel(rawCode, maxCodeChars("transfer", glyphH));
|
|
316
|
+
}
|
|
317
|
+
const nodeW = Math.max(glyphW, labelW);
|
|
318
|
+
const nodeH = glyphH + (lines.length > 0 ? LABEL_GAP + lines.length * FT_LABEL_LINE_H : 0);
|
|
319
|
+
return { nodeW, nodeH, glyphW, glyphH, labelLines: lines, code };
|
|
320
|
+
}
|
|
321
|
+
function eventTitle(event) {
|
|
322
|
+
if (event.title !== void 0) return event.title;
|
|
323
|
+
return event.code !== null && event.code !== "" ? `${event.code} \xB7 ${event.label}` : event.label;
|
|
324
|
+
}
|
|
325
|
+
function computeFaultTreeLayout(input, opts = {}) {
|
|
326
|
+
if (input.events.length === 0 && input.gates.length === 0) {
|
|
327
|
+
return { width: PADDING * 2, height: PADDING * 2, nodes: [], gates: [], elements: [] };
|
|
328
|
+
}
|
|
329
|
+
validateFaultTree(input);
|
|
330
|
+
const titleLabels = opts.titleLabels ?? FAULT_TREE_TITLE_LABELS_EN;
|
|
331
|
+
const eventById = new Map(input.events.map((e) => [e.id, e]));
|
|
332
|
+
const gateByEvent = new Map(input.gates.map((g) => [g.eventId, g]));
|
|
333
|
+
const seqByEvent = /* @__PURE__ */ new Map();
|
|
334
|
+
const newInst = (event, gate, depth) => {
|
|
335
|
+
const seq = seqByEvent.get(event.id) ?? 0;
|
|
336
|
+
seqByEvent.set(event.id, seq + 1);
|
|
337
|
+
const inst = {
|
|
338
|
+
event,
|
|
339
|
+
gate,
|
|
340
|
+
children: [],
|
|
341
|
+
cond: null,
|
|
342
|
+
depth,
|
|
343
|
+
m: measureEvent(event, opts.maxLabelChars),
|
|
344
|
+
title: eventTitle(event),
|
|
345
|
+
seq,
|
|
346
|
+
spanL: 0,
|
|
347
|
+
spanR: 0,
|
|
348
|
+
offset: 0,
|
|
349
|
+
cx: 0
|
|
350
|
+
};
|
|
351
|
+
return inst;
|
|
352
|
+
};
|
|
353
|
+
const build = (eventId, depth) => {
|
|
354
|
+
const event = eventById.get(eventId);
|
|
355
|
+
const gate = gateByEvent.get(eventId) ?? null;
|
|
356
|
+
const inst = newInst(event, gate, depth);
|
|
357
|
+
if (gate !== null) {
|
|
358
|
+
if (gate.type === "inhibit") {
|
|
359
|
+
inst.cond = newInst(eventById.get(gate.conditionId), null, depth);
|
|
360
|
+
}
|
|
361
|
+
for (const childId of gate.inputIds) inst.children.push(build(childId, depth + 1));
|
|
362
|
+
}
|
|
363
|
+
return inst;
|
|
364
|
+
};
|
|
365
|
+
const root = build(input.topId, 0);
|
|
366
|
+
const pack = (inst) => {
|
|
367
|
+
for (const c of inst.children) pack(c);
|
|
368
|
+
const condW = inst.cond !== null ? COND_GAP + inst.cond.m.nodeW : 0;
|
|
369
|
+
const selfL = inst.m.nodeW / 2;
|
|
370
|
+
const selfR = inst.m.nodeW / 2 + condW;
|
|
371
|
+
if (inst.children.length === 0) {
|
|
372
|
+
inst.spanL = selfL;
|
|
373
|
+
inst.spanR = selfR;
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
const xs = [0];
|
|
377
|
+
for (let i = 1; i < inst.children.length; i++) {
|
|
378
|
+
xs.push(xs[i - 1] + inst.children[i - 1].spanR + H_GAP + inst.children[i].spanL);
|
|
379
|
+
}
|
|
380
|
+
const first = inst.children[0];
|
|
381
|
+
const last2 = inst.children[inst.children.length - 1];
|
|
382
|
+
const px = (xs[0] + xs[xs.length - 1]) / 2;
|
|
383
|
+
inst.children.forEach((c, i) => c.offset = xs[i] - px);
|
|
384
|
+
inst.spanL = Math.max(selfL, px - (xs[0] - first.spanL));
|
|
385
|
+
inst.spanR = Math.max(selfR, xs[xs.length - 1] + last2.spanR - px);
|
|
386
|
+
};
|
|
387
|
+
pack(root);
|
|
388
|
+
root.cx = PADDING + root.spanL;
|
|
389
|
+
const placeX = (inst) => {
|
|
390
|
+
for (const c of inst.children) {
|
|
391
|
+
c.cx = inst.cx + c.offset;
|
|
392
|
+
placeX(c);
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
placeX(root);
|
|
396
|
+
const rowInsts = [];
|
|
397
|
+
const visitRows = (inst) => {
|
|
398
|
+
(rowInsts[inst.depth] ??= []).push(inst);
|
|
399
|
+
for (const c of inst.children) visitRows(c);
|
|
400
|
+
};
|
|
401
|
+
visitRows(root);
|
|
402
|
+
const depthCount = rowInsts.length;
|
|
403
|
+
const gateExtent = (inst) => {
|
|
404
|
+
const g = inst.gate;
|
|
405
|
+
if (g.type === "xor") return XOR_H;
|
|
406
|
+
if (g.type === "inhibit") {
|
|
407
|
+
const condBottom = INHIBIT_CY - 16 + inst.cond.m.nodeH;
|
|
408
|
+
return Math.max(INHIBIT_H, condBottom);
|
|
409
|
+
}
|
|
410
|
+
return GATE_H;
|
|
411
|
+
};
|
|
412
|
+
const rowH = [];
|
|
413
|
+
const gateZone = [];
|
|
414
|
+
for (let d = 0; d < depthCount; d++) {
|
|
415
|
+
const insts = rowInsts[d];
|
|
416
|
+
rowH.push(insts.reduce((m, i) => Math.max(m, i.m.nodeH), 0));
|
|
417
|
+
const gated = insts.filter((i) => i.gate !== null);
|
|
418
|
+
gateZone.push(gated.length === 0 ? 0 : STEM + gated.reduce((m, i) => Math.max(m, gateExtent(i)), 0));
|
|
419
|
+
}
|
|
420
|
+
const rowTop = [PADDING];
|
|
421
|
+
for (let d = 0; d < depthCount - 1; d++) {
|
|
422
|
+
rowTop.push(rowTop[d] + rowH[d] + gateZone[d] + CORRIDOR);
|
|
423
|
+
}
|
|
424
|
+
const busY = (d) => rowTop[d + 1] - CORRIDOR / 2;
|
|
425
|
+
const width = Math.ceil(PADDING * 2 + root.spanL + root.spanR);
|
|
426
|
+
const last = depthCount - 1;
|
|
427
|
+
const height = Math.ceil(rowTop[last] + rowH[last] + gateZone[last] + PADDING);
|
|
428
|
+
const instanceOf = (inst) => (seqByEvent.get(inst.event.id) ?? 0) > 1 ? inst.seq : null;
|
|
429
|
+
const nodes = [];
|
|
430
|
+
const gates = [];
|
|
431
|
+
const elements = [];
|
|
432
|
+
const gateTitle = (g) => g.type === "vote" ? `${titleLabels.gates.vote} ${g.k}/${g.inputIds.length}` : titleLabels.gates[g.type];
|
|
433
|
+
const pushNode = (inst, top) => {
|
|
434
|
+
const isRect = inst.event.kind === "intermediate";
|
|
435
|
+
const labelTop = !isRect && inst.m.labelLines.length > 0 ? top + inst.m.glyphH + LABEL_GAP : null;
|
|
436
|
+
nodes.push({
|
|
437
|
+
eventId: inst.event.id,
|
|
438
|
+
kind: inst.event.kind,
|
|
439
|
+
instance: instanceOf(inst),
|
|
440
|
+
cx: round(inst.cx),
|
|
441
|
+
top: round(top),
|
|
442
|
+
nodeW: round(inst.m.nodeW),
|
|
443
|
+
nodeH: round(inst.m.nodeH),
|
|
444
|
+
glyphW: round(inst.m.glyphW),
|
|
445
|
+
glyphH: round(inst.m.glyphH),
|
|
446
|
+
labelLines: inst.m.labelLines,
|
|
447
|
+
labelTop: labelTop === null ? null : round(labelTop),
|
|
448
|
+
code: inst.m.code,
|
|
449
|
+
depth: inst.depth,
|
|
450
|
+
title: inst.title
|
|
451
|
+
});
|
|
452
|
+
};
|
|
453
|
+
const emit = (inst) => {
|
|
454
|
+
const d = inst.depth;
|
|
455
|
+
pushNode(inst, rowTop[d]);
|
|
456
|
+
const g = inst.gate;
|
|
457
|
+
if (g === null) return;
|
|
458
|
+
const gateTop = rowTop[d] + rowH[d] + STEM;
|
|
459
|
+
const glyphW = g.type === "inhibit" ? INHIBIT_W : GATE_W;
|
|
460
|
+
const glyphH = g.type === "xor" ? XOR_H : g.type === "inhibit" ? INHIBIT_H : GATE_H;
|
|
461
|
+
const title = gateTitle(g);
|
|
462
|
+
gates.push({
|
|
463
|
+
gateId: g.id,
|
|
464
|
+
type: g.type,
|
|
465
|
+
cx: round(inst.cx),
|
|
466
|
+
top: round(gateTop),
|
|
467
|
+
glyphW,
|
|
468
|
+
glyphH,
|
|
469
|
+
title,
|
|
470
|
+
voteText: g.type === "vote" ? `${g.k}/${g.inputIds.length}` : null
|
|
471
|
+
});
|
|
472
|
+
if (inst.cond !== null) {
|
|
473
|
+
const cond = inst.cond;
|
|
474
|
+
const nodeLeft = inst.cx + INHIBIT_W / 2 + COND_GAP;
|
|
475
|
+
cond.cx = nodeLeft + cond.m.nodeW / 2;
|
|
476
|
+
const condTop = gateTop + INHIBIT_CY - 16;
|
|
477
|
+
pushNode(cond, condTop);
|
|
478
|
+
const ovalLeft = cond.cx - cond.m.glyphW / 2;
|
|
479
|
+
elements.push({
|
|
480
|
+
edgeId: FT_CONDITION_ID_BASE + g.id,
|
|
481
|
+
kind: "condition",
|
|
482
|
+
points: [
|
|
483
|
+
{ x: round(inst.cx + INHIBIT_W / 2), y: round(gateTop + INHIBIT_CY) },
|
|
484
|
+
{ x: round(ovalLeft), y: round(gateTop + INHIBIT_CY) }
|
|
485
|
+
],
|
|
486
|
+
instance: null,
|
|
487
|
+
title: titleLabels.condition
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
elements.push({
|
|
491
|
+
edgeId: FT_STEM_ID_BASE + g.id,
|
|
492
|
+
kind: "stem",
|
|
493
|
+
points: [
|
|
494
|
+
{ x: round(inst.cx), y: round(rowTop[d] + inst.m.nodeH) },
|
|
495
|
+
{ x: round(inst.cx), y: round(gateTop) }
|
|
496
|
+
],
|
|
497
|
+
instance: null,
|
|
498
|
+
title
|
|
499
|
+
});
|
|
500
|
+
const by = busY(d);
|
|
501
|
+
elements.push({
|
|
502
|
+
edgeId: FT_DROP_ID_BASE + g.id,
|
|
503
|
+
kind: "drop",
|
|
504
|
+
points: [
|
|
505
|
+
{ x: round(inst.cx), y: round(gateTop + glyphH) },
|
|
506
|
+
{ x: round(inst.cx), y: round(by) }
|
|
507
|
+
],
|
|
508
|
+
instance: null,
|
|
509
|
+
title
|
|
510
|
+
});
|
|
511
|
+
if (inst.children.length > 1) {
|
|
512
|
+
const xs = inst.children.map((c) => c.cx);
|
|
513
|
+
elements.push({
|
|
514
|
+
edgeId: FT_BUS_ID_BASE + g.id,
|
|
515
|
+
kind: "bus",
|
|
516
|
+
points: [
|
|
517
|
+
{ x: round(Math.min(...xs)), y: round(by) },
|
|
518
|
+
{ x: round(Math.max(...xs)), y: round(by) }
|
|
519
|
+
],
|
|
520
|
+
instance: null,
|
|
521
|
+
title
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
for (const c of inst.children) {
|
|
525
|
+
elements.push({
|
|
526
|
+
edgeId: FT_RISER_ID_BASE + c.event.id,
|
|
527
|
+
kind: "riser",
|
|
528
|
+
points: [
|
|
529
|
+
{ x: round(c.cx), y: round(by) },
|
|
530
|
+
{ x: round(c.cx), y: round(rowTop[d + 1]) }
|
|
531
|
+
],
|
|
532
|
+
instance: instanceOf(c),
|
|
533
|
+
title
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
for (const c of inst.children) emit(c);
|
|
537
|
+
};
|
|
538
|
+
emit(root);
|
|
539
|
+
return { width, height, nodes, gates, elements };
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// src/fault-tree/svg.ts
|
|
543
|
+
var GLYPH_STROKE = "#52525b";
|
|
544
|
+
var LABEL_FILL = "#3f3f46";
|
|
545
|
+
var EDGE_INK = "#71717a";
|
|
546
|
+
var GLYPH_ATTRS = `fill="transparent" stroke="${GLYPH_STROKE}" stroke-width="2"`;
|
|
547
|
+
var round2 = (n) => Math.round(n * 100) / 100;
|
|
548
|
+
function eventGlyph(n) {
|
|
549
|
+
const cx = n.cx;
|
|
550
|
+
const top = n.top;
|
|
551
|
+
if (n.kind === "intermediate") {
|
|
552
|
+
return `<rect x="${round2(cx - n.nodeW / 2)}" y="${top}" width="${n.nodeW}" height="${n.nodeH}" rx="2" ${GLYPH_ATTRS}/>`;
|
|
553
|
+
}
|
|
554
|
+
if (n.kind === "basic") {
|
|
555
|
+
return `<circle cx="${cx}" cy="${round2(top + 22)}" r="22" ${GLYPH_ATTRS}/>`;
|
|
556
|
+
}
|
|
557
|
+
if (n.kind === "undeveloped") {
|
|
558
|
+
const pts2 = `${cx},${top} ${round2(cx + 24)},${round2(top + 24)} ${cx},${round2(top + 48)} ${round2(cx - 24)},${round2(top + 24)}`;
|
|
559
|
+
return `<polygon points="${pts2}" ${GLYPH_ATTRS}/>`;
|
|
560
|
+
}
|
|
561
|
+
if (n.kind === "house") {
|
|
562
|
+
const yB = round2(top + 40);
|
|
563
|
+
const eave = round2(top + 16);
|
|
564
|
+
const pts2 = `${round2(cx - 22)},${yB} ${round2(cx - 22)},${eave} ${cx},${top} ${round2(cx + 22)},${eave} ${round2(cx + 22)},${yB}`;
|
|
565
|
+
return `<polygon points="${pts2}" ${GLYPH_ATTRS}/>`;
|
|
566
|
+
}
|
|
567
|
+
if (n.kind === "conditioning") {
|
|
568
|
+
return `<ellipse cx="${cx}" cy="${round2(top + 16)}" rx="${round2(n.glyphW / 2)}" ry="16" ${GLYPH_ATTRS}/>`;
|
|
569
|
+
}
|
|
570
|
+
const pts = `${cx},${top} ${round2(cx + 22)},${round2(top + 35)} ${round2(cx - 22)},${round2(top + 35)}`;
|
|
571
|
+
return `<polygon points="${pts}" ${GLYPH_ATTRS}/>`;
|
|
572
|
+
}
|
|
573
|
+
function nodeSvg(n) {
|
|
574
|
+
const pieces = [`<title>${xmlEscape(n.title)}</title>`, eventGlyph(n)];
|
|
575
|
+
if (n.code !== null && n.kind !== "intermediate") {
|
|
576
|
+
pieces.push(
|
|
577
|
+
`<text x="${n.cx}" y="${round2(n.top + n.glyphH / 2 + CODE_FONT * 0.32)}" text-anchor="middle" font-family="${FONT_FAMILY}" font-size="${CODE_FONT}" fill="${LABEL_FILL}">${xmlEscape(n.code)}</text>`
|
|
578
|
+
);
|
|
579
|
+
}
|
|
580
|
+
if (n.labelLines.length > 0) {
|
|
581
|
+
const firstBaseline = n.labelTop === null ? round2(n.top + 19) : round2(n.labelTop + 10);
|
|
582
|
+
const tspans = n.labelLines.map((line, i) => `<tspan x="${n.cx}" y="${round2(firstBaseline + i * FT_LABEL_LINE_H)}">${xmlEscape(line)}</tspan>`).join("");
|
|
583
|
+
pieces.push(
|
|
584
|
+
`<text text-anchor="middle" font-family="${FONT_FAMILY}" font-size="${FT_LABEL_FONT}" fill="${LABEL_FILL}">${tspans}</text>`
|
|
585
|
+
);
|
|
586
|
+
}
|
|
587
|
+
const instance = n.instance === null ? "" : ` data-instance="${n.instance}"`;
|
|
588
|
+
return `<g data-node-id="e${n.eventId}"${instance}>${pieces.join("")}</g>`;
|
|
589
|
+
}
|
|
590
|
+
function orBodyPath(cx, top, yB) {
|
|
591
|
+
return `M ${round2(cx - 22)} ${yB} Q ${cx} ${round2(yB - 14)} ${round2(cx + 22)} ${yB} Q ${round2(cx + 22)} ${round2(top + 14)} ${cx} ${top} Q ${round2(cx - 22)} ${round2(top + 14)} ${round2(cx - 22)} ${yB} Z`;
|
|
592
|
+
}
|
|
593
|
+
function gateGlyph(g) {
|
|
594
|
+
const cx = g.cx;
|
|
595
|
+
const top = g.top;
|
|
596
|
+
const yB = round2(top + 36);
|
|
597
|
+
if (g.type === "and") {
|
|
598
|
+
const d = `M ${round2(cx - 22)} ${yB} L ${round2(cx - 22)} ${round2(yB - 14)} A 22 22 0 0 1 ${round2(cx + 22)} ${round2(yB - 14)} L ${round2(cx + 22)} ${yB} Z`;
|
|
599
|
+
return `<path d="${d}" ${GLYPH_ATTRS}/>`;
|
|
600
|
+
}
|
|
601
|
+
if (g.type === "or") {
|
|
602
|
+
return `<path d="${orBodyPath(cx, top, yB)}" ${GLYPH_ATTRS}/>`;
|
|
603
|
+
}
|
|
604
|
+
if (g.type === "xor") {
|
|
605
|
+
const arc = `M ${round2(cx - 22)} ${round2(yB + 5)} Q ${cx} ${round2(yB - 9)} ${round2(cx + 22)} ${round2(yB + 5)}`;
|
|
606
|
+
return `<path d="${orBodyPath(cx, top, yB)}" ${GLYPH_ATTRS}/><path d="${arc}" ${GLYPH_ATTRS}/>`;
|
|
607
|
+
}
|
|
608
|
+
if (g.type === "inhibit") {
|
|
609
|
+
const pts = `${cx},${top} ${round2(cx + 18)},${round2(top + 12)} ${round2(cx + 18)},${round2(top + 34)} ${cx},${round2(top + 46)} ${round2(cx - 18)},${round2(top + 34)} ${round2(cx - 18)},${round2(top + 12)}`;
|
|
610
|
+
return `<polygon points="${pts}" ${GLYPH_ATTRS}/>`;
|
|
611
|
+
}
|
|
612
|
+
return `<path d="${orBodyPath(cx, top, yB)}" ${GLYPH_ATTRS}/><text x="${cx}" y="${round2(yB - 10)}" text-anchor="middle" font-family="${FONT_FAMILY}" font-size="9" fill="${LABEL_FILL}">${xmlEscape(g.voteText ?? "")}</text>`;
|
|
613
|
+
}
|
|
614
|
+
function gateSvg(g) {
|
|
615
|
+
return `<g data-node-id="g${g.gateId}"><title>${xmlEscape(g.title)}</title>${gateGlyph(g)}</g>`;
|
|
616
|
+
}
|
|
617
|
+
function elementSvg(el) {
|
|
618
|
+
const a = el.points[0];
|
|
619
|
+
const b = el.points[1];
|
|
620
|
+
const instance = el.instance === null ? "" : ` data-instance="${el.instance}"`;
|
|
621
|
+
return `<g data-edge-id="${el.edgeId}"${instance}><title>${xmlEscape(el.title)}</title><line x1="${a.x}" y1="${a.y}" x2="${b.x}" y2="${b.y}" stroke="${EDGE_INK}" stroke-width="1.5" stroke-opacity="0.75"/></g>`;
|
|
622
|
+
}
|
|
623
|
+
var MINI_ATTRS = `fill="transparent" stroke="${GLYPH_STROKE}" stroke-width="1.5"`;
|
|
624
|
+
function miniEventSwatch(kind, x, y) {
|
|
625
|
+
const cx = round2(x + LEGEND_SWATCH_W / 2);
|
|
626
|
+
if (kind === "intermediate") {
|
|
627
|
+
return `<rect x="${round2(cx - 7)}" y="${round2(y - 4.5)}" width="14" height="9" rx="1" ${MINI_ATTRS}/>`;
|
|
628
|
+
}
|
|
629
|
+
if (kind === "basic") return `<circle cx="${cx}" cy="${y}" r="6" ${MINI_ATTRS}/>`;
|
|
630
|
+
if (kind === "undeveloped") {
|
|
631
|
+
return `<polygon points="${cx},${round2(y - 7)} ${round2(cx + 7)},${y} ${cx},${round2(y + 7)} ${round2(cx - 7)},${y}" ${MINI_ATTRS}/>`;
|
|
632
|
+
}
|
|
633
|
+
if (kind === "house") {
|
|
634
|
+
return `<polygon points="${round2(cx - 6)},${round2(y + 5.5)} ${round2(cx - 6)},${round2(y - 1)} ${cx},${round2(y - 5.5)} ${round2(cx + 6)},${round2(y - 1)} ${round2(cx + 6)},${round2(y + 5.5)}" ${MINI_ATTRS}/>`;
|
|
635
|
+
}
|
|
636
|
+
if (kind === "conditioning") return `<ellipse cx="${cx}" cy="${y}" rx="9" ry="5.5" ${MINI_ATTRS}/>`;
|
|
637
|
+
return `<polygon points="${cx},${round2(y - 5)} ${round2(cx + 6)},${round2(y + 5)} ${round2(cx - 6)},${round2(y + 5)}" ${MINI_ATTRS}/>`;
|
|
638
|
+
}
|
|
639
|
+
function miniOrPath(cx, y) {
|
|
640
|
+
return `M ${round2(cx - 7)} ${round2(y + 5.5)} Q ${cx} ${round2(y + 1)} ${round2(cx + 7)} ${round2(y + 5.5)} Q ${round2(cx + 7)} ${round2(y - 1.5)} ${cx} ${round2(y - 5.5)} Q ${round2(cx - 7)} ${round2(y - 1.5)} ${round2(cx - 7)} ${round2(y + 5.5)} Z`;
|
|
641
|
+
}
|
|
642
|
+
function miniGateSwatch(type, x, y) {
|
|
643
|
+
const cx = round2(x + LEGEND_SWATCH_W / 2);
|
|
644
|
+
if (type === "and") {
|
|
645
|
+
const d = `M ${round2(cx - 7)} ${round2(y + 5.5)} L ${round2(cx - 7)} ${round2(y + 1.5)} A 7 7 0 0 1 ${round2(cx + 7)} ${round2(y + 1.5)} L ${round2(cx + 7)} ${round2(y + 5.5)} Z`;
|
|
646
|
+
return `<path d="${d}" ${MINI_ATTRS}/>`;
|
|
647
|
+
}
|
|
648
|
+
if (type === "xor") {
|
|
649
|
+
return `<path d="${miniOrPath(cx, y)}" ${MINI_ATTRS}/><path d="M ${round2(cx - 7)} ${round2(y + 7.5)} Q ${cx} ${round2(y + 3)} ${round2(cx + 7)} ${round2(y + 7.5)}" ${MINI_ATTRS}/>`;
|
|
650
|
+
}
|
|
651
|
+
if (type === "inhibit") {
|
|
652
|
+
return `<polygon points="${cx},${round2(y - 5.5)} ${round2(cx + 4.5)},${round2(y - 2.5)} ${round2(cx + 4.5)},${round2(y + 2.5)} ${cx},${round2(y + 5.5)} ${round2(cx - 4.5)},${round2(y + 2.5)} ${round2(cx - 4.5)},${round2(y - 2.5)}" ${MINI_ATTRS}/>`;
|
|
653
|
+
}
|
|
654
|
+
return `<path d="${miniOrPath(cx, y)}" ${MINI_ATTRS}/>`;
|
|
655
|
+
}
|
|
656
|
+
function faultTreeLayoutSvg(layout, opts = {}) {
|
|
657
|
+
const labels = opts.labels ?? FAULT_TREE_SVG_LABELS_EN;
|
|
658
|
+
const parts = [];
|
|
659
|
+
for (const el of layout.elements) parts.push(elementSvg(el));
|
|
660
|
+
for (const g of layout.gates) parts.push(gateSvg(g));
|
|
661
|
+
for (const n of layout.nodes) parts.push(nodeSvg(n));
|
|
662
|
+
let width = layout.width;
|
|
663
|
+
let height = layout.height;
|
|
664
|
+
if (opts.legend !== false && layout.nodes.length > 0) {
|
|
665
|
+
const kindsUsed = new Set(layout.nodes.map((n) => n.kind));
|
|
666
|
+
const typesUsed = new Set(layout.gates.map((g) => g.type));
|
|
667
|
+
const entries = [];
|
|
668
|
+
for (const kind of FAULT_TREE_EVENT_KINDS) {
|
|
669
|
+
if (!kindsUsed.has(kind)) continue;
|
|
670
|
+
entries.push({ swatch: (x, y) => miniEventSwatch(kind, x, y), label: labels.events[kind] });
|
|
671
|
+
}
|
|
672
|
+
for (const type of GATE_TYPES) {
|
|
673
|
+
if (!typesUsed.has(type)) continue;
|
|
674
|
+
entries.push({ swatch: (x, y) => miniGateSwatch(type, x, y), label: labels.gates[type] });
|
|
675
|
+
}
|
|
676
|
+
const block = legendBlock(entries, layout.height);
|
|
677
|
+
if (block.svg !== "") {
|
|
678
|
+
parts.push(block.svg);
|
|
679
|
+
width = Math.max(width, block.width);
|
|
680
|
+
height = block.height;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
const w = Math.ceil(width);
|
|
684
|
+
const h = Math.ceil(height);
|
|
685
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${w} ${h}" width="${w}" height="${h}" role="img" aria-label="${xmlEscape(labels.ariaLabel)}">` + parts.join("") + `</svg>`;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
// src/fault-tree/render.ts
|
|
689
|
+
function faultTreeSvg(input, opts = {}) {
|
|
690
|
+
const layout = computeFaultTreeLayout(input, {
|
|
691
|
+
...opts.maxLabelChars !== void 0 ? { maxLabelChars: opts.maxLabelChars } : {},
|
|
692
|
+
...opts.titleLabels !== void 0 ? { titleLabels: opts.titleLabels } : {}
|
|
693
|
+
});
|
|
694
|
+
const svg = faultTreeLayoutSvg(layout, {
|
|
695
|
+
...opts.legend === false ? { legend: false } : {},
|
|
696
|
+
...opts.svgLabels !== void 0 ? { labels: opts.svgLabels } : {}
|
|
697
|
+
});
|
|
698
|
+
return { svg, layout };
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
export { CODE_FONT, FAULT_TREE_EVENT_KINDS, FAULT_TREE_SVG_LABELS_EN, FAULT_TREE_TITLE_LABELS_EN, FT_BUS_ID_BASE, FT_CONDITION_ID_BASE, FT_DROP_ID_BASE, FT_LABEL_FONT, FT_LABEL_LINE_H, FT_RISER_ID_BASE, FT_STEM_ID_BASE, FaultTreeValidationError, GATE_TYPES, LABEL_GAP, computeFaultTreeLayout, faultTreeIssues, faultTreeLayoutSvg, faultTreeSvg, validateFaultTree };
|
|
702
|
+
//# sourceMappingURL=chunk-LRHHUJFZ.js.map
|
|
703
|
+
//# sourceMappingURL=chunk-LRHHUJFZ.js.map
|