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,1220 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkN7KOXOMX_cjs = require('./chunk-N7KOXOMX.cjs');
|
|
4
|
+
var chunkHDKDQAEQ_cjs = require('./chunk-HDKDQAEQ.cjs');
|
|
5
|
+
|
|
6
|
+
// src/diagrams/venn/parser.ts
|
|
7
|
+
var VennParseError = class extends Error {
|
|
8
|
+
constructor(message, line) {
|
|
9
|
+
super(line !== void 0 ? `Line ${line}: ${message}` : message);
|
|
10
|
+
this.line = line;
|
|
11
|
+
this.name = "VennParseError";
|
|
12
|
+
}
|
|
13
|
+
line;
|
|
14
|
+
};
|
|
15
|
+
var DEFAULT_CONFIG = {
|
|
16
|
+
mode: "auto",
|
|
17
|
+
proportional: false,
|
|
18
|
+
palette: "default",
|
|
19
|
+
blendMode: "multiply",
|
|
20
|
+
showCounts: "auto",
|
|
21
|
+
showPercent: false
|
|
22
|
+
};
|
|
23
|
+
function stripComment(line) {
|
|
24
|
+
let inQuote = false;
|
|
25
|
+
for (let i = 0; i < line.length; i++) {
|
|
26
|
+
const ch = line[i];
|
|
27
|
+
if (ch === '"') inQuote = !inQuote;
|
|
28
|
+
else if (ch === "#" && !inQuote) return line.slice(0, i);
|
|
29
|
+
}
|
|
30
|
+
return line;
|
|
31
|
+
}
|
|
32
|
+
function stripQuotes(s) {
|
|
33
|
+
const t = s.trim();
|
|
34
|
+
if (t.length >= 2 && t.startsWith('"') && t.endsWith('"')) return t.slice(1, -1);
|
|
35
|
+
return t;
|
|
36
|
+
}
|
|
37
|
+
function parseConfigProps(text2) {
|
|
38
|
+
const out = {};
|
|
39
|
+
let depth = 0;
|
|
40
|
+
let inQuote = false;
|
|
41
|
+
let buf = "";
|
|
42
|
+
const parts = [];
|
|
43
|
+
for (const ch of text2) {
|
|
44
|
+
if (ch === '"') inQuote = !inQuote;
|
|
45
|
+
else if (!inQuote && (ch === "[" || ch === "(" || ch === "{")) depth++;
|
|
46
|
+
else if (!inQuote && (ch === "]" || ch === ")" || ch === "}")) depth--;
|
|
47
|
+
if (ch === "," && depth === 0 && !inQuote) {
|
|
48
|
+
parts.push(buf);
|
|
49
|
+
buf = "";
|
|
50
|
+
} else {
|
|
51
|
+
buf += ch;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (buf.trim()) parts.push(buf);
|
|
55
|
+
for (const p of parts) {
|
|
56
|
+
const idx = p.indexOf(":");
|
|
57
|
+
if (idx < 0) continue;
|
|
58
|
+
const k = p.slice(0, idx).trim();
|
|
59
|
+
const v = p.slice(idx + 1).trim();
|
|
60
|
+
out[k] = stripQuotes(v);
|
|
61
|
+
}
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
64
|
+
function parseTitleAndProps(rest) {
|
|
65
|
+
const trimmed = rest.trim();
|
|
66
|
+
let i = 0;
|
|
67
|
+
let title2;
|
|
68
|
+
if (trimmed.startsWith('"')) {
|
|
69
|
+
const end = trimmed.indexOf('"', 1);
|
|
70
|
+
if (end < 0) throw new VennParseError("unterminated quoted title");
|
|
71
|
+
title2 = trimmed.slice(1, end);
|
|
72
|
+
i = end + 1;
|
|
73
|
+
}
|
|
74
|
+
const tail = trimmed.slice(i).trim();
|
|
75
|
+
let props = {};
|
|
76
|
+
if (tail.startsWith("[") && tail.endsWith("]")) {
|
|
77
|
+
props = parseConfigProps(tail.slice(1, -1));
|
|
78
|
+
}
|
|
79
|
+
return { title: title2, props };
|
|
80
|
+
}
|
|
81
|
+
function parseValue(raw) {
|
|
82
|
+
const t = raw.trim();
|
|
83
|
+
if (!t) return { kind: "none" };
|
|
84
|
+
if (t.startsWith("[") && t.endsWith("]")) {
|
|
85
|
+
const inner = t.slice(1, -1).trim();
|
|
86
|
+
if (!inner) return { kind: "list", value: [] };
|
|
87
|
+
const items = splitTopLevelCommas(inner).map((x) => stripQuotes(x.trim()));
|
|
88
|
+
return { kind: "list", value: items };
|
|
89
|
+
}
|
|
90
|
+
if (/^-?\d+(\.\d+)?%$/.test(t)) {
|
|
91
|
+
return { kind: "percent", value: parseFloat(t.slice(0, -1)) };
|
|
92
|
+
}
|
|
93
|
+
if (/^-?\d+$/.test(t)) {
|
|
94
|
+
return { kind: "integer", value: parseInt(t, 10) };
|
|
95
|
+
}
|
|
96
|
+
if (t.startsWith('"') && t.endsWith('"')) {
|
|
97
|
+
return { kind: "text", value: t.slice(1, -1) };
|
|
98
|
+
}
|
|
99
|
+
return { kind: "text", value: t };
|
|
100
|
+
}
|
|
101
|
+
function splitTopLevelCommas(s) {
|
|
102
|
+
const out = [];
|
|
103
|
+
let buf = "";
|
|
104
|
+
let depth = 0;
|
|
105
|
+
let inQuote = false;
|
|
106
|
+
for (const ch of s) {
|
|
107
|
+
if (ch === '"') inQuote = !inQuote;
|
|
108
|
+
else if (!inQuote && (ch === "{" || ch === "[" || ch === "(")) depth++;
|
|
109
|
+
else if (!inQuote && (ch === "}" || ch === "]" || ch === ")")) depth--;
|
|
110
|
+
if (ch === "," && depth === 0 && !inQuote) {
|
|
111
|
+
out.push(buf);
|
|
112
|
+
buf = "";
|
|
113
|
+
} else {
|
|
114
|
+
buf += ch;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (buf.length > 0) out.push(buf);
|
|
118
|
+
return out;
|
|
119
|
+
}
|
|
120
|
+
var EULER_KEYWORDS = {
|
|
121
|
+
subset: "subset",
|
|
122
|
+
disjoint: "disjoint",
|
|
123
|
+
overlap: "overlap"
|
|
124
|
+
};
|
|
125
|
+
function parseRegionKey(text2, knownSets) {
|
|
126
|
+
const trimmed = text2.trim();
|
|
127
|
+
const onlyMatch = /^([A-Za-z][\w-]*)\s+only$/i.exec(trimmed);
|
|
128
|
+
if (onlyMatch && onlyMatch[1]) {
|
|
129
|
+
const id = onlyMatch[1];
|
|
130
|
+
if (!knownSets.has(id)) {
|
|
131
|
+
throw new VennParseError(`unknown set id "${id}" in region key`);
|
|
132
|
+
}
|
|
133
|
+
return { sets: [id], only: true };
|
|
134
|
+
}
|
|
135
|
+
const parts = trimmed.split("&").map((p) => p.trim()).filter(Boolean);
|
|
136
|
+
if (parts.length === 0) {
|
|
137
|
+
throw new VennParseError(`empty region key: "${text2}"`);
|
|
138
|
+
}
|
|
139
|
+
for (const p of parts) {
|
|
140
|
+
if (!/^[A-Za-z][\w-]*$/.test(p)) {
|
|
141
|
+
throw new VennParseError(`invalid set ref "${p}" in region key`);
|
|
142
|
+
}
|
|
143
|
+
if (!knownSets.has(p)) {
|
|
144
|
+
throw new VennParseError(`unknown set id "${p}" in region key`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return { sets: [...parts], only: false };
|
|
148
|
+
}
|
|
149
|
+
function parseVennDSL(input) {
|
|
150
|
+
const lines = input.split(/\r?\n/);
|
|
151
|
+
let title2;
|
|
152
|
+
let headerSeen = false;
|
|
153
|
+
const config = { ...DEFAULT_CONFIG };
|
|
154
|
+
const sets = [];
|
|
155
|
+
const setsById = /* @__PURE__ */ new Map();
|
|
156
|
+
const regions = [];
|
|
157
|
+
const relations = [];
|
|
158
|
+
const metadata = {};
|
|
159
|
+
const knownSetIds = /* @__PURE__ */ new Set();
|
|
160
|
+
const applyConfigProps = (props) => {
|
|
161
|
+
for (const [k, v] of Object.entries(props)) {
|
|
162
|
+
applyConfig(k, v, config, metadata);
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
for (let i = 0; i < lines.length; i++) {
|
|
166
|
+
const raw = stripComment(lines[i] ?? "").trim();
|
|
167
|
+
if (!raw) continue;
|
|
168
|
+
const lineNo = i + 1;
|
|
169
|
+
if (!headerSeen && /^venn\b/i.test(raw)) {
|
|
170
|
+
const rest = raw.replace(/^venn\s*:?\s*/i, "");
|
|
171
|
+
const { title: t, props } = parseTitleAndProps(rest);
|
|
172
|
+
if (t !== void 0) title2 = t;
|
|
173
|
+
applyConfigProps(props);
|
|
174
|
+
headerSeen = true;
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
const configMatch = /^config\s*:\s*([a-zA-Z_]+)\s*=\s*(.+)$/.exec(raw);
|
|
178
|
+
if (configMatch && configMatch[1] && configMatch[2]) {
|
|
179
|
+
applyConfig(configMatch[1], configMatch[2].trim(), config, metadata);
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
const layoutMatch = /^layout\s+(venn|euler|auto)\s*$/i.exec(raw);
|
|
183
|
+
if (layoutMatch && layoutMatch[1]) {
|
|
184
|
+
config.mode = layoutMatch[1].toLowerCase();
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
const setMatch = /^set\s+([A-Za-z][\w-]*)\s+(.+)$/i.exec(raw);
|
|
188
|
+
if (setMatch && setMatch[1] && setMatch[2]) {
|
|
189
|
+
const id = setMatch[1];
|
|
190
|
+
const rest = setMatch[2];
|
|
191
|
+
const { title: label, props } = parseTitleAndProps(rest);
|
|
192
|
+
if (setsById.has(id)) {
|
|
193
|
+
throw new VennParseError(`duplicate set id "${id}"`, lineNo);
|
|
194
|
+
}
|
|
195
|
+
const color = props["color"] ?? props["fill"];
|
|
196
|
+
const setObj = { id, label: label ?? id, color };
|
|
197
|
+
sets.push(setObj);
|
|
198
|
+
setsById.set(id, setObj);
|
|
199
|
+
knownSetIds.add(id);
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
const enumMatch = /^([A-Za-z][\w-]*)\s*=\s*\{([^}]*)\}\s*$/.exec(raw);
|
|
203
|
+
if (enumMatch && enumMatch[1] !== void 0 && enumMatch[2] !== void 0) {
|
|
204
|
+
const id = enumMatch[1];
|
|
205
|
+
const items = splitTopLevelCommas(enumMatch[2]).map((x) => stripQuotes(x.trim())).filter((x) => x.length > 0);
|
|
206
|
+
let setObj = setsById.get(id);
|
|
207
|
+
if (!setObj) {
|
|
208
|
+
setObj = { id, label: id, elements: items };
|
|
209
|
+
sets.push(setObj);
|
|
210
|
+
setsById.set(id, setObj);
|
|
211
|
+
knownSetIds.add(id);
|
|
212
|
+
} else {
|
|
213
|
+
setObj.elements = items;
|
|
214
|
+
}
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
const eulerMatch = /^([A-Za-z][\w-]*)\s+(subset|in|disjoint|overlap)\s+([A-Za-z][\w-]*)\s*$/i.exec(
|
|
218
|
+
raw
|
|
219
|
+
);
|
|
220
|
+
if (eulerMatch && eulerMatch[1] && eulerMatch[2] && eulerMatch[3]) {
|
|
221
|
+
const from = eulerMatch[1];
|
|
222
|
+
const relRaw = eulerMatch[2].toLowerCase();
|
|
223
|
+
const rel = relRaw === "in" ? "subset" : relRaw;
|
|
224
|
+
const to = eulerMatch[3];
|
|
225
|
+
if (!knownSetIds.has(from)) {
|
|
226
|
+
throw new VennParseError(`unknown set "${from}" in relation`, lineNo);
|
|
227
|
+
}
|
|
228
|
+
if (!knownSetIds.has(to)) {
|
|
229
|
+
throw new VennParseError(`unknown set "${to}" in relation`, lineNo);
|
|
230
|
+
}
|
|
231
|
+
const relType = EULER_KEYWORDS[rel];
|
|
232
|
+
if (!relType) {
|
|
233
|
+
throw new VennParseError(`unknown relation "${rel}"`, lineNo);
|
|
234
|
+
}
|
|
235
|
+
relations.push({ from, to, type: relType });
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
let regionBody = raw;
|
|
239
|
+
if (/^region\s+/i.test(regionBody)) {
|
|
240
|
+
regionBody = regionBody.replace(/^region\s+/i, "");
|
|
241
|
+
}
|
|
242
|
+
const colonIdx = regionBody.indexOf(":");
|
|
243
|
+
if (colonIdx > 0) {
|
|
244
|
+
const keyPart = regionBody.slice(0, colonIdx);
|
|
245
|
+
const valuePart = regionBody.slice(colonIdx + 1).trim();
|
|
246
|
+
try {
|
|
247
|
+
const { sets: regionSets, only } = parseRegionKey(keyPart, knownSetIds);
|
|
248
|
+
const value = parseValue(valuePart);
|
|
249
|
+
regions.push({ sets: regionSets, only, value });
|
|
250
|
+
continue;
|
|
251
|
+
} catch (e) {
|
|
252
|
+
if (e instanceof VennParseError) {
|
|
253
|
+
throw new VennParseError(e.message, lineNo);
|
|
254
|
+
}
|
|
255
|
+
throw e;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
throw new VennParseError(`could not parse line: "${raw}"`, lineNo);
|
|
259
|
+
}
|
|
260
|
+
if (!headerSeen && sets.length === 0) {
|
|
261
|
+
throw new VennParseError("empty or missing 'venn' header");
|
|
262
|
+
}
|
|
263
|
+
const hasEnumeration = sets.some((s) => s.elements !== void 0);
|
|
264
|
+
if (hasEnumeration && regions.length === 0) {
|
|
265
|
+
regions.push(...deriveRegionsFromEnumeration(sets));
|
|
266
|
+
}
|
|
267
|
+
const ast = {
|
|
268
|
+
type: "venn",
|
|
269
|
+
...title2 !== void 0 ? { title: title2 } : {},
|
|
270
|
+
sets,
|
|
271
|
+
regions,
|
|
272
|
+
relations,
|
|
273
|
+
config,
|
|
274
|
+
...Object.keys(metadata).length > 0 ? { metadata } : {}
|
|
275
|
+
};
|
|
276
|
+
return ast;
|
|
277
|
+
}
|
|
278
|
+
function applyConfig(key, rawValue, config, metadata) {
|
|
279
|
+
const value = rawValue.trim().toLowerCase();
|
|
280
|
+
switch (key) {
|
|
281
|
+
case "diagram":
|
|
282
|
+
if (value === "venn" || value === "euler" || value === "auto") {
|
|
283
|
+
config.mode = value;
|
|
284
|
+
}
|
|
285
|
+
return;
|
|
286
|
+
case "proportional":
|
|
287
|
+
config.proportional = value === "true" || value === "yes" || value === "1";
|
|
288
|
+
return;
|
|
289
|
+
case "palette":
|
|
290
|
+
if (value === "default" || value === "brand" || value === "monochrome") {
|
|
291
|
+
config.palette = value;
|
|
292
|
+
}
|
|
293
|
+
return;
|
|
294
|
+
case "blendMode":
|
|
295
|
+
case "blendmode":
|
|
296
|
+
if (value === "multiply" || value === "screen" || value === "none") {
|
|
297
|
+
config.blendMode = value;
|
|
298
|
+
}
|
|
299
|
+
return;
|
|
300
|
+
case "showCounts":
|
|
301
|
+
case "showcounts":
|
|
302
|
+
config.showCounts = value === "true" ? true : value === "false" ? false : "auto";
|
|
303
|
+
return;
|
|
304
|
+
case "showPercent":
|
|
305
|
+
case "showpercent":
|
|
306
|
+
config.showPercent = value === "true" || value === "yes";
|
|
307
|
+
return;
|
|
308
|
+
default:
|
|
309
|
+
metadata[key] = rawValue.trim();
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
function deriveRegionsFromEnumeration(sets) {
|
|
313
|
+
const n = sets.length;
|
|
314
|
+
if (n === 0) return [];
|
|
315
|
+
const regions = [];
|
|
316
|
+
const total = 1 << n;
|
|
317
|
+
for (let mask = 1; mask < total; mask++) {
|
|
318
|
+
const included = [];
|
|
319
|
+
const excluded = [];
|
|
320
|
+
for (let i = 0; i < n; i++) {
|
|
321
|
+
if (mask >> i & 1) included.push(i);
|
|
322
|
+
else excluded.push(i);
|
|
323
|
+
}
|
|
324
|
+
const firstIdx = included[0];
|
|
325
|
+
if (firstIdx === void 0) continue;
|
|
326
|
+
const firstSetObj = sets[firstIdx];
|
|
327
|
+
if (!firstSetObj) continue;
|
|
328
|
+
const firstSet = firstSetObj.elements ?? [];
|
|
329
|
+
const members = firstSet.filter((el2) => {
|
|
330
|
+
for (const idx of included) {
|
|
331
|
+
const s = sets[idx];
|
|
332
|
+
const list = s?.elements ?? [];
|
|
333
|
+
if (!list.includes(el2)) return false;
|
|
334
|
+
}
|
|
335
|
+
for (const idx of excluded) {
|
|
336
|
+
const s = sets[idx];
|
|
337
|
+
const list = s?.elements ?? [];
|
|
338
|
+
if (list.includes(el2)) return false;
|
|
339
|
+
}
|
|
340
|
+
return true;
|
|
341
|
+
});
|
|
342
|
+
if (members.length === 0) continue;
|
|
343
|
+
const setIds = [];
|
|
344
|
+
for (const idx of included) {
|
|
345
|
+
const s = sets[idx];
|
|
346
|
+
if (s) setIds.push(s.id);
|
|
347
|
+
}
|
|
348
|
+
regions.push({
|
|
349
|
+
sets: setIds,
|
|
350
|
+
only: excluded.length > 0 && included.length < n,
|
|
351
|
+
value: { kind: "list", value: members }
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
return regions;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// src/diagrams/venn/geometry.ts
|
|
358
|
+
function twoCircleIntersectionArea(rA, rB, d) {
|
|
359
|
+
if (rA <= 0 || rB <= 0) return 0;
|
|
360
|
+
if (d >= rA + rB) return 0;
|
|
361
|
+
const rMin = Math.min(rA, rB);
|
|
362
|
+
if (d <= Math.abs(rA - rB)) return Math.PI * rMin * rMin;
|
|
363
|
+
const aTerm = (d * d + rA * rA - rB * rB) / (2 * d * rA);
|
|
364
|
+
const bTerm = (d * d + rB * rB - rA * rA) / (2 * d * rB);
|
|
365
|
+
const a = Math.max(-1, Math.min(1, aTerm));
|
|
366
|
+
const b = Math.max(-1, Math.min(1, bTerm));
|
|
367
|
+
const kSq = (-d + rA + rB) * (d + rA - rB) * (d - rA + rB) * (d + rA + rB);
|
|
368
|
+
const k = kSq > 0 ? Math.sqrt(kSq) : 0;
|
|
369
|
+
return rA * rA * Math.acos(a) + rB * rB * Math.acos(b) - 0.5 * k;
|
|
370
|
+
}
|
|
371
|
+
function solveTwoCircleDistance(rA, rB, target) {
|
|
372
|
+
const rMin = Math.min(rA, rB);
|
|
373
|
+
const maxArea = Math.PI * rMin * rMin;
|
|
374
|
+
if (target >= maxArea) return Math.abs(rA - rB);
|
|
375
|
+
if (target <= 0) return rA + rB;
|
|
376
|
+
let lo = Math.abs(rA - rB);
|
|
377
|
+
let hi = rA + rB;
|
|
378
|
+
for (let i = 0; i < 60; i++) {
|
|
379
|
+
const mid = 0.5 * (lo + hi);
|
|
380
|
+
const a = twoCircleIntersectionArea(rA, rB, mid);
|
|
381
|
+
if (a > target) {
|
|
382
|
+
lo = mid;
|
|
383
|
+
} else {
|
|
384
|
+
hi = mid;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
return 0.5 * (lo + hi);
|
|
388
|
+
}
|
|
389
|
+
function threeCircleClassic(cx, cy, r, offsetScale = 0.6) {
|
|
390
|
+
const o = r * offsetScale;
|
|
391
|
+
const cos30 = Math.cos(Math.PI / 6);
|
|
392
|
+
const sin30 = Math.sin(Math.PI / 6);
|
|
393
|
+
return [
|
|
394
|
+
{ id: "0", cx: cx - o * cos30, cy: cy + o * sin30, r },
|
|
395
|
+
{ id: "1", cx: cx + o * cos30, cy: cy + o * sin30, r },
|
|
396
|
+
{ id: "2", cx, cy: cy - o, r }
|
|
397
|
+
];
|
|
398
|
+
}
|
|
399
|
+
function fourEllipseStandard(cx, cy, a, b) {
|
|
400
|
+
const dx = 0.18 * a;
|
|
401
|
+
const dy = 0.1 * b;
|
|
402
|
+
return [
|
|
403
|
+
{ id: "0", cx: cx - dx, cy: cy - dy, rx: a, ry: b, rotation: -65 },
|
|
404
|
+
{ id: "1", cx: cx - 0.6 * dx, cy: cy - dy, rx: a, ry: b, rotation: -35 },
|
|
405
|
+
{ id: "2", cx: cx + 0.6 * dx, cy: cy - dy, rx: a, ry: b, rotation: 35 },
|
|
406
|
+
{ id: "3", cx: cx + dx, cy: cy - dy, rx: a, ry: b, rotation: 65 }
|
|
407
|
+
];
|
|
408
|
+
}
|
|
409
|
+
function pointInCircle(c, x, y) {
|
|
410
|
+
const dx = x - c.cx;
|
|
411
|
+
const dy = y - c.cy;
|
|
412
|
+
return dx * dx + dy * dy <= c.r * c.r;
|
|
413
|
+
}
|
|
414
|
+
function pointInEllipse(e, x, y) {
|
|
415
|
+
const dx = x - e.cx;
|
|
416
|
+
const dy = y - e.cy;
|
|
417
|
+
const rad = e.rotation * Math.PI / 180;
|
|
418
|
+
const cos = Math.cos(-rad);
|
|
419
|
+
const sin = Math.sin(-rad);
|
|
420
|
+
const rx = dx * cos - dy * sin;
|
|
421
|
+
const ry = dx * sin + dy * cos;
|
|
422
|
+
return rx * rx / (e.rx * e.rx) + ry * ry / (e.ry * e.ry) <= 1;
|
|
423
|
+
}
|
|
424
|
+
function circleBBox(c) {
|
|
425
|
+
return { x: c.cx - c.r, y: c.cy - c.r, w: 2 * c.r, h: 2 * c.r };
|
|
426
|
+
}
|
|
427
|
+
function ellipseBBox(e) {
|
|
428
|
+
const rad = e.rotation * Math.PI / 180;
|
|
429
|
+
const cos = Math.cos(rad);
|
|
430
|
+
const sin = Math.sin(rad);
|
|
431
|
+
const ux = e.rx * cos;
|
|
432
|
+
const uy = e.rx * sin;
|
|
433
|
+
const vx = e.ry * -sin;
|
|
434
|
+
const vy = e.ry * cos;
|
|
435
|
+
const hx = Math.sqrt(ux * ux + vx * vx);
|
|
436
|
+
const hy = Math.sqrt(uy * uy + vy * vy);
|
|
437
|
+
return { x: e.cx - hx, y: e.cy - hy, w: 2 * hx, h: 2 * hy };
|
|
438
|
+
}
|
|
439
|
+
function unionBBox(boxes) {
|
|
440
|
+
if (boxes.length === 0) return { x: 0, y: 0, w: 0, h: 0 };
|
|
441
|
+
let minX = Infinity;
|
|
442
|
+
let minY = Infinity;
|
|
443
|
+
let maxX = -Infinity;
|
|
444
|
+
let maxY = -Infinity;
|
|
445
|
+
for (const b of boxes) {
|
|
446
|
+
if (b.x < minX) minX = b.x;
|
|
447
|
+
if (b.y < minY) minY = b.y;
|
|
448
|
+
if (b.x + b.w > maxX) maxX = b.x + b.w;
|
|
449
|
+
if (b.y + b.h > maxY) maxY = b.y + b.h;
|
|
450
|
+
}
|
|
451
|
+
return { x: minX, y: minY, w: maxX - minX, h: maxY - minY };
|
|
452
|
+
}
|
|
453
|
+
function mulberry32(seed) {
|
|
454
|
+
let a = seed >>> 0;
|
|
455
|
+
return function() {
|
|
456
|
+
a = a + 1831565813 >>> 0;
|
|
457
|
+
let t = a;
|
|
458
|
+
t = Math.imul(t ^ t >>> 15, t | 1);
|
|
459
|
+
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
|
|
460
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
function regionCentroid(bounds, include, exclude, nSamples = 2e3, seed = 12648430) {
|
|
464
|
+
const rand = mulberry32(seed);
|
|
465
|
+
let hits = 0;
|
|
466
|
+
let sumX = 0;
|
|
467
|
+
let sumY = 0;
|
|
468
|
+
const interior = [];
|
|
469
|
+
for (let i = 0; i < nSamples; i++) {
|
|
470
|
+
const x = bounds.x + rand() * bounds.w;
|
|
471
|
+
const y = bounds.y + rand() * bounds.h;
|
|
472
|
+
let ok = true;
|
|
473
|
+
for (const s of include) {
|
|
474
|
+
if (!s.inside(x, y)) {
|
|
475
|
+
ok = false;
|
|
476
|
+
break;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
if (!ok) continue;
|
|
480
|
+
for (const s of exclude) {
|
|
481
|
+
if (s.inside(x, y)) {
|
|
482
|
+
ok = false;
|
|
483
|
+
break;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
if (!ok) continue;
|
|
487
|
+
hits++;
|
|
488
|
+
sumX += x;
|
|
489
|
+
sumY += y;
|
|
490
|
+
if (interior.length < 256) interior.push({ x, y });
|
|
491
|
+
}
|
|
492
|
+
const area = hits / nSamples * bounds.w * bounds.h;
|
|
493
|
+
if (hits === 0) return { cx: NaN, cy: NaN, area: 0, interior };
|
|
494
|
+
return { cx: sumX / hits, cy: sumY / hits, area, interior };
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// src/diagrams/venn/labels.ts
|
|
498
|
+
var MIN_AREA = 400;
|
|
499
|
+
var MIN_AREA_DENSE = 900;
|
|
500
|
+
var LABEL_H = 14;
|
|
501
|
+
var CHAR_W = 6.2;
|
|
502
|
+
var LABEL_GAP = 4;
|
|
503
|
+
function labelWidth(text2) {
|
|
504
|
+
return text2.length * CHAR_W + 8;
|
|
505
|
+
}
|
|
506
|
+
function bboxesOverlap(ax, ay, aw, ah, bx, by, bw, bh) {
|
|
507
|
+
return ax < bx + bw + LABEL_GAP && ax + aw + LABEL_GAP > bx && ay < by + bh + LABEL_GAP && ay + ah + LABEL_GAP > by;
|
|
508
|
+
}
|
|
509
|
+
function makeShapeEntry(shape) {
|
|
510
|
+
if (shape.kind === "circle") {
|
|
511
|
+
const c = shape;
|
|
512
|
+
return {
|
|
513
|
+
id: shape.id,
|
|
514
|
+
shape,
|
|
515
|
+
bbox: circleBBox(c),
|
|
516
|
+
inside: (x, y) => pointInCircle(c, x, y)
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
const e = shape;
|
|
520
|
+
return {
|
|
521
|
+
id: shape.id,
|
|
522
|
+
shape,
|
|
523
|
+
bbox: ellipseBBox(e),
|
|
524
|
+
inside: (x, y) => pointInEllipse(e, x, y)
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
function placeLabels(ast, shapes) {
|
|
528
|
+
const entries = shapes.map(makeShapeEntry);
|
|
529
|
+
const byId = /* @__PURE__ */ new Map();
|
|
530
|
+
for (const e of entries) byId.set(e.id, e);
|
|
531
|
+
const canvasBounds = unionBBox(entries.map((e) => e.bbox));
|
|
532
|
+
const canvasCx = canvasBounds.x + canvasBounds.w / 2;
|
|
533
|
+
const canvasCy = canvasBounds.y + canvasBounds.h / 2;
|
|
534
|
+
const dense = shapes.length >= 4;
|
|
535
|
+
const minArea = dense ? MIN_AREA_DENSE : MIN_AREA;
|
|
536
|
+
const candidates = [];
|
|
537
|
+
ast.regions.forEach((region, regionIdx) => {
|
|
538
|
+
const included = region.sets.map((id) => byId.get(id)).filter(isDefined);
|
|
539
|
+
if (included.length === 0) return;
|
|
540
|
+
const excluded = region.only ? entries.filter((e) => !region.sets.includes(e.id)) : [];
|
|
541
|
+
const bounds = unionBBox(included.map((e) => e.bbox));
|
|
542
|
+
const seed = 4919 + regionIdx * 2654435769;
|
|
543
|
+
const { cx, cy, area, interior } = regionCentroid(
|
|
544
|
+
bounds,
|
|
545
|
+
included,
|
|
546
|
+
excluded,
|
|
547
|
+
1500,
|
|
548
|
+
seed
|
|
549
|
+
);
|
|
550
|
+
const text2 = regionLabelText(region);
|
|
551
|
+
if (!text2) return;
|
|
552
|
+
if (!Number.isFinite(cx) || !Number.isFinite(cy) || area === 0) return;
|
|
553
|
+
candidates.push({
|
|
554
|
+
region,
|
|
555
|
+
text: text2,
|
|
556
|
+
cx,
|
|
557
|
+
cy,
|
|
558
|
+
area,
|
|
559
|
+
interior,
|
|
560
|
+
width: labelWidth(text2)
|
|
561
|
+
});
|
|
562
|
+
});
|
|
563
|
+
const byAreaDesc = [...candidates].sort((a, b) => b.area - a.area);
|
|
564
|
+
const placedInternal = [];
|
|
565
|
+
const externalQueue = [];
|
|
566
|
+
for (const c of byAreaDesc) {
|
|
567
|
+
if (c.area < minArea) {
|
|
568
|
+
externalQueue.push(c);
|
|
569
|
+
continue;
|
|
570
|
+
}
|
|
571
|
+
const x = c.cx - c.width / 2;
|
|
572
|
+
const y = c.cy - LABEL_H / 2;
|
|
573
|
+
const collides = placedInternal.some(
|
|
574
|
+
({ c: p, x: px, y: py }) => bboxesOverlap(x, y, c.width, LABEL_H, px, py, p.width, LABEL_H)
|
|
575
|
+
);
|
|
576
|
+
if (collides) {
|
|
577
|
+
externalQueue.push(c);
|
|
578
|
+
} else {
|
|
579
|
+
placedInternal.push({ c, x, y });
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
const externalPlacements = layoutExternal(
|
|
583
|
+
externalQueue,
|
|
584
|
+
canvasCx,
|
|
585
|
+
canvasCy,
|
|
586
|
+
canvasBounds
|
|
587
|
+
);
|
|
588
|
+
const placedMap = /* @__PURE__ */ new Map();
|
|
589
|
+
for (const { c } of placedInternal) {
|
|
590
|
+
placedMap.set(c.region, {
|
|
591
|
+
sets: c.region.sets,
|
|
592
|
+
label: c.text,
|
|
593
|
+
x: c.cx,
|
|
594
|
+
y: c.cy,
|
|
595
|
+
external: false
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
for (const p of externalPlacements) {
|
|
599
|
+
placedMap.set(p.region, p.label);
|
|
600
|
+
}
|
|
601
|
+
const out = [];
|
|
602
|
+
for (const region of ast.regions) {
|
|
603
|
+
const p = placedMap.get(region);
|
|
604
|
+
if (p) out.push(p);
|
|
605
|
+
}
|
|
606
|
+
return out;
|
|
607
|
+
}
|
|
608
|
+
function layoutExternal(queue, cx, cy, canvas) {
|
|
609
|
+
if (queue.length === 0) return [];
|
|
610
|
+
const withAngle = queue.map((c) => {
|
|
611
|
+
const dx = c.cx - cx;
|
|
612
|
+
const dy = c.cy - cy;
|
|
613
|
+
let theta = Math.atan2(dy, dx);
|
|
614
|
+
if (!Number.isFinite(theta)) theta = 0;
|
|
615
|
+
return { c, theta };
|
|
616
|
+
});
|
|
617
|
+
withAngle.sort((a, b) => a.theta - b.theta);
|
|
618
|
+
const radiusX = canvas.w / 2 + 40;
|
|
619
|
+
const radiusY = canvas.h / 2 + 28;
|
|
620
|
+
const minGap = Math.min(2 * Math.PI / Math.max(6, withAngle.length), 0.45);
|
|
621
|
+
const adjusted = redistributeAngles(
|
|
622
|
+
withAngle.map((w) => w.theta),
|
|
623
|
+
minGap
|
|
624
|
+
);
|
|
625
|
+
const out = [];
|
|
626
|
+
for (let i = 0; i < withAngle.length; i++) {
|
|
627
|
+
const { c } = withAngle[i];
|
|
628
|
+
const theta = adjusted[i];
|
|
629
|
+
const ux = Math.cos(theta);
|
|
630
|
+
const uy = Math.sin(theta);
|
|
631
|
+
const ex = cx + ux * radiusX;
|
|
632
|
+
const ey = cy + uy * radiusY;
|
|
633
|
+
const anchor = ux > 0.25 ? "start" : ux < -0.25 ? "end" : "middle";
|
|
634
|
+
const picked = c.interior[Math.floor(c.interior.length / 2)];
|
|
635
|
+
const start = picked ?? { x: c.cx, y: c.cy };
|
|
636
|
+
out.push({
|
|
637
|
+
region: c.region,
|
|
638
|
+
label: {
|
|
639
|
+
sets: c.region.sets,
|
|
640
|
+
label: c.text,
|
|
641
|
+
x: ex,
|
|
642
|
+
y: ey,
|
|
643
|
+
external: true,
|
|
644
|
+
leader: { x1: start.x, y1: start.y, x2: ex, y2: ey },
|
|
645
|
+
anchor
|
|
646
|
+
}
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
return out;
|
|
650
|
+
}
|
|
651
|
+
function redistributeAngles(thetas, minGap) {
|
|
652
|
+
const out = thetas.slice();
|
|
653
|
+
for (let i = 1; i < out.length; i++) {
|
|
654
|
+
const need = out[i - 1] + minGap;
|
|
655
|
+
if (out[i] < need) out[i] = need;
|
|
656
|
+
}
|
|
657
|
+
const wrapLimit = out[0] + Math.PI * 2 - minGap;
|
|
658
|
+
if (out.length > 1 && out[out.length - 1] > wrapLimit) {
|
|
659
|
+
const shift = out[out.length - 1] - wrapLimit;
|
|
660
|
+
for (let i = 0; i < out.length; i++) out[i] = out[i] - shift;
|
|
661
|
+
}
|
|
662
|
+
return out;
|
|
663
|
+
}
|
|
664
|
+
function regionLabelText(region) {
|
|
665
|
+
return formatRegionValue(region);
|
|
666
|
+
}
|
|
667
|
+
function isDefined(x) {
|
|
668
|
+
return x !== void 0;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
// src/diagrams/venn/layout.ts
|
|
672
|
+
var DEFAULTS = {
|
|
673
|
+
padding: 24,
|
|
674
|
+
canvasN2: { w: 480, h: 320, r: 110, d: 120 },
|
|
675
|
+
canvasN3: { w: 520, h: 480, r: 130, offset: 0.6 },
|
|
676
|
+
canvasN4: { w: 640, h: 480, a: 216, b: 120 },
|
|
677
|
+
canvasEuler: { w: 520, h: 420 }
|
|
678
|
+
};
|
|
679
|
+
function layoutVenn(ast, opts = {}) {
|
|
680
|
+
const mode = decideMode(ast);
|
|
681
|
+
if (mode === "euler") {
|
|
682
|
+
return layoutEuler(ast, opts);
|
|
683
|
+
}
|
|
684
|
+
const n = ast.sets.length;
|
|
685
|
+
if (n === 1) return layoutSingle(ast, opts);
|
|
686
|
+
if (n === 2) return layoutTwo(ast, opts);
|
|
687
|
+
if (n === 3) return layoutThree(ast, opts);
|
|
688
|
+
if (n === 4) return layoutFour(ast, opts);
|
|
689
|
+
return layoutEuler(ast, opts);
|
|
690
|
+
}
|
|
691
|
+
function decideMode(ast) {
|
|
692
|
+
if (ast.config.mode === "venn") return "venn";
|
|
693
|
+
if (ast.config.mode === "euler") return "euler";
|
|
694
|
+
return ast.relations.length > 0 ? "euler" : "venn";
|
|
695
|
+
}
|
|
696
|
+
function layoutSingle(ast, opts) {
|
|
697
|
+
const w = opts.width ?? 320;
|
|
698
|
+
const h = opts.height ?? 260;
|
|
699
|
+
const set = ast.sets[0];
|
|
700
|
+
if (!set) {
|
|
701
|
+
return { width: w, height: h, mode: "venn", shapes: [], labels: [], setLabels: [] };
|
|
702
|
+
}
|
|
703
|
+
const circle2 = { id: set.id, cx: w / 2, cy: h / 2, r: Math.min(w, h) / 3 };
|
|
704
|
+
const shapes = [{ kind: "circle", ...circle2 }];
|
|
705
|
+
const region = ast.regions.find((r) => r.sets.length === 1 && r.sets[0] === set.id);
|
|
706
|
+
const labels = region ? [
|
|
707
|
+
{
|
|
708
|
+
sets: [set.id],
|
|
709
|
+
label: formatRegionValue(region),
|
|
710
|
+
x: circle2.cx,
|
|
711
|
+
y: circle2.cy,
|
|
712
|
+
external: false
|
|
713
|
+
}
|
|
714
|
+
] : [];
|
|
715
|
+
const setLabels = [
|
|
716
|
+
{
|
|
717
|
+
id: set.id,
|
|
718
|
+
label: set.label,
|
|
719
|
+
x: circle2.cx,
|
|
720
|
+
y: circle2.cy - circle2.r - 12,
|
|
721
|
+
anchor: "middle"
|
|
722
|
+
}
|
|
723
|
+
];
|
|
724
|
+
return {
|
|
725
|
+
width: w,
|
|
726
|
+
height: h,
|
|
727
|
+
mode: "venn",
|
|
728
|
+
shapes,
|
|
729
|
+
labels,
|
|
730
|
+
setLabels,
|
|
731
|
+
...ast.title ? { title: { text: ast.title, x: w / 2, y: 24 } } : {}
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
function layoutTwo(ast, opts) {
|
|
735
|
+
const padding = opts.padding ?? DEFAULTS.padding;
|
|
736
|
+
const setA = ast.sets[0];
|
|
737
|
+
const setB = ast.sets[1];
|
|
738
|
+
if (!setA || !setB) {
|
|
739
|
+
throw new Error("layoutTwo requires two sets");
|
|
740
|
+
}
|
|
741
|
+
let rA = DEFAULTS.canvasN2.r;
|
|
742
|
+
let rB = DEFAULTS.canvasN2.r;
|
|
743
|
+
let d = DEFAULTS.canvasN2.d;
|
|
744
|
+
if (ast.config.proportional) {
|
|
745
|
+
const sizes = computeAbsoluteSetSizes(ast);
|
|
746
|
+
const sizeA = sizes.get(setA.id) ?? 1;
|
|
747
|
+
const sizeB = sizes.get(setB.id) ?? 1;
|
|
748
|
+
const inter = regionValue(ast, [setA.id, setB.id]) ?? 0;
|
|
749
|
+
const scale = 1 / Math.sqrt(Math.max(sizeA, sizeB));
|
|
750
|
+
rA = 110 * Math.sqrt(sizeA) * scale;
|
|
751
|
+
rB = 110 * Math.sqrt(sizeB) * scale;
|
|
752
|
+
const areaPerElem = Math.PI * rA * rA / Math.max(sizeA, 1e-6);
|
|
753
|
+
d = solveTwoCircleDistance(rA, rB, inter * areaPerElem);
|
|
754
|
+
}
|
|
755
|
+
const totalW = rA + rB + d + 2 * padding;
|
|
756
|
+
const w = Math.max(opts.width ?? DEFAULTS.canvasN2.w, totalW);
|
|
757
|
+
const h = opts.height ?? DEFAULTS.canvasN2.h;
|
|
758
|
+
const cxA = (w - d) / 2;
|
|
759
|
+
const cxB = cxA + d;
|
|
760
|
+
const cy = h / 2;
|
|
761
|
+
const circles = [
|
|
762
|
+
{ id: setA.id, cx: cxA, cy, r: rA },
|
|
763
|
+
{ id: setB.id, cx: cxB, cy, r: rB }
|
|
764
|
+
];
|
|
765
|
+
const shapes = circles.map((c) => ({ kind: "circle", ...c }));
|
|
766
|
+
const labels = placeLabels(ast, shapes);
|
|
767
|
+
const setLabels = [
|
|
768
|
+
{ id: setA.id, label: setA.label, x: cxA, y: cy - rA - 10, anchor: "middle" },
|
|
769
|
+
{ id: setB.id, label: setB.label, x: cxB, y: cy - rB - 10, anchor: "middle" }
|
|
770
|
+
];
|
|
771
|
+
return {
|
|
772
|
+
width: w,
|
|
773
|
+
height: h,
|
|
774
|
+
mode: "venn",
|
|
775
|
+
shapes,
|
|
776
|
+
labels,
|
|
777
|
+
setLabels,
|
|
778
|
+
...ast.title ? { title: { text: ast.title, x: w / 2, y: 24 } } : {}
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
function layoutThree(ast, opts) {
|
|
782
|
+
const w = opts.width ?? DEFAULTS.canvasN3.w;
|
|
783
|
+
const h = opts.height ?? DEFAULTS.canvasN3.h;
|
|
784
|
+
const r = DEFAULTS.canvasN3.r;
|
|
785
|
+
const offsetScale = DEFAULTS.canvasN3.offset;
|
|
786
|
+
const cx = w / 2;
|
|
787
|
+
const cy = h / 2 + 12;
|
|
788
|
+
let circles = threeCircleClassic(cx, cy, r, offsetScale);
|
|
789
|
+
if (ast.config.proportional) {
|
|
790
|
+
const adjusted = solveProportionalThree(ast, cx, cy);
|
|
791
|
+
if (adjusted) circles = adjusted;
|
|
792
|
+
}
|
|
793
|
+
const shapes = circles.map((c, i) => {
|
|
794
|
+
const setId = ast.sets[i]?.id ?? c.id;
|
|
795
|
+
return { kind: "circle", ...c, id: setId };
|
|
796
|
+
});
|
|
797
|
+
const labels = placeLabels(ast, shapes);
|
|
798
|
+
const setLabels = ast.sets.map((s, i) => {
|
|
799
|
+
const c = circles[i] ?? circles[0];
|
|
800
|
+
if (!c) {
|
|
801
|
+
return { id: s.id, label: s.label, x: cx, y: cy, anchor: "middle" };
|
|
802
|
+
}
|
|
803
|
+
const isTop = i === 2;
|
|
804
|
+
return {
|
|
805
|
+
id: s.id,
|
|
806
|
+
label: s.label,
|
|
807
|
+
x: isTop ? c.cx : c.cx + (i === 0 ? -c.r : c.r) * 0.7,
|
|
808
|
+
y: isTop ? c.cy - c.r - 10 : c.cy + c.r + 16,
|
|
809
|
+
anchor: isTop ? "middle" : i === 0 ? "end" : "start"
|
|
810
|
+
};
|
|
811
|
+
});
|
|
812
|
+
return {
|
|
813
|
+
width: w,
|
|
814
|
+
height: h,
|
|
815
|
+
mode: "venn",
|
|
816
|
+
shapes,
|
|
817
|
+
labels,
|
|
818
|
+
setLabels,
|
|
819
|
+
...ast.title ? { title: { text: ast.title, x: w / 2, y: 24 } } : {}
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
function layoutFour(ast, opts) {
|
|
823
|
+
const w = opts.width ?? DEFAULTS.canvasN4.w;
|
|
824
|
+
const h = opts.height ?? DEFAULTS.canvasN4.h;
|
|
825
|
+
const cx = w / 2;
|
|
826
|
+
const cy = h / 2 + 8;
|
|
827
|
+
const ellipses = fourEllipseStandard(cx, cy, DEFAULTS.canvasN4.a, DEFAULTS.canvasN4.b);
|
|
828
|
+
const shapes = ellipses.map((e, i) => ({
|
|
829
|
+
kind: "ellipse",
|
|
830
|
+
...e,
|
|
831
|
+
id: ast.sets[i]?.id ?? e.id
|
|
832
|
+
}));
|
|
833
|
+
const labels = placeLabels(ast, shapes);
|
|
834
|
+
const setLabels = ast.sets.map((s, i) => {
|
|
835
|
+
const e = ellipses[i];
|
|
836
|
+
if (!e) {
|
|
837
|
+
return { id: s.id, label: s.label, x: cx, y: cy, anchor: "middle" };
|
|
838
|
+
}
|
|
839
|
+
const bb = ellipseBBox(e);
|
|
840
|
+
const sign = i < 2 ? -1 : 1;
|
|
841
|
+
return {
|
|
842
|
+
id: s.id,
|
|
843
|
+
label: s.label,
|
|
844
|
+
x: bb.x + (sign < 0 ? 0 : bb.w),
|
|
845
|
+
y: bb.y + (i % 2 === 0 ? 14 : bb.h - 4),
|
|
846
|
+
anchor: sign < 0 ? "start" : "end"
|
|
847
|
+
};
|
|
848
|
+
});
|
|
849
|
+
return {
|
|
850
|
+
width: w,
|
|
851
|
+
height: h,
|
|
852
|
+
mode: "venn",
|
|
853
|
+
shapes,
|
|
854
|
+
labels,
|
|
855
|
+
setLabels,
|
|
856
|
+
...ast.title ? { title: { text: ast.title, x: w / 2, y: 24 } } : {}
|
|
857
|
+
};
|
|
858
|
+
}
|
|
859
|
+
function layoutEuler(ast, opts) {
|
|
860
|
+
const w = opts.width ?? DEFAULTS.canvasEuler.w;
|
|
861
|
+
const h = opts.height ?? DEFAULTS.canvasEuler.h;
|
|
862
|
+
const n = ast.sets.length;
|
|
863
|
+
if (n === 0) {
|
|
864
|
+
return {
|
|
865
|
+
width: w,
|
|
866
|
+
height: h,
|
|
867
|
+
mode: "euler",
|
|
868
|
+
shapes: [],
|
|
869
|
+
labels: [],
|
|
870
|
+
setLabels: []
|
|
871
|
+
};
|
|
872
|
+
}
|
|
873
|
+
const parents = /* @__PURE__ */ new Map();
|
|
874
|
+
const disjointPairs = /* @__PURE__ */ new Set();
|
|
875
|
+
for (const rel of ast.relations) {
|
|
876
|
+
if (rel.type === "subset") parents.set(rel.from, rel.to);
|
|
877
|
+
if (rel.type === "disjoint") disjointPairs.add(edgeKey(rel.from, rel.to));
|
|
878
|
+
}
|
|
879
|
+
const roots = ast.sets.filter((s) => !parents.has(s.id)).map((s) => s.id);
|
|
880
|
+
const circles = /* @__PURE__ */ new Map();
|
|
881
|
+
const rootRadius = Math.min(w, h) * 0.32;
|
|
882
|
+
const rootSpacing = Math.min(w / Math.max(roots.length, 1), 2 * rootRadius + 40);
|
|
883
|
+
const startX = w / 2 - (roots.length - 1) * rootSpacing / 2;
|
|
884
|
+
roots.forEach((rootId, i) => {
|
|
885
|
+
circles.set(rootId, { id: rootId, cx: startX + i * rootSpacing, cy: h / 2, r: rootRadius });
|
|
886
|
+
});
|
|
887
|
+
const childrenOf = /* @__PURE__ */ new Map();
|
|
888
|
+
for (const [child, par] of parents.entries()) {
|
|
889
|
+
const arr = childrenOf.get(par) ?? [];
|
|
890
|
+
arr.push(child);
|
|
891
|
+
childrenOf.set(par, arr);
|
|
892
|
+
}
|
|
893
|
+
const placeChildren = (parentId, depth) => {
|
|
894
|
+
const kids = childrenOf.get(parentId) ?? [];
|
|
895
|
+
if (kids.length === 0) return;
|
|
896
|
+
const parent = circles.get(parentId);
|
|
897
|
+
if (!parent) return;
|
|
898
|
+
const childR = parent.r * (kids.length === 1 ? 0.55 : 0.4);
|
|
899
|
+
kids.forEach((kid, i) => {
|
|
900
|
+
const offset = kids.length === 1 ? 0 : (i - (kids.length - 1) / 2) * childR * 1.4;
|
|
901
|
+
circles.set(kid, {
|
|
902
|
+
id: kid,
|
|
903
|
+
cx: parent.cx + offset,
|
|
904
|
+
cy: parent.cy + (depth % 2 === 0 ? 0 : -childR * 0.2),
|
|
905
|
+
r: childR
|
|
906
|
+
});
|
|
907
|
+
placeChildren(kid, depth + 1);
|
|
908
|
+
});
|
|
909
|
+
};
|
|
910
|
+
for (const r of roots) placeChildren(r, 0);
|
|
911
|
+
let sideX = 30;
|
|
912
|
+
for (const s of ast.sets) {
|
|
913
|
+
if (!circles.has(s.id)) {
|
|
914
|
+
circles.set(s.id, { id: s.id, cx: sideX + 60, cy: h / 2, r: 50 });
|
|
915
|
+
sideX += 130;
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
const shapes = ast.sets.map((s) => ({
|
|
919
|
+
kind: "circle",
|
|
920
|
+
...circles.get(s.id) ?? { id: s.id, cx: w / 2, cy: h / 2, r: 40 }
|
|
921
|
+
}));
|
|
922
|
+
const labels = placeLabels(ast, shapes);
|
|
923
|
+
const setLabels = ast.sets.map((s) => {
|
|
924
|
+
const c = circles.get(s.id) ?? { cx: w / 2, cy: h / 2, r: 40 };
|
|
925
|
+
return {
|
|
926
|
+
id: s.id,
|
|
927
|
+
label: s.label,
|
|
928
|
+
x: c.cx,
|
|
929
|
+
y: c.cy - c.r - 6,
|
|
930
|
+
anchor: "middle"
|
|
931
|
+
};
|
|
932
|
+
});
|
|
933
|
+
return {
|
|
934
|
+
width: w,
|
|
935
|
+
height: h,
|
|
936
|
+
mode: "euler",
|
|
937
|
+
shapes,
|
|
938
|
+
labels,
|
|
939
|
+
setLabels,
|
|
940
|
+
...ast.title ? { title: { text: ast.title, x: w / 2, y: 24 } } : {}
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
function edgeKey(a, b) {
|
|
944
|
+
return a < b ? `${a}|${b}` : `${b}|${a}`;
|
|
945
|
+
}
|
|
946
|
+
function computeAbsoluteSetSizes(ast) {
|
|
947
|
+
const out = /* @__PURE__ */ new Map();
|
|
948
|
+
for (const s of ast.sets) out.set(s.id, 0);
|
|
949
|
+
for (const r of ast.regions) {
|
|
950
|
+
const num = numericValue(r);
|
|
951
|
+
if (num === null) continue;
|
|
952
|
+
for (const sid of r.sets) {
|
|
953
|
+
out.set(sid, (out.get(sid) ?? 0) + num);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
return out;
|
|
957
|
+
}
|
|
958
|
+
function numericValue(r) {
|
|
959
|
+
if (r.value.kind === "integer" || r.value.kind === "percent") return r.value.value;
|
|
960
|
+
if (r.value.kind === "list") return r.value.value.length;
|
|
961
|
+
return null;
|
|
962
|
+
}
|
|
963
|
+
function regionValue(ast, sets) {
|
|
964
|
+
const needle = [...sets].sort().join("|");
|
|
965
|
+
for (const r of ast.regions) {
|
|
966
|
+
const key = [...r.sets].sort().join("|");
|
|
967
|
+
if (key === needle) {
|
|
968
|
+
return numericValue(r);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
return null;
|
|
972
|
+
}
|
|
973
|
+
function solveProportionalThree(ast, cx, cy) {
|
|
974
|
+
const setA = ast.sets[0];
|
|
975
|
+
const setB = ast.sets[1];
|
|
976
|
+
const setC = ast.sets[2];
|
|
977
|
+
if (!setA || !setB || !setC) return null;
|
|
978
|
+
const sizes = computeAbsoluteSetSizes(ast);
|
|
979
|
+
const s0 = sizes.get(setA.id) ?? 0;
|
|
980
|
+
const s1 = sizes.get(setB.id) ?? 0;
|
|
981
|
+
const s2 = sizes.get(setC.id) ?? 0;
|
|
982
|
+
if (s0 <= 0 || s1 <= 0 || s2 <= 0) return null;
|
|
983
|
+
const maxSize = Math.max(s0, s1, s2);
|
|
984
|
+
const baseR = 130;
|
|
985
|
+
const baseArea = Math.PI * baseR * baseR;
|
|
986
|
+
const perElem = baseArea / maxSize;
|
|
987
|
+
const r0 = Math.sqrt(s0 * perElem / Math.PI);
|
|
988
|
+
const r1 = Math.sqrt(s1 * perElem / Math.PI);
|
|
989
|
+
const r2 = Math.sqrt(s2 * perElem / Math.PI);
|
|
990
|
+
const p01 = regionValue(ast, [setA.id, setB.id]) ?? 0;
|
|
991
|
+
const p02 = regionValue(ast, [setA.id, setC.id]) ?? 0;
|
|
992
|
+
const p12 = regionValue(ast, [setB.id, setC.id]) ?? 0;
|
|
993
|
+
const d01 = solveTwoCircleDistance(r0, r1, p01 * perElem);
|
|
994
|
+
const d02 = solveTwoCircleDistance(r0, r2, p02 * perElem);
|
|
995
|
+
const d12 = solveTwoCircleDistance(r1, r2, p12 * perElem);
|
|
996
|
+
const x0 = cx - d01 / 2;
|
|
997
|
+
const y0 = cy + d01 * 0.1;
|
|
998
|
+
const x1 = x0 + d01;
|
|
999
|
+
const y1 = y0;
|
|
1000
|
+
const ex = x1 - x0;
|
|
1001
|
+
const ey = y1 - y0;
|
|
1002
|
+
const dxy = Math.sqrt(ex * ex + ey * ey);
|
|
1003
|
+
if (dxy < 1e-6) return null;
|
|
1004
|
+
const a = (d02 * d02 - d12 * d12 + dxy * dxy) / (2 * dxy);
|
|
1005
|
+
const hSq = d02 * d02 - a * a;
|
|
1006
|
+
const h = hSq > 0 ? Math.sqrt(hSq) : 0;
|
|
1007
|
+
const px = x0 + a * ex / dxy;
|
|
1008
|
+
const py = y0 + a * ey / dxy;
|
|
1009
|
+
const x2 = px - h * ey / dxy;
|
|
1010
|
+
const y2 = py - h * ex / dxy;
|
|
1011
|
+
const avgX = (x0 + x1 + x2) / 3;
|
|
1012
|
+
const avgY = (y0 + y1 + y2) / 3;
|
|
1013
|
+
const offX = cx - avgX;
|
|
1014
|
+
const offY = cy - avgY;
|
|
1015
|
+
return [
|
|
1016
|
+
{ id: setA.id, cx: x0 + offX, cy: y0 + offY, r: r0 },
|
|
1017
|
+
{ id: setB.id, cx: x1 + offX, cy: y1 + offY, r: r1 },
|
|
1018
|
+
{ id: setC.id, cx: x2 + offX, cy: y2 + offY, r: r2 }
|
|
1019
|
+
];
|
|
1020
|
+
}
|
|
1021
|
+
function formatRegionValue(r) {
|
|
1022
|
+
switch (r.value.kind) {
|
|
1023
|
+
case "integer":
|
|
1024
|
+
return String(r.value.value);
|
|
1025
|
+
case "percent":
|
|
1026
|
+
return `${r.value.value}%`;
|
|
1027
|
+
case "text":
|
|
1028
|
+
return r.value.value;
|
|
1029
|
+
case "list":
|
|
1030
|
+
return r.value.value.join(", ");
|
|
1031
|
+
case "none":
|
|
1032
|
+
return "";
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
// src/diagrams/venn/renderer.ts
|
|
1037
|
+
function ellipseEl(attrs) {
|
|
1038
|
+
return chunkHDKDQAEQ_cjs.el("ellipse", attrs);
|
|
1039
|
+
}
|
|
1040
|
+
function idSlug(id) {
|
|
1041
|
+
return id.replace(/[^A-Za-z0-9_-]/g, "_");
|
|
1042
|
+
}
|
|
1043
|
+
function buildCss(tokens) {
|
|
1044
|
+
return `
|
|
1045
|
+
.schematex-venn { background: ${tokens.bg}; font-family: system-ui, -apple-system, "Segoe UI", sans-serif; }
|
|
1046
|
+
.schematex-venn-title { font: 600 16px sans-serif; fill: ${tokens.text}; }
|
|
1047
|
+
.schematex-venn-set { stroke: ${tokens.vennSetStroke}; stroke-width: 1.25; }
|
|
1048
|
+
.schematex-venn-blend-multiply { mix-blend-mode: multiply; }
|
|
1049
|
+
.schematex-venn-blend-screen { mix-blend-mode: screen; }
|
|
1050
|
+
.schematex-venn-setlabel { font: 600 13px sans-serif; fill: ${tokens.text}; }
|
|
1051
|
+
.schematex-venn-label { font: 500 12px sans-serif; fill: ${tokens.vennLabelColor}; dominant-baseline: central; text-anchor: middle; }
|
|
1052
|
+
.schematex-venn-label-external { font: 500 11px sans-serif; fill: ${tokens.vennLabelColor}; dominant-baseline: central; }
|
|
1053
|
+
.schematex-venn-leader { stroke: ${tokens.vennLeaderColor}; stroke-width: 0.7; fill: none; opacity: 0.8; }
|
|
1054
|
+
.schematex-venn-leader-dot { fill: ${tokens.vennLeaderColor}; }
|
|
1055
|
+
`.trim();
|
|
1056
|
+
}
|
|
1057
|
+
function renderShape(shape, index, color, opacity, setLabel) {
|
|
1058
|
+
const classes = `schematex-venn-set schematex-venn-set-${index}`;
|
|
1059
|
+
const hoverTitle = chunkHDKDQAEQ_cjs.title(`Set ${setLabel}`);
|
|
1060
|
+
if (shape.kind === "circle") {
|
|
1061
|
+
return chunkHDKDQAEQ_cjs.el(
|
|
1062
|
+
"g",
|
|
1063
|
+
{ class: `schematex-venn-set-group schematex-venn-set-group-${index}` },
|
|
1064
|
+
[
|
|
1065
|
+
chunkHDKDQAEQ_cjs.circle({
|
|
1066
|
+
cx: shape.cx,
|
|
1067
|
+
cy: shape.cy,
|
|
1068
|
+
r: shape.r,
|
|
1069
|
+
class: classes,
|
|
1070
|
+
fill: color,
|
|
1071
|
+
"fill-opacity": opacity,
|
|
1072
|
+
"data-set-id": idSlug(shape.id)
|
|
1073
|
+
}),
|
|
1074
|
+
hoverTitle
|
|
1075
|
+
]
|
|
1076
|
+
);
|
|
1077
|
+
}
|
|
1078
|
+
return chunkHDKDQAEQ_cjs.el(
|
|
1079
|
+
"g",
|
|
1080
|
+
{ class: `schematex-venn-set-group schematex-venn-set-group-${index}` },
|
|
1081
|
+
[
|
|
1082
|
+
ellipseEl({
|
|
1083
|
+
cx: shape.cx,
|
|
1084
|
+
cy: shape.cy,
|
|
1085
|
+
rx: shape.rx,
|
|
1086
|
+
ry: shape.ry,
|
|
1087
|
+
transform: `rotate(${shape.rotation} ${shape.cx} ${shape.cy})`,
|
|
1088
|
+
class: classes,
|
|
1089
|
+
fill: color,
|
|
1090
|
+
"fill-opacity": opacity,
|
|
1091
|
+
"data-set-id": idSlug(shape.id)
|
|
1092
|
+
}),
|
|
1093
|
+
hoverTitle
|
|
1094
|
+
]
|
|
1095
|
+
);
|
|
1096
|
+
}
|
|
1097
|
+
function renderVennAST(ast, options = {}) {
|
|
1098
|
+
const layout = layoutVenn(ast);
|
|
1099
|
+
return renderVennLayout(ast, layout, options);
|
|
1100
|
+
}
|
|
1101
|
+
function renderVennLayout(ast, layout, options = {}) {
|
|
1102
|
+
const tokens = chunkN7KOXOMX_cjs.resolveVennTheme(options.theme ?? "default");
|
|
1103
|
+
const effectiveBlend = ast.config.blendMode === "none" ? "none" : ast.config.blendMode || tokens.vennBlendMode;
|
|
1104
|
+
const css = buildCss(tokens);
|
|
1105
|
+
const colors = tokens.vennSetColors;
|
|
1106
|
+
const shapeEls = layout.shapes.map((shape, i) => {
|
|
1107
|
+
const color = ast.sets[i]?.color ?? colors[i % colors.length] ?? "#4E79A7";
|
|
1108
|
+
return renderShape(shape, i, color, tokens.vennSetOpacity, ast.sets[i]?.label ?? shape.id);
|
|
1109
|
+
});
|
|
1110
|
+
const shapesGroup = chunkHDKDQAEQ_cjs.group(
|
|
1111
|
+
{
|
|
1112
|
+
class: `schematex-venn-shapes ${effectiveBlend !== "none" ? `schematex-venn-blend-${effectiveBlend}` : ""}`.trim(),
|
|
1113
|
+
...effectiveBlend !== "none" ? { style: `mix-blend-mode: ${effectiveBlend}` } : {}
|
|
1114
|
+
},
|
|
1115
|
+
shapeEls
|
|
1116
|
+
);
|
|
1117
|
+
const setLabelEls = layout.setLabels.map(
|
|
1118
|
+
(s) => chunkHDKDQAEQ_cjs.text(
|
|
1119
|
+
{
|
|
1120
|
+
x: s.x,
|
|
1121
|
+
y: s.y,
|
|
1122
|
+
class: "schematex-venn-setlabel",
|
|
1123
|
+
"text-anchor": s.anchor
|
|
1124
|
+
},
|
|
1125
|
+
s.label
|
|
1126
|
+
)
|
|
1127
|
+
);
|
|
1128
|
+
const labelEls = [];
|
|
1129
|
+
const leaderEls = [];
|
|
1130
|
+
for (const label of layout.labels) {
|
|
1131
|
+
const cls = label.external ? "schematex-venn-label schematex-venn-label-external" : "schematex-venn-label";
|
|
1132
|
+
labelEls.push(
|
|
1133
|
+
chunkHDKDQAEQ_cjs.text(
|
|
1134
|
+
{
|
|
1135
|
+
x: label.x,
|
|
1136
|
+
y: label.y,
|
|
1137
|
+
class: cls,
|
|
1138
|
+
"text-anchor": label.anchor ?? "middle",
|
|
1139
|
+
"data-region": label.sets.join("-")
|
|
1140
|
+
},
|
|
1141
|
+
label.label
|
|
1142
|
+
)
|
|
1143
|
+
);
|
|
1144
|
+
if (label.external && label.leader) {
|
|
1145
|
+
leaderEls.push(
|
|
1146
|
+
chunkHDKDQAEQ_cjs.path({
|
|
1147
|
+
d: `M ${label.leader.x1} ${label.leader.y1} L ${label.leader.x2} ${label.leader.y2}`,
|
|
1148
|
+
class: "schematex-venn-leader",
|
|
1149
|
+
"aria-hidden": "true"
|
|
1150
|
+
})
|
|
1151
|
+
);
|
|
1152
|
+
leaderEls.push(
|
|
1153
|
+
chunkHDKDQAEQ_cjs.circle({
|
|
1154
|
+
cx: label.leader.x1,
|
|
1155
|
+
cy: label.leader.y1,
|
|
1156
|
+
r: 1.5,
|
|
1157
|
+
class: "schematex-venn-leader-dot",
|
|
1158
|
+
"aria-hidden": "true"
|
|
1159
|
+
})
|
|
1160
|
+
);
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
const titleBlock = layout.title ? chunkHDKDQAEQ_cjs.text(
|
|
1164
|
+
{
|
|
1165
|
+
x: layout.title.x,
|
|
1166
|
+
y: layout.title.y,
|
|
1167
|
+
class: "schematex-venn-title",
|
|
1168
|
+
"text-anchor": "middle"
|
|
1169
|
+
},
|
|
1170
|
+
layout.title.text
|
|
1171
|
+
) : "";
|
|
1172
|
+
const nonEmptyRegions = ast.regions.length;
|
|
1173
|
+
const description = `Venn/Euler diagram${ast.title ? ` "${ast.title}"` : ""}: ${ast.sets.length} sets, ${nonEmptyRegions} regions.`;
|
|
1174
|
+
const body = [
|
|
1175
|
+
chunkHDKDQAEQ_cjs.title(ast.title ?? "Venn diagram"),
|
|
1176
|
+
chunkHDKDQAEQ_cjs.desc(description),
|
|
1177
|
+
chunkHDKDQAEQ_cjs.el("style", {}, css),
|
|
1178
|
+
titleBlock,
|
|
1179
|
+
shapesGroup,
|
|
1180
|
+
chunkHDKDQAEQ_cjs.group({ class: "schematex-venn-leaders" }, leaderEls),
|
|
1181
|
+
chunkHDKDQAEQ_cjs.group({ class: "schematex-venn-setlabels" }, setLabelEls),
|
|
1182
|
+
chunkHDKDQAEQ_cjs.group({ class: "schematex-venn-labels" }, labelEls)
|
|
1183
|
+
];
|
|
1184
|
+
return chunkHDKDQAEQ_cjs.svgRoot(
|
|
1185
|
+
{
|
|
1186
|
+
viewBox: `0 0 ${layout.width} ${layout.height}`,
|
|
1187
|
+
width: layout.width,
|
|
1188
|
+
height: layout.height,
|
|
1189
|
+
class: "schematex-venn",
|
|
1190
|
+
role: "img"
|
|
1191
|
+
},
|
|
1192
|
+
body
|
|
1193
|
+
);
|
|
1194
|
+
}
|
|
1195
|
+
function renderVenn(text2, options = {}) {
|
|
1196
|
+
const ast = parseVennDSL(text2);
|
|
1197
|
+
return renderVennAST(ast, options);
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
// src/diagrams/venn/index.ts
|
|
1201
|
+
var venn = {
|
|
1202
|
+
type: "venn",
|
|
1203
|
+
detect(text2) {
|
|
1204
|
+
const first = text2.trim().split("\n")[0]?.trim().toLowerCase() ?? "";
|
|
1205
|
+
return first.startsWith("venn");
|
|
1206
|
+
},
|
|
1207
|
+
render(text2, config) {
|
|
1208
|
+
return renderVenn(text2, { theme: config?.theme });
|
|
1209
|
+
}
|
|
1210
|
+
};
|
|
1211
|
+
|
|
1212
|
+
exports.VennParseError = VennParseError;
|
|
1213
|
+
exports.layoutVenn = layoutVenn;
|
|
1214
|
+
exports.parseVennDSL = parseVennDSL;
|
|
1215
|
+
exports.renderVenn = renderVenn;
|
|
1216
|
+
exports.renderVennAST = renderVennAST;
|
|
1217
|
+
exports.renderVennLayout = renderVennLayout;
|
|
1218
|
+
exports.venn = venn;
|
|
1219
|
+
//# sourceMappingURL=chunk-3FTUWAXK.cjs.map
|
|
1220
|
+
//# sourceMappingURL=chunk-3FTUWAXK.cjs.map
|