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