archaeopteryx 3.0.0 → 3.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/README.md +116 -21
- package/archaeopteryx.js +1651 -578
- package/forester.js +297 -11
- package/package.json +1 -1
package/forester.js
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
*
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
|
-
// v 3.
|
|
23
|
+
// v 3.1.0
|
|
24
24
|
// 2026-09-04
|
|
25
25
|
//
|
|
26
26
|
// forester.js is a general suite for dealing with phylogenetic trees.
|
|
@@ -649,6 +649,21 @@
|
|
|
649
649
|
const VIS_MAX_NUMERIC_UNIQUE_NUM = 9; // distinct/covered <= 0.9,
|
|
650
650
|
const VIS_MAX_NUMERIC_UNIQUE_DEN = 10; // integer-exact as well
|
|
651
651
|
const VIS_EXCLUDED_REF_PREFIX = 'style:';
|
|
652
|
+
// Refs that are never a visualization, however their values distribute.
|
|
653
|
+
// A taxon identifier repeats like a category and passes every statistical
|
|
654
|
+
// test above, yet says nothing a colour could carry that the species name
|
|
655
|
+
// beside it does not -- and reads as "11320" in a legend. Matched on the
|
|
656
|
+
// ref's local name with case and separators ignored, so vipr:NCBI_Taxon_Id,
|
|
657
|
+
// ncbi_taxid, taxon_id and taxonomy_id all count.
|
|
658
|
+
const VIS_EXCLUDED_LOCAL_NAME_RE = /(taxonomy|taxon|tax)id$/;
|
|
659
|
+
|
|
660
|
+
function visExcludedRef(ref) {
|
|
661
|
+
if (ref.indexOf(VIS_EXCLUDED_REF_PREFIX) === 0) {
|
|
662
|
+
return true;
|
|
663
|
+
}
|
|
664
|
+
let local = ref.substring(ref.indexOf(':') + 1).toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
665
|
+
return VIS_EXCLUDED_LOCAL_NAME_RE.test(local);
|
|
666
|
+
}
|
|
652
667
|
|
|
653
668
|
// ---- display normalization --------------------------------------------
|
|
654
669
|
//
|
|
@@ -796,6 +811,14 @@
|
|
|
796
811
|
.join(' ');
|
|
797
812
|
}
|
|
798
813
|
|
|
814
|
+
// The display name for a property ref, as the Color-by menu and legends
|
|
815
|
+
// show it: namespace dropped, then prettified. Exported so the node-data
|
|
816
|
+
// dialog names a property the same way the rest of the viewer does.
|
|
817
|
+
forester.propertyDisplayName = function (ref) {
|
|
818
|
+
let local = ref.indexOf(':') >= 0 ? ref.substring(ref.indexOf(':') + 1) : ref;
|
|
819
|
+
return prettifyVisLabel(local);
|
|
820
|
+
};
|
|
821
|
+
|
|
799
822
|
forester.visualizationCandidates = function (tree) {
|
|
800
823
|
let total = 0;
|
|
801
824
|
let stats = Object.create(null); // id -> {kind, ref, label, nodes, values:Set, multi}; null-proto: ids embed file refs
|
|
@@ -832,8 +855,7 @@
|
|
|
832
855
|
if (n.properties) {
|
|
833
856
|
for (let i = 0; i < n.properties.length; ++i) {
|
|
834
857
|
let p = n.properties[i];
|
|
835
|
-
if (p.ref && p.applies_to === 'node'
|
|
836
|
-
&& p.ref.indexOf(VIS_EXCLUDED_REF_PREFIX) !== 0) {
|
|
858
|
+
if (p.ref && p.applies_to === 'node' && !visExcludedRef(p.ref)) {
|
|
837
859
|
add('prop:' + p.ref, 'property', p.ref, null, p.value);
|
|
838
860
|
}
|
|
839
861
|
}
|
|
@@ -1302,6 +1324,17 @@
|
|
|
1302
1324
|
// Whether a tree is worth drawing to scale is a question about the
|
|
1303
1325
|
// majority of its branches, not about whether any branch has a length.
|
|
1304
1326
|
properties.branchesWithPositiveLength = 0;
|
|
1327
|
+
// Branches that carry a length AT ALL -- an explicit zero is a real
|
|
1328
|
+
// measurement, not a missing one, so these are counted separately from
|
|
1329
|
+
// the "positive" tally above. Split internal-vs-all because a missing
|
|
1330
|
+
// length means different things in the two places: an unmeasured TIP
|
|
1331
|
+
// still draws correctly (at its parent), while an unmeasured INTERNAL
|
|
1332
|
+
// branch destroys the scale. All four counts exclude the root, which
|
|
1333
|
+
// has no branch above it.
|
|
1334
|
+
properties.branchCount = 0;
|
|
1335
|
+
properties.branchesWithLength = 0;
|
|
1336
|
+
properties.internalBranchCount = 0;
|
|
1337
|
+
properties.internalBranchesWithLength = 0;
|
|
1305
1338
|
properties.averageBranchLength = 0;
|
|
1306
1339
|
let bl_counter = 0;
|
|
1307
1340
|
let bl_sum = 0;
|
|
@@ -1309,8 +1342,23 @@
|
|
|
1309
1342
|
// exist -- skewing the branch-length fraction the viewer uses to choose
|
|
1310
1343
|
// between a phylogram and a cladogram -- and from phyloXML would take
|
|
1311
1344
|
// the tree's own name for the longest node name.
|
|
1312
|
-
|
|
1345
|
+
let rootNode = realRootOf(tree);
|
|
1346
|
+
forester.preOrderTraversalAll(rootNode, function (n) {
|
|
1313
1347
|
properties.nodeCount += 1;
|
|
1348
|
+
if (n !== rootNode) {
|
|
1349
|
+
let internal = !!(n.children || n._children);
|
|
1350
|
+
let measured = typeof n.branch_length === 'number' && isFinite(n.branch_length);
|
|
1351
|
+
properties.branchCount += 1;
|
|
1352
|
+
if (measured) {
|
|
1353
|
+
properties.branchesWithLength += 1;
|
|
1354
|
+
}
|
|
1355
|
+
if (internal) {
|
|
1356
|
+
properties.internalBranchCount += 1;
|
|
1357
|
+
if (measured) {
|
|
1358
|
+
properties.internalBranchesWithLength += 1;
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1314
1362
|
if (n.name && n.name.length > 0) {
|
|
1315
1363
|
properties.nodeNames = true;
|
|
1316
1364
|
if (n.name.length > properties.longestNodeName) {
|
|
@@ -1916,9 +1964,12 @@
|
|
|
1916
1964
|
if (confidenceValuesAsInternalNames === undefined) {
|
|
1917
1965
|
confidenceValuesAsInternalNames = false;
|
|
1918
1966
|
}
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1967
|
+
// The two options used to be mutually exclusive and throwing. They are
|
|
1968
|
+
// not: brackets are consumed by the tokenizer, bare labels are handled
|
|
1969
|
+
// afterwards, so a file mixing the two dialects is read correctly with
|
|
1970
|
+
// both on. forester-Java never had this guard, so it was never part of
|
|
1971
|
+
// the shared contract -- and throwing on a combination a user
|
|
1972
|
+
// plausibly wants is what forced callers into workarounds.
|
|
1922
1973
|
|
|
1923
1974
|
let ancs = [];
|
|
1924
1975
|
let x = {};
|
|
@@ -3560,6 +3611,117 @@
|
|
|
3560
3611
|
{name: 'Miaolingian', young: 497, old: 506.5, color: '#A6CF86'},
|
|
3561
3612
|
{name: 'Series 2', young: 506.5, old: 521, color: '#99C078'},
|
|
3562
3613
|
{name: 'Terreneuvian', young: 521, old: 538.8, color: '#8CB06C'}
|
|
3614
|
+
],
|
|
3615
|
+
// The 101 ratified Phanerozoic stages, plus the Pridoli: a Series with
|
|
3616
|
+
// no stages of its own, standing in the stage row for its own span
|
|
3617
|
+
// (419.62-422.7 Ma) as on the printed ICS chart -- without it the row
|
|
3618
|
+
// would have a hole there. Same source as every rank above
|
|
3619
|
+
// (Macrostrat international timescale, id 11), which the ranks above
|
|
3620
|
+
// match byte for byte; the desktop generated its table from the same
|
|
3621
|
+
// query, so the two programs band from identical data.
|
|
3622
|
+
age: [
|
|
3623
|
+
{name: 'Meghalayan', young: 0, old: 0.0042, color: '#FEF2E0'},
|
|
3624
|
+
{name: 'Northgrippian', young: 0.0042, old: 0.0082, color: '#FEF2E0'},
|
|
3625
|
+
{name: 'Greenlandian', young: 0.0082, old: 0.0117, color: '#FEF2E0'},
|
|
3626
|
+
{name: 'Late Pleistocene', young: 0.0117, old: 0.129, color: '#FFF2C7'},
|
|
3627
|
+
{name: 'Chibanian', young: 0.129, old: 0.774, color: '#FFF2C7'},
|
|
3628
|
+
{name: 'Calabrian', young: 0.774, old: 1.8, color: '#FFF2C7'},
|
|
3629
|
+
{name: 'Gelasian', young: 1.8, old: 2.58, color: '#FFEDB3'},
|
|
3630
|
+
{name: 'Piacenzian', young: 2.58, old: 3.6, color: '#FFFFBF'},
|
|
3631
|
+
{name: 'Zanclean', young: 3.6, old: 5.333, color: '#FFFFB3'},
|
|
3632
|
+
{name: 'Messinian', young: 5.333, old: 7.246, color: '#FFFF73'},
|
|
3633
|
+
{name: 'Tortonian', young: 7.246, old: 11.63, color: '#FFFF66'},
|
|
3634
|
+
{name: 'Serravallian', young: 11.63, old: 13.82, color: '#FFFF59'},
|
|
3635
|
+
{name: 'Langhian', young: 13.82, old: 15.98, color: '#FFFF4D'},
|
|
3636
|
+
{name: 'Burdigalian', young: 15.98, old: 20.45, color: '#FFFF41'},
|
|
3637
|
+
{name: 'Aquitanian', young: 20.45, old: 23.04, color: '#FFFF33'},
|
|
3638
|
+
{name: 'Chattian', young: 23.04, old: 27.3, color: '#FEE6AA'},
|
|
3639
|
+
{name: 'Rupelian', young: 27.3, old: 33.9, color: '#FED99A'},
|
|
3640
|
+
{name: 'Priabonian', young: 33.9, old: 37.71, color: '#FDCDA1'},
|
|
3641
|
+
{name: 'Bartonian', young: 37.71, old: 41.03, color: '#FDC091'},
|
|
3642
|
+
{name: 'Lutetian', young: 41.03, old: 48.07, color: '#FCB482'},
|
|
3643
|
+
{name: 'Ypresian', young: 48.07, old: 56, color: '#FCA773'},
|
|
3644
|
+
{name: 'Thanetian', young: 56, old: 59.24, color: '#FDBF6F'},
|
|
3645
|
+
{name: 'Selandian', young: 59.24, old: 61.66, color: '#FEBF65'},
|
|
3646
|
+
{name: 'Danian', young: 61.66, old: 66, color: '#FDB462'},
|
|
3647
|
+
{name: 'Maastrichtian', young: 66, old: 72.2, color: '#F2FA8C'},
|
|
3648
|
+
{name: 'Campanian', young: 72.2, old: 83.6, color: '#E6F47F'},
|
|
3649
|
+
{name: 'Santonian', young: 83.6, old: 85.7, color: '#D9EF74'},
|
|
3650
|
+
{name: 'Coniacian', young: 85.7, old: 89.8, color: '#CCE968'},
|
|
3651
|
+
{name: 'Turonian', young: 89.8, old: 93.9, color: '#BFE35D'},
|
|
3652
|
+
{name: 'Cenomanian', young: 93.9, old: 100.5, color: '#B3DE53'},
|
|
3653
|
+
{name: 'Albian', young: 100.5, old: 113.2, color: '#CCEA97'},
|
|
3654
|
+
{name: 'Aptian', young: 113.2, old: 121.4, color: '#BFE48A'},
|
|
3655
|
+
{name: 'Barremian', young: 121.4, old: 125.77, color: '#B3DF7F'},
|
|
3656
|
+
{name: 'Hauterivian', young: 125.77, old: 132.6, color: '#A6D975'},
|
|
3657
|
+
{name: 'Valanginian', young: 132.6, old: 137.05, color: '#99D36A'},
|
|
3658
|
+
{name: 'Berriasian', young: 137.05, old: 143.1, color: '#8CCD60'},
|
|
3659
|
+
{name: 'Tithonian', young: 143.1, old: 149.2, color: '#D9F1F7'},
|
|
3660
|
+
{name: 'Kimmeridgian', young: 149.2, old: 154.8, color: '#CCECF4'},
|
|
3661
|
+
{name: 'Oxfordian', young: 154.8, old: 161.5, color: '#BFE7F1'},
|
|
3662
|
+
{name: 'Callovian', young: 161.5, old: 165.3, color: '#BFE7E5'},
|
|
3663
|
+
{name: 'Bathonian', young: 165.3, old: 168.2, color: '#B3E2E3'},
|
|
3664
|
+
{name: 'Bajocian', young: 168.2, old: 170.9, color: '#A6DDE0'},
|
|
3665
|
+
{name: 'Aalenian', young: 170.9, old: 174.7, color: '#9AD9DD'},
|
|
3666
|
+
{name: 'Toarcian', young: 174.7, old: 184.2, color: '#99CEE3'},
|
|
3667
|
+
{name: 'Pliensbachian', young: 184.2, old: 192.9, color: '#80C5DD'},
|
|
3668
|
+
{name: 'Sinemurian', young: 192.9, old: 199.5, color: '#67BCD8'},
|
|
3669
|
+
{name: 'Hettangian', young: 199.5, old: 201.4, color: '#4EB3D3'},
|
|
3670
|
+
{name: 'Rhaetian', young: 201.4, old: 205.7, color: '#E3B9DB'},
|
|
3671
|
+
{name: 'Norian', young: 205.7, old: 227.3, color: '#D6AAD3'},
|
|
3672
|
+
{name: 'Carnian', young: 227.3, old: 237, color: '#C99BCB'},
|
|
3673
|
+
{name: 'Ladinian', young: 237, old: 241.464, color: '#C983BF'},
|
|
3674
|
+
{name: 'Anisian', young: 241.464, old: 246.7, color: '#BC75B7'},
|
|
3675
|
+
{name: 'Olenekian', young: 246.7, old: 249.9, color: '#B051A5'},
|
|
3676
|
+
{name: 'Induan', young: 249.9, old: 251.902, color: '#A4469F'},
|
|
3677
|
+
{name: 'Changhsingian', young: 251.902, old: 254.14, color: '#FCC0B2'},
|
|
3678
|
+
{name: 'Wuchiapingian', young: 254.14, old: 259.51, color: '#FCB4A2'},
|
|
3679
|
+
{name: 'Capitanian', young: 259.51, old: 264.28, color: '#FB9A85'},
|
|
3680
|
+
{name: 'Wordian', young: 264.28, old: 266.9, color: '#FB8D76'},
|
|
3681
|
+
{name: 'Roadian', young: 266.9, old: 274.4, color: '#FB8069'},
|
|
3682
|
+
{name: 'Kungurian', young: 274.4, old: 283.3, color: '#E38776'},
|
|
3683
|
+
{name: 'Artinskian', young: 283.3, old: 290.1, color: '#E37B68'},
|
|
3684
|
+
{name: 'Sakmarian', young: 290.1, old: 293.52, color: '#E36F5C'},
|
|
3685
|
+
{name: 'Asselian', young: 293.52, old: 298.9, color: '#E36350'},
|
|
3686
|
+
{name: 'Gzhelian', young: 298.9, old: 303.7, color: '#CCD4C7'},
|
|
3687
|
+
{name: 'Kasimovian', young: 303.7, old: 307, color: '#BFD0C5'},
|
|
3688
|
+
{name: 'Moscovian', young: 307, old: 315.2, color: '#C7CBB9'},
|
|
3689
|
+
{name: 'Bashkirian', young: 315.2, old: 323.4, color: '#99C2B5'},
|
|
3690
|
+
{name: 'Serpukhovian', young: 323.4, old: 330.3, color: '#BFC26B'},
|
|
3691
|
+
{name: 'Visean', young: 330.3, old: 346.7, color: '#A6B96C'},
|
|
3692
|
+
{name: 'Tournaisian', young: 346.7, old: 358.86, color: '#8CB06C'},
|
|
3693
|
+
{name: 'Famennian', young: 358.86, old: 372.15, color: '#F2EDC5'},
|
|
3694
|
+
{name: 'Frasnian', young: 372.15, old: 382.31, color: '#F2EDAD'},
|
|
3695
|
+
{name: 'Givetian', young: 382.31, old: 387.95, color: '#F1E185'},
|
|
3696
|
+
{name: 'Eifelian', young: 387.95, old: 393.47, color: '#F1D576'},
|
|
3697
|
+
{name: 'Emsian', young: 393.47, old: 410.62, color: '#E5D075'},
|
|
3698
|
+
{name: 'Pragian', young: 410.62, old: 413.02, color: '#E5C468'},
|
|
3699
|
+
{name: 'Lochkovian', young: 413.02, old: 419.62, color: '#E5B75A'},
|
|
3700
|
+
{name: 'Pridoli', young: 419.62, old: 422.7, color: '#E6F5E1'},
|
|
3701
|
+
{name: 'Ludfordian', young: 422.7, old: 425, color: '#D9F0DF'},
|
|
3702
|
+
{name: 'Gorstian', young: 425, old: 426.7, color: '#CCECDD'},
|
|
3703
|
+
{name: 'Homerian', young: 426.7, old: 430.6, color: '#CCEBD1'},
|
|
3704
|
+
{name: 'Sheinwoodian', young: 430.6, old: 432.9, color: '#BFE6C3'},
|
|
3705
|
+
{name: 'Telychian', young: 432.9, old: 438.6, color: '#BFE6CF'},
|
|
3706
|
+
{name: 'Aeronian', young: 438.6, old: 440.5, color: '#B3E1C2'},
|
|
3707
|
+
{name: 'Rhuddanian', young: 440.5, old: 443.1, color: '#A6DCB5'},
|
|
3708
|
+
{name: 'Hirnantian', young: 443.1, old: 445.2, color: '#A6DBAB'},
|
|
3709
|
+
{name: 'Katian', young: 445.2, old: 452.8, color: '#99D69F'},
|
|
3710
|
+
{name: 'Sandbian', young: 452.8, old: 458.2, color: '#8CD094'},
|
|
3711
|
+
{name: 'Darriwilian', young: 458.2, old: 469.4, color: '#74C69C'},
|
|
3712
|
+
{name: 'Dapingian', young: 469.4, old: 471.3, color: '#66C092'},
|
|
3713
|
+
{name: 'Floian', young: 471.3, old: 477.1, color: '#41B087'},
|
|
3714
|
+
{name: 'Tremadocian', young: 477.1, old: 486.85, color: '#33A97E'},
|
|
3715
|
+
{name: 'Stage 10', young: 486.85, old: 491, color: '#E6F5C9'},
|
|
3716
|
+
{name: 'Jiangshanian', young: 491, old: 494.2, color: '#D9F0BB'},
|
|
3717
|
+
{name: 'Paibian', young: 494.2, old: 497, color: '#CCEBAE'},
|
|
3718
|
+
{name: 'Guzhangian', young: 497, old: 500.5, color: '#CCDFAA'},
|
|
3719
|
+
{name: 'Drumian', young: 500.5, old: 504.5, color: '#BFD99D'},
|
|
3720
|
+
{name: 'Wuliuan', young: 504.5, old: 506.5, color: '#B3D492'},
|
|
3721
|
+
{name: 'Stage 4', young: 506.5, old: 514.5, color: '#B3CA8E'},
|
|
3722
|
+
{name: 'Stage 3', young: 514.5, old: 521, color: '#A6C583'},
|
|
3723
|
+
{name: 'Stage 2', young: 521, old: 529, color: '#A6BA80'},
|
|
3724
|
+
{name: 'Fortunian', young: 529, old: 538.8, color: '#99B575'}
|
|
3563
3725
|
]
|
|
3564
3726
|
};
|
|
3565
3727
|
|
|
@@ -3573,10 +3735,26 @@
|
|
|
3573
3735
|
return ivs.length > 0 ? ivs[ivs.length - 1].old : 0;
|
|
3574
3736
|
};
|
|
3575
3737
|
|
|
3576
|
-
// The [coarse, fine] rank pair for
|
|
3577
|
-
//
|
|
3578
|
-
//
|
|
3579
|
-
|
|
3738
|
+
// The [coarse, fine] rank pair for the span [youngMa, oldMa] a tree
|
|
3739
|
+
// actually occupies -- the youngest tip to the root, not 0 to the root,
|
|
3740
|
+
// so a fossil-only clade bands on its own window rather than on the
|
|
3741
|
+
// whole stretch back from the present.
|
|
3742
|
+
//
|
|
3743
|
+
// It adapts FINER as well as coarser. A narrow Phanerozoic window --
|
|
3744
|
+
// one or two Series -- bands Series over STAGE (the ICS ages), which is
|
|
3745
|
+
// what a 66-100 Ma dinosaur tree needs to say anything at all: over
|
|
3746
|
+
// Period/Epoch it would read "Cretaceous / Late Cretaceous" and nothing
|
|
3747
|
+
// more. Wider than two Series the stages would be slivers, so the
|
|
3748
|
+
// existing ladder takes over: the finest pair that still fully covers the
|
|
3749
|
+
// range, so a deep tree never shows blank band segments. Shared rule with
|
|
3750
|
+
// the desktop's GeologicTimeScale.bandRanks -- change both or neither.
|
|
3751
|
+
forester.geoBandRanks = function (youngMa, oldMa) {
|
|
3752
|
+
if (oldMa <= forester.geoCoverage('age')) { // stages are Phanerozoic-only
|
|
3753
|
+
let series = forester.geoOverlapping('epoch', youngMa, oldMa).length;
|
|
3754
|
+
if (series > 0 && series <= 2) {
|
|
3755
|
+
return ['epoch', 'age'];
|
|
3756
|
+
}
|
|
3757
|
+
}
|
|
3580
3758
|
if (oldMa <= forester.geoCoverage('epoch')) {
|
|
3581
3759
|
return ['period', 'epoch'];
|
|
3582
3760
|
}
|
|
@@ -4075,6 +4253,114 @@
|
|
|
4075
4253
|
// extractors: {name, taxonomy, sequence}, each a function(externalNode)
|
|
4076
4254
|
// returning the text that field would contribute to the node's label, or
|
|
4077
4255
|
// null / '' when it contributes nothing.
|
|
4256
|
+
/**
|
|
4257
|
+
* Reads bare numeric internal node labels as confidence values -- the
|
|
4258
|
+
* dialect nearly every tree writer emits (RAxML, IQ-TREE, FastTree, MEGA,
|
|
4259
|
+
* BV-BRC) writes branch support as `)100:0.05`, which the New Hampshire
|
|
4260
|
+
* grammar makes an internal node NAME.
|
|
4261
|
+
*
|
|
4262
|
+
* The rule is shared with the desktop Java Archaeopteryx; change it only
|
|
4263
|
+
* together with that implementation.
|
|
4264
|
+
*
|
|
4265
|
+
* mode 'auto' (the default) is ALL-OR-NOTHING: every candidate label must
|
|
4266
|
+
* look like support, so a tree of real clade names is never touched.
|
|
4267
|
+
* mode 'confidence' is PER-NODE and unbounded: every numeric label is
|
|
4268
|
+
* promoted whatever its value, which is how a user forces a mixed tree
|
|
4269
|
+
* (some clade names, some support) or an out-of-range scale. mode 'label'
|
|
4270
|
+
* does nothing -- the labels stay names.
|
|
4271
|
+
*
|
|
4272
|
+
* The ROOT is never promoted in any mode: a confidence belongs to the
|
|
4273
|
+
* branch ABOVE a node and the root has none, so a numeric label there is
|
|
4274
|
+
* almost always the tree's name -- `)99;` would otherwise lose it while
|
|
4275
|
+
* `)MyTree;` kept it.
|
|
4276
|
+
*
|
|
4277
|
+
* Promotion MOVES: the label is cleared as the confidence is added, or the
|
|
4278
|
+
* value would be drawn twice.
|
|
4279
|
+
*
|
|
4280
|
+
* Pure apart from the mutation it is asked to make, and returns the number
|
|
4281
|
+
* of labels promoted so the CALLER can react (the viewer switches its
|
|
4282
|
+
* confidence display on, so support values appear rather than names
|
|
4283
|
+
* silently vanishing). Deliberately does not touch display state itself.
|
|
4284
|
+
*
|
|
4285
|
+
* @param phy - the phylogeny
|
|
4286
|
+
* @param mode - 'auto' | 'confidence' | 'label'
|
|
4287
|
+
* @returns {number} how many internal labels became confidences
|
|
4288
|
+
*/
|
|
4289
|
+
forester.promoteInternalLabelsToConfidence = function (phy, mode) {
|
|
4290
|
+
if (mode === 'label') {
|
|
4291
|
+
return 0;
|
|
4292
|
+
}
|
|
4293
|
+
if (mode !== 'confidence') {
|
|
4294
|
+
mode = 'auto';
|
|
4295
|
+
}
|
|
4296
|
+
let root = forester.getTreeRoot(phy);
|
|
4297
|
+
let candidates = [];
|
|
4298
|
+
let anyConfidence = false;
|
|
4299
|
+
forester.preOrderTraversalAll(root, function (n) {
|
|
4300
|
+
if (n === root || !(n.children || n._children)) {
|
|
4301
|
+
return;
|
|
4302
|
+
}
|
|
4303
|
+
if (n.confidences && n.confidences.length > 0) {
|
|
4304
|
+
anyConfidence = true;
|
|
4305
|
+
}
|
|
4306
|
+
if (typeof n.name === 'string' && n.name.length > 0) {
|
|
4307
|
+
candidates.push(n);
|
|
4308
|
+
}
|
|
4309
|
+
});
|
|
4310
|
+
if (candidates.length < 1) {
|
|
4311
|
+
return 0;
|
|
4312
|
+
}
|
|
4313
|
+
let numeric = candidates.map(function (n) {
|
|
4314
|
+
let v = Number(n.name);
|
|
4315
|
+
return (n.name.trim().length > 0 && isFinite(v)) ? v : null;
|
|
4316
|
+
});
|
|
4317
|
+
let chosen;
|
|
4318
|
+
if (mode === 'confidence') {
|
|
4319
|
+
// Per node: promote what parses, leave the rest alone.
|
|
4320
|
+
chosen = candidates.filter(function (n, i) {
|
|
4321
|
+
return numeric[i] !== null;
|
|
4322
|
+
});
|
|
4323
|
+
} else {
|
|
4324
|
+
// A file mixing `)[100]:` and `)95:` leaves the bracketed nodes
|
|
4325
|
+
// with no label at all, so one bare numeric label beside real
|
|
4326
|
+
// confidences is corroborated rather than lonely. Without this a
|
|
4327
|
+
// mixed-dialect tree ships some confidences and one stray name.
|
|
4328
|
+
let need = anyConfidence ? 1 : 2;
|
|
4329
|
+
if (candidates.length < need) {
|
|
4330
|
+
return 0;
|
|
4331
|
+
}
|
|
4332
|
+
if (numeric.some(function (v) {
|
|
4333
|
+
// All-or-nothing: one real clade name and the tree is names.
|
|
4334
|
+
// The range covers every scale in use -- 0-1 posteriors,
|
|
4335
|
+
// 0-100 bootstrap, 0-1000 MrBayes -- and excludes identifiers
|
|
4336
|
+
// such as NCBI taxids that would read as "support 9606".
|
|
4337
|
+
return v === null || v < 0 || v > 1000;
|
|
4338
|
+
})) {
|
|
4339
|
+
return 0;
|
|
4340
|
+
}
|
|
4341
|
+
// Clade numbering is overwhelmingly 1..N, each exactly once. A
|
|
4342
|
+
// real support tree containing every integer 1..N once and nothing
|
|
4343
|
+
// else is essentially impossible at n >= 3, and without this guard
|
|
4344
|
+
// a clade-numbered tree silently becomes "1%, 2%, 3% support".
|
|
4345
|
+
let distinct = new Set(numeric);
|
|
4346
|
+
let min = Math.min.apply(null, numeric);
|
|
4347
|
+
let max = Math.max.apply(null, numeric);
|
|
4348
|
+
if (distinct.size === numeric.length && min === 1 && max === numeric.length) {
|
|
4349
|
+
return 0;
|
|
4350
|
+
}
|
|
4351
|
+
chosen = candidates;
|
|
4352
|
+
}
|
|
4353
|
+
chosen.forEach(function (n) {
|
|
4354
|
+
let v = Number(n.name);
|
|
4355
|
+
// Type deliberately left unset: the value cannot distinguish
|
|
4356
|
+
// bootstrap from posterior from aLRT from TBE, and a wrong type
|
|
4357
|
+
// printed beside a number in a published figure is worse than none.
|
|
4358
|
+
pushConfidence(n, v, '');
|
|
4359
|
+
n.name = '';
|
|
4360
|
+
});
|
|
4361
|
+
return chosen.length;
|
|
4362
|
+
};
|
|
4363
|
+
|
|
4078
4364
|
forester.suggestLabelFields = function (root, extractors) {
|
|
4079
4365
|
const FIELDS = ['name', 'taxonomy', 'sequence'];
|
|
4080
4366
|
const CONTAINMENT_MIN = 0.9;
|
package/package.json
CHANGED