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,895 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunkN7KOXOMX_cjs = require('./chunk-N7KOXOMX.cjs');
|
|
4
|
+
var chunkHDKDQAEQ_cjs = require('./chunk-HDKDQAEQ.cjs');
|
|
5
|
+
|
|
6
|
+
// src/diagrams/phylo/parser.ts
|
|
7
|
+
var PhyloParseError = class extends Error {
|
|
8
|
+
constructor(message) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = "PhyloParseError";
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
var _pos = 0;
|
|
14
|
+
var _src = "";
|
|
15
|
+
var _nextId = 0;
|
|
16
|
+
function genId() {
|
|
17
|
+
return `_n${_nextId++}`;
|
|
18
|
+
}
|
|
19
|
+
function peek() {
|
|
20
|
+
return _src[_pos] ?? "";
|
|
21
|
+
}
|
|
22
|
+
function advance() {
|
|
23
|
+
return _src[_pos++] ?? "";
|
|
24
|
+
}
|
|
25
|
+
function skipWhitespace() {
|
|
26
|
+
while (_pos < _src.length && /\s/.test(_src[_pos])) _pos++;
|
|
27
|
+
}
|
|
28
|
+
function parseNewickName() {
|
|
29
|
+
skipWhitespace();
|
|
30
|
+
if (peek() === "'") {
|
|
31
|
+
advance();
|
|
32
|
+
let name2 = "";
|
|
33
|
+
while (_pos < _src.length) {
|
|
34
|
+
if (peek() === "'") {
|
|
35
|
+
advance();
|
|
36
|
+
if (peek() === "'") {
|
|
37
|
+
name2 += "'";
|
|
38
|
+
advance();
|
|
39
|
+
} else {
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
} else {
|
|
43
|
+
name2 += advance();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return name2;
|
|
47
|
+
}
|
|
48
|
+
let name = "";
|
|
49
|
+
while (_pos < _src.length && !/[\s():,;[\]']/.test(_src[_pos])) {
|
|
50
|
+
name += advance();
|
|
51
|
+
}
|
|
52
|
+
return name;
|
|
53
|
+
}
|
|
54
|
+
function parseNewickLength() {
|
|
55
|
+
skipWhitespace();
|
|
56
|
+
if (peek() !== ":") return void 0;
|
|
57
|
+
advance();
|
|
58
|
+
skipWhitespace();
|
|
59
|
+
let numStr = "";
|
|
60
|
+
while (_pos < _src.length && /[0-9eE.+-]/.test(_src[_pos])) {
|
|
61
|
+
numStr += advance();
|
|
62
|
+
}
|
|
63
|
+
if (!numStr) return void 0;
|
|
64
|
+
const val = Number(numStr);
|
|
65
|
+
return Number.isNaN(val) ? void 0 : val;
|
|
66
|
+
}
|
|
67
|
+
function parseNHX() {
|
|
68
|
+
skipWhitespace();
|
|
69
|
+
if (peek() !== "[") return void 0;
|
|
70
|
+
const start = _pos;
|
|
71
|
+
advance();
|
|
72
|
+
let content = "";
|
|
73
|
+
let depth = 1;
|
|
74
|
+
while (_pos < _src.length && depth > 0) {
|
|
75
|
+
if (peek() === "[") depth++;
|
|
76
|
+
if (peek() === "]") depth--;
|
|
77
|
+
if (depth > 0) content += advance();
|
|
78
|
+
else advance();
|
|
79
|
+
}
|
|
80
|
+
if (content.startsWith("&&NHX:")) {
|
|
81
|
+
const pairs = content.slice(6).split(":");
|
|
82
|
+
const nhx = {};
|
|
83
|
+
let support;
|
|
84
|
+
for (const pair of pairs) {
|
|
85
|
+
const eq = pair.indexOf("=");
|
|
86
|
+
if (eq === -1) continue;
|
|
87
|
+
const key = pair.slice(0, eq);
|
|
88
|
+
const val = pair.slice(eq + 1);
|
|
89
|
+
nhx[key] = val;
|
|
90
|
+
if (key === "B") {
|
|
91
|
+
const num2 = Number(val);
|
|
92
|
+
if (!Number.isNaN(num2)) support = num2;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return { support, nhx };
|
|
96
|
+
}
|
|
97
|
+
const num = Number(content.trim());
|
|
98
|
+
if (!Number.isNaN(num) && content.trim().length > 0) {
|
|
99
|
+
return { support: num };
|
|
100
|
+
}
|
|
101
|
+
_pos = start;
|
|
102
|
+
return void 0;
|
|
103
|
+
}
|
|
104
|
+
function parseNewickSubtree() {
|
|
105
|
+
skipWhitespace();
|
|
106
|
+
let children = [];
|
|
107
|
+
if (peek() === "(") {
|
|
108
|
+
advance();
|
|
109
|
+
children = [];
|
|
110
|
+
children.push(parseNewickSubtree());
|
|
111
|
+
skipWhitespace();
|
|
112
|
+
while (peek() === ",") {
|
|
113
|
+
advance();
|
|
114
|
+
children.push(parseNewickSubtree());
|
|
115
|
+
skipWhitespace();
|
|
116
|
+
}
|
|
117
|
+
skipWhitespace();
|
|
118
|
+
if (peek() === ")") advance();
|
|
119
|
+
}
|
|
120
|
+
const name = parseNewickName();
|
|
121
|
+
const nhxData = parseNHX();
|
|
122
|
+
const branchLength = parseNewickLength();
|
|
123
|
+
const nhxData2 = nhxData ? void 0 : parseNHX();
|
|
124
|
+
const merged = nhxData ?? nhxData2;
|
|
125
|
+
const isLeaf = children.length === 0;
|
|
126
|
+
const id = name || genId();
|
|
127
|
+
return {
|
|
128
|
+
id,
|
|
129
|
+
label: name || void 0,
|
|
130
|
+
branchLength,
|
|
131
|
+
support: merged?.support,
|
|
132
|
+
children,
|
|
133
|
+
isLeaf,
|
|
134
|
+
nhx: merged?.nhx
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
function parseNewick(newick) {
|
|
138
|
+
_pos = 0;
|
|
139
|
+
_src = newick.trim();
|
|
140
|
+
_nextId = 0;
|
|
141
|
+
if (_src.endsWith(";")) {
|
|
142
|
+
_src = _src.slice(0, -1);
|
|
143
|
+
}
|
|
144
|
+
const root = parseNewickSubtree();
|
|
145
|
+
return root;
|
|
146
|
+
}
|
|
147
|
+
function parseIndentTree(lines) {
|
|
148
|
+
_nextId = 0;
|
|
149
|
+
const parsed = [];
|
|
150
|
+
for (const raw of lines) {
|
|
151
|
+
if (!raw.trim() || raw.trim().startsWith("#")) continue;
|
|
152
|
+
const indent = raw.search(/\S/);
|
|
153
|
+
const content = raw.trim();
|
|
154
|
+
let name;
|
|
155
|
+
let branchLength;
|
|
156
|
+
let support;
|
|
157
|
+
let contentClean = content;
|
|
158
|
+
const supportMatch = contentClean.match(/\[(\d+(?:\.\d+)?)\]\s*$/);
|
|
159
|
+
if (supportMatch) {
|
|
160
|
+
support = Number(supportMatch[1]);
|
|
161
|
+
contentClean = contentClean.slice(0, supportMatch.index).trim();
|
|
162
|
+
}
|
|
163
|
+
const colonIdx = contentClean.indexOf(":");
|
|
164
|
+
if (colonIdx === -1) {
|
|
165
|
+
name = contentClean;
|
|
166
|
+
} else {
|
|
167
|
+
name = contentClean.slice(0, colonIdx).trim();
|
|
168
|
+
const lenStr = contentClean.slice(colonIdx + 1).trim();
|
|
169
|
+
if (lenStr) {
|
|
170
|
+
const num = Number(lenStr);
|
|
171
|
+
if (!Number.isNaN(num)) branchLength = num;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
parsed.push({ indent, name, branchLength, support });
|
|
175
|
+
}
|
|
176
|
+
if (parsed.length === 0) {
|
|
177
|
+
throw new PhyloParseError("Empty indent tree definition");
|
|
178
|
+
}
|
|
179
|
+
function buildTree(startIdx, parentIndent) {
|
|
180
|
+
const line2 = parsed[startIdx];
|
|
181
|
+
const children2 = [];
|
|
182
|
+
let idx2 = startIdx + 1;
|
|
183
|
+
while (idx2 < parsed.length && parsed[idx2].indent > parentIndent) {
|
|
184
|
+
if (parsed[idx2].indent === line2.indent + 2 || parsed[idx2].indent > line2.indent) {
|
|
185
|
+
const childIndent = parsed[idx2].indent;
|
|
186
|
+
const result = buildTree(idx2, childIndent);
|
|
187
|
+
children2.push(result.node);
|
|
188
|
+
idx2 = result.nextIdx;
|
|
189
|
+
} else {
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
const id = line2.name || genId();
|
|
194
|
+
return {
|
|
195
|
+
node: {
|
|
196
|
+
id,
|
|
197
|
+
label: line2.name || void 0,
|
|
198
|
+
branchLength: line2.branchLength,
|
|
199
|
+
support: line2.support,
|
|
200
|
+
children: children2,
|
|
201
|
+
isLeaf: children2.length === 0
|
|
202
|
+
},
|
|
203
|
+
nextIdx: idx2
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
const rootLine = parsed[0];
|
|
207
|
+
const rootIndent = rootLine.indent;
|
|
208
|
+
const children = [];
|
|
209
|
+
let idx = 1;
|
|
210
|
+
while (idx < parsed.length) {
|
|
211
|
+
if (parsed[idx].indent > rootIndent) {
|
|
212
|
+
const result = buildTree(idx, parsed[idx].indent);
|
|
213
|
+
children.push(result.node);
|
|
214
|
+
idx = result.nextIdx;
|
|
215
|
+
} else {
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
const rootId = rootLine.name || genId();
|
|
220
|
+
return {
|
|
221
|
+
id: rootId,
|
|
222
|
+
label: rootLine.name || void 0,
|
|
223
|
+
branchLength: rootLine.branchLength,
|
|
224
|
+
support: rootLine.support,
|
|
225
|
+
children,
|
|
226
|
+
isLeaf: children.length === 0
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
function parseHeaderProps(propsStr) {
|
|
230
|
+
const result = {
|
|
231
|
+
layout: "rectangular",
|
|
232
|
+
mode: "phylogram",
|
|
233
|
+
unrooted: false
|
|
234
|
+
};
|
|
235
|
+
const pairs = propsStr.split(",").map((s) => s.trim());
|
|
236
|
+
for (const pair of pairs) {
|
|
237
|
+
if (pair === "unrooted") {
|
|
238
|
+
result.unrooted = true;
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
const colonIdx = pair.indexOf(":");
|
|
242
|
+
if (colonIdx === -1) continue;
|
|
243
|
+
const key = pair.slice(0, colonIdx).trim();
|
|
244
|
+
const val = pair.slice(colonIdx + 1).trim().replace(/^["']|["']$/g, "");
|
|
245
|
+
switch (key) {
|
|
246
|
+
case "layout":
|
|
247
|
+
if (["rectangular", "slanted", "circular", "unrooted"].includes(val)) {
|
|
248
|
+
result.layout = val;
|
|
249
|
+
}
|
|
250
|
+
break;
|
|
251
|
+
case "mode":
|
|
252
|
+
if (["phylogram", "cladogram", "chronogram"].includes(val)) {
|
|
253
|
+
result.mode = val;
|
|
254
|
+
}
|
|
255
|
+
break;
|
|
256
|
+
case "branch-width":
|
|
257
|
+
result.branchWidth = Number(val);
|
|
258
|
+
break;
|
|
259
|
+
case "openAngle":
|
|
260
|
+
result.openAngle = Number(val);
|
|
261
|
+
break;
|
|
262
|
+
case "mrsd":
|
|
263
|
+
result.mrsd = val;
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
if (result.layout === "unrooted") {
|
|
268
|
+
result.unrooted = true;
|
|
269
|
+
}
|
|
270
|
+
return result;
|
|
271
|
+
}
|
|
272
|
+
function parseCladeLine(line2) {
|
|
273
|
+
const match = line2.match(
|
|
274
|
+
/^clade\s+(\S+)\s*=\s*\(([^)]+)\)\s*(?:\[([^\]]*)\])?\s*$/
|
|
275
|
+
);
|
|
276
|
+
if (!match) return null;
|
|
277
|
+
const id = match[1];
|
|
278
|
+
const members = match[2].split(",").map((s) => s.trim()).filter(Boolean);
|
|
279
|
+
const propsStr = match[3] ?? "";
|
|
280
|
+
let color;
|
|
281
|
+
let label;
|
|
282
|
+
let highlight;
|
|
283
|
+
if (propsStr) {
|
|
284
|
+
const colorMatch = propsStr.match(/color:\s*"([^"]+)"/);
|
|
285
|
+
if (colorMatch) color = colorMatch[1];
|
|
286
|
+
const labelMatch = propsStr.match(/label:\s*"([^"]+)"/);
|
|
287
|
+
if (labelMatch) label = labelMatch[1];
|
|
288
|
+
const hlMatch = propsStr.match(/highlight:\s*(\w+)/);
|
|
289
|
+
if (hlMatch && ["branch", "background", "both"].includes(hlMatch[1])) {
|
|
290
|
+
highlight = hlMatch[1];
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return { id, members, color, label, highlight };
|
|
294
|
+
}
|
|
295
|
+
function parsePhylo(text2) {
|
|
296
|
+
const lines = text2.split("\n");
|
|
297
|
+
let lineIdx = 0;
|
|
298
|
+
while (lineIdx < lines.length && !lines[lineIdx].trim()) lineIdx++;
|
|
299
|
+
const headerLine = lines[lineIdx]?.trim() ?? "";
|
|
300
|
+
if (!headerLine.toLowerCase().startsWith("phylo")) {
|
|
301
|
+
throw new PhyloParseError("Phylo document must start with 'phylo'");
|
|
302
|
+
}
|
|
303
|
+
lineIdx++;
|
|
304
|
+
let title2;
|
|
305
|
+
const titleMatch = headerLine.match(/"([^"]+)"/);
|
|
306
|
+
if (titleMatch) title2 = titleMatch[1];
|
|
307
|
+
let headerProps = {
|
|
308
|
+
layout: "rectangular",
|
|
309
|
+
mode: "phylogram",
|
|
310
|
+
unrooted: false
|
|
311
|
+
};
|
|
312
|
+
const propsMatch = headerLine.match(/\[([^\]]+)\]/);
|
|
313
|
+
if (propsMatch) {
|
|
314
|
+
headerProps = parseHeaderProps(propsMatch[1]);
|
|
315
|
+
}
|
|
316
|
+
let root = null;
|
|
317
|
+
let scaleLabel;
|
|
318
|
+
let outgroup;
|
|
319
|
+
const clades = [];
|
|
320
|
+
const indentLines = [];
|
|
321
|
+
let inIndentTree = false;
|
|
322
|
+
while (lineIdx < lines.length) {
|
|
323
|
+
const raw = lines[lineIdx];
|
|
324
|
+
const trimmed = raw.trim();
|
|
325
|
+
lineIdx++;
|
|
326
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
327
|
+
if (trimmed.startsWith("newick:")) {
|
|
328
|
+
const newickStr = trimmed.slice(7).trim().replace(/^["']|["']$/g, "");
|
|
329
|
+
root = parseNewick(newickStr);
|
|
330
|
+
inIndentTree = false;
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
if (trimmed.startsWith("scale")) {
|
|
334
|
+
const scaleMatch = trimmed.match(/scale\s+"([^"]+)"/);
|
|
335
|
+
if (scaleMatch) {
|
|
336
|
+
scaleLabel = scaleMatch[1];
|
|
337
|
+
} else {
|
|
338
|
+
scaleLabel = trimmed.slice(5).trim().replace(/^["']|["']$/g, "") || "substitutions/site";
|
|
339
|
+
}
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
if (trimmed.startsWith("outgroup:")) {
|
|
343
|
+
outgroup = trimmed.slice(9).trim();
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
if (trimmed.startsWith("clade ")) {
|
|
347
|
+
const clade = parseCladeLine(trimmed);
|
|
348
|
+
if (clade) clades.push(clade);
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
if (trimmed.startsWith("style")) {
|
|
352
|
+
const styleProps = trimmed.match(/\[([^\]]+)\]/);
|
|
353
|
+
if (styleProps) {
|
|
354
|
+
headerProps = { ...headerProps, ...parseHeaderProps(styleProps[1]) };
|
|
355
|
+
}
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
if (trimmed.endsWith(":") && (trimmed === "root:" || !trimmed.includes(" "))) {
|
|
359
|
+
inIndentTree = true;
|
|
360
|
+
indentLines.push(raw);
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
363
|
+
if (inIndentTree) {
|
|
364
|
+
indentLines.push(raw);
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
if (!root && indentLines.length > 0) {
|
|
369
|
+
root = parseIndentTree(indentLines);
|
|
370
|
+
}
|
|
371
|
+
if (!root) {
|
|
372
|
+
throw new PhyloParseError("No tree definition found (newick: or indent tree)");
|
|
373
|
+
}
|
|
374
|
+
return {
|
|
375
|
+
type: "phylo",
|
|
376
|
+
title: title2,
|
|
377
|
+
root,
|
|
378
|
+
unrooted: headerProps.unrooted,
|
|
379
|
+
layout: headerProps.layout,
|
|
380
|
+
mode: headerProps.mode,
|
|
381
|
+
clades,
|
|
382
|
+
scaleLabel,
|
|
383
|
+
mrsd: headerProps.mrsd,
|
|
384
|
+
outgroup,
|
|
385
|
+
metadata: {}
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// src/diagrams/phylo/layout.ts
|
|
390
|
+
function collectLeaves(node) {
|
|
391
|
+
if (node.isLeaf) return [node];
|
|
392
|
+
const leaves = [];
|
|
393
|
+
for (const child of node.children) {
|
|
394
|
+
leaves.push(...collectLeaves(child));
|
|
395
|
+
}
|
|
396
|
+
return leaves;
|
|
397
|
+
}
|
|
398
|
+
function maxRootToTip(node, distSoFar) {
|
|
399
|
+
if (node.isLeaf) return distSoFar;
|
|
400
|
+
let maxDist = distSoFar;
|
|
401
|
+
for (const child of node.children) {
|
|
402
|
+
const childDist = maxRootToTip(child, distSoFar + (child.branchLength ?? 0));
|
|
403
|
+
if (childDist > maxDist) maxDist = childDist;
|
|
404
|
+
}
|
|
405
|
+
return maxDist;
|
|
406
|
+
}
|
|
407
|
+
function maxDepth(node) {
|
|
408
|
+
if (node.isLeaf) return 0;
|
|
409
|
+
let max = 0;
|
|
410
|
+
for (const child of node.children) {
|
|
411
|
+
const d = maxDepth(child) + 1;
|
|
412
|
+
if (d > max) max = d;
|
|
413
|
+
}
|
|
414
|
+
return max;
|
|
415
|
+
}
|
|
416
|
+
function estimateLabelWidth(node) {
|
|
417
|
+
const label = node.label ?? node.id;
|
|
418
|
+
return label.length * 7.2 + 6;
|
|
419
|
+
}
|
|
420
|
+
function buildCladeMap(ast) {
|
|
421
|
+
const branchToCladeMap = /* @__PURE__ */ new Map();
|
|
422
|
+
for (const clade of ast.clades) {
|
|
423
|
+
const memberSet = new Set(clade.members);
|
|
424
|
+
markCladeBranches(ast.root, memberSet, clade.id, branchToCladeMap);
|
|
425
|
+
}
|
|
426
|
+
return branchToCladeMap;
|
|
427
|
+
}
|
|
428
|
+
function markCladeBranches(node, memberSet, cladeId, result) {
|
|
429
|
+
if (node.isLeaf) {
|
|
430
|
+
return memberSet.has(node.id);
|
|
431
|
+
}
|
|
432
|
+
const childResults = [];
|
|
433
|
+
for (const child of node.children) {
|
|
434
|
+
childResults.push(markCladeBranches(child, memberSet, cladeId, result));
|
|
435
|
+
}
|
|
436
|
+
const allIn = childResults.every(Boolean);
|
|
437
|
+
const anyIn = childResults.some(Boolean);
|
|
438
|
+
if (allIn && anyIn) {
|
|
439
|
+
for (const child of node.children) {
|
|
440
|
+
result.set(child.id, cladeId);
|
|
441
|
+
}
|
|
442
|
+
return true;
|
|
443
|
+
}
|
|
444
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
445
|
+
if (childResults[i]) {
|
|
446
|
+
result.set(node.children[i].id, cladeId);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
return false;
|
|
450
|
+
}
|
|
451
|
+
var TIP_SPACING = 20;
|
|
452
|
+
var PADDING_LEFT = 20;
|
|
453
|
+
var PADDING_RIGHT = 20;
|
|
454
|
+
var PADDING_TOP = 20;
|
|
455
|
+
var PADDING_BOTTOM = 40;
|
|
456
|
+
function layoutPhylo(ast) {
|
|
457
|
+
const leaves = collectLeaves(ast.root);
|
|
458
|
+
const numLeaves = leaves.length;
|
|
459
|
+
const tipSpacing = TIP_SPACING;
|
|
460
|
+
const maxLabelWidth = Math.max(...leaves.map(estimateLabelWidth), 60);
|
|
461
|
+
const maxDist = maxRootToTip(ast.root, 0);
|
|
462
|
+
const isCladogram = ast.mode === "cladogram";
|
|
463
|
+
const availableWidth = Math.max(300, numLeaves * 30 + maxLabelWidth + 100);
|
|
464
|
+
const plotWidth = availableWidth - PADDING_LEFT - PADDING_RIGHT - maxLabelWidth;
|
|
465
|
+
let scale;
|
|
466
|
+
if (isCladogram || maxDist === 0) {
|
|
467
|
+
const depth = maxDepth(ast.root);
|
|
468
|
+
scale = depth > 0 ? plotWidth / depth : plotWidth;
|
|
469
|
+
} else {
|
|
470
|
+
scale = plotWidth / maxDist;
|
|
471
|
+
}
|
|
472
|
+
const nodeMap = /* @__PURE__ */ new Map();
|
|
473
|
+
let leafIdx = 0;
|
|
474
|
+
function assignLeafY(node) {
|
|
475
|
+
if (node.isLeaf) {
|
|
476
|
+
const y = PADDING_TOP + leafIdx * tipSpacing;
|
|
477
|
+
nodeMap.set(node.id, { node, x: 0, y });
|
|
478
|
+
leafIdx++;
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
for (const child of node.children) {
|
|
482
|
+
assignLeafY(child);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
assignLeafY(ast.root);
|
|
486
|
+
function assignInternalY(node) {
|
|
487
|
+
const existing = nodeMap.get(node.id);
|
|
488
|
+
if (node.isLeaf && existing) return existing.y;
|
|
489
|
+
const childYs = node.children.map(assignInternalY);
|
|
490
|
+
const y = (Math.min(...childYs) + Math.max(...childYs)) / 2;
|
|
491
|
+
if (!existing) {
|
|
492
|
+
nodeMap.set(node.id, { node, x: 0, y });
|
|
493
|
+
} else {
|
|
494
|
+
existing.y = y;
|
|
495
|
+
}
|
|
496
|
+
return y;
|
|
497
|
+
}
|
|
498
|
+
assignInternalY(ast.root);
|
|
499
|
+
function assignX(node, parentX, depth) {
|
|
500
|
+
let x;
|
|
501
|
+
if (node === ast.root) {
|
|
502
|
+
x = PADDING_LEFT;
|
|
503
|
+
} else if (isCladogram) {
|
|
504
|
+
if (node.isLeaf) {
|
|
505
|
+
x = PADDING_LEFT + plotWidth;
|
|
506
|
+
} else {
|
|
507
|
+
x = PADDING_LEFT + depth * (plotWidth / maxDepth(ast.root));
|
|
508
|
+
}
|
|
509
|
+
} else {
|
|
510
|
+
x = parentX + (node.branchLength ?? 0) * scale;
|
|
511
|
+
}
|
|
512
|
+
const layoutNode = nodeMap.get(node.id);
|
|
513
|
+
if (layoutNode) layoutNode.x = x;
|
|
514
|
+
for (const child of node.children) {
|
|
515
|
+
assignX(child, x, depth + 1);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
assignX(ast.root, PADDING_LEFT, 0);
|
|
519
|
+
if (isCladogram) {
|
|
520
|
+
assignCladogramInternalX(ast.root, nodeMap);
|
|
521
|
+
}
|
|
522
|
+
const cladeMap = buildCladeMap(ast);
|
|
523
|
+
const branches = [];
|
|
524
|
+
function generateBranches(node) {
|
|
525
|
+
const parentLayout = nodeMap.get(node.id);
|
|
526
|
+
if (!parentLayout) return;
|
|
527
|
+
if (node.children.length === 0) return;
|
|
528
|
+
const childLayouts = node.children.map((c) => nodeMap.get(c.id)).filter((l) => l !== void 0);
|
|
529
|
+
if (childLayouts.length === 0) return;
|
|
530
|
+
const minY = Math.min(...childLayouts.map((c) => c.y));
|
|
531
|
+
const maxY = Math.max(...childLayouts.map((c) => c.y));
|
|
532
|
+
if (ast.layout === "slanted") {
|
|
533
|
+
for (const child of node.children) {
|
|
534
|
+
const childLayout = nodeMap.get(child.id);
|
|
535
|
+
if (!childLayout) continue;
|
|
536
|
+
const pathStr = `M ${parentLayout.x},${parentLayout.y} L ${childLayout.x},${childLayout.y}`;
|
|
537
|
+
branches.push({
|
|
538
|
+
path: pathStr,
|
|
539
|
+
fromId: node.id,
|
|
540
|
+
toId: child.id,
|
|
541
|
+
cladeId: cladeMap.get(child.id),
|
|
542
|
+
isConnector: false
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
} else {
|
|
546
|
+
branches.push({
|
|
547
|
+
path: `M ${parentLayout.x},${minY} V ${maxY}`,
|
|
548
|
+
fromId: node.id,
|
|
549
|
+
toId: node.id,
|
|
550
|
+
isConnector: true
|
|
551
|
+
});
|
|
552
|
+
for (const child of node.children) {
|
|
553
|
+
const childLayout = nodeMap.get(child.id);
|
|
554
|
+
if (!childLayout) continue;
|
|
555
|
+
const pathStr = `M ${parentLayout.x},${childLayout.y} H ${childLayout.x}`;
|
|
556
|
+
branches.push({
|
|
557
|
+
path: pathStr,
|
|
558
|
+
fromId: node.id,
|
|
559
|
+
toId: child.id,
|
|
560
|
+
cladeId: cladeMap.get(child.id),
|
|
561
|
+
isConnector: false
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
for (const child of node.children) {
|
|
566
|
+
generateBranches(child);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
generateBranches(ast.root);
|
|
570
|
+
const allNodes = Array.from(nodeMap.values());
|
|
571
|
+
let maxX = Math.max(...allNodes.map((n) => n.x + (n.node.isLeaf ? estimateLabelWidth(n.node) : 0)));
|
|
572
|
+
let maxCladeLabelWidth = 0;
|
|
573
|
+
for (const clade of ast.clades) {
|
|
574
|
+
if (clade.label && clade.highlight && clade.highlight !== "branch") {
|
|
575
|
+
const w = clade.label.length * 8 + 30;
|
|
576
|
+
if (w > maxCladeLabelWidth) maxCladeLabelWidth = w;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
maxX += maxCladeLabelWidth;
|
|
580
|
+
const maxNodeY = Math.max(...allNodes.map((n) => n.y));
|
|
581
|
+
const width = Math.max(maxX + PADDING_RIGHT, availableWidth);
|
|
582
|
+
const height = maxNodeY + PADDING_TOP + PADDING_BOTTOM;
|
|
583
|
+
return {
|
|
584
|
+
width,
|
|
585
|
+
height,
|
|
586
|
+
nodes: allNodes,
|
|
587
|
+
branches,
|
|
588
|
+
ast,
|
|
589
|
+
scale
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
function assignCladogramInternalX(node, nodeMap) {
|
|
593
|
+
if (node.isLeaf) {
|
|
594
|
+
return nodeMap.get(node.id)?.x ?? 0;
|
|
595
|
+
}
|
|
596
|
+
let minChildX = Infinity;
|
|
597
|
+
for (const child of node.children) {
|
|
598
|
+
const childX = assignCladogramInternalX(child, nodeMap);
|
|
599
|
+
if (childX < minChildX) minChildX = childX;
|
|
600
|
+
}
|
|
601
|
+
const layout = nodeMap.get(node.id);
|
|
602
|
+
if (layout) {
|
|
603
|
+
layout.x = minChildX - 40;
|
|
604
|
+
if (layout.x < PADDING_LEFT) layout.x = PADDING_LEFT;
|
|
605
|
+
}
|
|
606
|
+
return layout?.x ?? PADDING_LEFT;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// src/diagrams/phylo/renderer.ts
|
|
610
|
+
var TIP_LABEL_GAP = 6;
|
|
611
|
+
var SUPPORT_THRESHOLD = 50;
|
|
612
|
+
function getSupportColor(value, t) {
|
|
613
|
+
if (value >= 95) return t.supportGood;
|
|
614
|
+
if (value >= 75) return t.supportMedium;
|
|
615
|
+
if (value >= 50) return t.supportWarn;
|
|
616
|
+
return t.supportBad;
|
|
617
|
+
}
|
|
618
|
+
function isSpeciesBinomial(label) {
|
|
619
|
+
const parts = label.trim().split(/\s+/);
|
|
620
|
+
if (parts.length !== 2) return false;
|
|
621
|
+
return /^[A-Z][a-z]+$/.test(parts[0]) && /^[a-z]+$/.test(parts[1]);
|
|
622
|
+
}
|
|
623
|
+
function buildCSS(ast, t) {
|
|
624
|
+
const cladeColors = ast.clades.map((c, i) => {
|
|
625
|
+
const color = c.color ?? t.cladeColors[i % t.cladeColors.length];
|
|
626
|
+
return `.schematex-phylo-clade-${c.id} { stroke: ${color}; }
|
|
627
|
+
.schematex-phylo-clade-bg-${c.id} { fill: ${color}; fill-opacity: 0.12; }
|
|
628
|
+
.schematex-phylo-clade-label-${c.id} { fill: ${color}; }`;
|
|
629
|
+
});
|
|
630
|
+
return `
|
|
631
|
+
.schematex-phylo {${chunkN7KOXOMX_cjs.cssCustomProperties(t)}
|
|
632
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
633
|
+
background: ${t.bg};
|
|
634
|
+
}
|
|
635
|
+
.schematex-phylo-branch { fill: none; stroke: ${t.text}; stroke-width: ${chunkN7KOXOMX_cjs.STROKE_WIDTH.normal}; stroke-linecap: round; }
|
|
636
|
+
.schematex-phylo-branch-connector { fill: none; stroke: ${t.text}; stroke-width: ${chunkN7KOXOMX_cjs.STROKE_WIDTH.normal}; }
|
|
637
|
+
.schematex-phylo-tip-label { font-size: ${chunkN7KOXOMX_cjs.FONT_SIZE.label}px; fill: ${t.text}; dominant-baseline: central; }
|
|
638
|
+
.schematex-phylo-tip-label-italic { font-style: italic; }
|
|
639
|
+
.schematex-phylo-support-label { font-size: ${chunkN7KOXOMX_cjs.FONT_SIZE.small}px; fill: ${t.textMuted}; text-anchor: middle; dominant-baseline: auto; }
|
|
640
|
+
.schematex-phylo-support-dot { stroke: none; }
|
|
641
|
+
.schematex-phylo-scale-bar line { stroke: ${t.text}; stroke-width: ${chunkN7KOXOMX_cjs.STROKE_WIDTH.normal}; }
|
|
642
|
+
.schematex-phylo-scale-bar text { font-size: 10px; fill: ${t.text}; text-anchor: middle; }
|
|
643
|
+
.schematex-phylo-scale-tick { stroke: ${t.text}; stroke-width: ${chunkN7KOXOMX_cjs.STROKE_WIDTH.thin}; }
|
|
644
|
+
.schematex-phylo-title { font-size: ${chunkN7KOXOMX_cjs.FONT_SIZE.title}px; font-weight: bold; fill: ${t.text}; text-anchor: middle; }
|
|
645
|
+
.schematex-phylo-clade-label { font-size: 13px; font-weight: bold; }
|
|
646
|
+
.schematex-phylo-root-marker { fill: none; stroke: ${t.text}; stroke-width: ${chunkN7KOXOMX_cjs.STROKE_WIDTH.normal}; }
|
|
647
|
+
${cladeColors.join("\n")}
|
|
648
|
+
`.trim();
|
|
649
|
+
}
|
|
650
|
+
function computeScaleBar(scale, plotWidth) {
|
|
651
|
+
if (scale <= 0) return { length: 0.1, label: "0.1", pxLength: 50 };
|
|
652
|
+
const targetPx = plotWidth * 0.2;
|
|
653
|
+
const magnitudes = [1e-3, 2e-3, 5e-3, 0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 2, 5, 10, 20, 50, 100];
|
|
654
|
+
let best = magnitudes[0];
|
|
655
|
+
let bestDiff = Infinity;
|
|
656
|
+
for (const m of magnitudes) {
|
|
657
|
+
const diff = Math.abs(m * scale - targetPx);
|
|
658
|
+
if (diff < bestDiff) {
|
|
659
|
+
bestDiff = diff;
|
|
660
|
+
best = m;
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
return {
|
|
664
|
+
length: best,
|
|
665
|
+
label: best < 0.01 ? best.toExponential() : String(best),
|
|
666
|
+
pxLength: best * scale
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
function renderScaleBar(layout, t, scaleLabel) {
|
|
670
|
+
if (layout.ast.mode === "cladogram") return "";
|
|
671
|
+
const plotWidth = layout.width - 40;
|
|
672
|
+
const bar = computeScaleBar(layout.scale, plotWidth);
|
|
673
|
+
if (bar.pxLength < 5) return "";
|
|
674
|
+
const x = 20;
|
|
675
|
+
const y = layout.height - 20;
|
|
676
|
+
const elements = [
|
|
677
|
+
chunkHDKDQAEQ_cjs.line({ x1: x, y1: y, x2: x + bar.pxLength, y2: y, class: "schematex-phylo-scale-bar" }),
|
|
678
|
+
chunkHDKDQAEQ_cjs.line({ x1: x, y1: y - 4, x2: x, y2: y + 4, class: "schematex-phylo-scale-tick" }),
|
|
679
|
+
chunkHDKDQAEQ_cjs.line({ x1: x + bar.pxLength, y1: y - 4, x2: x + bar.pxLength, y2: y + 4, class: "schematex-phylo-scale-tick" }),
|
|
680
|
+
chunkHDKDQAEQ_cjs.text({ x: x + bar.pxLength / 2, y: y + 16, "text-anchor": "middle", class: "schematex-phylo-scale-bar" }, bar.label)
|
|
681
|
+
];
|
|
682
|
+
if (scaleLabel) {
|
|
683
|
+
elements.push(
|
|
684
|
+
chunkHDKDQAEQ_cjs.text(
|
|
685
|
+
{ x: x + bar.pxLength / 2, y: y + 28, "text-anchor": "middle", "font-size": "9", fill: t.textMuted },
|
|
686
|
+
scaleLabel
|
|
687
|
+
)
|
|
688
|
+
);
|
|
689
|
+
}
|
|
690
|
+
return chunkHDKDQAEQ_cjs.group({ class: "schematex-phylo-scale-bar" }, elements);
|
|
691
|
+
}
|
|
692
|
+
function renderCladeBackgrounds(layout, t) {
|
|
693
|
+
const elements = [];
|
|
694
|
+
for (let ci = 0; ci < layout.ast.clades.length; ci++) {
|
|
695
|
+
const clade = layout.ast.clades[ci];
|
|
696
|
+
const hl = clade.highlight ?? "branch";
|
|
697
|
+
if (hl === "branch") continue;
|
|
698
|
+
const memberNodes = layout.nodes.filter(
|
|
699
|
+
(n) => n.node.isLeaf && clade.members.includes(n.node.id)
|
|
700
|
+
);
|
|
701
|
+
if (memberNodes.length === 0) continue;
|
|
702
|
+
const minY = Math.min(...memberNodes.map((n) => n.y)) - 10;
|
|
703
|
+
const maxY = Math.max(...memberNodes.map((n) => n.y)) + 10;
|
|
704
|
+
const minX = Math.min(...memberNodes.map((n) => n.x)) - 20;
|
|
705
|
+
const maxX = Math.max(...memberNodes.map((n) => {
|
|
706
|
+
const labelW = (n.node.label ?? n.node.id).length * 7.2 + TIP_LABEL_GAP + 8;
|
|
707
|
+
return n.x + labelW;
|
|
708
|
+
}));
|
|
709
|
+
const color = clade.color ?? t.cladeColors[ci % t.cladeColors.length];
|
|
710
|
+
elements.push(
|
|
711
|
+
chunkHDKDQAEQ_cjs.rect({
|
|
712
|
+
x: minX,
|
|
713
|
+
y: minY,
|
|
714
|
+
width: maxX - minX,
|
|
715
|
+
height: maxY - minY,
|
|
716
|
+
rx: 4,
|
|
717
|
+
class: `schematex-phylo-clade-bg schematex-phylo-clade-bg-${clade.id}`,
|
|
718
|
+
fill: color,
|
|
719
|
+
"fill-opacity": 0.12
|
|
720
|
+
})
|
|
721
|
+
);
|
|
722
|
+
if (clade.label) {
|
|
723
|
+
elements.push(
|
|
724
|
+
chunkHDKDQAEQ_cjs.text(
|
|
725
|
+
{
|
|
726
|
+
x: maxX + 4,
|
|
727
|
+
y: (minY + maxY) / 2,
|
|
728
|
+
class: `schematex-phylo-clade-label schematex-phylo-clade-label-${clade.id}`,
|
|
729
|
+
fill: color,
|
|
730
|
+
"font-weight": "bold",
|
|
731
|
+
"font-size": "13",
|
|
732
|
+
"dominant-baseline": "central"
|
|
733
|
+
},
|
|
734
|
+
clade.label
|
|
735
|
+
)
|
|
736
|
+
);
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
return elements;
|
|
740
|
+
}
|
|
741
|
+
function renderPhylo(layout) {
|
|
742
|
+
const { ast, nodes, branches } = layout;
|
|
743
|
+
const t = chunkN7KOXOMX_cjs.resolveBiologyTheme(ast.metadata?.theme ?? "default");
|
|
744
|
+
const css = buildCSS(ast, t);
|
|
745
|
+
const titleOffset = ast.title ? 30 : 0;
|
|
746
|
+
const totalHeight = layout.height + titleOffset;
|
|
747
|
+
const totalWidth = layout.width;
|
|
748
|
+
const branchElements = [];
|
|
749
|
+
const nodeElements = [];
|
|
750
|
+
const labelElements = [];
|
|
751
|
+
for (const branch of branches) {
|
|
752
|
+
const cladeIdx = branch.cladeId ? ast.clades.findIndex((c) => c.id === branch.cladeId) : -1;
|
|
753
|
+
const cladeColor = cladeIdx >= 0 ? ast.clades[cladeIdx].color ?? t.cladeColors[cladeIdx % t.cladeColors.length] : void 0;
|
|
754
|
+
const cls = branch.isConnector ? "schematex-phylo-branch schematex-phylo-branch-connector" : `schematex-phylo-branch schematex-phylo-branch-internal${branch.cladeId ? ` schematex-phylo-clade-${branch.cladeId}` : ""}`;
|
|
755
|
+
const attrs = {
|
|
756
|
+
d: branch.path,
|
|
757
|
+
class: cls
|
|
758
|
+
};
|
|
759
|
+
if (cladeColor && !branch.isConnector) {
|
|
760
|
+
attrs.stroke = cladeColor;
|
|
761
|
+
}
|
|
762
|
+
branchElements.push(chunkHDKDQAEQ_cjs.path(attrs));
|
|
763
|
+
}
|
|
764
|
+
const rootLayout = nodes.find((n) => n.node === ast.root);
|
|
765
|
+
if (rootLayout && !ast.unrooted) {
|
|
766
|
+
nodeElements.push(
|
|
767
|
+
chunkHDKDQAEQ_cjs.circle({
|
|
768
|
+
cx: rootLayout.x,
|
|
769
|
+
cy: rootLayout.y,
|
|
770
|
+
r: 5,
|
|
771
|
+
class: "schematex-phylo-root-marker"
|
|
772
|
+
})
|
|
773
|
+
);
|
|
774
|
+
}
|
|
775
|
+
for (const layoutNode of nodes) {
|
|
776
|
+
const { node, x, y } = layoutNode;
|
|
777
|
+
if (!node.isLeaf && node.support !== void 0) {
|
|
778
|
+
const support = node.support > 1 ? node.support : node.support * 100;
|
|
779
|
+
if (support >= SUPPORT_THRESHOLD) {
|
|
780
|
+
const color = getSupportColor(support, t);
|
|
781
|
+
nodeElements.push(
|
|
782
|
+
chunkHDKDQAEQ_cjs.circle({
|
|
783
|
+
cx: x,
|
|
784
|
+
cy: y,
|
|
785
|
+
r: 4,
|
|
786
|
+
class: "schematex-phylo-support-dot",
|
|
787
|
+
fill: color
|
|
788
|
+
})
|
|
789
|
+
);
|
|
790
|
+
labelElements.push(
|
|
791
|
+
chunkHDKDQAEQ_cjs.text(
|
|
792
|
+
{ x, y: y - 8, class: "schematex-phylo-support-label" },
|
|
793
|
+
String(Math.round(support))
|
|
794
|
+
)
|
|
795
|
+
);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
if (node.isLeaf) {
|
|
799
|
+
const label = node.label ?? node.id;
|
|
800
|
+
const italic = isSpeciesBinomial(label);
|
|
801
|
+
const cls = `schematex-phylo-tip-label${italic ? " schematex-phylo-tip-label-italic" : ""}`;
|
|
802
|
+
labelElements.push(
|
|
803
|
+
chunkHDKDQAEQ_cjs.text(
|
|
804
|
+
{
|
|
805
|
+
x: x + TIP_LABEL_GAP,
|
|
806
|
+
y,
|
|
807
|
+
class: cls,
|
|
808
|
+
"font-style": italic ? "italic" : void 0,
|
|
809
|
+
"data-taxon-id": node.id
|
|
810
|
+
},
|
|
811
|
+
label
|
|
812
|
+
)
|
|
813
|
+
);
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
const cladeBgElements = renderCladeBackgrounds(layout, t);
|
|
817
|
+
const scaleBarEl = renderScaleBar(layout, t, ast.scaleLabel);
|
|
818
|
+
const titleEl = ast.title ? chunkHDKDQAEQ_cjs.text(
|
|
819
|
+
{ x: totalWidth / 2, y: 20, class: "schematex-phylo-title" },
|
|
820
|
+
ast.title
|
|
821
|
+
) : "";
|
|
822
|
+
const leafCount = nodes.filter((n) => n.node.isLeaf).length;
|
|
823
|
+
const svgContent = [
|
|
824
|
+
chunkHDKDQAEQ_cjs.title(`Phylogenetic Tree${ast.title ? `: ${ast.title}` : ""}`),
|
|
825
|
+
chunkHDKDQAEQ_cjs.desc(`Phylogenetic tree with ${leafCount} taxa, ${ast.mode} mode, ${ast.layout} layout`),
|
|
826
|
+
chunkHDKDQAEQ_cjs.el("style", {}, css)
|
|
827
|
+
];
|
|
828
|
+
if (titleEl) svgContent.push(titleEl);
|
|
829
|
+
const transformY = titleOffset;
|
|
830
|
+
if (cladeBgElements.length > 0) {
|
|
831
|
+
svgContent.push(
|
|
832
|
+
chunkHDKDQAEQ_cjs.group(
|
|
833
|
+
{ class: "schematex-phylo-clade-highlights", transform: transformY ? `translate(0,${transformY})` : void 0 },
|
|
834
|
+
cladeBgElements
|
|
835
|
+
)
|
|
836
|
+
);
|
|
837
|
+
}
|
|
838
|
+
svgContent.push(
|
|
839
|
+
chunkHDKDQAEQ_cjs.group(
|
|
840
|
+
{ class: "schematex-phylo-branches", transform: transformY ? `translate(0,${transformY})` : void 0 },
|
|
841
|
+
branchElements
|
|
842
|
+
)
|
|
843
|
+
);
|
|
844
|
+
svgContent.push(
|
|
845
|
+
chunkHDKDQAEQ_cjs.group(
|
|
846
|
+
{ class: "schematex-phylo-nodes", transform: transformY ? `translate(0,${transformY})` : void 0 },
|
|
847
|
+
nodeElements
|
|
848
|
+
)
|
|
849
|
+
);
|
|
850
|
+
svgContent.push(
|
|
851
|
+
chunkHDKDQAEQ_cjs.group(
|
|
852
|
+
{ class: "schematex-phylo-labels", transform: transformY ? `translate(0,${transformY})` : void 0 },
|
|
853
|
+
labelElements
|
|
854
|
+
)
|
|
855
|
+
);
|
|
856
|
+
if (scaleBarEl) {
|
|
857
|
+
svgContent.push(
|
|
858
|
+
chunkHDKDQAEQ_cjs.group(
|
|
859
|
+
{ transform: transformY ? `translate(0,${transformY})` : void 0 },
|
|
860
|
+
[scaleBarEl]
|
|
861
|
+
)
|
|
862
|
+
);
|
|
863
|
+
}
|
|
864
|
+
return chunkHDKDQAEQ_cjs.svgRoot(
|
|
865
|
+
{
|
|
866
|
+
class: "schematex-diagram schematex-phylo",
|
|
867
|
+
viewBox: `0 0 ${totalWidth} ${totalHeight}`,
|
|
868
|
+
width: totalWidth,
|
|
869
|
+
height: totalHeight
|
|
870
|
+
},
|
|
871
|
+
svgContent
|
|
872
|
+
);
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
// src/diagrams/phylo/index.ts
|
|
876
|
+
var phylo = {
|
|
877
|
+
type: "phylo",
|
|
878
|
+
detect(text2) {
|
|
879
|
+
const firstLine = text2.trim().split("\n")[0]?.trim().toLowerCase() ?? "";
|
|
880
|
+
return firstLine === "phylo" || firstLine.startsWith("phylo ");
|
|
881
|
+
},
|
|
882
|
+
render(text2) {
|
|
883
|
+
const ast = parsePhylo(text2);
|
|
884
|
+
const layout = layoutPhylo(ast);
|
|
885
|
+
return renderPhylo(layout);
|
|
886
|
+
}
|
|
887
|
+
};
|
|
888
|
+
|
|
889
|
+
exports.PhyloParseError = PhyloParseError;
|
|
890
|
+
exports.layoutPhylo = layoutPhylo;
|
|
891
|
+
exports.parsePhylo = parsePhylo;
|
|
892
|
+
exports.phylo = phylo;
|
|
893
|
+
exports.renderPhylo = renderPhylo;
|
|
894
|
+
//# sourceMappingURL=chunk-XQ52ICHU.cjs.map
|
|
895
|
+
//# sourceMappingURL=chunk-XQ52ICHU.cjs.map
|