schematex 0.6.9 → 0.7.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/dist/ai/ai-sdk.cjs +8 -8
- package/dist/ai/ai-sdk.js +3 -3
- package/dist/ai/index.cjs +14 -14
- package/dist/ai/index.js +3 -3
- package/dist/browser.cjs +9 -9
- package/dist/browser.js +3 -3
- package/dist/{chunk-25TO5A4F.cjs → chunk-CTMJ3XP2.cjs} +156 -9
- package/dist/chunk-CTMJ3XP2.cjs.map +1 -0
- package/dist/{chunk-HL5PS6MG.js → chunk-EENA7KNU.js} +324 -27
- package/dist/chunk-EENA7KNU.js.map +1 -0
- package/dist/{chunk-5IZL57YJ.cjs → chunk-HFATQXFN.cjs} +1455 -158
- package/dist/chunk-HFATQXFN.cjs.map +1 -0
- package/dist/{chunk-3DPWFWQU.js → chunk-IFNNV54X.js} +1454 -157
- package/dist/chunk-IFNNV54X.js.map +1 -0
- package/dist/{chunk-Q5V4LUQR.js → chunk-IFXHIYZE.js} +154 -7
- package/dist/chunk-IFXHIYZE.js.map +1 -0
- package/dist/{chunk-BL57NQKN.cjs → chunk-JAYJ2G4R.cjs} +324 -27
- package/dist/chunk-JAYJ2G4R.cjs.map +1 -0
- package/dist/diagrams/phylo/index.cjs +6 -6
- package/dist/diagrams/phylo/index.d.cts +21 -0
- package/dist/diagrams/phylo/index.d.ts +21 -0
- package/dist/diagrams/phylo/index.js +1 -1
- package/dist/index.cjs +25 -25
- package/dist/index.js +3 -3
- package/dist/react.cjs +3 -3
- package/dist/react.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-25TO5A4F.cjs.map +0 -1
- package/dist/chunk-3DPWFWQU.js.map +0 -1
- package/dist/chunk-5IZL57YJ.cjs.map +0 -1
- package/dist/chunk-BL57NQKN.cjs.map +0 -1
- package/dist/chunk-HL5PS6MG.js.map +0 -1
- package/dist/chunk-Q5V4LUQR.js.map +0 -1
|
@@ -250,6 +250,9 @@ function parseHeaderProps(propsStr) {
|
|
|
250
250
|
case "mode":
|
|
251
251
|
if (["phylogram", "cladogram", "chronogram"].includes(val)) {
|
|
252
252
|
result.mode = val;
|
|
253
|
+
} else if (val === "dendrogram") {
|
|
254
|
+
result.mode = "cladogram";
|
|
255
|
+
result.dendrogram = true;
|
|
253
256
|
}
|
|
254
257
|
break;
|
|
255
258
|
case "branch-width":
|
|
@@ -313,6 +316,7 @@ function parsePhylo(text2) {
|
|
|
313
316
|
let root = null;
|
|
314
317
|
let scaleLabel;
|
|
315
318
|
let outgroup;
|
|
319
|
+
let cut;
|
|
316
320
|
const clades = [];
|
|
317
321
|
const indentLines = [];
|
|
318
322
|
let inIndentTree = false;
|
|
@@ -340,6 +344,12 @@ function parsePhylo(text2) {
|
|
|
340
344
|
outgroup = trimmed.slice(9).trim();
|
|
341
345
|
continue;
|
|
342
346
|
}
|
|
347
|
+
if (/^cut\b/i.test(trimmed)) {
|
|
348
|
+
const cutStr = trimmed.replace(/^cut\s*:?\s*/i, "").trim();
|
|
349
|
+
const cutVal = Number(cutStr);
|
|
350
|
+
if (!Number.isNaN(cutVal)) cut = cutVal;
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
343
353
|
if (trimmed.startsWith("clade ")) {
|
|
344
354
|
const clade = parseCladeLine(trimmed);
|
|
345
355
|
if (clade) clades.push(clade);
|
|
@@ -368,6 +378,9 @@ function parsePhylo(text2) {
|
|
|
368
378
|
if (!root) {
|
|
369
379
|
throw new PhyloParseError("No tree definition found (newick: or indent tree)");
|
|
370
380
|
}
|
|
381
|
+
const metadata = {};
|
|
382
|
+
if (headerProps.dendrogram) metadata.dendrogram = "true";
|
|
383
|
+
if (cut !== void 0) metadata.cut = String(cut);
|
|
371
384
|
return {
|
|
372
385
|
type: "phylo",
|
|
373
386
|
title: title2,
|
|
@@ -379,11 +392,25 @@ function parsePhylo(text2) {
|
|
|
379
392
|
scaleLabel,
|
|
380
393
|
mrsd: headerProps.mrsd,
|
|
381
394
|
outgroup,
|
|
382
|
-
metadata
|
|
395
|
+
metadata
|
|
383
396
|
};
|
|
384
397
|
}
|
|
385
398
|
|
|
386
|
-
// src/diagrams/phylo/
|
|
399
|
+
// src/diagrams/phylo/dendrogram.ts
|
|
400
|
+
var TIP_SPACING = 24;
|
|
401
|
+
var PADDING_LEFT = 20;
|
|
402
|
+
var PADDING_RIGHT = 20;
|
|
403
|
+
var PADDING_TOP = 24;
|
|
404
|
+
var PADDING_BOTTOM = 52;
|
|
405
|
+
function isDendrogram(ast) {
|
|
406
|
+
return ast.metadata?.dendrogram === "true";
|
|
407
|
+
}
|
|
408
|
+
function getCut(ast) {
|
|
409
|
+
const raw = ast.metadata?.cut;
|
|
410
|
+
if (raw === void 0) return void 0;
|
|
411
|
+
const val = Number(raw);
|
|
412
|
+
return Number.isNaN(val) ? void 0 : val;
|
|
413
|
+
}
|
|
387
414
|
function collectLeaves(node) {
|
|
388
415
|
if (node.isLeaf) return [node];
|
|
389
416
|
const leaves = [];
|
|
@@ -392,6 +419,166 @@ function collectLeaves(node) {
|
|
|
392
419
|
}
|
|
393
420
|
return leaves;
|
|
394
421
|
}
|
|
422
|
+
function estimateLabelWidth(node) {
|
|
423
|
+
const label = node.label ?? node.id;
|
|
424
|
+
return label.length * 7.2 + 6;
|
|
425
|
+
}
|
|
426
|
+
function computeHeights(node, out) {
|
|
427
|
+
if (node.isLeaf) {
|
|
428
|
+
out.set(node.id, 0);
|
|
429
|
+
return 0;
|
|
430
|
+
}
|
|
431
|
+
let max = 0;
|
|
432
|
+
for (const child of node.children) {
|
|
433
|
+
const childHeight = computeHeights(child, out);
|
|
434
|
+
const branch = child.branchLength ?? 0;
|
|
435
|
+
const reach = childHeight + branch;
|
|
436
|
+
if (reach > max) max = reach;
|
|
437
|
+
}
|
|
438
|
+
out.set(node.id, max);
|
|
439
|
+
return max;
|
|
440
|
+
}
|
|
441
|
+
function computeClusters(ast, heights, cutValue) {
|
|
442
|
+
const clusters = [];
|
|
443
|
+
function visit(node, parentHeight) {
|
|
444
|
+
const h = heights.get(node.id) ?? 0;
|
|
445
|
+
if (h <= cutValue && parentHeight > cutValue) {
|
|
446
|
+
clusters.push({ rootId: node.id, leafIds: collectLeaves(node).map((l) => l.id) });
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
if (h > cutValue) {
|
|
450
|
+
for (const child of node.children) visit(child, h);
|
|
451
|
+
} else {
|
|
452
|
+
clusters.push({ rootId: node.id, leafIds: collectLeaves(node).map((l) => l.id) });
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
visit(ast.root, Number.POSITIVE_INFINITY);
|
|
456
|
+
return clusters;
|
|
457
|
+
}
|
|
458
|
+
function layoutDendrogram(ast) {
|
|
459
|
+
const leaves = collectLeaves(ast.root);
|
|
460
|
+
const numLeaves = leaves.length;
|
|
461
|
+
const heights = /* @__PURE__ */ new Map();
|
|
462
|
+
const maxHeight = computeHeights(ast.root, heights);
|
|
463
|
+
const maxLabelWidth = Math.max(...leaves.map(estimateLabelWidth), 60);
|
|
464
|
+
const availableWidth = Math.max(320, numLeaves * 36 + maxLabelWidth + 120);
|
|
465
|
+
const plotWidth = availableWidth - PADDING_LEFT - PADDING_RIGHT - maxLabelWidth;
|
|
466
|
+
const scale = maxHeight > 0 ? plotWidth / maxHeight : plotWidth;
|
|
467
|
+
const baselineX = PADDING_LEFT + plotWidth;
|
|
468
|
+
const heightToX = (h) => baselineX - h * scale;
|
|
469
|
+
const nodeMap = /* @__PURE__ */ new Map();
|
|
470
|
+
let leafIdx = 0;
|
|
471
|
+
function assignLeaf(node) {
|
|
472
|
+
if (node.isLeaf) {
|
|
473
|
+
const y = PADDING_TOP + leafIdx * TIP_SPACING;
|
|
474
|
+
nodeMap.set(node.id, { node, x: baselineX, y });
|
|
475
|
+
leafIdx++;
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
for (const child of node.children) assignLeaf(child);
|
|
479
|
+
}
|
|
480
|
+
assignLeaf(ast.root);
|
|
481
|
+
function assignInternal(node) {
|
|
482
|
+
const existing = nodeMap.get(node.id);
|
|
483
|
+
if (node.isLeaf && existing) return existing.y;
|
|
484
|
+
const childYs = node.children.map(assignInternal);
|
|
485
|
+
const y = (Math.min(...childYs) + Math.max(...childYs)) / 2;
|
|
486
|
+
const x = heightToX(heights.get(node.id) ?? 0);
|
|
487
|
+
if (existing) {
|
|
488
|
+
existing.y = y;
|
|
489
|
+
existing.x = x;
|
|
490
|
+
} else {
|
|
491
|
+
nodeMap.set(node.id, { node, x, y });
|
|
492
|
+
}
|
|
493
|
+
return y;
|
|
494
|
+
}
|
|
495
|
+
assignInternal(ast.root);
|
|
496
|
+
const cut = getCut(ast);
|
|
497
|
+
const leafCluster = /* @__PURE__ */ new Map();
|
|
498
|
+
let clusterCount = 0;
|
|
499
|
+
if (cut !== void 0) {
|
|
500
|
+
const clusters = computeClusters(ast, heights, cut);
|
|
501
|
+
clusterCount = clusters.length;
|
|
502
|
+
clusters.forEach((cluster, idx) => {
|
|
503
|
+
for (const leafId of cluster.leafIds) leafCluster.set(leafId, idx);
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
function subtreeCluster(node) {
|
|
507
|
+
const ids = collectLeaves(node).map((l) => l.id);
|
|
508
|
+
const first = leafCluster.get(ids[0]);
|
|
509
|
+
if (first === void 0) return void 0;
|
|
510
|
+
for (const id of ids) {
|
|
511
|
+
if (leafCluster.get(id) !== first) return void 0;
|
|
512
|
+
}
|
|
513
|
+
return first;
|
|
514
|
+
}
|
|
515
|
+
const branches = [];
|
|
516
|
+
function generate(node) {
|
|
517
|
+
if (node.children.length === 0) return;
|
|
518
|
+
const parent = nodeMap.get(node.id);
|
|
519
|
+
if (!parent) return;
|
|
520
|
+
const childLayouts = node.children.map((c) => nodeMap.get(c.id)).filter((l) => l !== void 0);
|
|
521
|
+
if (childLayouts.length === 0) return;
|
|
522
|
+
const minY = Math.min(...childLayouts.map((c) => c.y));
|
|
523
|
+
const maxY = Math.max(...childLayouts.map((c) => c.y));
|
|
524
|
+
branches.push({
|
|
525
|
+
path: `M ${parent.x},${minY} V ${maxY}`,
|
|
526
|
+
fromId: node.id,
|
|
527
|
+
toId: node.id,
|
|
528
|
+
isConnector: true
|
|
529
|
+
});
|
|
530
|
+
for (const child of node.children) {
|
|
531
|
+
const childLayout = nodeMap.get(child.id);
|
|
532
|
+
if (!childLayout) continue;
|
|
533
|
+
const cluster = subtreeCluster(child);
|
|
534
|
+
branches.push({
|
|
535
|
+
path: `M ${parent.x},${childLayout.y} H ${childLayout.x}`,
|
|
536
|
+
fromId: node.id,
|
|
537
|
+
toId: child.id,
|
|
538
|
+
cladeId: cluster !== void 0 ? `cut${cluster}` : void 0,
|
|
539
|
+
isConnector: false
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
for (const child of node.children) generate(child);
|
|
543
|
+
}
|
|
544
|
+
generate(ast.root);
|
|
545
|
+
const allNodes = Array.from(nodeMap.values());
|
|
546
|
+
const maxX = Math.max(
|
|
547
|
+
...allNodes.map((n) => n.x + (n.node.isLeaf ? estimateLabelWidth(n.node) : 0))
|
|
548
|
+
);
|
|
549
|
+
const maxNodeY = Math.max(...allNodes.map((n) => n.y));
|
|
550
|
+
const width = Math.max(maxX + PADDING_RIGHT, availableWidth);
|
|
551
|
+
const height = maxNodeY + PADDING_TOP + PADDING_BOTTOM;
|
|
552
|
+
return {
|
|
553
|
+
width,
|
|
554
|
+
height,
|
|
555
|
+
nodes: allNodes,
|
|
556
|
+
branches,
|
|
557
|
+
ast,
|
|
558
|
+
scale,
|
|
559
|
+
// Dendrogram-specific extras stashed for the renderer (see types below).
|
|
560
|
+
dendrogram: {
|
|
561
|
+
maxHeight,
|
|
562
|
+
baselineX,
|
|
563
|
+
plotLeftX: PADDING_LEFT,
|
|
564
|
+
scale,
|
|
565
|
+
cut,
|
|
566
|
+
clusterCount,
|
|
567
|
+
leafCluster,
|
|
568
|
+
heights
|
|
569
|
+
}
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// src/diagrams/phylo/layout.ts
|
|
574
|
+
function collectLeaves2(node) {
|
|
575
|
+
if (node.isLeaf) return [node];
|
|
576
|
+
const leaves = [];
|
|
577
|
+
for (const child of node.children) {
|
|
578
|
+
leaves.push(...collectLeaves2(child));
|
|
579
|
+
}
|
|
580
|
+
return leaves;
|
|
581
|
+
}
|
|
395
582
|
function maxRootToTip(node, distSoFar) {
|
|
396
583
|
if (node.isLeaf) return distSoFar;
|
|
397
584
|
let maxDist = distSoFar;
|
|
@@ -410,7 +597,7 @@ function maxDepth(node) {
|
|
|
410
597
|
}
|
|
411
598
|
return max;
|
|
412
599
|
}
|
|
413
|
-
function
|
|
600
|
+
function estimateLabelWidth2(node) {
|
|
414
601
|
const label = node.label ?? node.id;
|
|
415
602
|
return label.length * 7.2 + 6;
|
|
416
603
|
}
|
|
@@ -445,20 +632,23 @@ function markCladeBranches(node, memberSet, cladeId, result) {
|
|
|
445
632
|
}
|
|
446
633
|
return false;
|
|
447
634
|
}
|
|
448
|
-
var
|
|
449
|
-
var
|
|
450
|
-
var
|
|
451
|
-
var
|
|
452
|
-
var
|
|
635
|
+
var TIP_SPACING2 = 20;
|
|
636
|
+
var PADDING_LEFT2 = 20;
|
|
637
|
+
var PADDING_RIGHT2 = 20;
|
|
638
|
+
var PADDING_TOP2 = 20;
|
|
639
|
+
var PADDING_BOTTOM2 = 40;
|
|
453
640
|
function layoutPhylo(ast) {
|
|
454
|
-
|
|
641
|
+
if (isDendrogram(ast)) {
|
|
642
|
+
return layoutDendrogram(ast);
|
|
643
|
+
}
|
|
644
|
+
const leaves = collectLeaves2(ast.root);
|
|
455
645
|
const numLeaves = leaves.length;
|
|
456
|
-
const tipSpacing =
|
|
457
|
-
const maxLabelWidth = Math.max(...leaves.map(
|
|
646
|
+
const tipSpacing = TIP_SPACING2;
|
|
647
|
+
const maxLabelWidth = Math.max(...leaves.map(estimateLabelWidth2), 60);
|
|
458
648
|
const maxDist = maxRootToTip(ast.root, 0);
|
|
459
649
|
const isCladogram = ast.mode === "cladogram";
|
|
460
650
|
const availableWidth = Math.max(300, numLeaves * 30 + maxLabelWidth + 100);
|
|
461
|
-
const plotWidth = availableWidth -
|
|
651
|
+
const plotWidth = availableWidth - PADDING_LEFT2 - PADDING_RIGHT2 - maxLabelWidth;
|
|
462
652
|
let scale;
|
|
463
653
|
if (isCladogram || maxDist === 0) {
|
|
464
654
|
const depth = maxDepth(ast.root);
|
|
@@ -470,7 +660,7 @@ function layoutPhylo(ast) {
|
|
|
470
660
|
let leafIdx = 0;
|
|
471
661
|
function assignLeafY(node) {
|
|
472
662
|
if (node.isLeaf) {
|
|
473
|
-
const y =
|
|
663
|
+
const y = PADDING_TOP2 + leafIdx * tipSpacing;
|
|
474
664
|
nodeMap.set(node.id, { node, x: 0, y });
|
|
475
665
|
leafIdx++;
|
|
476
666
|
return;
|
|
@@ -496,12 +686,12 @@ function layoutPhylo(ast) {
|
|
|
496
686
|
function assignX(node, parentX, depth) {
|
|
497
687
|
let x;
|
|
498
688
|
if (node === ast.root) {
|
|
499
|
-
x =
|
|
689
|
+
x = PADDING_LEFT2;
|
|
500
690
|
} else if (isCladogram) {
|
|
501
691
|
if (node.isLeaf) {
|
|
502
|
-
x =
|
|
692
|
+
x = PADDING_LEFT2 + plotWidth;
|
|
503
693
|
} else {
|
|
504
|
-
x =
|
|
694
|
+
x = PADDING_LEFT2 + depth * (plotWidth / maxDepth(ast.root));
|
|
505
695
|
}
|
|
506
696
|
} else {
|
|
507
697
|
x = parentX + (node.branchLength ?? 0) * scale;
|
|
@@ -512,7 +702,7 @@ function layoutPhylo(ast) {
|
|
|
512
702
|
assignX(child, x, depth + 1);
|
|
513
703
|
}
|
|
514
704
|
}
|
|
515
|
-
assignX(ast.root,
|
|
705
|
+
assignX(ast.root, PADDING_LEFT2, 0);
|
|
516
706
|
if (isCladogram) {
|
|
517
707
|
assignCladogramInternalX(ast.root, nodeMap);
|
|
518
708
|
}
|
|
@@ -565,7 +755,7 @@ function layoutPhylo(ast) {
|
|
|
565
755
|
}
|
|
566
756
|
generateBranches(ast.root);
|
|
567
757
|
const allNodes = Array.from(nodeMap.values());
|
|
568
|
-
let maxX = Math.max(...allNodes.map((n) => n.x + (n.node.isLeaf ?
|
|
758
|
+
let maxX = Math.max(...allNodes.map((n) => n.x + (n.node.isLeaf ? estimateLabelWidth2(n.node) : 0)));
|
|
569
759
|
let maxCladeLabelWidth = 0;
|
|
570
760
|
for (const clade of ast.clades) {
|
|
571
761
|
if (clade.label && clade.highlight && clade.highlight !== "branch") {
|
|
@@ -575,8 +765,8 @@ function layoutPhylo(ast) {
|
|
|
575
765
|
}
|
|
576
766
|
maxX += maxCladeLabelWidth;
|
|
577
767
|
const maxNodeY = Math.max(...allNodes.map((n) => n.y));
|
|
578
|
-
const width = Math.max(maxX +
|
|
579
|
-
const height = maxNodeY +
|
|
768
|
+
const width = Math.max(maxX + PADDING_RIGHT2, availableWidth);
|
|
769
|
+
const height = maxNodeY + PADDING_TOP2 + PADDING_BOTTOM2;
|
|
580
770
|
return {
|
|
581
771
|
width,
|
|
582
772
|
height,
|
|
@@ -598,9 +788,9 @@ function assignCladogramInternalX(node, nodeMap) {
|
|
|
598
788
|
const layout = nodeMap.get(node.id);
|
|
599
789
|
if (layout) {
|
|
600
790
|
layout.x = minChildX - 40;
|
|
601
|
-
if (layout.x <
|
|
791
|
+
if (layout.x < PADDING_LEFT2) layout.x = PADDING_LEFT2;
|
|
602
792
|
}
|
|
603
|
-
return layout?.x ??
|
|
793
|
+
return layout?.x ?? PADDING_LEFT2;
|
|
604
794
|
}
|
|
605
795
|
|
|
606
796
|
// src/diagrams/phylo/renderer.ts
|
|
@@ -624,12 +814,25 @@ function buildCSS(ast, t) {
|
|
|
624
814
|
.schematex-phylo-clade-bg-${c.id} { fill: ${color}; fill-opacity: 0.12; }
|
|
625
815
|
.schematex-phylo-clade-label-${c.id} { fill: ${color}; }`;
|
|
626
816
|
});
|
|
817
|
+
const cutColors = [];
|
|
818
|
+
if (ast.metadata?.dendrogram === "true" && ast.metadata?.cut !== void 0) {
|
|
819
|
+
for (let i = 0; i < t.cladeColors.length; i++) {
|
|
820
|
+
cutColors.push(
|
|
821
|
+
`.schematex-phylo-clade-cut${i} { stroke: ${t.cladeColors[i % t.cladeColors.length]}; }`
|
|
822
|
+
);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
627
825
|
return `
|
|
628
826
|
.schematex-phylo {${cssCustomProperties(t)}
|
|
629
827
|
font-family: system-ui, -apple-system, sans-serif;
|
|
630
828
|
}
|
|
631
829
|
.schematex-phylo-branch { fill: none; stroke: ${t.text}; stroke-width: ${STROKE_WIDTH.normal}; stroke-linecap: round; }
|
|
632
830
|
.schematex-phylo-branch-connector { fill: none; stroke: ${t.text}; stroke-width: ${STROKE_WIDTH.normal}; }
|
|
831
|
+
.schematex-phylo-dendro-axis line { stroke: ${t.text}; stroke-width: ${STROKE_WIDTH.thin}; }
|
|
832
|
+
.schematex-phylo-dendro-axis text { font-size: 10px; fill: ${t.textMuted}; text-anchor: middle; }
|
|
833
|
+
.schematex-phylo-dendro-cut { stroke: ${t.supportBad}; stroke-width: ${STROKE_WIDTH.normal}; stroke-dasharray: 5 4; fill: none; }
|
|
834
|
+
.schematex-phylo-dendro-cut-label { font-size: 10px; fill: ${t.supportBad}; text-anchor: start; }
|
|
835
|
+
${cutColors.join("\n")}
|
|
633
836
|
.schematex-phylo-tip-label { font-size: ${FONT_SIZE.label}px; fill: ${t.text}; dominant-baseline: central; }
|
|
634
837
|
.schematex-phylo-tip-label-italic { font-style: italic; }
|
|
635
838
|
.schematex-phylo-support-label { font-size: ${FONT_SIZE.small}px; fill: ${t.textMuted}; text-anchor: middle; dominant-baseline: auto; }
|
|
@@ -734,6 +937,75 @@ function renderCladeBackgrounds(layout, t) {
|
|
|
734
937
|
}
|
|
735
938
|
return elements;
|
|
736
939
|
}
|
|
940
|
+
function niceStep(maxHeight, targetTicks) {
|
|
941
|
+
if (maxHeight <= 0) return 1;
|
|
942
|
+
const rough = maxHeight / targetTicks;
|
|
943
|
+
const mag = Math.pow(10, Math.floor(Math.log10(rough)));
|
|
944
|
+
const norm = rough / mag;
|
|
945
|
+
let step;
|
|
946
|
+
if (norm < 1.5) step = 1;
|
|
947
|
+
else if (norm < 3) step = 2;
|
|
948
|
+
else if (norm < 7) step = 5;
|
|
949
|
+
else step = 10;
|
|
950
|
+
return step * mag;
|
|
951
|
+
}
|
|
952
|
+
function formatTick(value) {
|
|
953
|
+
if (value === 0) return "0";
|
|
954
|
+
if (Math.abs(value) < 0.01) return value.toExponential(0);
|
|
955
|
+
return String(Math.round(value * 1e3) / 1e3);
|
|
956
|
+
}
|
|
957
|
+
function renderDendrogramAxis(layout, t) {
|
|
958
|
+
const d = layout.dendrogram;
|
|
959
|
+
if (!d) return "";
|
|
960
|
+
const axisY = layout.height - 28;
|
|
961
|
+
const elements = [];
|
|
962
|
+
elements.push(
|
|
963
|
+
line({ x1: d.plotLeftX, y1: axisY, x2: d.baselineX, y2: axisY })
|
|
964
|
+
);
|
|
965
|
+
const step = niceStep(d.maxHeight, 5);
|
|
966
|
+
for (let h = 0; h <= d.maxHeight + step * 1e-3; h += step) {
|
|
967
|
+
const x = d.baselineX - h * d.scale;
|
|
968
|
+
elements.push(line({ x1: x, y1: axisY, x2: x, y2: axisY + 4 }));
|
|
969
|
+
elements.push(
|
|
970
|
+
text({ x, y: axisY + 16, "text-anchor": "middle" }, formatTick(h))
|
|
971
|
+
);
|
|
972
|
+
}
|
|
973
|
+
if (layout.ast.scaleLabel) {
|
|
974
|
+
elements.push(
|
|
975
|
+
text(
|
|
976
|
+
{
|
|
977
|
+
x: (d.plotLeftX + d.baselineX) / 2,
|
|
978
|
+
y: axisY + 28,
|
|
979
|
+
"text-anchor": "middle",
|
|
980
|
+
"font-size": "9",
|
|
981
|
+
fill: t.textMuted
|
|
982
|
+
},
|
|
983
|
+
layout.ast.scaleLabel
|
|
984
|
+
)
|
|
985
|
+
);
|
|
986
|
+
}
|
|
987
|
+
return group({ class: "schematex-phylo-dendro-axis" }, elements);
|
|
988
|
+
}
|
|
989
|
+
function renderCutLine(layout) {
|
|
990
|
+
const d = layout.dendrogram;
|
|
991
|
+
if (!d || d.cut === void 0) return "";
|
|
992
|
+
const x = d.baselineX - d.cut * d.scale;
|
|
993
|
+
const topY = 8;
|
|
994
|
+
const bottomY = layout.height - 32;
|
|
995
|
+
return group({}, [
|
|
996
|
+
line({
|
|
997
|
+
x1: x,
|
|
998
|
+
y1: topY,
|
|
999
|
+
x2: x,
|
|
1000
|
+
y2: bottomY,
|
|
1001
|
+
class: "schematex-phylo-dendro-cut"
|
|
1002
|
+
}),
|
|
1003
|
+
text(
|
|
1004
|
+
{ x: x + 4, y: topY + 4, class: "schematex-phylo-dendro-cut-label" },
|
|
1005
|
+
`cut = ${formatTick(d.cut)}`
|
|
1006
|
+
)
|
|
1007
|
+
]);
|
|
1008
|
+
}
|
|
737
1009
|
function renderPhylo(layout) {
|
|
738
1010
|
const { ast, nodes, branches } = layout;
|
|
739
1011
|
const t = resolveBiologyTheme(ast.metadata?.theme ?? "default");
|
|
@@ -810,15 +1082,24 @@ function renderPhylo(layout) {
|
|
|
810
1082
|
}
|
|
811
1083
|
}
|
|
812
1084
|
const cladeBgElements = renderCladeBackgrounds(layout, t);
|
|
813
|
-
const
|
|
1085
|
+
const isDendro = layout.dendrogram !== void 0;
|
|
1086
|
+
const scaleBarEl = isDendro ? "" : renderScaleBar(layout, t, ast.scaleLabel);
|
|
1087
|
+
const dendroAxisEl = isDendro ? renderDendrogramAxis(layout, t) : "";
|
|
1088
|
+
const cutLineEl = isDendro ? renderCutLine(layout) : "";
|
|
814
1089
|
const titleEl = ast.title ? text(
|
|
815
1090
|
{ x: totalWidth / 2, y: 20, class: "schematex-phylo-title" },
|
|
816
1091
|
ast.title
|
|
817
1092
|
) : "";
|
|
818
1093
|
const leafCount = nodes.filter((n) => n.node.isLeaf).length;
|
|
1094
|
+
const isDendrogramMode = layout.dendrogram !== void 0;
|
|
1095
|
+
const descMode = isDendrogramMode ? "dendrogram" : ast.mode;
|
|
1096
|
+
const descTitle = isDendrogramMode ? "Dendrogram" : "Phylogenetic Tree";
|
|
1097
|
+
const cutSuffix = isDendrogramMode && layout.dendrogram?.cut !== void 0 ? `, cut at ${layout.dendrogram.cut} into ${layout.dendrogram.clusterCount} clusters` : "";
|
|
819
1098
|
const svgContent = [
|
|
820
|
-
title(
|
|
821
|
-
desc(
|
|
1099
|
+
title(`${descTitle}${ast.title ? `: ${ast.title}` : ""}`),
|
|
1100
|
+
desc(
|
|
1101
|
+
`${isDendrogramMode ? "Dendrogram" : "Phylogenetic tree"} with ${leafCount} taxa, ${descMode} mode, ${ast.layout} layout${cutSuffix}`
|
|
1102
|
+
),
|
|
822
1103
|
el("style", {}, css)
|
|
823
1104
|
];
|
|
824
1105
|
if (titleEl) svgContent.push(titleEl);
|
|
@@ -857,6 +1138,22 @@ function renderPhylo(layout) {
|
|
|
857
1138
|
)
|
|
858
1139
|
);
|
|
859
1140
|
}
|
|
1141
|
+
if (dendroAxisEl) {
|
|
1142
|
+
svgContent.push(
|
|
1143
|
+
group(
|
|
1144
|
+
{ transform: transformY ? `translate(0,${transformY})` : void 0 },
|
|
1145
|
+
[dendroAxisEl]
|
|
1146
|
+
)
|
|
1147
|
+
);
|
|
1148
|
+
}
|
|
1149
|
+
if (cutLineEl) {
|
|
1150
|
+
svgContent.push(
|
|
1151
|
+
group(
|
|
1152
|
+
{ transform: transformY ? `translate(0,${transformY})` : void 0 },
|
|
1153
|
+
[cutLineEl]
|
|
1154
|
+
)
|
|
1155
|
+
);
|
|
1156
|
+
}
|
|
860
1157
|
return svgRoot(
|
|
861
1158
|
{
|
|
862
1159
|
class: "schematex-diagram schematex-phylo",
|
|
@@ -884,5 +1181,5 @@ var phylo = {
|
|
|
884
1181
|
};
|
|
885
1182
|
|
|
886
1183
|
export { PhyloParseError, layoutPhylo, parsePhylo, phylo, renderPhylo };
|
|
887
|
-
//# sourceMappingURL=chunk-
|
|
888
|
-
//# sourceMappingURL=chunk-
|
|
1184
|
+
//# sourceMappingURL=chunk-EENA7KNU.js.map
|
|
1185
|
+
//# sourceMappingURL=chunk-EENA7KNU.js.map
|