schematex 0.1.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/LICENSE +661 -0
- package/README.md +379 -0
- package/dist/chunk-2MQWZ2XY.cjs +453 -0
- package/dist/chunk-2MQWZ2XY.cjs.map +1 -0
- package/dist/chunk-2UKC6ZCY.cjs +1803 -0
- package/dist/chunk-2UKC6ZCY.cjs.map +1 -0
- package/dist/chunk-34X3ZJ6E.cjs +783 -0
- package/dist/chunk-34X3ZJ6E.cjs.map +1 -0
- package/dist/chunk-3FTUWAXK.cjs +1220 -0
- package/dist/chunk-3FTUWAXK.cjs.map +1 -0
- package/dist/chunk-3J7TFUOC.js +745 -0
- package/dist/chunk-3J7TFUOC.js.map +1 -0
- package/dist/chunk-47ZC6EMJ.js +1009 -0
- package/dist/chunk-47ZC6EMJ.js.map +1 -0
- package/dist/chunk-4DBRNOPA.cjs +750 -0
- package/dist/chunk-4DBRNOPA.cjs.map +1 -0
- package/dist/chunk-4G7ZIBHN.js +778 -0
- package/dist/chunk-4G7ZIBHN.js.map +1 -0
- package/dist/chunk-5C7DPDHQ.js +1321 -0
- package/dist/chunk-5C7DPDHQ.js.map +1 -0
- package/dist/chunk-ADOXGKAK.js +1251 -0
- package/dist/chunk-ADOXGKAK.js.map +1 -0
- package/dist/chunk-BE5HNDA5.cjs +874 -0
- package/dist/chunk-BE5HNDA5.cjs.map +1 -0
- package/dist/chunk-CZRM7LT7.js +889 -0
- package/dist/chunk-CZRM7LT7.js.map +1 -0
- package/dist/chunk-D4JTSPOL.js +1795 -0
- package/dist/chunk-D4JTSPOL.js.map +1 -0
- package/dist/chunk-DS47NTWZ.cjs +1034 -0
- package/dist/chunk-DS47NTWZ.cjs.map +1 -0
- package/dist/chunk-FDLZEKEB.js +449 -0
- package/dist/chunk-FDLZEKEB.js.map +1 -0
- package/dist/chunk-FGPTCDUT.cjs +1851 -0
- package/dist/chunk-FGPTCDUT.cjs.map +1 -0
- package/dist/chunk-HDKDQAEQ.cjs +86 -0
- package/dist/chunk-HDKDQAEQ.cjs.map +1 -0
- package/dist/chunk-IX554O5K.js +346 -0
- package/dist/chunk-IX554O5K.js.map +1 -0
- package/dist/chunk-KLJEK547.js +71 -0
- package/dist/chunk-KLJEK547.js.map +1 -0
- package/dist/chunk-LMFSHK45.js +1028 -0
- package/dist/chunk-LMFSHK45.js.map +1 -0
- package/dist/chunk-MDICUK6F.cjs +1258 -0
- package/dist/chunk-MDICUK6F.cjs.map +1 -0
- package/dist/chunk-N7KOXOMX.cjs +363 -0
- package/dist/chunk-N7KOXOMX.cjs.map +1 -0
- package/dist/chunk-NYCIK4SU.cjs +775 -0
- package/dist/chunk-NYCIK4SU.cjs.map +1 -0
- package/dist/chunk-PDPHRZZT.js +770 -0
- package/dist/chunk-PDPHRZZT.js.map +1 -0
- package/dist/chunk-ROFLJ74T.js +1212 -0
- package/dist/chunk-ROFLJ74T.js.map +1 -0
- package/dist/chunk-S6BK5DB6.cjs +845 -0
- package/dist/chunk-S6BK5DB6.cjs.map +1 -0
- package/dist/chunk-U4I37IBN.js +874 -0
- package/dist/chunk-U4I37IBN.js.map +1 -0
- package/dist/chunk-U5GGE6PJ.js +839 -0
- package/dist/chunk-U5GGE6PJ.js.map +1 -0
- package/dist/chunk-UHLYS3W5.cjs +1015 -0
- package/dist/chunk-UHLYS3W5.cjs.map +1 -0
- package/dist/chunk-URSKIHSY.cjs +881 -0
- package/dist/chunk-URSKIHSY.cjs.map +1 -0
- package/dist/chunk-V6WO7RK7.cjs +1056 -0
- package/dist/chunk-V6WO7RK7.cjs.map +1 -0
- package/dist/chunk-VFQCTXOX.js +869 -0
- package/dist/chunk-VFQCTXOX.js.map +1 -0
- package/dist/chunk-XQ52ICHU.cjs +895 -0
- package/dist/chunk-XQ52ICHU.cjs.map +1 -0
- package/dist/chunk-XX4BKS7Y.js +1051 -0
- package/dist/chunk-XX4BKS7Y.js.map +1 -0
- package/dist/chunk-XXU36667.js +1844 -0
- package/dist/chunk-XXU36667.js.map +1 -0
- package/dist/chunk-ZX7QKZK2.cjs +1326 -0
- package/dist/chunk-ZX7QKZK2.cjs.map +1 -0
- package/dist/diagrams/blockdiagram/index.cjs +25 -0
- package/dist/diagrams/blockdiagram/index.cjs.map +1 -0
- package/dist/diagrams/blockdiagram/index.d.cts +67 -0
- package/dist/diagrams/blockdiagram/index.d.ts +67 -0
- package/dist/diagrams/blockdiagram/index.js +4 -0
- package/dist/diagrams/blockdiagram/index.js.map +1 -0
- package/dist/diagrams/circuit/index.cjs +34 -0
- package/dist/diagrams/circuit/index.cjs.map +1 -0
- package/dist/diagrams/circuit/index.d.cts +138 -0
- package/dist/diagrams/circuit/index.d.ts +138 -0
- package/dist/diagrams/circuit/index.js +5 -0
- package/dist/diagrams/circuit/index.js.map +1 -0
- package/dist/diagrams/ecomap/index.cjs +30 -0
- package/dist/diagrams/ecomap/index.cjs.map +1 -0
- package/dist/diagrams/ecomap/index.d.cts +15 -0
- package/dist/diagrams/ecomap/index.d.ts +15 -0
- package/dist/diagrams/ecomap/index.js +5 -0
- package/dist/diagrams/ecomap/index.js.map +1 -0
- package/dist/diagrams/entity/index.cjs +26 -0
- package/dist/diagrams/entity/index.cjs.map +1 -0
- package/dist/diagrams/entity/index.d.cts +54 -0
- package/dist/diagrams/entity/index.d.ts +54 -0
- package/dist/diagrams/entity/index.js +5 -0
- package/dist/diagrams/entity/index.js.map +1 -0
- package/dist/diagrams/fishbone/index.cjs +34 -0
- package/dist/diagrams/fishbone/index.cjs.map +1 -0
- package/dist/diagrams/fishbone/index.d.cts +185 -0
- package/dist/diagrams/fishbone/index.d.ts +185 -0
- package/dist/diagrams/fishbone/index.js +5 -0
- package/dist/diagrams/fishbone/index.js.map +1 -0
- package/dist/diagrams/flowchart/index.cjs +34 -0
- package/dist/diagrams/flowchart/index.cjs.map +1 -0
- package/dist/diagrams/flowchart/index.d.cts +2 -0
- package/dist/diagrams/flowchart/index.d.ts +2 -0
- package/dist/diagrams/flowchart/index.js +5 -0
- package/dist/diagrams/flowchart/index.js.map +1 -0
- package/dist/diagrams/genogram/index.cjs +38 -0
- package/dist/diagrams/genogram/index.cjs.map +1 -0
- package/dist/diagrams/genogram/index.d.cts +20 -0
- package/dist/diagrams/genogram/index.d.ts +20 -0
- package/dist/diagrams/genogram/index.js +5 -0
- package/dist/diagrams/genogram/index.js.map +1 -0
- package/dist/diagrams/ladder/index.cjs +26 -0
- package/dist/diagrams/ladder/index.cjs.map +1 -0
- package/dist/diagrams/ladder/index.d.cts +49 -0
- package/dist/diagrams/ladder/index.d.ts +49 -0
- package/dist/diagrams/ladder/index.js +5 -0
- package/dist/diagrams/ladder/index.js.map +1 -0
- package/dist/diagrams/logic/index.cjs +26 -0
- package/dist/diagrams/logic/index.cjs.map +1 -0
- package/dist/diagrams/logic/index.d.cts +73 -0
- package/dist/diagrams/logic/index.d.ts +73 -0
- package/dist/diagrams/logic/index.js +5 -0
- package/dist/diagrams/logic/index.js.map +1 -0
- package/dist/diagrams/orgchart/index.cjs +30 -0
- package/dist/diagrams/orgchart/index.cjs.map +1 -0
- package/dist/diagrams/orgchart/index.d.cts +100 -0
- package/dist/diagrams/orgchart/index.d.ts +100 -0
- package/dist/diagrams/orgchart/index.js +5 -0
- package/dist/diagrams/orgchart/index.js.map +1 -0
- package/dist/diagrams/pedigree/index.cjs +30 -0
- package/dist/diagrams/pedigree/index.cjs.map +1 -0
- package/dist/diagrams/pedigree/index.d.cts +15 -0
- package/dist/diagrams/pedigree/index.d.ts +15 -0
- package/dist/diagrams/pedigree/index.js +5 -0
- package/dist/diagrams/pedigree/index.js.map +1 -0
- package/dist/diagrams/phylo/index.cjs +30 -0
- package/dist/diagrams/phylo/index.cjs.map +1 -0
- package/dist/diagrams/phylo/index.d.cts +32 -0
- package/dist/diagrams/phylo/index.d.ts +32 -0
- package/dist/diagrams/phylo/index.js +5 -0
- package/dist/diagrams/phylo/index.js.map +1 -0
- package/dist/diagrams/sld/index.cjs +26 -0
- package/dist/diagrams/sld/index.cjs.map +1 -0
- package/dist/diagrams/sld/index.d.cts +58 -0
- package/dist/diagrams/sld/index.d.ts +58 -0
- package/dist/diagrams/sld/index.js +5 -0
- package/dist/diagrams/sld/index.js.map +1 -0
- package/dist/diagrams/sociogram/index.cjs +26 -0
- package/dist/diagrams/sociogram/index.cjs.map +1 -0
- package/dist/diagrams/sociogram/index.d.cts +76 -0
- package/dist/diagrams/sociogram/index.d.ts +76 -0
- package/dist/diagrams/sociogram/index.js +5 -0
- package/dist/diagrams/sociogram/index.js.map +1 -0
- package/dist/diagrams/timing/index.cjs +21 -0
- package/dist/diagrams/timing/index.cjs.map +1 -0
- package/dist/diagrams/timing/index.d.cts +9 -0
- package/dist/diagrams/timing/index.d.ts +9 -0
- package/dist/diagrams/timing/index.js +4 -0
- package/dist/diagrams/timing/index.js.map +1 -0
- package/dist/diagrams/venn/index.cjs +38 -0
- package/dist/diagrams/venn/index.cjs.map +1 -0
- package/dist/diagrams/venn/index.d.cts +69 -0
- package/dist/diagrams/venn/index.d.ts +69 -0
- package/dist/diagrams/venn/index.js +5 -0
- package/dist/diagrams/venn/index.js.map +1 -0
- package/dist/index-BSlza1YY.d.ts +150 -0
- package/dist/index-BXefHVce.d.cts +150 -0
- package/dist/index.cjs +2033 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +29 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +1944 -0
- package/dist/index.js.map +1 -0
- package/dist/types-DqfcYkcY.d.cts +741 -0
- package/dist/types-DqfcYkcY.d.ts +741 -0
- package/package.json +163 -0
|
@@ -0,0 +1,874 @@
|
|
|
1
|
+
import { resolveFishboneTheme } from './chunk-IX554O5K.js';
|
|
2
|
+
import { line, text, title, desc, el, defs, group, svgRoot, rect, polygon } from './chunk-KLJEK547.js';
|
|
3
|
+
|
|
4
|
+
// src/diagrams/fishbone/parser.ts
|
|
5
|
+
var FishboneParseError = class extends Error {
|
|
6
|
+
constructor(message) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = "FishboneParseError";
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
var SLOPE_PRESETS = {
|
|
12
|
+
gentle: 0.45,
|
|
13
|
+
normal: 0.6,
|
|
14
|
+
steep: 0.75
|
|
15
|
+
};
|
|
16
|
+
function parseFishboneDSL(text2) {
|
|
17
|
+
const rawLines = text2.split(/\r?\n/);
|
|
18
|
+
let title2;
|
|
19
|
+
let effect = "";
|
|
20
|
+
let orientation = "ltr";
|
|
21
|
+
let width;
|
|
22
|
+
let height;
|
|
23
|
+
let sides;
|
|
24
|
+
let ribSlope;
|
|
25
|
+
let density;
|
|
26
|
+
let causeSide;
|
|
27
|
+
const categories = [];
|
|
28
|
+
const causesByCategory = /* @__PURE__ */ new Map();
|
|
29
|
+
let lastLevel1 = null;
|
|
30
|
+
let headerSeen = false;
|
|
31
|
+
const getCat = (id) => categories.find((c) => c.id === id || c.label === id);
|
|
32
|
+
for (let i = 0; i < rawLines.length; i++) {
|
|
33
|
+
const raw = rawLines[i] ?? "";
|
|
34
|
+
const line2 = stripComment(raw).trimEnd();
|
|
35
|
+
if (!line2.trim()) continue;
|
|
36
|
+
const indent = countIndent(raw);
|
|
37
|
+
const trimmed = line2.trim();
|
|
38
|
+
if (!headerSeen && /^fishbone\b/i.test(trimmed)) {
|
|
39
|
+
const m = trimmed.match(/^fishbone\s*:?\s*(.*)$/i);
|
|
40
|
+
if (m && m[1]) title2 = stripQuotes(m[1]);
|
|
41
|
+
headerSeen = true;
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
if (indent >= 2 && trimmed.startsWith("-")) {
|
|
45
|
+
if (!lastLevel1) {
|
|
46
|
+
throw new FishboneParseError(
|
|
47
|
+
`Sub-cause at line ${i + 1} has no preceding Level-1 cause: "${trimmed}"`
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
const subText = stripQuotes(trimmed.slice(1).trim());
|
|
51
|
+
if (!subText) continue;
|
|
52
|
+
lastLevel1.children.push({ label: subText, children: [] });
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (/^effect\b/i.test(trimmed)) {
|
|
56
|
+
const m = trimmed.match(/^effect\s*:?\s*(.*)$/i);
|
|
57
|
+
if (m) effect = stripQuotes(m[1] ?? "");
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (/^config\b/i.test(trimmed)) {
|
|
61
|
+
const m = trimmed.match(/^config\s+([a-zA-Z]+)\s*=\s*(.+)$/i);
|
|
62
|
+
if (m) {
|
|
63
|
+
const key = m[1].toLowerCase();
|
|
64
|
+
const val = stripQuotes(m[2].trim());
|
|
65
|
+
if (key === "direction") {
|
|
66
|
+
orientation = val === "left" || val === "rtl" ? "rtl" : "ltr";
|
|
67
|
+
} else if (key === "width") {
|
|
68
|
+
const n = Number(val);
|
|
69
|
+
if (Number.isFinite(n)) width = n;
|
|
70
|
+
} else if (key === "height") {
|
|
71
|
+
const n = Number(val);
|
|
72
|
+
if (Number.isFinite(n)) height = n;
|
|
73
|
+
} else if (key === "sides") {
|
|
74
|
+
const v = val.toLowerCase();
|
|
75
|
+
if (v === "both" || v === "top" || v === "bottom") sides = v;
|
|
76
|
+
} else if (key === "slope" || key === "ribslope") {
|
|
77
|
+
const preset = SLOPE_PRESETS[val.toLowerCase()];
|
|
78
|
+
if (preset !== void 0) ribSlope = preset;
|
|
79
|
+
else {
|
|
80
|
+
const n = Number(val);
|
|
81
|
+
if (Number.isFinite(n) && n > 0 && n < 3) ribSlope = n;
|
|
82
|
+
}
|
|
83
|
+
} else if (key === "density") {
|
|
84
|
+
const v = val.toLowerCase();
|
|
85
|
+
if (v === "compact" || v === "normal" || v === "spacious") density = v;
|
|
86
|
+
} else if (key === "causeside" || key === "cause-side") {
|
|
87
|
+
const v = val.toLowerCase();
|
|
88
|
+
if (v === "head" || v === "tail" || v === "both") causeSide = v;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (/^category\b/i.test(trimmed)) {
|
|
94
|
+
const compact = trimmed.match(/^category\s+([^:]+?)\s*:\s*(.+)$/i);
|
|
95
|
+
const structured = trimmed.match(
|
|
96
|
+
/^category\s+([a-zA-Z][\w-]*)\s+("[^"]*"|[^\s[]+)(?:\s*(\[.*\]))?\s*$/i
|
|
97
|
+
);
|
|
98
|
+
if (structured) {
|
|
99
|
+
const id = structured[1];
|
|
100
|
+
const label = stripQuotes(structured[2]);
|
|
101
|
+
const props = parseProps(structured[3] ?? "");
|
|
102
|
+
if (!getCat(id)) {
|
|
103
|
+
const sideProp = props["side"]?.toLowerCase();
|
|
104
|
+
const side = sideProp === "top" || sideProp === "bottom" ? sideProp : void 0;
|
|
105
|
+
const orderProp = props["order"];
|
|
106
|
+
const orderNum = orderProp !== void 0 ? Number(orderProp) : NaN;
|
|
107
|
+
const order = Number.isFinite(orderNum) ? orderNum : void 0;
|
|
108
|
+
categories.push({ id, label, color: props["color"], side, order });
|
|
109
|
+
causesByCategory.set(id, []);
|
|
110
|
+
}
|
|
111
|
+
lastLevel1 = null;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (compact) {
|
|
115
|
+
const label = stripQuotes(compact[1].trim());
|
|
116
|
+
const id = slugify(label);
|
|
117
|
+
const rest = compact[2].trim();
|
|
118
|
+
if (!getCat(id)) {
|
|
119
|
+
categories.push({ id, label });
|
|
120
|
+
causesByCategory.set(id, []);
|
|
121
|
+
}
|
|
122
|
+
const bucket = causesByCategory.get(id);
|
|
123
|
+
for (const part of rest.split(/[;,]/)) {
|
|
124
|
+
const txt = stripQuotes(part.trim());
|
|
125
|
+
if (txt) {
|
|
126
|
+
const node = { label: txt, children: [] };
|
|
127
|
+
bucket.push(node);
|
|
128
|
+
lastLevel1 = node;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
const causeMatch = trimmed.match(/^([a-zA-Z][\w-]*)\s*:\s*(.+)$/);
|
|
135
|
+
if (causeMatch) {
|
|
136
|
+
const catId = causeMatch[1];
|
|
137
|
+
const cat = getCat(catId);
|
|
138
|
+
if (!cat) {
|
|
139
|
+
throw new FishboneParseError(
|
|
140
|
+
`Unknown category "${catId}" at line ${i + 1}. Declare with \`category ${catId} "..."\` first.`
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
const bucket = causesByCategory.get(cat.id);
|
|
144
|
+
const rest = causeMatch[2].trim();
|
|
145
|
+
const { text: causeText } = splitTrailingProps(rest);
|
|
146
|
+
const label = stripQuotes(causeText);
|
|
147
|
+
if (!label) continue;
|
|
148
|
+
const node = { label, children: [] };
|
|
149
|
+
bucket.push(node);
|
|
150
|
+
lastLevel1 = node;
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (!effect) {
|
|
155
|
+
effect = title2 ?? "";
|
|
156
|
+
}
|
|
157
|
+
if (categories.length === 0) {
|
|
158
|
+
throw new FishboneParseError(
|
|
159
|
+
"Fishbone requires at least one `category`. See docs/reference/13-FISHBONE-STANDARD.md."
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
const majors = categories.map((c) => ({
|
|
163
|
+
label: c.label,
|
|
164
|
+
color: c.color,
|
|
165
|
+
children: causesByCategory.get(c.id) ?? [],
|
|
166
|
+
side: c.side,
|
|
167
|
+
order: c.order
|
|
168
|
+
}));
|
|
169
|
+
return {
|
|
170
|
+
type: "fishbone",
|
|
171
|
+
title: title2,
|
|
172
|
+
effect,
|
|
173
|
+
majors,
|
|
174
|
+
orientation,
|
|
175
|
+
width,
|
|
176
|
+
height,
|
|
177
|
+
sides,
|
|
178
|
+
ribSlope,
|
|
179
|
+
density,
|
|
180
|
+
causeSide
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
function stripComment(s) {
|
|
184
|
+
let out = "";
|
|
185
|
+
let inQuote = false;
|
|
186
|
+
for (const ch of s) {
|
|
187
|
+
if (ch === '"') inQuote = !inQuote;
|
|
188
|
+
if (ch === "#" && !inQuote) break;
|
|
189
|
+
out += ch;
|
|
190
|
+
}
|
|
191
|
+
return out;
|
|
192
|
+
}
|
|
193
|
+
function stripQuotes(v) {
|
|
194
|
+
const t = v.trim();
|
|
195
|
+
if (t.length >= 2 && t.startsWith('"') && t.endsWith('"')) return t.slice(1, -1);
|
|
196
|
+
return t;
|
|
197
|
+
}
|
|
198
|
+
function countIndent(raw) {
|
|
199
|
+
let n = 0;
|
|
200
|
+
for (const ch of raw) {
|
|
201
|
+
if (ch === " ") n += 1;
|
|
202
|
+
else if (ch === " ") n += 2;
|
|
203
|
+
else break;
|
|
204
|
+
}
|
|
205
|
+
return n;
|
|
206
|
+
}
|
|
207
|
+
function slugify(s) {
|
|
208
|
+
return s.toLowerCase().replace(/[^\w\u4e00-\u9fff]+/g, "-").replace(/^-+|-+$/g, "") || "cat";
|
|
209
|
+
}
|
|
210
|
+
function splitTrailingProps(s) {
|
|
211
|
+
const idx = s.lastIndexOf("[");
|
|
212
|
+
if (idx < 0 || !s.trimEnd().endsWith("]")) return { text: s, props: {} };
|
|
213
|
+
const text2 = s.slice(0, idx).trim();
|
|
214
|
+
const props = parseProps(s.slice(idx));
|
|
215
|
+
return { text: text2, props };
|
|
216
|
+
}
|
|
217
|
+
function parseProps(raw) {
|
|
218
|
+
const out = {};
|
|
219
|
+
const m = raw.match(/^\[(.*)\]$/s);
|
|
220
|
+
if (!m) return out;
|
|
221
|
+
const inside = m[1];
|
|
222
|
+
for (const part of splitTopLevelCommas(inside)) {
|
|
223
|
+
const [k, ...rest] = part.split(":");
|
|
224
|
+
if (!k || rest.length === 0) continue;
|
|
225
|
+
out[k.trim()] = stripQuotes(rest.join(":").trim());
|
|
226
|
+
}
|
|
227
|
+
return out;
|
|
228
|
+
}
|
|
229
|
+
function splitTopLevelCommas(inside) {
|
|
230
|
+
const parts = [];
|
|
231
|
+
let depth = 0;
|
|
232
|
+
let buf = "";
|
|
233
|
+
let inQuote = false;
|
|
234
|
+
for (const ch of inside) {
|
|
235
|
+
if (ch === '"') inQuote = !inQuote;
|
|
236
|
+
if (!inQuote) {
|
|
237
|
+
if (ch === "[") depth += 1;
|
|
238
|
+
else if (ch === "]") depth -= 1;
|
|
239
|
+
else if (ch === "," && depth === 0) {
|
|
240
|
+
parts.push(buf);
|
|
241
|
+
buf = "";
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
buf += ch;
|
|
246
|
+
}
|
|
247
|
+
if (buf.trim()) parts.push(buf);
|
|
248
|
+
return parts;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// src/diagrams/fishbone/layout.ts
|
|
252
|
+
var FB_CONST = {
|
|
253
|
+
PADDING: 40,
|
|
254
|
+
HEAD_W: 90,
|
|
255
|
+
HEAD_H: 80,
|
|
256
|
+
TAIL_LEN: 40,
|
|
257
|
+
SPINE_OFFSET_FROM_TAIL: 40,
|
|
258
|
+
// distance from canvas left to spine start
|
|
259
|
+
RIB_SLOPE: 0.6,
|
|
260
|
+
// dx/dy — consistent across all ribs
|
|
261
|
+
RIB_BASE_EXTENT_Y: 30,
|
|
262
|
+
// vertical distance of first slot from spine
|
|
263
|
+
ROW_HEIGHT: 30,
|
|
264
|
+
// vertical gap between Level-1 slots
|
|
265
|
+
SUB_ROW_HEIGHT: 17,
|
|
266
|
+
BRANCH_LEN: 30,
|
|
267
|
+
// horizontal Level-1 branch length
|
|
268
|
+
SUB_TICK_LEN: 10,
|
|
269
|
+
HEADER_W: 132,
|
|
270
|
+
HEADER_H: 34,
|
|
271
|
+
HEADER_GAP: 12,
|
|
272
|
+
// gap between rib endpoint and header pill
|
|
273
|
+
COL_STEP: 130,
|
|
274
|
+
// spine x gap between adjacent ribs
|
|
275
|
+
COL_FIRST_OFFSET: 170,
|
|
276
|
+
// first rib's distance from spine start
|
|
277
|
+
LABEL_FONT: 12,
|
|
278
|
+
HEADER_FONT: 14,
|
|
279
|
+
SUB_FONT: 11,
|
|
280
|
+
EFFECT_FONT: 14,
|
|
281
|
+
LABEL_GAP: 6,
|
|
282
|
+
// gap from branch end to label
|
|
283
|
+
MIN_ROWS_PER_HALF: 4,
|
|
284
|
+
// aesthetic minimum rib length
|
|
285
|
+
COL_GAP_BETWEEN_LABELS: 18,
|
|
286
|
+
// min horizontal breathing between adjacent ribs' label columns
|
|
287
|
+
HEAD_PAD_X: 44,
|
|
288
|
+
// total horizontal padding around effect text inside head
|
|
289
|
+
HEAD_PAD_Y: 20,
|
|
290
|
+
// min vertical padding around effect text inside head
|
|
291
|
+
TITLE_CLEARANCE: 24
|
|
292
|
+
// extra gap between title baseline and top header pill
|
|
293
|
+
};
|
|
294
|
+
function estimateTextWidth(s, fontSize) {
|
|
295
|
+
let w = 0;
|
|
296
|
+
for (const ch of s) {
|
|
297
|
+
const cp = ch.codePointAt(0) ?? 0;
|
|
298
|
+
const isWide = cp >= 12288 && cp <= 12543 || cp >= 13312 && cp <= 40959 || cp >= 44032 && cp <= 55215 || cp >= 65280 && cp <= 65519;
|
|
299
|
+
w += fontSize * (isWide ? 1 : 0.56);
|
|
300
|
+
}
|
|
301
|
+
return w;
|
|
302
|
+
}
|
|
303
|
+
var DENSITY = {
|
|
304
|
+
compact: {
|
|
305
|
+
rowHeight: 24,
|
|
306
|
+
subRowHeight: 15,
|
|
307
|
+
colStep: 110,
|
|
308
|
+
headerW: 118,
|
|
309
|
+
headerH: 30,
|
|
310
|
+
headerGap: 8,
|
|
311
|
+
minRowsPerHalf: 3,
|
|
312
|
+
colFirstOffset: 140
|
|
313
|
+
},
|
|
314
|
+
normal: {
|
|
315
|
+
rowHeight: 30,
|
|
316
|
+
subRowHeight: 17,
|
|
317
|
+
colStep: 130,
|
|
318
|
+
headerW: 132,
|
|
319
|
+
headerH: 34,
|
|
320
|
+
headerGap: 12,
|
|
321
|
+
minRowsPerHalf: 4,
|
|
322
|
+
colFirstOffset: 170
|
|
323
|
+
},
|
|
324
|
+
spacious: {
|
|
325
|
+
rowHeight: 36,
|
|
326
|
+
subRowHeight: 19,
|
|
327
|
+
colStep: 150,
|
|
328
|
+
headerW: 148,
|
|
329
|
+
headerH: 38,
|
|
330
|
+
headerGap: 16,
|
|
331
|
+
minRowsPerHalf: 5,
|
|
332
|
+
colFirstOffset: 200
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
function layoutFishbone(ast, opts) {
|
|
336
|
+
const majors = ast.majors.length > 0 ? ast.majors : [];
|
|
337
|
+
majors.length;
|
|
338
|
+
const density = ast.density ?? "normal";
|
|
339
|
+
const D = DENSITY[density];
|
|
340
|
+
const ribSlope = ast.ribSlope ?? FB_CONST.RIB_SLOPE;
|
|
341
|
+
const causeSideSetting = ast.causeSide ?? "head";
|
|
342
|
+
const sides = ast.sides ?? "both";
|
|
343
|
+
const topMajors = [];
|
|
344
|
+
const botMajors = [];
|
|
345
|
+
if (sides === "top" || sides === "bottom") {
|
|
346
|
+
const bucket = sides === "top" ? topMajors : botMajors;
|
|
347
|
+
for (const m of majors) {
|
|
348
|
+
if (m.side === "top") topMajors.push(m);
|
|
349
|
+
else if (m.side === "bottom") botMajors.push(m);
|
|
350
|
+
else bucket.push(m);
|
|
351
|
+
}
|
|
352
|
+
} else {
|
|
353
|
+
const autoPool = [];
|
|
354
|
+
for (const m of majors) {
|
|
355
|
+
if (m.side === "top") topMajors.push(m);
|
|
356
|
+
else if (m.side === "bottom") botMajors.push(m);
|
|
357
|
+
else autoPool.push(m);
|
|
358
|
+
}
|
|
359
|
+
for (const m of autoPool) {
|
|
360
|
+
if (topMajors.length <= botMajors.length) topMajors.push(m);
|
|
361
|
+
else botMajors.push(m);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
const byOrder = (a, b) => {
|
|
365
|
+
const ao = a.order ?? Number.POSITIVE_INFINITY;
|
|
366
|
+
const bo = b.order ?? Number.POSITIVE_INFINITY;
|
|
367
|
+
return ao - bo;
|
|
368
|
+
};
|
|
369
|
+
topMajors.sort(byOrder);
|
|
370
|
+
botMajors.sort(byOrder);
|
|
371
|
+
const nTop = topMajors.length;
|
|
372
|
+
const nBot = botMajors.length;
|
|
373
|
+
const ribRowHeights = (m) => {
|
|
374
|
+
if (m.children.length === 0) return [];
|
|
375
|
+
return m.children.map(
|
|
376
|
+
(c) => D.rowHeight + c.children.length * D.subRowHeight
|
|
377
|
+
);
|
|
378
|
+
};
|
|
379
|
+
const topExtents = topMajors.map(
|
|
380
|
+
(m) => sumOrMin(ribRowHeights(m), D.minRowsPerHalf * D.rowHeight)
|
|
381
|
+
);
|
|
382
|
+
const botExtents = botMajors.map(
|
|
383
|
+
(m) => sumOrMin(ribRowHeights(m), D.minRowsPerHalf * D.rowHeight)
|
|
384
|
+
);
|
|
385
|
+
const effectTextW = estimateTextWidth(ast.effect, FB_CONST.EFFECT_FONT);
|
|
386
|
+
const rawHeadW = effectTextW + FB_CONST.HEAD_PAD_X;
|
|
387
|
+
const headEffectiveH = Math.max(FB_CONST.HEAD_H, Math.ceil(rawHeadW / 2));
|
|
388
|
+
const taperAtText = 1 - FB_CONST.EFFECT_FONT / headEffectiveH;
|
|
389
|
+
const headEffectiveW = Math.max(FB_CONST.HEAD_W, Math.ceil(rawHeadW / taperAtText));
|
|
390
|
+
const minHalfFromHead = Math.ceil(headEffectiveH / 2) + 6;
|
|
391
|
+
const topHalfExtent = nTop > 0 ? Math.max(
|
|
392
|
+
minHalfFromHead,
|
|
393
|
+
D.minRowsPerHalf * D.rowHeight,
|
|
394
|
+
...topExtents.length ? topExtents : [0]
|
|
395
|
+
) : sides === "bottom" ? Math.max(minHalfFromHead, 0) : 0;
|
|
396
|
+
const botHalfExtent = nBot > 0 ? Math.max(
|
|
397
|
+
minHalfFromHead,
|
|
398
|
+
D.minRowsPerHalf * D.rowHeight,
|
|
399
|
+
...botExtents.length ? botExtents : [0]
|
|
400
|
+
) : sides === "top" ? Math.max(minHalfFromHead, 0) : 0;
|
|
401
|
+
const nCols = Math.max(nTop, nBot, 1);
|
|
402
|
+
let maxCauseLabelW = 0;
|
|
403
|
+
let maxSubLabelW = 0;
|
|
404
|
+
for (const m of majors) {
|
|
405
|
+
for (const c of m.children) {
|
|
406
|
+
maxCauseLabelW = Math.max(
|
|
407
|
+
maxCauseLabelW,
|
|
408
|
+
estimateTextWidth(c.label, FB_CONST.LABEL_FONT)
|
|
409
|
+
);
|
|
410
|
+
for (const sc of c.children) {
|
|
411
|
+
maxSubLabelW = Math.max(
|
|
412
|
+
maxSubLabelW,
|
|
413
|
+
estimateTextWidth(sc.label, FB_CONST.SUB_FONT)
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
const subExtent = maxSubLabelW > 0 ? FB_CONST.SUB_TICK_LEN + 4 + maxSubLabelW : 0;
|
|
419
|
+
const labelColW = Math.max(maxCauseLabelW, subExtent);
|
|
420
|
+
const labelExtentsPerCol = causeSideSetting === "both" ? 2 : 1;
|
|
421
|
+
const minColStep = FB_CONST.BRANCH_LEN + FB_CONST.LABEL_GAP + labelColW + FB_CONST.COL_GAP_BETWEEN_LABELS;
|
|
422
|
+
const colStep = Math.max(
|
|
423
|
+
D.colStep,
|
|
424
|
+
Math.ceil(minColStep * (labelExtentsPerCol === 2 ? 1.15 : 1))
|
|
425
|
+
);
|
|
426
|
+
const spineStartX = FB_CONST.PADDING + FB_CONST.TAIL_LEN + FB_CONST.SPINE_OFFSET_FROM_TAIL;
|
|
427
|
+
const firstRibX = spineStartX + D.colFirstOffset;
|
|
428
|
+
const lastRibX = firstRibX + (nCols - 1) * colStep;
|
|
429
|
+
const spineEndX = lastRibX + Math.max(40, FB_CONST.BRANCH_LEN + FB_CONST.LABEL_GAP + labelColW + 12);
|
|
430
|
+
const headX = spineEndX;
|
|
431
|
+
const headTipXAdj = headX + headEffectiveW;
|
|
432
|
+
const width = ast.width ?? Math.ceil(headTipXAdj + FB_CONST.PADDING);
|
|
433
|
+
const title2 = ast.title;
|
|
434
|
+
const titleReserve = title2 ? FB_CONST.TITLE_CLEARANCE + 20 : 0;
|
|
435
|
+
const spineY = FB_CONST.PADDING + titleReserve + topHalfExtent + D.headerH / 2 + 12;
|
|
436
|
+
const height = ast.height ?? Math.ceil(
|
|
437
|
+
spineY + botHalfExtent + D.headerH / 2 + 12 + FB_CONST.PADDING
|
|
438
|
+
);
|
|
439
|
+
const ribs = [];
|
|
440
|
+
const palette = opts?.palette ?? resolveFishboneTheme("default").palette;
|
|
441
|
+
const textBBoxes = [];
|
|
442
|
+
const buildHalfRibs = (halfMajors, startIndex, half, halfExtent) => {
|
|
443
|
+
for (let i = 0; i < halfMajors.length; i++) {
|
|
444
|
+
const major = halfMajors[i];
|
|
445
|
+
const globalIdx = startIndex + i;
|
|
446
|
+
const color = major.color ?? palette[globalIdx % palette.length];
|
|
447
|
+
const spineX = firstRibX + i * colStep;
|
|
448
|
+
const endY = half === "top" ? spineY - halfExtent : spineY + halfExtent;
|
|
449
|
+
const endX = spineX - halfExtent * ribSlope;
|
|
450
|
+
const extraDist = D.headerGap + D.headerH / 2;
|
|
451
|
+
const headerCenterY = half === "top" ? endY - extraDist : endY + extraDist;
|
|
452
|
+
const headerCenterX = endX - extraDist * ribSlope;
|
|
453
|
+
const headerW = Math.max(
|
|
454
|
+
D.headerW,
|
|
455
|
+
estimateTextWidth(major.label, FB_CONST.HEADER_FONT) + 28
|
|
456
|
+
);
|
|
457
|
+
const headerX = headerCenterX - headerW / 2;
|
|
458
|
+
const headerY = headerCenterY - D.headerH / 2;
|
|
459
|
+
textBBoxes.push({
|
|
460
|
+
x: headerX,
|
|
461
|
+
y: headerY,
|
|
462
|
+
w: headerW,
|
|
463
|
+
h: D.headerH
|
|
464
|
+
});
|
|
465
|
+
const causes = [];
|
|
466
|
+
let accum = FB_CONST.RIB_BASE_EXTENT_Y;
|
|
467
|
+
for (let s = 0; s < major.children.length; s++) {
|
|
468
|
+
const child = major.children[s];
|
|
469
|
+
const rowH = D.rowHeight + child.children.length * D.subRowHeight;
|
|
470
|
+
const slotOffset = accum + rowH / 2 - D.rowHeight / 2;
|
|
471
|
+
const ribY = half === "top" ? spineY - slotOffset : spineY + slotOffset;
|
|
472
|
+
const ribX = spineX - slotOffset * ribSlope;
|
|
473
|
+
const causeDir = causeSideSetting === "tail" ? "tail" : causeSideSetting === "both" ? s % 2 === 0 ? "head" : "tail" : "head";
|
|
474
|
+
const branchY = ribY;
|
|
475
|
+
const branchX = causeDir === "head" ? ribX + FB_CONST.BRANCH_LEN : ribX - FB_CONST.BRANCH_LEN;
|
|
476
|
+
const labelX = causeDir === "head" ? branchX + FB_CONST.LABEL_GAP : branchX - FB_CONST.LABEL_GAP;
|
|
477
|
+
const labelY = branchY;
|
|
478
|
+
const labelAnchor = causeDir === "head" ? "start" : "end";
|
|
479
|
+
const labelW = estimateTextWidth(child.label, FB_CONST.LABEL_FONT);
|
|
480
|
+
textBBoxes.push({
|
|
481
|
+
x: causeDir === "head" ? labelX - 2 : labelX - labelW - 2,
|
|
482
|
+
y: labelY - FB_CONST.LABEL_FONT / 2 - 2,
|
|
483
|
+
w: labelW + 4,
|
|
484
|
+
h: FB_CONST.LABEL_FONT + 4
|
|
485
|
+
});
|
|
486
|
+
const subCauses = [];
|
|
487
|
+
for (let si = 0; si < child.children.length; si++) {
|
|
488
|
+
const sub = child.children[si];
|
|
489
|
+
const subY = labelY + (si + 1) * D.subRowHeight;
|
|
490
|
+
const tickX1 = causeDir === "head" ? labelX + 2 : labelX - 2;
|
|
491
|
+
const tickX2 = causeDir === "head" ? tickX1 + FB_CONST.SUB_TICK_LEN : tickX1 - FB_CONST.SUB_TICK_LEN;
|
|
492
|
+
const subX = causeDir === "head" ? tickX2 + 4 : tickX2 - 4;
|
|
493
|
+
subCauses.push({
|
|
494
|
+
label: sub.label,
|
|
495
|
+
x: subX,
|
|
496
|
+
y: subY,
|
|
497
|
+
tickX1,
|
|
498
|
+
tickX2,
|
|
499
|
+
tickY: subY,
|
|
500
|
+
anchor: causeDir === "head" ? "start" : "end"
|
|
501
|
+
});
|
|
502
|
+
const subW = estimateTextWidth(sub.label, FB_CONST.SUB_FONT);
|
|
503
|
+
textBBoxes.push({
|
|
504
|
+
x: causeDir === "head" ? subX - 2 : subX - subW - 2,
|
|
505
|
+
y: subY - FB_CONST.SUB_FONT / 2 - 2,
|
|
506
|
+
w: subW + 4,
|
|
507
|
+
h: FB_CONST.SUB_FONT + 4
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
causes.push({
|
|
511
|
+
label: child.label,
|
|
512
|
+
ribIndex: globalIdx,
|
|
513
|
+
slotIndex: s,
|
|
514
|
+
ribX,
|
|
515
|
+
ribY,
|
|
516
|
+
branchX,
|
|
517
|
+
branchY,
|
|
518
|
+
labelX,
|
|
519
|
+
labelY,
|
|
520
|
+
labelAnchor,
|
|
521
|
+
causeSide: causeDir,
|
|
522
|
+
subCauses
|
|
523
|
+
});
|
|
524
|
+
accum += rowH;
|
|
525
|
+
}
|
|
526
|
+
ribs.push({
|
|
527
|
+
index: globalIdx,
|
|
528
|
+
half,
|
|
529
|
+
label: major.label,
|
|
530
|
+
color,
|
|
531
|
+
spineX,
|
|
532
|
+
spineY,
|
|
533
|
+
endX,
|
|
534
|
+
endY,
|
|
535
|
+
headerX,
|
|
536
|
+
headerY,
|
|
537
|
+
headerW,
|
|
538
|
+
headerH: D.headerH,
|
|
539
|
+
causes
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
};
|
|
543
|
+
buildHalfRibs(topMajors, 0, "top", topHalfExtent);
|
|
544
|
+
buildHalfRibs(botMajors, nTop, "bottom", botHalfExtent);
|
|
545
|
+
const head = {
|
|
546
|
+
x: headX,
|
|
547
|
+
y: spineY,
|
|
548
|
+
tipX: headTipXAdj,
|
|
549
|
+
tipY: spineY,
|
|
550
|
+
w: headEffectiveW,
|
|
551
|
+
h: headEffectiveH,
|
|
552
|
+
label: ast.effect
|
|
553
|
+
};
|
|
554
|
+
textBBoxes.push({
|
|
555
|
+
x: headX + 4,
|
|
556
|
+
y: spineY - headEffectiveH / 2 + 4,
|
|
557
|
+
w: headEffectiveW - 8,
|
|
558
|
+
h: headEffectiveH - 8
|
|
559
|
+
});
|
|
560
|
+
return {
|
|
561
|
+
width,
|
|
562
|
+
height,
|
|
563
|
+
orientation: ast.orientation,
|
|
564
|
+
spineY,
|
|
565
|
+
spineStartX,
|
|
566
|
+
spineEndX,
|
|
567
|
+
tailForkTipTop: { x: FB_CONST.PADDING, y: spineY - FB_CONST.TAIL_LEN },
|
|
568
|
+
tailForkTipBot: { x: FB_CONST.PADDING, y: spineY + FB_CONST.TAIL_LEN },
|
|
569
|
+
head,
|
|
570
|
+
ribs,
|
|
571
|
+
textBBoxes,
|
|
572
|
+
title: title2
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
function sumOrMin(arr, min) {
|
|
576
|
+
if (arr.length === 0) return min;
|
|
577
|
+
const s = arr.reduce((a, b) => a + b, 0);
|
|
578
|
+
return Math.max(s, min);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// src/diagrams/fishbone/renderer.ts
|
|
582
|
+
var CSS = `
|
|
583
|
+
.sx-fb { background: var(--schematex-fb-bg, #ffffff); font-family: system-ui, -apple-system, "Segoe UI", sans-serif; }
|
|
584
|
+
.sx-fb-title { font: 600 16px sans-serif; fill: #111; }
|
|
585
|
+
.sx-fb-spine { stroke: var(--schematex-fb-spine, #141413); stroke-width: 2; stroke-linecap: butt; fill: none; }
|
|
586
|
+
.sx-fb-tail { stroke: var(--schematex-fb-spine, #141413); stroke-width: 2; stroke-linecap: round; fill: none; }
|
|
587
|
+
.sx-fb-head { stroke-width: 0.5; }
|
|
588
|
+
.sx-fb-head-text { font: 500 14px sans-serif; text-anchor: middle; dominant-baseline: central; }
|
|
589
|
+
.sx-fb-rib { stroke-width: 1.5; fill: none; }
|
|
590
|
+
.sx-fb-header-pill { stroke-width: 0.6; }
|
|
591
|
+
.sx-fb-header-text { font: 500 14px sans-serif; text-anchor: middle; dominant-baseline: central; }
|
|
592
|
+
.sx-fb-branch { stroke-width: 0.8; opacity: 0.6; fill: none; }
|
|
593
|
+
.sx-fb-cause-label { font: 400 12px sans-serif; fill: #3d3d3a; dominant-baseline: central; }
|
|
594
|
+
.sx-fb-sub-tick { stroke-width: 0.7; opacity: 0.5; fill: none; }
|
|
595
|
+
.sx-fb-sub-label { font: 400 11px sans-serif; fill: #555; dominant-baseline: central; }
|
|
596
|
+
`.trim();
|
|
597
|
+
function lighten(hex, amount) {
|
|
598
|
+
const m = hex.match(/^#?([0-9a-f]{6})$/i);
|
|
599
|
+
if (!m) return hex;
|
|
600
|
+
const n = parseInt(m[1], 16);
|
|
601
|
+
const r = n >> 16 & 255;
|
|
602
|
+
const g = n >> 8 & 255;
|
|
603
|
+
const b = n & 255;
|
|
604
|
+
const rr = Math.round(r + (255 - r) * amount);
|
|
605
|
+
const gg = Math.round(g + (255 - g) * amount);
|
|
606
|
+
const bb = Math.round(b + (255 - b) * amount);
|
|
607
|
+
return `#${[rr, gg, bb].map((v) => v.toString(16).padStart(2, "0")).join("")}`;
|
|
608
|
+
}
|
|
609
|
+
function darken(hex, amount) {
|
|
610
|
+
const m = hex.match(/^#?([0-9a-f]{6})$/i);
|
|
611
|
+
if (!m) return hex;
|
|
612
|
+
const n = parseInt(m[1], 16);
|
|
613
|
+
const r = n >> 16 & 255;
|
|
614
|
+
const g = n >> 8 & 255;
|
|
615
|
+
const b = n & 255;
|
|
616
|
+
const rr = Math.round(r * (1 - amount));
|
|
617
|
+
const gg = Math.round(g * (1 - amount));
|
|
618
|
+
const bb = Math.round(b * (1 - amount));
|
|
619
|
+
return `#${[rr, gg, bb].map((v) => v.toString(16).padStart(2, "0")).join("")}`;
|
|
620
|
+
}
|
|
621
|
+
function randomId() {
|
|
622
|
+
return Math.random().toString(36).slice(2, 8);
|
|
623
|
+
}
|
|
624
|
+
function buildMask(id, w, h, bboxes) {
|
|
625
|
+
const base = rect({ x: 0, y: 0, width: w, height: h, fill: "white" });
|
|
626
|
+
const holes = bboxes.map(
|
|
627
|
+
(b) => rect({
|
|
628
|
+
x: b.x,
|
|
629
|
+
y: b.y,
|
|
630
|
+
width: b.w,
|
|
631
|
+
height: b.h,
|
|
632
|
+
fill: "black",
|
|
633
|
+
rx: 2
|
|
634
|
+
})
|
|
635
|
+
).join("\n");
|
|
636
|
+
return el("mask", { id, maskUnits: "userSpaceOnUse" }, [base, holes]);
|
|
637
|
+
}
|
|
638
|
+
function renderHead(layout, ltr) {
|
|
639
|
+
const h = layout.head;
|
|
640
|
+
const tipX = ltr ? h.tipX : h.x - (h.tipX - h.x);
|
|
641
|
+
const leftX = ltr ? h.x : h.x;
|
|
642
|
+
const rightX = ltr ? h.tipX : tipX;
|
|
643
|
+
const points = ltr ? `${leftX},${h.y - h.h / 2} ${leftX},${h.y + h.h / 2} ${rightX},${h.y}` : `${rightX},${h.y - h.h / 2} ${rightX},${h.y + h.h / 2} ${tipX},${h.y}`;
|
|
644
|
+
const fill = "#faece7";
|
|
645
|
+
const stroke = "#993c1d";
|
|
646
|
+
const textX = ltr ? leftX + h.w * 0.38 : rightX - h.w * 0.38;
|
|
647
|
+
return group({ class: "sx-fb-head-g" }, [
|
|
648
|
+
polygon({
|
|
649
|
+
points,
|
|
650
|
+
class: "sx-fb-head",
|
|
651
|
+
fill,
|
|
652
|
+
stroke
|
|
653
|
+
}),
|
|
654
|
+
text(
|
|
655
|
+
{
|
|
656
|
+
x: textX,
|
|
657
|
+
y: h.y,
|
|
658
|
+
class: "sx-fb-head-text",
|
|
659
|
+
fill: darken(stroke, 0.3)
|
|
660
|
+
},
|
|
661
|
+
h.label
|
|
662
|
+
)
|
|
663
|
+
]);
|
|
664
|
+
}
|
|
665
|
+
function renderRibs(layout, maskUrl) {
|
|
666
|
+
const parts = [];
|
|
667
|
+
for (const rib of layout.ribs) {
|
|
668
|
+
const pillFill = lighten(rib.color, 0.82);
|
|
669
|
+
const pillStroke = rib.color;
|
|
670
|
+
const headerTextFill = darken(rib.color, 0.3);
|
|
671
|
+
parts.push(
|
|
672
|
+
line({
|
|
673
|
+
x1: rib.spineX,
|
|
674
|
+
y1: rib.spineY,
|
|
675
|
+
x2: rib.endX,
|
|
676
|
+
y2: rib.endY,
|
|
677
|
+
class: "sx-fb-rib",
|
|
678
|
+
stroke: rib.color,
|
|
679
|
+
mask: maskUrl
|
|
680
|
+
})
|
|
681
|
+
);
|
|
682
|
+
parts.push(
|
|
683
|
+
line({
|
|
684
|
+
x1: rib.endX,
|
|
685
|
+
y1: rib.endY,
|
|
686
|
+
x2: rib.headerX + rib.headerW / 2,
|
|
687
|
+
y2: rib.headerY + rib.headerH / 2,
|
|
688
|
+
class: "sx-fb-rib",
|
|
689
|
+
stroke: rib.color
|
|
690
|
+
})
|
|
691
|
+
);
|
|
692
|
+
parts.push(
|
|
693
|
+
rect({
|
|
694
|
+
x: rib.headerX,
|
|
695
|
+
y: rib.headerY,
|
|
696
|
+
width: rib.headerW,
|
|
697
|
+
height: rib.headerH,
|
|
698
|
+
rx: 8,
|
|
699
|
+
class: "sx-fb-header-pill",
|
|
700
|
+
fill: pillFill,
|
|
701
|
+
stroke: pillStroke
|
|
702
|
+
})
|
|
703
|
+
);
|
|
704
|
+
parts.push(
|
|
705
|
+
text(
|
|
706
|
+
{
|
|
707
|
+
x: rib.headerX + rib.headerW / 2,
|
|
708
|
+
y: rib.headerY + rib.headerH / 2,
|
|
709
|
+
class: "sx-fb-header-text",
|
|
710
|
+
fill: headerTextFill
|
|
711
|
+
},
|
|
712
|
+
rib.label
|
|
713
|
+
)
|
|
714
|
+
);
|
|
715
|
+
for (const cause of rib.causes) {
|
|
716
|
+
parts.push(
|
|
717
|
+
line({
|
|
718
|
+
x1: cause.ribX,
|
|
719
|
+
y1: cause.ribY,
|
|
720
|
+
x2: cause.branchX,
|
|
721
|
+
y2: cause.branchY,
|
|
722
|
+
class: "sx-fb-branch",
|
|
723
|
+
stroke: rib.color,
|
|
724
|
+
mask: maskUrl
|
|
725
|
+
})
|
|
726
|
+
);
|
|
727
|
+
parts.push(
|
|
728
|
+
text(
|
|
729
|
+
{
|
|
730
|
+
x: cause.labelX,
|
|
731
|
+
y: cause.labelY,
|
|
732
|
+
class: "sx-fb-cause-label",
|
|
733
|
+
"text-anchor": cause.labelAnchor
|
|
734
|
+
},
|
|
735
|
+
cause.label
|
|
736
|
+
)
|
|
737
|
+
);
|
|
738
|
+
for (const sub of cause.subCauses) {
|
|
739
|
+
parts.push(
|
|
740
|
+
line({
|
|
741
|
+
x1: sub.tickX1,
|
|
742
|
+
y1: sub.tickY,
|
|
743
|
+
x2: sub.tickX2,
|
|
744
|
+
y2: sub.tickY,
|
|
745
|
+
class: "sx-fb-sub-tick",
|
|
746
|
+
stroke: rib.color
|
|
747
|
+
})
|
|
748
|
+
);
|
|
749
|
+
parts.push(
|
|
750
|
+
text(
|
|
751
|
+
{
|
|
752
|
+
x: sub.x,
|
|
753
|
+
y: sub.y,
|
|
754
|
+
class: "sx-fb-sub-label",
|
|
755
|
+
"text-anchor": sub.anchor
|
|
756
|
+
},
|
|
757
|
+
sub.label
|
|
758
|
+
)
|
|
759
|
+
);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
return group({ class: "sx-fb-ribs" }, parts);
|
|
764
|
+
}
|
|
765
|
+
function renderFishboneAST(ast, options = {}) {
|
|
766
|
+
const themeName = options.theme ?? ast.metadata?.["theme"] ?? "default";
|
|
767
|
+
const tokens = resolveFishboneTheme(themeName);
|
|
768
|
+
const layout = layoutFishbone(ast, { palette: tokens.palette });
|
|
769
|
+
const ltr = layout.orientation !== "rtl";
|
|
770
|
+
const maskId = `sx-fb-mask-${randomId()}`;
|
|
771
|
+
const maskUrl = `url(#${maskId})`;
|
|
772
|
+
const mask = buildMask(maskId, layout.width, layout.height, layout.textBBoxes);
|
|
773
|
+
const spine = line({
|
|
774
|
+
x1: layout.spineStartX,
|
|
775
|
+
y1: layout.spineY,
|
|
776
|
+
x2: layout.spineEndX,
|
|
777
|
+
y2: layout.spineY,
|
|
778
|
+
class: "sx-fb-spine"
|
|
779
|
+
});
|
|
780
|
+
const tailTop = line({
|
|
781
|
+
x1: layout.spineStartX,
|
|
782
|
+
y1: layout.spineY,
|
|
783
|
+
x2: layout.tailForkTipTop.x,
|
|
784
|
+
y2: layout.tailForkTipTop.y,
|
|
785
|
+
class: "sx-fb-tail"
|
|
786
|
+
});
|
|
787
|
+
const tailBot = line({
|
|
788
|
+
x1: layout.spineStartX,
|
|
789
|
+
y1: layout.spineY,
|
|
790
|
+
x2: layout.tailForkTipBot.x,
|
|
791
|
+
y2: layout.tailForkTipBot.y,
|
|
792
|
+
class: "sx-fb-tail"
|
|
793
|
+
});
|
|
794
|
+
const titleBlock = layout.title ? text(
|
|
795
|
+
{
|
|
796
|
+
x: layout.width / 2,
|
|
797
|
+
y: 28,
|
|
798
|
+
class: "sx-fb-title",
|
|
799
|
+
"text-anchor": "middle"
|
|
800
|
+
},
|
|
801
|
+
layout.title
|
|
802
|
+
) : "";
|
|
803
|
+
const head = renderHead(layout, ltr);
|
|
804
|
+
const ribs = renderRibs(layout, maskUrl);
|
|
805
|
+
const inner = [
|
|
806
|
+
title(layout.title ? `${layout.title} \u2014 Fishbone diagram` : "Fishbone diagram"),
|
|
807
|
+
desc(
|
|
808
|
+
`Ishikawa cause-and-effect diagram. Effect: ${ast.effect}. ${ast.majors.length} categories.`
|
|
809
|
+
),
|
|
810
|
+
el("style", {}, CSS),
|
|
811
|
+
defs([mask]),
|
|
812
|
+
titleBlock,
|
|
813
|
+
tailTop,
|
|
814
|
+
tailBot,
|
|
815
|
+
spine,
|
|
816
|
+
head,
|
|
817
|
+
ribs
|
|
818
|
+
];
|
|
819
|
+
if (!ltr) {
|
|
820
|
+
const mirrored = group(
|
|
821
|
+
{
|
|
822
|
+
transform: `translate(${layout.width} 0) scale(-1 1)`
|
|
823
|
+
},
|
|
824
|
+
[tailTop, tailBot, spine, head, ribs]
|
|
825
|
+
);
|
|
826
|
+
return svgRoot(
|
|
827
|
+
{
|
|
828
|
+
viewBox: `0 0 ${layout.width} ${layout.height}`,
|
|
829
|
+
width: layout.width,
|
|
830
|
+
height: layout.height,
|
|
831
|
+
class: "sx-fb",
|
|
832
|
+
role: "img"
|
|
833
|
+
},
|
|
834
|
+
[
|
|
835
|
+
title(layout.title ?? "Fishbone"),
|
|
836
|
+
desc(`Ishikawa diagram (head-left). Effect: ${ast.effect}.`),
|
|
837
|
+
el("style", {}, CSS),
|
|
838
|
+
defs([mask]),
|
|
839
|
+
titleBlock,
|
|
840
|
+
mirrored
|
|
841
|
+
]
|
|
842
|
+
);
|
|
843
|
+
}
|
|
844
|
+
return svgRoot(
|
|
845
|
+
{
|
|
846
|
+
viewBox: `0 0 ${layout.width} ${layout.height}`,
|
|
847
|
+
width: layout.width,
|
|
848
|
+
height: layout.height,
|
|
849
|
+
class: "sx-fb",
|
|
850
|
+
role: "img"
|
|
851
|
+
},
|
|
852
|
+
inner
|
|
853
|
+
);
|
|
854
|
+
}
|
|
855
|
+
function renderFishbone(text2, options = {}) {
|
|
856
|
+
const ast = parseFishboneDSL(text2);
|
|
857
|
+
return renderFishboneAST(ast, options);
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
// src/diagrams/fishbone/index.ts
|
|
861
|
+
var fishbone = {
|
|
862
|
+
type: "fishbone",
|
|
863
|
+
detect(text2) {
|
|
864
|
+
const first = text2.trim().split("\n")[0]?.trim().toLowerCase() ?? "";
|
|
865
|
+
return first.startsWith("fishbone");
|
|
866
|
+
},
|
|
867
|
+
render(text2) {
|
|
868
|
+
return renderFishbone(text2);
|
|
869
|
+
}
|
|
870
|
+
};
|
|
871
|
+
|
|
872
|
+
export { FB_CONST, fishbone, layoutFishbone, parseFishboneDSL, renderFishbone, renderFishboneAST };
|
|
873
|
+
//# sourceMappingURL=chunk-U4I37IBN.js.map
|
|
874
|
+
//# sourceMappingURL=chunk-U4I37IBN.js.map
|