archaeopteryx 1.8.1 → 2.3.2

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/forester.js CHANGED
@@ -1,1944 +1,1749 @@
1
- /**
2
- * Copyright (C) 2019 Christian M. Zmasek
3
- * Copyright (C) 2019 Yun Zhang
4
- * Copyright (C) 2019 J. Craig Venter Institute
5
- * All rights reserved
6
- *
7
- * This library is free software; you can redistribute it and/or
8
- * modify it under the terms of the GNU Lesser General Public
9
- * License as published by the Free Software Foundation; either
10
- * version 2.1 of the License, or (at your option) any later version.
11
- *
12
- * This library is distributed in the hope that it will be useful,
13
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15
- * Lesser General Public License for more details.
16
- *
17
- * You should have received a copy of the GNU Lesser General Public
18
- * License along with this library; if not, write to the Free Software
19
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
20
- *
21
- */
22
-
23
- // v 1.8.1
24
- // 2019-05-16
25
- //
26
- // forester.js is a general suite for dealing with phylogenetic trees.
27
- //
28
- // forester.js is used by Archaeopteryx.js:
29
- // https://github.com/cmzmasek/archaeopteryx-js
30
- // https://www.npmjs.com/package/archaeopteryx
31
- //
32
- // Availability:
33
- // https://github.com/cmzmasek/archaeopteryx-js
34
- // https://www.npmjs.com/package/archaeopteryx
35
- //
36
- // Dependencies: none
37
- //
38
- //
39
- // In the following is a basic example shows how to parse a New Hampshire formatted String
40
- // into to a object representing a phylogenetic tree.
41
- // Followed by pre- and post-order traversal,
42
- // and writing back to a New Hampshire formatted String.
43
- //
44
- // Change './forester' to 'forester' if you use this code outside of this package
45
- //
46
- // var forester = require('./forester').forester;
47
- //
48
- // var newHampshireFormattedString = "(((a:1,b:1,c:1)N:2,(d:1,e:1)M:4)O:4,f:1)R:1;";
49
- // var phylogeneticTree = forester.parseNewHampshire(newHampshireFormattedString);
50
- //
51
- // console.log('Pre-order traversal:');
52
- // forester.preOrderTraversalAll(forester.getTreeRoot(phylogeneticTree), function (n) {
53
- // console.log(n.name + ':' + n.branch_length);
54
- // });
55
- //
56
- // console.log('Post-order traversal:');
57
- // forester.postOrderTraversalAll(forester.getTreeRoot(phylogeneticTree), function (n) {
58
- // console.log(n.name + ':' + n.branch_length);
59
- // });
60
- //
61
- // console.log('In New Hampshire format:');
62
- // var nh = forester.toNewHampshire(phylogeneticTree);
63
- // console.log(nh);
64
-
65
- (function forester() {
66
-
67
- "use strict";
68
-
69
- var BRANCH_EVENT_REF = 'aptx:branch_event';
70
- var BRANCH_EVENT_DATATYPE = 'xsd:string';
71
- var BRANCH_EVENT_APPLIES_TO = 'parent_branch';
72
- var NH_FORMAT_ERR = 'New Hampshire (Newick) format error: ';
73
-
74
- var NUMBERS_ONLY_PATTERN = /^[-+]?[0-9\\.]+$/;
75
-
76
- var MSA_RESIDUE_SORT_MAP = new Map();
77
- MSA_RESIDUE_SORT_MAP.set('A', 0);
78
- MSA_RESIDUE_SORT_MAP.set('C', 1);
79
- MSA_RESIDUE_SORT_MAP.set('D', 2);
80
- MSA_RESIDUE_SORT_MAP.set('E', 3);
81
- MSA_RESIDUE_SORT_MAP.set('F', 4);
82
- MSA_RESIDUE_SORT_MAP.set('G', 5);
83
- MSA_RESIDUE_SORT_MAP.set('H', 6);
84
- MSA_RESIDUE_SORT_MAP.set('I', 7);
85
- MSA_RESIDUE_SORT_MAP.set('K', 8);
86
- MSA_RESIDUE_SORT_MAP.set('L', 9);
87
- MSA_RESIDUE_SORT_MAP.set('M', 10);
88
- MSA_RESIDUE_SORT_MAP.set('N', 11);
89
- MSA_RESIDUE_SORT_MAP.set('P', 12);
90
- MSA_RESIDUE_SORT_MAP.set('Q', 13);
91
- MSA_RESIDUE_SORT_MAP.set('R', 14);
92
- MSA_RESIDUE_SORT_MAP.set('S', 15);
93
- MSA_RESIDUE_SORT_MAP.set('T', 16);
94
- MSA_RESIDUE_SORT_MAP.set('U', 17);// Uracil
95
- MSA_RESIDUE_SORT_MAP.set('V', 18);
96
- MSA_RESIDUE_SORT_MAP.set('W', 19);
97
- MSA_RESIDUE_SORT_MAP.set('Y', 20);
98
- MSA_RESIDUE_SORT_MAP.set('B', 21);// Asparagine or aspartic acid
99
- MSA_RESIDUE_SORT_MAP.set('Z', 22);// Glutamine or glutamic acid
100
- MSA_RESIDUE_SORT_MAP.set('X', 23);
101
- MSA_RESIDUE_SORT_MAP.set('?', 24);
102
- MSA_RESIDUE_SORT_MAP.set('-', 25);
103
- MSA_RESIDUE_SORT_MAP.set('.', 26);
104
-
105
-
106
- /**
107
- * Sets links to parent nodes for all nodes in a
108
- * phyloXML-based tree object
109
- *
110
- * @param phy - A phyloXML-based tree object.
111
- */
112
- forester.addParents = function (phy) {
113
- if (phy.children) {
114
- for (var i = phy.children.length - 1; i >= 0; --i) {
115
- var c = phy.children[i];
116
- c.parent = phy;
117
- forester.addParents(c);
118
- }
119
- }
120
- };
121
-
122
- /**
123
- * Returns the real root node of a
124
- * phyloXML-based tree object.
125
- * Precondition: needs to have parents set.
126
- *
127
- * @param phy - A phyloXML-based tree object or node.
128
- * @returns {*} - The real tree root node.
129
- */
130
- forester.getTreeRoot = function (phy) {
131
- var root = phy;
132
- if (!root.parent && root.children && root.children.length === 1) {
133
- root = root.children[0];
134
- }
135
- while (root.parent && root.parent.parent) {
136
- root = root.parent;
137
- }
138
- return root;
139
- };
140
-
141
- /**
142
- * Visits all non-collapsed child nodes of a node
143
- * while applying a function in pre-order.
144
- *
145
- * @param node - The root of the subtree to traverse.
146
- * @param fn - The function to apply.
147
- */
148
- forester.preOrderTraversal = function (node, fn) {
149
- fn(node);
150
- if (node.children) {
151
- for (var i = node.children.length - 1; i >= 0; --i) {
152
- forester.preOrderTraversal(node.children[i], fn);
153
- }
154
- }
155
- };
156
-
157
- /**
158
- * Visits all child nodes of a node
159
- * while applying a function in pre-order.
160
- *
161
- * @param node - The root of the subtree to traverse.
162
- * @param fn - The function to apply.
163
- */
164
- forester.preOrderTraversalAll = function (node, fn) {
165
- fn(node);
166
- if (node.children) {
167
- for (var i = node.children.length - 1; i >= 0; --i) {
168
- forester.preOrderTraversalAll(node.children[i], fn);
169
- }
170
- }
171
- else if (node._children) {
172
- for (var ii = node._children.length - 1; ii >= 0; --ii) {
173
- forester.preOrderTraversalAll(node._children[ii], fn);
174
- }
175
- }
176
- };
177
-
178
- forester.postOrderTraversalAll = function (node, fn) {
179
- if (node.children) {
180
- var l = node.children.length;
181
- for (var i = 0; i < l; ++i) {
182
- forester.postOrderTraversalAll(node.children[i], fn);
183
- }
184
- }
185
- else if (node._children) {
186
- var ll = node._children.length;
187
- for (var ii = 0; ii < ll; ++ii) {
188
- forester.postOrderTraversalAll(node._children[ii], fn);
189
- }
190
- }
191
- fn(node);
192
- };
193
-
194
-
195
- forester.findByNodeName = function (node, name) {
196
- var found = [];
197
- forester.preOrderTraversalAll(node, function (n) {
198
- if (n.name === name) {
199
- found.push(n);
200
- }
201
- });
202
- return found;
203
- };
204
-
205
- forester.findByTaxonomyCode = function (node, code) {
206
- var found = [];
207
- forester.preOrderTraversalAll(node, function (n) {
208
- if (n.taxonomies && n.taxonomies.length > 0 && n.taxonomies[0].code === code) {
209
- found.push(n);
210
- }
211
- });
212
- return found;
213
- };
214
-
215
- forester.findByTaxonomyScientificName = function (node, scientificName) {
216
- var found = [];
217
- forester.preOrderTraversalAll(node, function (n) {
218
- if (n.taxonomies && n.taxonomies.length > 0 && n.taxonomies[0].scientific_name === scientificName) {
219
- found.push(n);
220
- }
221
- });
222
- return found;
223
- };
224
-
225
- /**
226
- * To delete a sub-tree or external node.
227
- *
228
- * @param phy
229
- * @param nodeToDelete
230
- */
231
- forester.deleteSubtree = function (phy, nodeToDelete) {
232
- if (!phy) {
233
- throw ( "cannot delete null tree" );
234
- }
235
- if (!nodeToDelete) {
236
- throw ( "cannot delete null node" );
237
- }
238
- if (!nodeToDelete.parent || !nodeToDelete.parent.parent) {
239
- throw ( "cannot delete root" );
240
- }
241
- if (!nodeToDelete.parent.parent.parent) {
242
- throw ( "cannot delete direct child of root" );
243
- }
244
-
245
- var p = nodeToDelete.parent;
246
-
247
- if ((p.children) && (p.children.length > 1)) {
248
- var i = p.children.indexOf(nodeToDelete);
249
- if (i !== -1) {
250
- p.children.splice(i, 1);
251
- }
252
- }
253
- if ((p._children) && (p._children.length > 1)) {
254
- var ii = p._children.indexOf(nodeToDelete);
255
- if (ii !== -1) {
256
- p._children.splice(ii, 1);
257
- }
258
- }
259
-
260
- if (p.children.length === 1) {
261
- var pp = p.parent;
262
- var cni = forester.getChildNodeIndex(pp, p);
263
- if ((cni < 0) || (cni > ( pp.children.length - 1 ) )) {
264
- throw ( "this should never have happened, child node index = " + cni );
265
- }
266
- var x = p.children[0];
267
- var nbl = undefined;
268
- if (x.branch_length || p.branch_length) {
269
- nbl = ( x.branch_length > 0 ? x.branch_length : 0 ) + ( p.branch_length > 0 ? p.branch_length : 0 );
270
- }
271
- x.parent = pp;
272
- pp.children[cni] = x;
273
- x.branch_length = nbl;
274
- }
275
-
276
- };
277
-
278
-
279
- /**
280
- * To re-root a tree object.
281
- *
282
- * @param phy - The tree to be re-rooted.
283
- * @param node - The node on where to place the new root (on its parent branch).
284
- * @param branchLength - The branch length to use if new root is not placed in the middle (if
285
- * non-negative).
286
- */
287
- forester.reRoot = function (phy, node, branchLength) {
288
- if (!phy) {
289
- throw ( "cannot re-root null tree" );
290
- }
291
- if (!node) {
292
- throw ( "cannot re-root on null node" );
293
- }
294
- if (!branchLength) {
295
- branchLength = -1;
296
- }
297
- if (forester.isString(node)) {
298
- var nodes = forester.findByNodeName(phy, node);
299
- if (nodes.length > 1) {
300
- throw ("node name '" + node + "' is not unique");
301
- }
302
- else if (nodes.length < 1) {
303
- throw ("node name '" + node + "' is not found");
304
- }
305
- node = nodes[0];
306
- }
307
-
308
- phy.rooted = true;
309
- var root = forester.getTreeRoot(phy);
310
-
311
- if (!node.parent || !node.parent.parent) {
312
- //do noting
313
- }
314
- else if (!node.parent.parent.parent) {
315
- if (( node.parent.children.length === 2 ) && ( branchLength >= 0 )) {
316
- var d = node.parent.children[0].branch_length
317
- + node.parent.children[1].branch_length;
318
- var other;
319
- if (node.parent.children[0] === node) {
320
- other = node.parent.children[1];
321
- }
322
- else {
323
- other = node.parent.children[0];
324
- }
325
- node.branch_length = branchLength;
326
- var dm = d - branchLength;
327
- if (dm >= 0) {
328
- other.branch_length = dm;
329
- }
330
- else {
331
- other.branch_length = 0;
332
- }
333
- }
334
- if (node.parent.children.length > 2) {
335
- var index = forester.getChildNodeIndex(node.parent, node);
336
- var dn = node.branch_length;
337
- var prev_root = root;
338
- prev_root.children.splice(index, 1);
339
- var nr = {};
340
- nr.children = [];
341
- forester.setChildNode(nr, 0, node);
342
- forester.setChildNode(nr, 1, prev_root);
343
-
344
- forester.copyBranchData(node, prev_root);
345
-
346
- phy.children[0] = nr;
347
- nr.parent = phy;
348
- if (branchLength >= 0) {
349
- node.branch_length = branchLength;
350
- var dnmp = dn - branchLength;
351
- if (dnmp >= 0) {
352
- prev_root.branch_length = dnmp;
353
- }
354
- else {
355
- prev_root.branch_length = 0;
356
- }
357
- }
358
- else {
359
- if (dn >= 0) {
360
- var dn2 = dn / 2.0;
361
- node.branch_length = dn2;
362
- prev_root.branch_length = dn2;
363
- }
364
- }
365
- }
366
- }
367
- else {
368
- var a = node;
369
- var new_root = {};
370
- var distance1;
371
- var distance2 = 0.0;
372
- var branch_data_1;
373
- var branch_data_2 = null;
374
- var b = a.parent;
375
- var c = b.parent;
376
-
377
- new_root.children = [];
378
- forester.setChildNode(new_root, 0, a);
379
- forester.setChildNode(new_root, 1, b);
380
-
381
- distance1 = c.branch_length;
382
-
383
- branch_data_1 = forester.getBranchData(c);
384
-
385
- c.branch_length = b.branch_length;
386
-
387
- forester.copyBranchData(b, c);
388
- forester.copyBranchData(a, b);
389
-
390
- // New root is always placed in the middle of the branch:
391
- if (!a.branch_length) {
392
- b.branch_length = undefined;
393
- }
394
- else {
395
- if (branchLength >= 0.0) {
396
- var diff = a.branch_length - branchLength;
397
- a.branch_length = branchLength;
398
- b.branch_length = ( diff >= 0.0 ? diff : 0.0 );
399
- }
400
- else {
401
- var d2 = a.branch_length / 2.0;
402
- a.branch_length = d2;
403
- b.branch_length = d2;
404
- }
405
- }
406
- setChildNodeOnly(b, forester.getChildNodeIndex(b, a), c);
407
- // moving to the old root, swapping references:
408
- while (c.parent.parent) {
409
- a = b;
410
- b = c;
411
- c = c.parent;
412
- setChildNodeOnly(b, forester.getChildNodeIndex(b, a), c);
413
- b.parent = a;
414
- distance2 = c.branch_length;
415
- branch_data_2 = forester.getBranchData(c);
416
- c.branch_length = distance1;
417
- forester.setBranchData(c, branch_data_1);
418
- distance1 = distance2;
419
- branch_data_1 = branch_data_2;
420
- }
421
- // removing the old root:
422
- if (c.children.length == 2) {
423
- var node2 = c.children[1 - forester.getChildNodeIndex(c, b)];
424
- node2.parent = b;
425
- if (( !c.branch_length )
426
- && ( !node2.branch_length )) {
427
- node2.branch_length = undefined;
428
- }
429
- else {
430
- node2.branch_length = ( c.branch_length >= 0.0 ? c.branch_length : 0.0 )
431
- + ( node2.branch_length >= 0.0 ? node2.branch_length : 0.0 );
432
- }
433
- var cbd = forester.getBranchData(c);
434
- if (cbd) {
435
- forester.setBranchData(node2, cbd);
436
- }
437
- var l = b.children.length;
438
- for (var i = 0; i < l; ++i) {
439
- if (b.children[i] === c) {
440
- setChildNodeOnly(b, i, node2);
441
- break;
442
- }
443
- }
444
- }
445
- else {
446
- c.parent = b;
447
- forester.removeChildNode(c, forester.getChildNodeIndex(c, b));
448
- }
449
- phy.children[0] = new_root;
450
- new_root.parent = phy;
451
- forester.addParents(phy);
452
- }
453
-
454
- function setChildNodeOnly(parentNode, i, node) {
455
- if (parentNode.children.length <= i) {
456
- parentNode.children.push(node);
457
- }
458
- else {
459
- parentNode.children[i] = node;
460
- }
461
- }
462
- };
463
-
464
- forester.midpointRoot = function (phy) {
465
- var root = forester.getTreeRoot(phy);
466
- var extNodes = forester.getAllExternalNodes(root);
467
- if (( extNodes.length < 2 ) || ( forester.calcMaxBranchLength(root) <= 0 )) {
468
- return;
469
- }
470
- var counter = 0;
471
- var totalNodes = forester.getAllNodes(phy).length;
472
- while (true) {
473
- if (++counter > (totalNodes + 1)) {
474
- throw( 'this should not have happened: midpoint rooting does not converge' );
475
- }
476
- var a = null;
477
- var da = 0;
478
- var db = 0;
479
- var cl = forester.getTreeRoot(phy).children.length;
480
- for (var i = 0; i < cl; ++i) {
481
- var f = forester.getFurthestDescendant(forester.getTreeRoot(phy).children[i]);
482
- var df = forester.getDistance(f, forester.getTreeRoot(phy));
483
- if (df > 0) {
484
- if (df > da) {
485
- db = da;
486
- da = df;
487
- a = f;
488
- }
489
- else if (df > db) {
490
- db = df;
491
- }
492
- }
493
- }
494
- var diff = da - db;
495
- if (diff < 0.0001) {
496
- break;
497
- }
498
- var x = da - ( diff / 2.0 );
499
- while (( x > a.branch_length ) && a.parent) {
500
- x -= ( a.branch_length > 0 ? a.branch_length : 0 );
501
- a = a.parent;
502
- }
503
- forester.reRoot(phy, a, x);
504
- }
505
- };
506
-
507
- forester.getFurthestDescendant = function (node) {
508
- var children = forester.getAllExternalNodes(node);
509
- var farthest = null;
510
- var longest = -1000000;
511
- var l = children.length;
512
- for (var i = 0; i < l; ++i) {
513
- var dist = forester.getDistance(children[i], node);
514
- if (dist > longest) {
515
- farthest = children[i];
516
- longest = dist;
517
- }
518
- }
519
- return farthest;
520
- };
521
-
522
- /**
523
- * Calculates the distance between PhylogenyNodes n1 and n2.
524
- * PRECONDITION: n1 is a descendant of n2.
525
- *
526
- * @param n1 a descendant of n2
527
- * @param n2
528
- * @returns {number} distance between n1 and n2
529
- */
530
- forester.getDistance = function (n1, n2) {
531
- var d = 0.0;
532
- while (n1 !== n2) {
533
- if (n1.branch_length > 0.0) {
534
- d += n1.branch_length;
535
- }
536
- n1 = n1.parent;
537
- }
538
- return d;
539
- };
540
-
541
- forester.removeChildNode = function (parentNode, i) {
542
- if (!parentNode.children) {
543
- throw ( "cannot remove the child node for a external node" );
544
- }
545
- if (( i >= parentNode.children.length ) || ( i < 0 )) {
546
- throw ( "attempt to get child node " + i + " of a node with "
547
- + parentNode.children.length + " child nodes." );
548
- }
549
- parentNode.children[i].parent = undefined;
550
- parentNode.children.splice(i, 1);
551
- };
552
-
553
-
554
- /**
555
- * Inserts node node at the specified position i into the list of
556
- * child nodes of parentNode. This does not allow null slots in the list of child nodes:
557
- * If i is larger than the number of child nodes, node is just added to the
558
- * list, not placed at index i.
559
- */
560
- forester.setChildNode = function (parentNode, i, node) {
561
- node.parent = parentNode;
562
- if (parentNode.children.length <= i) {
563
- parentNode.children.push(node);
564
- }
565
- else {
566
- parentNode.children[i] = node;
567
- }
568
- };
569
-
570
-
571
- forester.getBranchData = function (node) {
572
- var branchData = null;
573
- if (node.width || node.color || node.confidences) {
574
- branchData = {};
575
- branchData.width = node.width;
576
- branchData.color = node.color;
577
- branchData.confidences = node.confidences;
578
- }
579
- return branchData;
580
- };
581
-
582
-
583
- forester.setBranchData = function (node, branchData) {
584
- if (branchData) {
585
- node.width = branchData.width;
586
- node.color = branchData.color;
587
- node.confidences = branchData.confidences;
588
- }
589
- };
590
-
591
- forester.unCollapseAll = function (node) {
592
- forester.preOrderTraversal(node, function (d) {
593
- if (d._children) {
594
- d.children = d._children;
595
- d._children = null;
596
- }
597
- });
598
- };
599
-
600
- forester.copyBranchData = function (nodeFrom, nodeTo) {
601
- nodeTo.width = nodeFrom.width;
602
- nodeTo.color = nodeFrom.color;
603
- nodeTo.confidences = nodeFrom.confidences;
604
- };
605
-
606
-
607
- forester.getChildNodeIndex = function (parentNode, childNode) {
608
- if (!parentNode) {
609
- throw ( "cannot get the child index for a root node" );
610
- }
611
- var c = parentNode.children.length;
612
- for (var i = 0; i < c; ++i) {
613
- if (parentNode.children[i] === childNode) {
614
- return i;
615
- }
616
- }
617
- throw ( "unexpected exception: Could not determine the child index for a node" );
618
- };
619
-
620
-
621
- forester.getChildren = function (node) {
622
- return node._children ? node._children : (node.children ? node.children : []);
623
- };
624
-
625
-
626
- forester.calcAverageTreeHeight = function (node, externalDescendants) {
627
- var c = externalDescendants ? externalDescendants : forester.getAllExternalNodes(node);
628
- var l = c.length;
629
- var s = 0;
630
- for (var i = 0; i < l; ++i) {
631
- var cc = c[i];
632
- while (cc != node) {
633
- if (cc.branch_length > 0) {
634
- s += cc.branch_length;
635
- }
636
- cc = cc.parent;
637
- }
638
- }
639
- return s / l;
640
- };
641
-
642
- forester.setToArray = function (set) {
643
- var array = [];
644
- if (set) {
645
- set.forEach(function (e) {
646
- array.push(e);
647
- });
648
- }
649
- return array;
650
- };
651
-
652
- forester.setToSortedArray = function (set) {
653
- var array = [];
654
- if (set) {
655
- set.forEach(function (e) {
656
- array.push(e);
657
- });
658
- }
659
- return array.sort();
660
- };
661
-
662
- forester.calcMinMaxInSet = function (set) {
663
- var array = [];
664
- var first = true;
665
- var min = 0;
666
- var max = 0;
667
- if (set) {
668
- set.forEach(function (e) {
669
- e = parseFloat(e);
670
- if (first) {
671
- first = false;
672
- min = e;
673
- max = e;
674
- }
675
- else {
676
- if (e < min) {
677
- min = e;
678
- }
679
- if (e > max) {
680
- max = e;
681
- }
682
- }
683
- });
684
- }
685
- array[0] = min;
686
- array[1] = max;
687
- return array;
688
- };
689
-
690
- forester.calcMinMeanMaxInSet = function (set) {
691
- var array = [];
692
- var first = true;
693
- var min = 0;
694
- var max = 0;
695
- var mean = 0;
696
- var sum = 0;
697
- var n = 0;
698
- if (set) {
699
- set.forEach(function (e) {
700
- e = parseFloat(e);
701
- ++n;
702
- sum += e;
703
- if (first) {
704
- first = false;
705
- min = e;
706
- max = e;
707
- }
708
- else {
709
- if (e < min) {
710
- min = e;
711
- }
712
- if (e > max) {
713
- max = e;
714
- }
715
- }
716
- });
717
- }
718
- if (n > 0) {
719
- mean = sum / n;
720
- }
721
- array[0] = min;
722
- array[1] = mean;
723
- array[2] = max;
724
- return array;
725
- };
726
-
727
-
728
- /**
729
- * This collects all properties in a tree
730
- * and returns them as dictionary of Sets mapping
731
- * keys to values.
732
- * It only collects properly formed properties
733
- * (as per phyloXML standard), which means
734
- * that 'applies_to' and 'datatype' have to be present.
735
- *
736
- *
737
- * @param phy - A phyloXML-based tree object or node.
738
- * @param appliesTo - 'phylogeny', 'clade', 'node', 'annotation', 'parent_branch', or 'other'.
739
- * @param externalOnly - To collect from external nodes only.
740
- * @returns {{}}
741
- */
742
- forester.collectProperties = function (phy, appliesTo, externalOnly) {
743
- var props = {};
744
- forester.preOrderTraversalAll(phy, function (n) {
745
-
746
- if (!externalOnly || externalOnly !== true || (!n.children && !n._children)) {
747
- if (n.properties && n.properties.length > 0) {
748
- var propertiesLength = n.properties.length;
749
- for (var i = 0; i < propertiesLength; ++i) {
750
- var property = n.properties[i];
751
- if (property.ref && property.value && property.datatype && property.applies_to && property.applies_to === appliesTo) {
752
- var ref = property.ref;
753
- if (!props[ref]) {
754
- props[ref] = new Set();
755
- }
756
- props[ref].add(property.value);
757
- }
758
- }
759
- }
760
- }
761
- });
762
- return props;
763
- };
764
-
765
- forester.collectPropertyRefs = function (phy, appliesTo, externalOnly) {
766
- var propertyRefs = new Set();
767
- forester.preOrderTraversalAll(phy, function (n) {
768
-
769
- if (!externalOnly || externalOnly !== true || (!n.children && !n._children)) {
770
- if (n.properties && n.properties.length > 0) {
771
- var propertiesLength = n.properties.length;
772
- for (var i = 0; i < propertiesLength; ++i) {
773
- var property = n.properties[i];
774
- if (property.ref && property.value && property.datatype && property.applies_to && property.applies_to === appliesTo) {
775
- propertyRefs.add(property.ref);
776
- }
777
- }
778
- }
779
- }
780
- });
781
- return propertyRefs;
782
- };
783
-
784
-
785
- forester.shortenProperties = function (phy, appliesTo, externalOnly, sourceRef, targetRef) {
786
- forester.preOrderTraversalAll(phy, function (n) {
787
- if (!externalOnly || externalOnly !== true || (!n.children && !n._children)) {
788
- if (n.properties && n.properties.length > 0) {
789
- var propertiesLength = n.properties.length;
790
- for (var i = 0; i < propertiesLength; ++i) {
791
- var property = n.properties[i];
792
- if (property.ref && property.value && property.datatype && property.applies_to && property.applies_to === appliesTo) {
793
- if (property.ref === sourceRef) {
794
- var s = property.value.trim().split(/\s+/);
795
- if (s && s.length > 1) {
796
- var newProp = {};
797
- newProp.ref = targetRef;
798
- if (s.length == 2) {
799
- newProp.value = s[0];
800
- }
801
- else {
802
- newProp.value = s[0] + ' ' + s[1];
803
- }
804
- newProp.datatype = property.datatype;
805
- newProp.applies_to = property.applies_to;
806
- n.properties.push(newProp);
807
- }
808
- }
809
- }
810
- }
811
- }
812
- }
813
- });
814
- };
815
-
816
- forester.collectBasicTreeProperties = function (tree) {
817
- var properties = {};
818
- properties.internalNodeData = false;
819
- properties.nodeNames = false;
820
- properties.longestNodeName = 0;
821
- properties.branchLengths = false;
822
- properties.confidences = false;
823
- properties.nodeEvents = false;
824
- properties.sequences = false;
825
- properties.taxonomies = false;
826
- properties.alignedMolSeqs = true;
827
- properties.maxMolSeqLength = 0;
828
- properties.externalNodesCount = 0;
829
- properties.molSeqResiduesPerPosition = null;
830
- var molSeqs = [];
831
- forester.preOrderTraversalAll(tree, function (n) {
832
- if (n.name && n.name.length > 0) {
833
- properties.nodeNames = true;
834
- if (n.name.length > properties.longestNodeName) {
835
- properties.longestNodeName = n.name.length;
836
- }
837
- if ((n.children || n._children) && ( n.parent )) {
838
- properties.internalNodeData = true;
839
- }
840
- }
841
- if (!(n.children || n._children)) {
842
- properties.externalNodesCount += 1;
843
- }
844
- if (n.branch_length && n.branch_length > 0) {
845
- properties.branchLengths = true;
846
- }
847
- if (n.events) {
848
- properties.nodeEvents = true;
849
- }
850
- if (n.sequences && n.sequences.length > 0) {
851
- properties.sequences = true;
852
-
853
- if (n.children || n._children) {
854
- properties.internalNodeData = true;
855
- }
856
- else {
857
- var s = n.sequences[0];
858
- if (s.mol_seq && s.mol_seq.value) {
859
- if (s.mol_seq.value.length > properties.maxMolSeqLength) {
860
- properties.maxMolSeqLength = s.mol_seq.value.length;
861
- }
862
- if (!s.mol_seq.is_aligned) {
863
- properties.alignedMolSeqs = false;
864
- }
865
- else {
866
- molSeqs.push(s.mol_seq.value);
867
- }
868
- }
869
- }
870
- }
871
- if (n.taxonomies && n.taxonomies.length > 0) {
872
- properties.taxonomies = true;
873
- if (n.children || n._children) {
874
- properties.internalNodeData = true;
875
- }
876
- }
877
- if (n.confidences && n.confidences.length > 0) {
878
- properties.confidences = true;
879
- }
880
- if (n.properties && n.properties.length > 0) {
881
- var l = n.properties.length;
882
- for (var p = 0; p < l; ++p) {
883
- if (n.properties[p].ref === BRANCH_EVENT_REF
884
- && n.properties[p].datatype === BRANCH_EVENT_DATATYPE
885
- && n.properties[p].applies_to === BRANCH_EVENT_APPLIES_TO) {
886
- properties.branchEvents = true;
887
- }
888
- }
889
- }
890
-
891
- });
892
-
893
- if (properties.alignedMolSeqs) {
894
- properties.molSeqResiduesPerPosition = [];
895
- for (var p = 0, maxLen = properties.maxMolSeqLength; p < maxLen; ++p) {
896
- var mySet = new Set();
897
- for (var i = 0, seqsLen = molSeqs.length; i < seqsLen; ++i) {
898
- var molSeq = molSeqs[i];
899
- var c = molSeq[p];
900
- if (c) {
901
- c = c.toUpperCase();
902
- mySet.add(c);
903
- if (!MSA_RESIDUE_SORT_MAP.has(c)) {
904
- throw ( "Unknown MSA residue '" + c + "'" );
905
- }
906
- }
907
-
908
- }
909
- var myArray = forester.setToArray(mySet);
910
- myArray.sort(function (a, b) {
911
- return MSA_RESIDUE_SORT_MAP.get(a) - MSA_RESIDUE_SORT_MAP.get(b);
912
- });
913
- properties.molSeqResiduesPerPosition.push(myArray);
914
- }
915
- }
916
- return properties;
917
- };
918
-
919
-
920
- forester.searchData = function (query,
921
- phy,
922
- caseSensitive,
923
- partial,
924
- regex) {
925
- var nodes = new Set();
926
- if (!phy || !query || query.length < 1) {
927
- return nodes;
928
- }
929
- var my_query = query.trim();
930
- if (my_query.length < 1) {
931
- return nodes;
932
- }
933
- my_query = my_query.replace(/\s\s+/g, ' ');
934
-
935
- if (!regex) {
936
- my_query = my_query.replace(/\+\++/g, '+');
937
- }
938
-
939
- var queries = [];
940
-
941
- if (!regex && ( my_query.indexOf(",") >= 0 )) {
942
- queries = my_query.split(",");
943
- }
944
- else {
945
- queries.push(my_query);
946
- }
947
- var queriesLength = queries.length;
948
-
949
- for (var i = 0; i < queriesLength; ++i) {
950
- var q = queries[i];
951
- if (q) {
952
- q = q.trim();
953
- if (q.length > 0) {
954
- forester.preOrderTraversalAll(phy, matcher);
955
- }
956
- }
957
- }
958
-
959
- return nodes;
960
-
961
-
962
- function matcher(node) {
963
- var mqueries = [];
964
- if (!regex && ( q.indexOf("+") >= 0 )) {
965
- mqueries = q.split("+");
966
- }
967
- else {
968
- mqueries.push(q);
969
- }
970
- var mqueriesLength = mqueries.length;
971
- var match = true;
972
- for (var i = 0; i < mqueriesLength; ++i) {
973
- var mq = mqueries[i];
974
- if (mq) {
975
- mq = mq.trim();
976
- if (mq.length > 0) {
977
- var ndf = null;
978
- if (( mq.length > 3 ) && ( mq.indexOf(":") === 2 )) {
979
- ndf = makeNDF(mq);
980
- if (ndf) {
981
- mq = mq.substring(3);
982
- }
983
- }
984
- var lmatch = false;
985
- if (( ( ndf === null ) || ( ndf === "NN" ) )
986
- && matchme(node.name, mq, caseSensitive, partial, regex)) {
987
- lmatch = true;
988
- }
989
- else if (( ( ndf === null ) || ( ndf === "TC" ) ) && node.taxonomies
990
- && node.taxonomies.length > 0
991
- && matchme(node.taxonomies[0].code,
992
- mq,
993
- caseSensitive,
994
- partial,
995
- regex)) {
996
- lmatch = true;
997
- }
998
- else if (( ( ndf === null ) || ( ndf === "TS" ) ) && node.taxonomies
999
- && node.taxonomies.length > 0
1000
- && matchme(node.taxonomies[0].scientific_name,
1001
- mq,
1002
- caseSensitive,
1003
- partial,
1004
- regex)) {
1005
- lmatch = true;
1006
- }
1007
- else if (( ( ndf === null ) || ( ndf === "TN" ) ) && node.taxonomies
1008
- && node.taxonomies.length > 0
1009
- && matchme(node.taxonomies[0].common_name,
1010
- mq,
1011
- caseSensitive,
1012
- partial,
1013
- regex)) {
1014
- lmatch = true;
1015
- }
1016
- else if (( ( ndf === null ) || ( ndf === "SY" ) ) && node.taxonomies
1017
- && node.taxonomies.length > 0
1018
- && matchme(node.taxonomies[0].synonym,
1019
- mq,
1020
- caseSensitive,
1021
- partial,
1022
- regex)) {
1023
- lmatch = true;
1024
- }
1025
- else if (( ( ndf === null ) || ( ndf === "TI" ) ) && node.taxonomies
1026
- && node.taxonomies.length > 0 && node.taxonomies[0].id
1027
- && matchme(node.taxonomies[0].id.value,
1028
- mq,
1029
- caseSensitive,
1030
- partial,
1031
- regex)) {
1032
- lmatch = true;
1033
- }
1034
- else if (( ( ndf === null ) || ( ndf === "SN" ) ) && node.sequences
1035
- && node.sequences.length > 0
1036
- && matchme(node.sequences[0].name,
1037
- mq,
1038
- caseSensitive,
1039
- partial,
1040
- regex)) {
1041
- lmatch = true;
1042
- }
1043
- else if (( ( ndf === null ) || ( ndf === "GN" ) ) && node.sequences
1044
- && node.sequences.length > 0
1045
- && matchme(node.sequences[0].gene_name,
1046
- mq,
1047
- caseSensitive,
1048
- partial,
1049
- regex)) {
1050
- lmatch = true;
1051
- }
1052
- else if (( ( ndf === null ) || ( ndf === "SS" ) ) && node.sequences
1053
- && node.sequences.length > 0
1054
- && matchme(node.sequences[0].symbol,
1055
- mq,
1056
- caseSensitive,
1057
- partial,
1058
- regex)) {
1059
- lmatch = true;
1060
- }
1061
- else if (( ( ndf === null ) || ( ndf === "SA" ) ) && node.sequences
1062
- && node.sequences.length > 0 && node.sequences[0].accession
1063
- && matchme(node.sequences[0].accession.value,
1064
- mq,
1065
- caseSensitive,
1066
- partial,
1067
- regex)) {
1068
- lmatch = true;
1069
- }
1070
- if (!lmatch) {
1071
- match = false;
1072
- break;
1073
- }
1074
-
1075
- } // if (mq.length > 0)
1076
- else {
1077
- match = false;
1078
- }
1079
- } // if (mq)
1080
- else {
1081
- match = false;
1082
- }
1083
- } // for (var i = 0; i < mqueriesLength; ++i)
1084
- if (match) {
1085
- nodes.add(node);
1086
- }
1087
- }
1088
-
1089
- function matchme(s,
1090
- query,
1091
- caseSensitive,
1092
- partial,
1093
- regex) {
1094
- if (!s || !query) {
1095
- return false;
1096
- }
1097
- var my_s = s.trim();
1098
- var my_query = query.trim();
1099
- if (!caseSensitive && !regex) {
1100
- my_s = my_s.toLowerCase();
1101
- my_query = my_query.toLowerCase();
1102
- }
1103
- if (regex) {
1104
- var re = null;
1105
- try {
1106
- if (caseSensitive) {
1107
- re = new RegExp(my_query);
1108
- }
1109
- else {
1110
- re = new RegExp(my_query, 'i');
1111
- }
1112
- }
1113
- catch (err) {
1114
- return false;
1115
- }
1116
- if (re) {
1117
- return ( my_s.search(re) > -1 );
1118
- }
1119
- else {
1120
- return false;
1121
- }
1122
- }
1123
- else if (partial) {
1124
- return ( my_s.indexOf(my_query) > -1 );
1125
- }
1126
- else {
1127
- var np = new RegExp("(\\b|_)" + escapeRegExp(my_query) + "(\\b|_)");
1128
- if (np) {
1129
- return ( my_s.search(np) > -1 );
1130
- }
1131
- else {
1132
- return false;
1133
- }
1134
- }
1135
- }
1136
-
1137
- function escapeRegExp(str) {
1138
- return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string
1139
- }
1140
-
1141
- function makeNDF(query) {
1142
- var str = query.substring(0, 2);
1143
- if (str === "NN"
1144
- || str === "TC"
1145
- || str === "TN"
1146
- || str === "TS"
1147
- || str === "TI"
1148
- || str === "SY"
1149
- || str === "SN"
1150
- || str === "GN"
1151
- || str === "SS"
1152
- || str === "SA"
1153
- || str === "AN"
1154
- || str === "XR"
1155
- || str === "MS") {
1156
- return str;
1157
- }
1158
- else {
1159
- return null;
1160
- }
1161
- }
1162
- };
1163
-
1164
-
1165
- /**
1166
- * This calculates the sum of the external
1167
- * descendants of a node. It does not count descendants
1168
- * of collapsed nodes.
1169
- *
1170
- * @param node - A node.
1171
- * @returns {number} - The sum of external descendants.
1172
- */
1173
- forester.calcSumOfExternalDescendants = function (node) {
1174
- var nodes = 0;
1175
- forester.preOrderTraversal(node, function (n) {
1176
- if (!n.children) {
1177
- ++nodes;
1178
- }
1179
- });
1180
- return nodes;
1181
- };
1182
-
1183
- /**
1184
- * This calculates the sum of all the external
1185
- * descendants of a node. It does count descendants
1186
- * of collapsed nodes.
1187
- *
1188
- * @param node - A node.
1189
- * @returns {number} - The sum of all external descendants.
1190
- */
1191
- forester.calcSumOfAllExternalDescendants = function (node) {
1192
- var nodes = 0;
1193
- forester.preOrderTraversalAll(node, function (n) {
1194
- if (!(n.children || n._children)) {
1195
- ++nodes;
1196
- }
1197
- });
1198
- return nodes;
1199
- };
1200
-
1201
- /**
1202
- * Returns true if at least one of the child nodes
1203
- * of node is collapsed.
1204
- *
1205
- * @param node - A node.
1206
- * @returns {boolean} - true if at least one of the child nodes is
1207
- * collapsed
1208
- */
1209
- forester.isHasCollapsedNodes = function (node) {
1210
- var collapsed = false;
1211
- forester.preOrderTraversalAll(node, function (n) {
1212
- if (n._children) {
1213
- collapsed = true;
1214
-
1215
- }
1216
- });
1217
- return collapsed;
1218
- };
1219
-
1220
- forester.getAllExternalNodes = function (node) {
1221
- var nodes = [];
1222
- forester.preOrderTraversalAll(node, function (n) {
1223
- if (!n.children && !n._children) {
1224
- nodes.push(n);
1225
- }
1226
- });
1227
- return nodes;
1228
- };
1229
-
1230
- forester.getAllNodes = function (phy) {
1231
- var nodes = [];
1232
- forester.preOrderTraversalAll(forester.getTreeRoot(phy), function (n) {
1233
- nodes.push(n);
1234
- });
1235
- return nodes;
1236
- };
1237
-
1238
- forester.calcMaxDepth = function (node) {
1239
- var max = 0;
1240
- forester.preOrderTraversalAll(node, function (n) {
1241
- if (!n.children && !n._children) {
1242
- var steps = forester.calcDepth(n);
1243
- if (steps > max) {
1244
- max = steps;
1245
- }
1246
- }
1247
- });
1248
- return max;
1249
- };
1250
-
1251
- forester.calcDepth = function (node) {
1252
-
1253
- var steps = 0;
1254
- while (node.parent && node.parent.parent) {
1255
- steps++;
1256
- node = node.parent;
1257
- }
1258
- return steps;
1259
- };
1260
-
1261
-
1262
- forester.calcBranchLengthSimpleStatistics = function (node) {
1263
- var stats = {};
1264
- stats.mean = 0;
1265
- stats.min = Number.MAX_VALUE;
1266
- stats.max = 0;
1267
- stats.n = 0;
1268
- var sum = 0;
1269
- forester.preOrderTraversalAll(node, function (n) {
1270
- if (n !== node && n.branch_length && n.branch_length >= 0) {
1271
- ++stats.n;
1272
- sum += n.branch_length;
1273
- if (n.branch_length < stats.min) {
1274
- stats.min = n.branch_length;
1275
- }
1276
- if (n.branch_length > stats.max) {
1277
- stats.max = n.branch_length;
1278
- }
1279
- }
1280
- });
1281
- if (stats.n > 0) {
1282
- stats.mean = sum / stats.n;
1283
- }
1284
- return stats;
1285
- };
1286
-
1287
- forester.calcMaxBranchLength = function (node) {
1288
- var max = 0;
1289
- forester.preOrderTraversalAll(node, function (n) {
1290
- if (n !== node && n.branch_length && (n.branch_length > max)) {
1291
- max = n.branch_length;
1292
- }
1293
- });
1294
- return max;
1295
- };
1296
-
1297
-
1298
- forester.isHasNodeData = function (node) {
1299
- return ( (node.name && node.name.length > 0 ) ||
1300
- (node.taxonomies && node.taxonomies.length > 0) ||
1301
- (node.sequences && node.sequences.length > 0) ||
1302
- (node.properties && node.properties.length > 0) );
1303
- };
1304
-
1305
-
1306
- forester.removeMaxBranchLength = function (node) {
1307
- forester.preOrderTraversalAll(node, function (n) {
1308
- if (n.max) {
1309
- n.max = undefined;
1310
- }
1311
- });
1312
- };
1313
-
1314
-
1315
- forester.collapseToBranchLength = function (root, branchLength) {
1316
- if (root.children && root.children.length === 1) {
1317
- collapseToBranchLengthHelper(root.children[0], branchLength);
1318
- }
1319
-
1320
- function collapseToBranchLengthHelper(n, branchLength) {
1321
- if (!(n.children || n._children)) {
1322
- return;
1323
- }
1324
-
1325
- if (!n.max) {
1326
- n.max = forester.calcMaxBranchLength(n);
1327
- }
1328
- var max = n.max;
1329
- if (max < branchLength) {
1330
- forester.collapse(n);
1331
- }
1332
- else {
1333
- forester.unCollapse(n);
1334
- for (var i = n.children.length - 1; i >= 0; i--) {
1335
- collapseToBranchLengthHelper(n.children[i], branchLength);
1336
- }
1337
- }
1338
- }
1339
- };
1340
-
1341
- forester.collapseToDepth = function (root, depth) {
1342
- if (root.children && root.children.length === 1) {
1343
- collapseToDepthHelper(root.children[0], 0, depth);
1344
- }
1345
-
1346
- function collapseToDepthHelper(n, d, depth) {
1347
- if (!n.children && !n._children) {
1348
- return;
1349
- }
1350
- if (d >= depth) {
1351
- forester.collapse(n);
1352
- }
1353
- else {
1354
- forester.unCollapse(n);
1355
- ++d;
1356
- for (var i = n.children.length - 1; i >= 0; i--) {
1357
- collapseToDepthHelper(n.children[i], d, depth);
1358
- }
1359
- }
1360
- }
1361
- };
1362
-
1363
- forester.collapse = function (node) {
1364
- if (node.children) {
1365
- node._children = node.children;
1366
- node.children = null;
1367
- }
1368
- };
1369
-
1370
- forester.unCollapse = function (node) {
1371
- if (node._children) {
1372
- node.children = node._children;
1373
- node._children = null;
1374
- }
1375
- };
1376
-
1377
- /**
1378
- * To parse a New Hampshire (Newick) formatted tree.
1379
- *
1380
- * @param nhStr - A New Hampshire (Newick) formatted string.
1381
- * @param confidenceValuesInBrackets - Set to true if confidence values are in brackets (default: true)
1382
- * Format is: name:distance[confidence]
1383
- * Example: "bcl2:0.000393[95]"
1384
- * @param confidenceValuesAsInternalNames - Set to true if confidence values are represented by internal names (default: false).
1385
- * @returns {{}} - A phylogenetic tree object.
1386
- */
1387
- forester.parseNewHampshire = function (nhStr, confidenceValuesInBrackets, confidenceValuesAsInternalNames) {
1388
-
1389
- var NH_FORMAT_ERR_OPEN_PARENS = NH_FORMAT_ERR + 'likely cause: number of open parentheses is larger than number of close parentheses';
1390
- var NH_FORMAT_ERR_CLOSE_PARENS = NH_FORMAT_ERR + 'likely cause: number of close parentheses is larger than number of open parentheses';
1391
-
1392
- if (confidenceValuesInBrackets == undefined) {
1393
- confidenceValuesInBrackets = true;
1394
- }
1395
- if (confidenceValuesAsInternalNames == undefined) {
1396
- confidenceValuesAsInternalNames = false;
1397
- }
1398
- if ((confidenceValuesInBrackets === true ) && (confidenceValuesAsInternalNames === true)) {
1399
- throw ( "confidence values cannot be both in brackets and as internal node names" );
1400
- }
1401
-
1402
- var ancs = [];
1403
- var x = {};
1404
- var ss = nhStr.split(/(;|\(|\)|,|:|"|')/);
1405
- var ssl = ss.length;
1406
- var in_double_q = false;
1407
- var in_single_q = false;
1408
- var buffer = '';
1409
- for (var i = 0; i < ssl; ++i) {
1410
- var element = ss[i].replace(/\s+/g, '');
1411
-
1412
- if (element === '"' && !in_single_q) {
1413
- if (!in_double_q) {
1414
- in_double_q = true;
1415
- }
1416
- else {
1417
- in_double_q = false;
1418
- if (x.name && x.name.length > 0) {
1419
- x.name = x.name + buffer;
1420
- }
1421
- else {
1422
- x.name = buffer;
1423
- }
1424
- buffer = '';
1425
- }
1426
- }
1427
- else if (element === "'" && !in_double_q) {
1428
- if (!in_single_q) {
1429
- in_single_q = true;
1430
- }
1431
- else {
1432
- in_single_q = false;
1433
- if (x.name && x.name.length > 0) {
1434
- x.name = x.name + buffer;
1435
- }
1436
- else {
1437
- x.name = buffer;
1438
- }
1439
- buffer = '';
1440
- }
1441
- }
1442
- else {
1443
- if (in_double_q || in_single_q) {
1444
- buffer += ss[i].replace(/\s+/g, ' ');
1445
- }
1446
- else {
1447
- if (element === '(') {
1448
- if (!x) {
1449
- throw ( NH_FORMAT_ERR_CLOSE_PARENS );
1450
- }
1451
- var subtree1 = {};
1452
- x.children = [subtree1];
1453
- ancs.push(x);
1454
- x = subtree1;
1455
- }
1456
- else if (element === ',') {
1457
- if (ancs.length === 0) {
1458
- throw (NH_FORMAT_ERR_CLOSE_PARENS);
1459
- }
1460
- var subtree2 = {};
1461
- ancs[ancs.length - 1].children.push(subtree2);
1462
- x = subtree2;
1463
- }
1464
- else if (element === ')') {
1465
- x = ancs.pop();
1466
- }
1467
- else if (element === ':') {
1468
- }
1469
- else {
1470
- var e = ss[i - 1];
1471
- if (e) {
1472
- e = e.trim();
1473
- if (( e === ')' ) || ( e === '(' ) || ( e === ',')) {
1474
- if (element && element.length > 0) {
1475
- if (element.charAt(element.length - 1) === "]") {
1476
- var o = element.indexOf('[');
1477
- if (o > -1) {
1478
- if (confidenceValuesInBrackets === true) {
1479
- addConfidence(x, element);
1480
- }
1481
- x.name = element.substring(0, o);
1482
- }
1483
- else {
1484
- x.name = element;
1485
- }
1486
- }
1487
- else {
1488
- x.name = element;
1489
- var op = x.name.indexOf('[');
1490
- if (op > -1) {
1491
- var cl = x.name.indexOf(']');
1492
- if (cl > op) {
1493
- x.name = x.name.substring(0, op) + x.name.substring(cl + 1, x.name.length);
1494
- }
1495
- }
1496
- }
1497
- }
1498
- }
1499
- else if (e === ':') {
1500
- if (element && element.length > 0) {
1501
- if (element.charAt(element.length - 1) === ']') {
1502
- var o1 = element.indexOf('[');
1503
- if (o1 > -1) {
1504
- if (confidenceValuesInBrackets === true) {
1505
- addConfidence(x, element);
1506
- }
1507
- var bl = parseFloat(element.substring(0, o1));
1508
- if (forester.isNumber(bl)) {
1509
- x.branch_length = bl;
1510
- }
1511
- }
1512
- }
1513
- else {
1514
- var b = parseFloat(parseFloat(element));
1515
- if (forester.isNumber(b)) {
1516
- x.branch_length = b;
1517
- }
1518
- else {
1519
- throw ( NH_FORMAT_ERR + 'could not parse branch-length from "' + element + '"' );
1520
- }
1521
- }
1522
- }
1523
- }
1524
- else if (e === '"' || e === "'") {
1525
- if ((element && element.length > 0) && (x.name && x.name.length > 0 )) {
1526
- if (element.charAt(element.length - 1) === "]") {
1527
- var opp = element.indexOf('[');
1528
- if (opp > -1) {
1529
- if (confidenceValuesInBrackets === true) {
1530
- addConfidence(x, element);
1531
- }
1532
- x.name = x.name + element.substring(0, opp);
1533
- }
1534
- else {
1535
- x.name = x.name + element;
1536
- }
1537
- }
1538
- else {
1539
- x.name = x.name + element;
1540
- }
1541
- }
1542
- }
1543
- }
1544
- }
1545
- }
1546
- }
1547
- }
1548
- if (ancs.length !== 0) {
1549
- throw ( NH_FORMAT_ERR_OPEN_PARENS );
1550
- }
1551
- if (!x) {
1552
- throw ( NH_FORMAT_ERR_CLOSE_PARENS );
1553
- }
1554
-
1555
- var phy = {};
1556
- phy.children = [x];
1557
-
1558
- forester.addParents(phy);
1559
-
1560
- if (confidenceValuesAsInternalNames === true) {
1561
- moveInternalNodeNamesToConfidenceValues(phy);
1562
- }
1563
-
1564
- return phy;
1565
-
1566
- function addConfidence(x, element) {
1567
- var confValue = parseConfidence(element);
1568
- if (confValue != null) {
1569
- x.confidences = [];
1570
- var conf = {};
1571
- conf.value = confValue;
1572
- conf.type = 'unknown';
1573
- x.confidences.push(conf);
1574
- }
1575
- }
1576
-
1577
- function parseConfidence(str) {
1578
- var o = str.indexOf('[');
1579
- if (o > -1) {
1580
- var s = str.substring(o + 1, element.length - 1);
1581
- if (NUMBERS_ONLY_PATTERN.test(s)) {
1582
- var confValue = parseFloat(s);
1583
- if (forester.isNumber(confValue)) {
1584
- return confValue;
1585
- }
1586
- else {
1587
- throw ( NH_FORMAT_ERR + 'could not parse confidence value from "' + str + '"' );
1588
- }
1589
- }
1590
- }
1591
- return null;
1592
- }
1593
-
1594
- function moveInternalNodeNamesToConfidenceValues(node) {
1595
- forester.preOrderTraversalAll(node, function (n) {
1596
- if (n.children || n._children) {
1597
- if (n.name) {
1598
- var s = n.name;
1599
- if (NUMBERS_ONLY_PATTERN.test(s)) {
1600
- var confValue = parseFloat(s);
1601
- if ((confValue != null) && (forester.isNumber(confValue))) {
1602
- n.confidences = [];
1603
- var conf1 = {};
1604
- conf1.value = confValue;
1605
- conf1.type = 'unknown';
1606
- n.confidences.push(conf1);
1607
- n.name = undefined;
1608
- }
1609
- }
1610
- }
1611
- }
1612
- });
1613
- }
1614
- };
1615
-
1616
- forester.isNumber = function (v) {
1617
- if (v === undefined || v === null) {
1618
- return false;
1619
- }
1620
- if (v !== v) {
1621
- // This can only be true if the v is NaN
1622
- return false;
1623
- }
1624
- return true;
1625
- };
1626
-
1627
- forester.getOneDistinctTaxonomy = function (node) {
1628
- var id = null;
1629
- var code = null;
1630
- var sn = null;
1631
- var cn = null;
1632
- var result = true;
1633
- var sawTax = false;
1634
- forester.preOrderTraversalAll(node, function (n) {
1635
- if (n.taxonomies && n.taxonomies.length === 1) {
1636
- var tax = n.taxonomies[0];
1637
- if (tax.code && tax.code.length > 0) {
1638
- sawTax = true;
1639
- if (code === null) {
1640
- code = tax.code;
1641
- }
1642
- else if (code != tax.code) {
1643
- result = false;
1644
- return;
1645
- }
1646
- }
1647
- if (tax.scientific_name && tax.scientific_name.length > 0) {
1648
- sawTax = true;
1649
- if (sn === null) {
1650
- sn = tax.scientific_name;
1651
- }
1652
- else if (sn != tax.scientific_name) {
1653
- result = false;
1654
- return;
1655
- }
1656
- }
1657
- if (tax.common_name && tax.common_name.length > 0) {
1658
- sawTax = true;
1659
- if (cn === null) {
1660
- cn = tax.common_name;
1661
- }
1662
- else if (cn != tax.common_name) {
1663
- result = false;
1664
- return;
1665
- }
1666
- }
1667
- if (tax.id && tax.id.value && tax.id.value.length > 0) {
1668
- sawTax = true;
1669
- var myid;
1670
- if (tax.id.provider && tax.id.provider.length > 0) {
1671
- myid = tax.id.provider + ':' + tax.id.value;
1672
- }
1673
- else {
1674
- myid = tax.id.value;
1675
- }
1676
- if (id === null) {
1677
- id = myid;
1678
- }
1679
- else if (id != myid) {
1680
- result = false;
1681
-
1682
- }
1683
- }
1684
- }
1685
- else if (!n.children && !n._children) {
1686
- // If an external node lacks taxonomy, return false.
1687
- result = false;
1688
- }
1689
- });
1690
- if (!sawTax) {
1691
- return null;
1692
- }
1693
- if (result === true) {
1694
-
1695
- if (sn) {
1696
- return sn;
1697
- }
1698
- else if (code) {
1699
- return code;
1700
- }
1701
- else if (cn) {
1702
- return cn;
1703
- }
1704
- else if (id) {
1705
- return id;
1706
- }
1707
- }
1708
- return null;
1709
- };
1710
-
1711
- forester.getOneDistinctNodePropertyValue = function (node, propertyRef) {
1712
- var propValue = null;
1713
- var result = true;
1714
- forester.preOrderTraversalAll(node, function (n) {
1715
- if (n.properties && n.properties.length > 0) {
1716
- var propertiesLength = n.properties.length;
1717
- var gotIt = false;
1718
- for (var i = 0; i < propertiesLength; ++i) {
1719
- var property = n.properties[i];
1720
- if (property.ref && property.value && (property.applies_to === 'node') && ( property.ref === propertyRef) && ( property.value.length > 0)) {
1721
- if (propValue === null) {
1722
- propValue = property.value;
1723
- }
1724
- else if (propValue != property.value) {
1725
- result = false;
1726
- return;
1727
- }
1728
- gotIt = true;
1729
- }
1730
- }
1731
- if (!gotIt && !n.children && !n._children) {
1732
- // If an external node lacks propertyRef, return false.
1733
- result = false;
1734
-
1735
- }
1736
- }
1737
- });
1738
- if (propValue === null) {
1739
- return null;
1740
- }
1741
- if (result === true) {
1742
- return propValue;
1743
- }
1744
- else {
1745
- return null;
1746
- }
1747
- };
1748
-
1749
- /**
1750
- * To be deprecated!
1751
- *
1752
- * @param phy
1753
- * @returns {{}}
1754
- */
1755
- forester.moveSimpleCharacteristicsToProperties = function (phy) {
1756
- var apptype;
1757
- if (phy.desc) {
1758
- apptype = 'ird:'
1759
- }
1760
- else {
1761
- apptype = 'vipr:'
1762
- }
1763
-
1764
- var HOST = apptype + 'Host';
1765
- var COUNTRY = apptype + 'Country';
1766
- var YEAR = apptype + 'Year';
1767
- var HA = apptype + 'HA';
1768
- var NA = apptype + 'NA';
1769
- var NODE = 'node';
1770
- var STRING = 'xsd:string';
1771
- var INT = 'xsd:integer';
1772
-
1773
- forester.preOrderTraversalAll(phy, function (n) {
1774
- if (n.simple_characteristics) {
1775
- var sc = n.simple_characteristics;
1776
- var props;
1777
- if (sc.country && sc.country.length > 0) {
1778
- props = {};
1779
- props.ref = COUNTRY;
1780
- props.datatype = STRING;
1781
- props.applies_to = NODE;
1782
- props.value = sc.country;
1783
- addProperties(n, props);
1784
- }
1785
- if (sc.host && sc.host.length > 0) {
1786
- props = {};
1787
- props.ref = HOST;
1788
- props.datatype = STRING;
1789
- props.applies_to = NODE;
1790
- props.value = sc.host;
1791
- addProperties(n, props);
1792
- }
1793
- if (sc.year && sc.year.length > 0) {
1794
- props = {};
1795
- props.ref = YEAR;
1796
- props.datatype = INT;
1797
- props.applies_to = NODE;
1798
- props.value = parseInt(sc.year);
1799
- addProperties(n, props);
1800
- }
1801
- if (sc.ha && sc.ha.length > 0) {
1802
- props = {};
1803
- props.ref = HA;
1804
- props.datatype = INT;
1805
- props.applies_to = NODE;
1806
- props.value = parseInt(sc.ha);
1807
- addProperties(n, props);
1808
- }
1809
- if (sc.na && sc.na.length > 0) {
1810
- props = {};
1811
- props.ref = NA;
1812
- props.datatype = INT;
1813
- props.applies_to = NODE;
1814
- props.value = parseInt(sc.na);
1815
- addProperties(n, props);
1816
- }
1817
- n.simple_characteristics = undefined;
1818
- }
1819
- });
1820
- function addProperties(n, props) {
1821
- if (props) {
1822
- if (!n.properties) {
1823
- n.properties = [];
1824
- }
1825
- var alreadyHave = false;
1826
- var l = n.properties.length;
1827
- for (var i = 0; i < l; ++i) {
1828
- if (n.properties[i].ref === props.ref) {
1829
- alreadyHave = true;
1830
- break;
1831
- }
1832
- }
1833
- if (!alreadyHave) {
1834
- n.properties.push(props);
1835
- }
1836
- }
1837
- }
1838
- };
1839
-
1840
-
1841
- /**
1842
- * To convert a phylogentic tree object to a New Hampshire (Newick) formatted string.
1843
- *
1844
- * @param phy - A phylogentic tree object.
1845
- * @param decPointsMax - Maximal number of decimal points for branch lengths (optional)
1846
- * @param replaceChars - To replace illegal characters (),:;"' instead of surrounding with quotation marks
1847
- * @param writeConfidences - to write confidence values in brackets
1848
- * @returns {*} - a New Hampshire (Newick) formatted string.
1849
- */
1850
- forester.toNewHampshire = function (phy, decPointsMax, replaceChars, writeConfidences) {
1851
- var nh = "";
1852
- if (phy.children && phy.children.length === 1) {
1853
- toNewHampshireHelper(phy.children[0], true);
1854
- }
1855
- if (nh.length > 0) {
1856
- return nh + ";";
1857
- }
1858
- return nh;
1859
-
1860
- function toNewHampshireHelper(node, last) {
1861
- if (node.children) {
1862
- var l = node.children.length;
1863
- nh += "(";
1864
- for (var i = 0; i < l; ++i) {
1865
- toNewHampshireHelper(node.children[i], i === l - 1);
1866
- }
1867
- nh += ")";
1868
- }
1869
- else if (node._children) {
1870
- var ll = node._children.length;
1871
- nh += "(";
1872
- for (var ii = 0; ii < ll; ++ii) {
1873
- toNewHampshireHelper(node._children[ii], ii === ll - 1);
1874
- }
1875
- nh += ")";
1876
- }
1877
- if (node.name && node.name.length > 0) {
1878
- if (replaceChars === true) {
1879
- nh += replaceUnsafeChars(node.name);
1880
- }
1881
- else {
1882
- var myName = node.name.replace(/\s+/g, ' ');
1883
- if (/[\s,():;'"\[\]]/.test(myName)) {
1884
- if ((myName.indexOf('"') > -1) && (myName.indexOf("'") > -1 )) {
1885
- nh += '"' + myName.replace(/"/g, "'") + '"';
1886
- }
1887
- else if (myName.indexOf('"') > -1) {
1888
- nh += "'" + myName + "'";
1889
- }
1890
- else {
1891
- nh += '"' + myName + '"';
1892
- }
1893
- }
1894
- else {
1895
- nh += myName;
1896
- }
1897
- }
1898
- }
1899
- if (node.branch_length !== undefined && node.branch_length !== null) {
1900
- if (decPointsMax && decPointsMax > 0) {
1901
- nh += ":" + forester.roundNumber(node.branch_length, decPointsMax);
1902
- }
1903
- else {
1904
- nh += ":" + node.branch_length;
1905
- }
1906
- }
1907
- if (writeConfidences && node.confidences && node.confidences.length === 1 && node.confidences[0].value !== undefined && node.confidences[0].value !== null) {
1908
- if (decPointsMax && decPointsMax > 0) {
1909
- nh += "[" + forester.roundNumber(node.confidences[0].value, decPointsMax) + "]";
1910
- }
1911
- else {
1912
- nh += "[" + node.confidences[0].value + "]";
1913
- }
1914
- }
1915
- if (!last) {
1916
- nh += ",";
1917
- }
1918
- }
1919
-
1920
- function replaceUnsafeChars(str) {
1921
- return str.replace(/[\s,():;'"\[\]]+/g, '_');
1922
- }
1923
- };
1924
-
1925
- forester.roundNumber = function (num, dec) {
1926
- return Math.round(num * Math.pow(10, dec)) / Math.pow(10, dec);
1927
- };
1928
-
1929
- forester.isString = function (s) {
1930
- return (typeof s === 'string' || s instanceof String);
1931
- };
1932
-
1933
-
1934
- // --------------------------------------------------------------
1935
- // For exporting
1936
- // --------------------------------------------------------------
1937
- if (typeof module !== 'undefined' && module.exports && !global.xmldocAssumeBrowser)
1938
- module.exports.forester = forester;
1939
- else if (typeof window !== "undefined")
1940
- window.forester = forester;
1941
- else
1942
- this.forester = forester;
1943
- })();
1
+ /**
2
+ * Copyright (C) 2026 Christian M. Zmasek
3
+ * Copyright (C) 2026 Yun Zhang
4
+ * Copyright (C) 2026 J. Craig Venter Institute
5
+ * All rights reserved
6
+ *
7
+ * This library is free software; you can redistribute it and/or
8
+ * modify it under the terms of the GNU Lesser General Public
9
+ * License as published by the Free Software Foundation; either
10
+ * version 2.1 of the License, or (at your option) any later version.
11
+ *
12
+ * This library is distributed in the hope that it will be useful,
13
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15
+ * Lesser General Public License for more details.
16
+ *
17
+ * You should have received a copy of the GNU Lesser General Public
18
+ * License along with this library; if not, write to the Free Software
19
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
20
+ *
21
+ */
22
+
23
+ // v 2.3.2
24
+ // 2026-04-22
25
+ //
26
+ // forester.js is a general suite for dealing with phylogenetic trees.
27
+ //
28
+ // forester.js is used by Archaeopteryx.js:
29
+ // https://github.com/cmzmasek/archaeopteryx-js
30
+ // https://www.npmjs.com/package/archaeopteryx
31
+ //
32
+ // Availability:
33
+ // https://github.com/cmzmasek/archaeopteryx-js
34
+ // https://www.npmjs.com/package/archaeopteryx
35
+ //
36
+ // Dependencies: none
37
+ //
38
+ //
39
+ // In the following is a basic example shows how to parse a New Hampshire formatted String
40
+ // into to a object representing a phylogenetic tree.
41
+ // Followed by pre- and post-order traversal,
42
+ // and writing back to a New Hampshire formatted String.
43
+ //
44
+ // Change './forester' to 'forester' if you use this code outside of this package
45
+ //
46
+ // let forester = require('./forester').forester;
47
+ //
48
+ // let newHampshireFormattedString = "(((a:1,b:1,c:1)N:2,(d:1,e:1)M:4)O:4,f:1)R:1;";
49
+ // let phylogeneticTree = forester.parseNewHampshire(newHampshireFormattedString);
50
+ //
51
+ // console.log('Pre-order traversal:');
52
+ // forester.preOrderTraversalAll(forester.getTreeRoot(phylogeneticTree), function (n) {
53
+ // console.log(n.name + ':' + n.branch_length);
54
+ // });
55
+ //
56
+ // console.log('Post-order traversal:');
57
+ // forester.postOrderTraversalAll(forester.getTreeRoot(phylogeneticTree), function (n) {
58
+ // console.log(n.name + ':' + n.branch_length);
59
+ // });
60
+ //
61
+ // console.log('In New Hampshire format:');
62
+ // let nh = forester.toNewHampshire(phylogeneticTree);
63
+ // console.log(nh);
64
+
65
+
66
+ (function forester() {
67
+
68
+ "use strict";
69
+
70
+ const BRANCH_EVENT_REF = 'aptx:branch_event';
71
+ const BRANCH_EVENT_DATATYPE = 'xsd:string';
72
+ const BRANCH_EVENT_APPLIES_TO = 'parent_branch';
73
+ const NH_FORMAT_ERR = 'New Hampshire (Newick) format error: ';
74
+
75
+ const NUMBERS_ONLY_PATTERN = /^[-+]?[0-9\\.]+$/;
76
+
77
+ const MSA_RESIDUE_SORT_MAP = new Map();
78
+ MSA_RESIDUE_SORT_MAP.set('A', 0);
79
+ MSA_RESIDUE_SORT_MAP.set('C', 1);
80
+ MSA_RESIDUE_SORT_MAP.set('D', 2);
81
+ MSA_RESIDUE_SORT_MAP.set('E', 3);
82
+ MSA_RESIDUE_SORT_MAP.set('F', 4);
83
+ MSA_RESIDUE_SORT_MAP.set('G', 5);
84
+ MSA_RESIDUE_SORT_MAP.set('H', 6);
85
+ MSA_RESIDUE_SORT_MAP.set('I', 7);
86
+ MSA_RESIDUE_SORT_MAP.set('K', 8);
87
+ MSA_RESIDUE_SORT_MAP.set('L', 9);
88
+ MSA_RESIDUE_SORT_MAP.set('M', 10);
89
+ MSA_RESIDUE_SORT_MAP.set('N', 11);
90
+ MSA_RESIDUE_SORT_MAP.set('P', 12);
91
+ MSA_RESIDUE_SORT_MAP.set('Q', 13);
92
+ MSA_RESIDUE_SORT_MAP.set('R', 14);
93
+ MSA_RESIDUE_SORT_MAP.set('S', 15);
94
+ MSA_RESIDUE_SORT_MAP.set('T', 16);
95
+ MSA_RESIDUE_SORT_MAP.set('U', 17);// Uracil
96
+ MSA_RESIDUE_SORT_MAP.set('V', 18);
97
+ MSA_RESIDUE_SORT_MAP.set('W', 19);
98
+ MSA_RESIDUE_SORT_MAP.set('Y', 20);
99
+ MSA_RESIDUE_SORT_MAP.set('B', 21);// Asparagine or aspartic acid
100
+ MSA_RESIDUE_SORT_MAP.set('Z', 22);// Glutamine or glutamic acid
101
+ MSA_RESIDUE_SORT_MAP.set('X', 23);
102
+ MSA_RESIDUE_SORT_MAP.set('?', 24);
103
+ MSA_RESIDUE_SORT_MAP.set('-', 25);
104
+ MSA_RESIDUE_SORT_MAP.set('.', 26);
105
+
106
+
107
+ /**
108
+ * Sets links to parent nodes for all nodes in a
109
+ * phyloXML-based tree object
110
+ *
111
+ * @param phy - A phyloXML-based tree object.
112
+ */
113
+ forester.addParents = function (phy) {
114
+ if (phy.children) {
115
+ for (let i = phy.children.length - 1; i >= 0; --i) {
116
+ let c = phy.children[i];
117
+ c.parent = phy;
118
+ forester.addParents(c);
119
+ }
120
+ }
121
+ };
122
+
123
+ /**
124
+ * Returns the real root node of a
125
+ * phyloXML-based tree object.
126
+ * Precondition: needs to have parents set.
127
+ *
128
+ * @param phy - A phyloXML-based tree object or node.
129
+ * @returns {*} - The real tree root node.
130
+ */
131
+ forester.getTreeRoot = function (phy) {
132
+ let root = phy;
133
+ if (!root.parent && root.children && root.children.length === 1) {
134
+ root = root.children[0];
135
+ }
136
+ while (root.parent && root.parent.parent) {
137
+ root = root.parent;
138
+ }
139
+ return root;
140
+ };
141
+
142
+ /**
143
+ * Visits all non-collapsed child nodes of a node
144
+ * while applying a function in pre-order.
145
+ *
146
+ * @param node - The root of the subtree to traverse.
147
+ * @param fn - The function to apply.
148
+ */
149
+ forester.preOrderTraversal = function (node, fn) {
150
+ fn(node);
151
+ if (node.children) {
152
+ for (let i = node.children.length - 1; i >= 0; --i) {
153
+ forester.preOrderTraversal(node.children[i], fn);
154
+ }
155
+ }
156
+ };
157
+
158
+ /**
159
+ * Visits all child nodes of a node
160
+ * while applying a function in pre-order.
161
+ *
162
+ * @param node - The root of the subtree to traverse.
163
+ * @param fn - The function to apply.
164
+ */
165
+ forester.preOrderTraversalAll = function (node, fn) {
166
+ fn(node);
167
+ if (node.children) {
168
+ for (let i = node.children.length - 1; i >= 0; --i) {
169
+ forester.preOrderTraversalAll(node.children[i], fn);
170
+ }
171
+ } else if (node._children) {
172
+ for (let ii = node._children.length - 1; ii >= 0; --ii) {
173
+ forester.preOrderTraversalAll(node._children[ii], fn);
174
+ }
175
+ }
176
+ };
177
+
178
+ forester.postOrderTraversalAll = function (node, fn) {
179
+ if (node.children) {
180
+ let l = node.children.length;
181
+ for (let i = 0; i < l; ++i) {
182
+ forester.postOrderTraversalAll(node.children[i], fn);
183
+ }
184
+ } else if (node._children) {
185
+ let ll = node._children.length;
186
+ for (let ii = 0; ii < ll; ++ii) {
187
+ forester.postOrderTraversalAll(node._children[ii], fn);
188
+ }
189
+ }
190
+ fn(node);
191
+ };
192
+
193
+
194
+ forester.findByNodeName = function (node, name) {
195
+ let found = [];
196
+ forester.preOrderTraversalAll(node, function (n) {
197
+ if (n.name === name) {
198
+ found.push(n);
199
+ }
200
+ });
201
+ return found;
202
+ };
203
+
204
+ forester.findByTaxonomyCode = function (node, code) {
205
+ let found = [];
206
+ forester.preOrderTraversalAll(node, function (n) {
207
+ if (n.taxonomies && n.taxonomies.length > 0 && n.taxonomies[0].code === code) {
208
+ found.push(n);
209
+ }
210
+ });
211
+ return found;
212
+ };
213
+
214
+ forester.findByTaxonomyScientificName = function (node, scientificName) {
215
+ let found = [];
216
+ forester.preOrderTraversalAll(node, function (n) {
217
+ if (n.taxonomies && n.taxonomies.length > 0 && n.taxonomies[0].scientific_name === scientificName) {
218
+ found.push(n);
219
+ }
220
+ });
221
+ return found;
222
+ };
223
+
224
+
225
+ forester.filterByNodeProperty = function (positive, phy, propertyMap) {
226
+ if (!phy) {
227
+ throw ("cannot delete null tree");
228
+ }
229
+ if (!propertyMap) {
230
+ throw ("property list is null");
231
+ }
232
+ const toDelete = [];
233
+ forester.preOrderTraversalAll(phy, function (n) {
234
+ if (!n.children && !n._children) {
235
+ if (n.properties && n.properties.length > 0) {
236
+ const propertiesLength = n.properties.length;
237
+ for (let i = 0; i < propertiesLength; ++i) {
238
+ const property = n.properties[i];
239
+ if (property.ref && property.value && property.applies_to === 'node') {
240
+ if (positive) {
241
+ if (property.ref in propertyMap && !propertyMap[property.ref].includes(property.value)) {
242
+ toDelete.push(n);
243
+ }
244
+ } else {
245
+ if (property.ref in propertyMap && propertyMap[property.ref].includes(property.value)) {
246
+ toDelete.push(n);
247
+ }
248
+ }
249
+ }
250
+ }
251
+ }
252
+ }
253
+ });
254
+ const l = toDelete.length;
255
+ console.log(toDelete);
256
+ for (let i = 0; i < l; ++i) {
257
+ forester.deleteSubtree(phy, toDelete[i]);
258
+ }
259
+ };
260
+
261
+
262
+ /**
263
+ * To delete a sub-tree or external node.
264
+ *
265
+ * @param phy
266
+ * @param nodeToDelete
267
+ */
268
+ forester.deleteSubtree = function (phy, nodeToDelete) {
269
+ if (!phy) {
270
+ throw ("cannot delete null tree");
271
+ }
272
+ if (!nodeToDelete) {
273
+ throw ("cannot delete null node");
274
+ }
275
+ if (!nodeToDelete.parent || !nodeToDelete.parent.parent) {
276
+ throw ("cannot delete root");
277
+ }
278
+ if (!nodeToDelete.parent.parent.parent) {
279
+ throw ("cannot delete direct child of root");
280
+ }
281
+
282
+ let p = nodeToDelete.parent;
283
+
284
+ if ((p.children) && (p.children.length > 1)) {
285
+ let i = p.children.indexOf(nodeToDelete);
286
+ if (i !== -1) {
287
+ p.children.splice(i, 1);
288
+ }
289
+ }
290
+ if ((p._children) && (p._children.length > 1)) {
291
+ let ii = p._children.indexOf(nodeToDelete);
292
+ if (ii !== -1) {
293
+ p._children.splice(ii, 1);
294
+ }
295
+ }
296
+
297
+ if (p.children.length === 1) {
298
+ let pp = p.parent;
299
+ let cni = forester.getChildNodeIndex(pp, p);
300
+ if ((cni < 0) || (cni > (pp.children.length - 1))) {
301
+ throw ("this should never have happened, child node index = " + cni);
302
+ }
303
+ let x = p.children[0];
304
+ let nbl = undefined;
305
+ if (x.branch_length || p.branch_length) {
306
+ nbl = (x.branch_length > 0 ? x.branch_length : 0) + (p.branch_length > 0 ? p.branch_length : 0);
307
+ }
308
+ x.parent = pp;
309
+ pp.children[cni] = x;
310
+ x.branch_length = nbl;
311
+ }
312
+
313
+ };
314
+
315
+
316
+ /**
317
+ * To re-root a tree object.
318
+ *
319
+ * @param phy - The tree to be re-rooted.
320
+ * @param node - The node on where to place the new root (on its parent branch).
321
+ * @param branchLength - The branch length to use if new root is not placed in the middle (if
322
+ * non-negative).
323
+ */
324
+ forester.reRoot = function (phy, node, branchLength) {
325
+ if (!phy) {
326
+ throw ("cannot re-root null tree");
327
+ }
328
+ if (!node) {
329
+ throw ("cannot re-root on null node");
330
+ }
331
+ if (!branchLength) {
332
+ branchLength = -1;
333
+ }
334
+ if (forester.isString(node)) {
335
+ let nodes = forester.findByNodeName(phy, node);
336
+ if (nodes.length > 1) {
337
+ throw ("node name '" + node + "' is not unique");
338
+ } else if (nodes.length < 1) {
339
+ throw ("node name '" + node + "' is not found");
340
+ }
341
+ node = nodes[0];
342
+ }
343
+
344
+ phy.rooted = true;
345
+ let root = forester.getTreeRoot(phy);
346
+
347
+ if (!node.parent || !node.parent.parent) {
348
+ //do noting
349
+ } else if (!node.parent.parent.parent) {
350
+ if ((node.parent.children.length === 2) && (branchLength >= 0)) {
351
+ let d = node.parent.children[0].branch_length + node.parent.children[1].branch_length;
352
+ let other;
353
+ if (node.parent.children[0] === node) {
354
+ other = node.parent.children[1];
355
+ } else {
356
+ other = node.parent.children[0];
357
+ }
358
+ node.branch_length = branchLength;
359
+ let dm = d - branchLength;
360
+ if (dm >= 0) {
361
+ other.branch_length = dm;
362
+ } else {
363
+ other.branch_length = 0;
364
+ }
365
+ }
366
+ if (node.parent.children.length > 2) {
367
+ let index = forester.getChildNodeIndex(node.parent, node);
368
+ let dn = node.branch_length;
369
+ let prev_root = root;
370
+ prev_root.children.splice(index, 1);
371
+ let nr = {};
372
+ nr.children = [];
373
+ forester.setChildNode(nr, 0, node);
374
+ forester.setChildNode(nr, 1, prev_root);
375
+
376
+ forester.copyBranchData(node, prev_root);
377
+
378
+ phy.children[0] = nr;
379
+ nr.parent = phy;
380
+ if (branchLength >= 0) {
381
+ node.branch_length = branchLength;
382
+ let dnmp = dn - branchLength;
383
+ if (dnmp >= 0) {
384
+ prev_root.branch_length = dnmp;
385
+ } else {
386
+ prev_root.branch_length = 0;
387
+ }
388
+ } else {
389
+ if (dn >= 0) {
390
+ let dn2 = dn / 2.0;
391
+ node.branch_length = dn2;
392
+ prev_root.branch_length = dn2;
393
+ }
394
+ }
395
+ }
396
+ } else {
397
+ let a = node;
398
+ let new_root = {};
399
+ let distance1;
400
+ let distance2 = 0.0;
401
+ let branch_data_1;
402
+ let branch_data_2 = null;
403
+ let b = a.parent;
404
+ let c = b.parent;
405
+
406
+ new_root.children = [];
407
+ forester.setChildNode(new_root, 0, a);
408
+ forester.setChildNode(new_root, 1, b);
409
+
410
+ distance1 = c.branch_length;
411
+
412
+ branch_data_1 = forester.getBranchData(c);
413
+
414
+ c.branch_length = b.branch_length;
415
+
416
+ forester.copyBranchData(b, c);
417
+ forester.copyBranchData(a, b);
418
+
419
+ // New root is always placed in the middle of the branch:
420
+ if (!a.branch_length) {
421
+ b.branch_length = undefined;
422
+ } else {
423
+ if (branchLength >= 0.0) {
424
+ let diff = a.branch_length - branchLength;
425
+ a.branch_length = branchLength;
426
+ b.branch_length = (diff >= 0.0 ? diff : 0.0);
427
+ } else {
428
+ let d2 = a.branch_length / 2.0;
429
+ a.branch_length = d2;
430
+ b.branch_length = d2;
431
+ }
432
+ }
433
+ setChildNodeOnly(b, forester.getChildNodeIndex(b, a), c);
434
+ // moving to the old root, swapping references:
435
+ while (c.parent.parent) {
436
+ a = b;
437
+ b = c;
438
+ c = c.parent;
439
+ setChildNodeOnly(b, forester.getChildNodeIndex(b, a), c);
440
+ b.parent = a;
441
+ distance2 = c.branch_length;
442
+ branch_data_2 = forester.getBranchData(c);
443
+ c.branch_length = distance1;
444
+ forester.setBranchData(c, branch_data_1);
445
+ distance1 = distance2;
446
+ branch_data_1 = branch_data_2;
447
+ }
448
+ // removing the old root:
449
+ if (c.children.length === 2) {
450
+ let node2 = c.children[1 - forester.getChildNodeIndex(c, b)];
451
+ node2.parent = b;
452
+ if ((!c.branch_length) && (!node2.branch_length)) {
453
+ node2.branch_length = undefined;
454
+ } else {
455
+ node2.branch_length = (c.branch_length >= 0.0 ? c.branch_length : 0.0) + (node2.branch_length >= 0.0 ? node2.branch_length : 0.0);
456
+ }
457
+ let cbd = forester.getBranchData(c);
458
+ if (cbd) {
459
+ forester.setBranchData(node2, cbd);
460
+ }
461
+ let l = b.children.length;
462
+ for (let i = 0; i < l; ++i) {
463
+ if (b.children[i] === c) {
464
+ setChildNodeOnly(b, i, node2);
465
+ break;
466
+ }
467
+ }
468
+ } else {
469
+ c.parent = b;
470
+ forester.removeChildNode(c, forester.getChildNodeIndex(c, b));
471
+ }
472
+ phy.children[0] = new_root;
473
+ new_root.parent = phy;
474
+ forester.addParents(phy);
475
+ }
476
+
477
+ function setChildNodeOnly(parentNode, i, node) {
478
+ if (parentNode.children.length <= i) {
479
+ parentNode.children.push(node);
480
+ } else {
481
+ parentNode.children[i] = node;
482
+ }
483
+ }
484
+ };
485
+
486
+ forester.midpointRoot = function (phy) {
487
+ let root = forester.getTreeRoot(phy);
488
+ let extNodes = forester.getAllExternalNodes(root);
489
+ if ((extNodes.length < 2) || (forester.calcMaxBranchLength(root) <= 0)) {
490
+ return;
491
+ }
492
+ let counter = 0;
493
+ let totalNodes = forester.getAllNodes(phy).length;
494
+ while (true) {
495
+ if (++counter > (totalNodes + 1)) {
496
+ throw('this should not have happened: midpoint rooting does not converge');
497
+ }
498
+ let a = null;
499
+ let da = 0;
500
+ let db = 0;
501
+ let cl = forester.getTreeRoot(phy).children.length;
502
+ for (let i = 0; i < cl; ++i) {
503
+ let f = forester.getFurthestDescendant(forester.getTreeRoot(phy).children[i]);
504
+ let df = forester.getDistance(f, forester.getTreeRoot(phy));
505
+ if (df > 0) {
506
+ if (df > da) {
507
+ db = da;
508
+ da = df;
509
+ a = f;
510
+ } else if (df > db) {
511
+ db = df;
512
+ }
513
+ }
514
+ }
515
+ let diff = da - db;
516
+ if (diff < 0.0001) {
517
+ break;
518
+ }
519
+ let x = da - (diff / 2.0);
520
+ while ((x > a.branch_length) && a.parent) {
521
+ x -= (a.branch_length > 0 ? a.branch_length : 0);
522
+ a = a.parent;
523
+ }
524
+ forester.reRoot(phy, a, x);
525
+ }
526
+ };
527
+
528
+ forester.getFurthestDescendant = function (node) {
529
+ let children = forester.getAllExternalNodes(node);
530
+ let farthest = null;
531
+ let longest = -1000000;
532
+ let l = children.length;
533
+ for (let i = 0; i < l; ++i) {
534
+ let dist = forester.getDistance(children[i], node);
535
+ if (dist > longest) {
536
+ farthest = children[i];
537
+ longest = dist;
538
+ }
539
+ }
540
+ return farthest;
541
+ };
542
+
543
+ /**
544
+ * Calculates the distance between PhylogenyNodes n1 and n2.
545
+ * PRECONDITION: n1 is a descendant of n2.
546
+ *
547
+ * @param n1 a descendant of n2
548
+ * @param n2
549
+ * @returns {number} distance between n1 and n2
550
+ */
551
+ forester.getDistance = function (n1, n2) {
552
+ let d = 0.0;
553
+ while (n1 !== n2) {
554
+ if (n1.branch_length > 0.0) {
555
+ d += n1.branch_length;
556
+ }
557
+ n1 = n1.parent;
558
+ }
559
+ return d;
560
+ };
561
+
562
+ forester.removeChildNode = function (parentNode, i) {
563
+ if (!parentNode.children) {
564
+ throw ("cannot remove the child node for a external node");
565
+ }
566
+ if ((i >= parentNode.children.length) || (i < 0)) {
567
+ throw ("attempt to get child node " + i + " of a node with " + parentNode.children.length + " child nodes.");
568
+ }
569
+ parentNode.children[i].parent = undefined;
570
+ parentNode.children.splice(i, 1);
571
+ };
572
+
573
+
574
+ /**
575
+ * Inserts node node at the specified position i into the list of
576
+ * child nodes of parentNode. This does not allow null slots in the list of child nodes:
577
+ * If i is larger than the number of child nodes, node is just added to the
578
+ * list, not placed at index i.
579
+ */
580
+ forester.setChildNode = function (parentNode, i, node) {
581
+ node.parent = parentNode;
582
+ if (parentNode.children.length <= i) {
583
+ parentNode.children.push(node);
584
+ } else {
585
+ parentNode.children[i] = node;
586
+ }
587
+ };
588
+
589
+
590
+ forester.getBranchData = function (node) {
591
+ let branchData = null;
592
+ if (node.width || node.color || node.confidences) {
593
+ branchData = {};
594
+ branchData.width = node.width;
595
+ branchData.color = node.color;
596
+ branchData.confidences = node.confidences;
597
+ }
598
+ return branchData;
599
+ };
600
+
601
+
602
+ forester.setBranchData = function (node, branchData) {
603
+ if (branchData) {
604
+ node.width = branchData.width;
605
+ node.color = branchData.color;
606
+ node.confidences = branchData.confidences;
607
+ }
608
+ };
609
+
610
+ forester.unCollapseAll = function (node) {
611
+ forester.preOrderTraversal(node, function (d) {
612
+ if (d._children) {
613
+ d.children = d._children;
614
+ d._children = null;
615
+ }
616
+ });
617
+ };
618
+
619
+ forester.copyBranchData = function (nodeFrom, nodeTo) {
620
+ nodeTo.width = nodeFrom.width;
621
+ nodeTo.color = nodeFrom.color;
622
+ nodeTo.confidences = nodeFrom.confidences;
623
+ };
624
+
625
+
626
+ forester.getChildNodeIndex = function (parentNode, childNode) {
627
+ if (!parentNode) {
628
+ throw ("cannot get the child index for a root node");
629
+ }
630
+ let c = parentNode.children.length;
631
+ for (let i = 0; i < c; ++i) {
632
+ if (parentNode.children[i] === childNode) {
633
+ return i;
634
+ }
635
+ }
636
+ throw ("unexpected exception: Could not determine the child index for a node");
637
+ };
638
+
639
+
640
+ forester.getChildren = function (node) {
641
+ return node._children ? node._children : (node.children ? node.children : []);
642
+ };
643
+
644
+
645
+ forester.calcAverageTreeHeight = function (node, externalDescendants) {
646
+ let c = externalDescendants ? externalDescendants : forester.getAllExternalNodes(node);
647
+ let l = c.length;
648
+ let s = 0;
649
+ for (let i = 0; i < l; ++i) {
650
+ let cc = c[i];
651
+ while (cc !== node) {
652
+ if (cc.branch_length > 0) {
653
+ s += cc.branch_length;
654
+ }
655
+ cc = cc.parent;
656
+ }
657
+ }
658
+ return s / l;
659
+ };
660
+
661
+ forester.setToArray = function (set) {
662
+ let array = [];
663
+ if (set) {
664
+ set.forEach(function (e) {
665
+ array.push(e);
666
+ });
667
+ }
668
+ return array;
669
+ };
670
+
671
+ forester.setToSortedArray = function (set) {
672
+ let array = [];
673
+ if (set) {
674
+ set.forEach(function (e) {
675
+ array.push(e);
676
+ });
677
+ }
678
+ return array.sort();
679
+ };
680
+
681
+ forester.calcMinMaxInSet = function (set) {
682
+ let array = [];
683
+ let first = true;
684
+ let min = 0;
685
+ let max = 0;
686
+ if (set) {
687
+ set.forEach(function (e) {
688
+ e = parseFloat(e);
689
+ if (first) {
690
+ first = false;
691
+ min = e;
692
+ max = e;
693
+ } else {
694
+ if (e < min) {
695
+ min = e;
696
+ }
697
+ if (e > max) {
698
+ max = e;
699
+ }
700
+ }
701
+ });
702
+ }
703
+ array[0] = min;
704
+ array[1] = max;
705
+ return array;
706
+ };
707
+
708
+ forester.calcMinMeanMaxInSet = function (set) {
709
+ let array = [];
710
+ let first = true;
711
+ let min = 0;
712
+ let max = 0;
713
+ let mean = 0;
714
+ let sum = 0;
715
+ let n = 0;
716
+ if (set) {
717
+ set.forEach(function (e) {
718
+ e = parseFloat(e);
719
+ ++n;
720
+ sum += e;
721
+ if (first) {
722
+ first = false;
723
+ min = e;
724
+ max = e;
725
+ } else {
726
+ if (e < min) {
727
+ min = e;
728
+ }
729
+ if (e > max) {
730
+ max = e;
731
+ }
732
+ }
733
+ });
734
+ }
735
+ if (n > 0) {
736
+ mean = sum / n;
737
+ }
738
+ array[0] = min;
739
+ array[1] = mean;
740
+ array[2] = max;
741
+ return array;
742
+ };
743
+
744
+
745
+ /**
746
+ * This collects all properties in a tree
747
+ * and returns them as dictionary of Sets mapping
748
+ * keys to values.
749
+ * It only collects properly formed properties
750
+ * (as per phyloXML standard), which means
751
+ * that 'applies_to' and 'datatype' have to be present.
752
+ *
753
+ *
754
+ * @param phy - A phyloXML-based tree object or node.
755
+ * @param appliesTo - 'phylogeny', 'clade', 'node', 'annotation', 'parent_branch', or 'other'.
756
+ * @param externalOnly - To collect from external nodes only.
757
+ * @returns {{}}
758
+ */
759
+ forester.collectProperties = function (phy, appliesTo, externalOnly) {
760
+ let props = {};
761
+ forester.preOrderTraversalAll(phy, function (n) {
762
+
763
+ if (!externalOnly || externalOnly !== true || (!n.children && !n._children)) {
764
+ if (n.properties && n.properties.length > 0) {
765
+ let propertiesLength = n.properties.length;
766
+ for (let i = 0; i < propertiesLength; ++i) {
767
+ let property = n.properties[i];
768
+ if (property.ref && property.value && property.datatype && property.applies_to && property.applies_to === appliesTo) {
769
+ let ref = property.ref;
770
+ if (!props[ref]) {
771
+ props[ref] = new Set();
772
+ }
773
+ props[ref].add(property.value);
774
+ }
775
+ }
776
+ }
777
+ }
778
+ });
779
+ return props;
780
+ };
781
+
782
+
783
+ /**
784
+ *
785
+ * Special method for IRD database.
786
+ * Returns true if at least one 'ird:Host' property with 'Avian' found
787
+ *
788
+ * @param phy
789
+ * @param targetValue
790
+ * @param fromRef
791
+ * @param toRef
792
+ * @returns {boolean}
793
+ */
794
+ forester.splitProperty = function (phy, targetValue, fromRef, toRef) {
795
+ let found = false;
796
+ let targetValue_ = targetValue + ' ';
797
+ forester.preOrderTraversalAll(phy, function (n) {
798
+ if (n.properties && n.properties.length > 0) {
799
+ let propertiesLength = n.properties.length;
800
+ for (let i = 0; i < propertiesLength; ++i) {
801
+ let property = n.properties[i];
802
+ if (property.ref === fromRef && property.value) {
803
+ let newValue = '';
804
+ if (property.value.startsWith(targetValue_)) {
805
+ newValue = targetValue;
806
+ found = true;
807
+ } else {
808
+ newValue = property.value;
809
+ }
810
+ let newproperty = {};
811
+ newproperty.ref = toRef;
812
+ newproperty.value = newValue;
813
+ newproperty.datatype = 'xsd:string';
814
+ newproperty.applies_to = 'node';
815
+ n.properties.push(newproperty);
816
+ }
817
+ }
818
+ }
819
+ });
820
+ return found;
821
+ };
822
+
823
+ forester.collectPropertyRefs = function (phy, appliesTo, externalOnly) {
824
+ let propertyRefs = new Set();
825
+ forester.preOrderTraversalAll(phy, function (n) {
826
+
827
+ if (!externalOnly || externalOnly !== true || (!n.children && !n._children)) {
828
+ if (n.properties && n.properties.length > 0) {
829
+ let propertiesLength = n.properties.length;
830
+ for (let i = 0; i < propertiesLength; ++i) {
831
+ let property = n.properties[i];
832
+ if (property.ref && property.value && property.datatype && property.applies_to && property.applies_to === appliesTo) {
833
+ propertyRefs.add(property.ref);
834
+ }
835
+ }
836
+ }
837
+ }
838
+ });
839
+ return propertyRefs;
840
+ };
841
+
842
+
843
+ forester.shortenProperties = function (phy, appliesTo, externalOnly, sourceRef, targetRef) {
844
+ forester.preOrderTraversalAll(phy, function (n) {
845
+ if (!externalOnly || externalOnly !== true || (!n.children && !n._children)) {
846
+ if (n.properties && n.properties.length > 0) {
847
+ let propertiesLength = n.properties.length;
848
+ for (let i = 0; i < propertiesLength; ++i) {
849
+ let property = n.properties[i];
850
+ if (property.ref && property.value && property.datatype && property.applies_to && property.applies_to === appliesTo) {
851
+ if (property.ref === sourceRef) {
852
+ let s = property.value.trim().split(/\s+/);
853
+ if (s && s.length > 1) {
854
+ let newProp = {};
855
+ newProp.ref = targetRef;
856
+ if (s.length === 2) {
857
+ newProp.value = s[0];
858
+ } else {
859
+ newProp.value = s[0] + ' ' + s[1];
860
+ }
861
+ newProp.datatype = property.datatype;
862
+ newProp.applies_to = property.applies_to;
863
+ n.properties.push(newProp);
864
+ }
865
+ }
866
+ }
867
+ }
868
+ }
869
+ }
870
+ });
871
+ };
872
+
873
+ forester.collectBasicTreeProperties = function (tree) {
874
+ let properties = {};
875
+ properties.internalNodeData = false;
876
+ properties.nodeNames = false;
877
+ properties.longestNodeName = 0;
878
+ properties.branchLengths = false;
879
+ properties.confidences = false;
880
+ properties.nodeEvents = false;
881
+ properties.sequences = false;
882
+ properties.taxonomies = false;
883
+ properties.alignedMolSeqs = true;
884
+ properties.maxMolSeqLength = 0;
885
+ properties.externalNodesCount = 0;
886
+ properties.molSeqResiduesPerPosition = null;
887
+ properties.averageBranchLength = 0;
888
+ let bl_counter = 0;
889
+ let bl_sum = 0;
890
+ let molSeqs = [];
891
+ forester.preOrderTraversalAll(tree, function (n) {
892
+ if (n.name && n.name.length > 0) {
893
+ properties.nodeNames = true;
894
+ if (n.name.length > properties.longestNodeName) {
895
+ properties.longestNodeName = n.name.length;
896
+ }
897
+ if ((n.children || n._children) && (n.parent)) {
898
+ properties.internalNodeData = true;
899
+ }
900
+ }
901
+ if (!(n.children || n._children)) {
902
+ properties.externalNodesCount += 1;
903
+ }
904
+ if (n.branch_length && n.branch_length > 0) {
905
+ properties.branchLengths = true;
906
+ bl_sum += n.branch_length;
907
+ bl_counter += 1;
908
+ }
909
+ if (n.events) {
910
+ properties.nodeEvents = true;
911
+ }
912
+ if (n.sequences && n.sequences.length > 0) {
913
+ properties.sequences = true;
914
+
915
+ if (n.children || n._children) {
916
+ properties.internalNodeData = true;
917
+ } else {
918
+ let s = n.sequences[0];
919
+ if (s.mol_seq && s.mol_seq.value) {
920
+ if (s.mol_seq.value.length > properties.maxMolSeqLength) {
921
+ properties.maxMolSeqLength = s.mol_seq.value.length;
922
+ }
923
+ if (!s.mol_seq.is_aligned) {
924
+ properties.alignedMolSeqs = false;
925
+ } else {
926
+ molSeqs.push(s.mol_seq.value);
927
+ }
928
+ }
929
+ }
930
+ }
931
+ if (n.taxonomies && n.taxonomies.length > 0) {
932
+ properties.taxonomies = true;
933
+ if (n.children || n._children) {
934
+ properties.internalNodeData = true;
935
+ }
936
+ }
937
+ if (n.confidences && n.confidences.length > 0) {
938
+ properties.confidences = true;
939
+ }
940
+ if (n.properties && n.properties.length > 0) {
941
+ let l = n.properties.length;
942
+ for (let p = 0; p < l; ++p) {
943
+ if (n.properties[p].ref === BRANCH_EVENT_REF && n.properties[p].datatype === BRANCH_EVENT_DATATYPE && n.properties[p].applies_to === BRANCH_EVENT_APPLIES_TO) {
944
+ properties.branchEvents = true;
945
+ }
946
+ }
947
+ }
948
+
949
+ });
950
+
951
+ if (properties.alignedMolSeqs) {
952
+ properties.molSeqResiduesPerPosition = [];
953
+ for (let p = 0, maxLen = properties.maxMolSeqLength; p < maxLen; ++p) {
954
+ let mySet = new Set();
955
+ for (let i = 0, seqsLen = molSeqs.length; i < seqsLen; ++i) {
956
+ let molSeq = molSeqs[i];
957
+ let c = molSeq[p];
958
+ if (c) {
959
+ c = c.toUpperCase();
960
+ mySet.add(c);
961
+ if (!MSA_RESIDUE_SORT_MAP.has(c)) {
962
+ throw ("Unknown MSA residue '" + c + "'");
963
+ }
964
+ }
965
+
966
+ }
967
+ let myArray = forester.setToArray(mySet);
968
+ myArray.sort(function (a, b) {
969
+ return MSA_RESIDUE_SORT_MAP.get(a) - MSA_RESIDUE_SORT_MAP.get(b);
970
+ });
971
+ properties.molSeqResiduesPerPosition.push(myArray);
972
+ }
973
+ }
974
+
975
+ if (bl_counter > 0) {
976
+ properties.averageBranchLength = bl_sum / bl_counter;
977
+ }
978
+
979
+
980
+ return properties;
981
+ };
982
+
983
+ /**
984
+ * This calculates the sum of the external
985
+ * descendants of a node. It does not count descendants
986
+ * of collapsed nodes.
987
+ *
988
+ * @param node - A node.
989
+ * @returns {number} - The sum of external descendants.
990
+ */
991
+ forester.calcSumOfExternalDescendants = function (node) {
992
+ let nodes = 0;
993
+ forester.preOrderTraversal(node, function (n) {
994
+ if (!n.children) {
995
+ ++nodes;
996
+ }
997
+ });
998
+ return nodes;
999
+ };
1000
+
1001
+ /**
1002
+ * This calculates the sum of all the external
1003
+ * descendants of a node. It does count descendants
1004
+ * of collapsed nodes.
1005
+ *
1006
+ * @param node - A node.
1007
+ * @returns {number} - The sum of all external descendants.
1008
+ */
1009
+ forester.calcSumOfAllExternalDescendants = function (node) {
1010
+ let nodes = 0;
1011
+ forester.preOrderTraversalAll(node, function (n) {
1012
+ if (!(n.children || n._children)) {
1013
+ ++nodes;
1014
+ }
1015
+ });
1016
+ return nodes;
1017
+ };
1018
+
1019
+ /**
1020
+ * Returns true if at least one of the child nodes
1021
+ * of node is collapsed.
1022
+ *
1023
+ * @param node - A node.
1024
+ * @returns {boolean} - true if at least one of the child nodes is
1025
+ * collapsed
1026
+ */
1027
+ forester.isHasCollapsedNodes = function (node) {
1028
+ let collapsed = false;
1029
+ forester.preOrderTraversalAll(node, function (n) {
1030
+ if (n._children) {
1031
+ collapsed = true;
1032
+
1033
+ }
1034
+ });
1035
+ return collapsed;
1036
+ };
1037
+
1038
+ forester.getAllExternalNodes = function (node) {
1039
+ let nodes = [];
1040
+ forester.preOrderTraversalAll(node, function (n) {
1041
+ if (!n.children && !n._children) {
1042
+ nodes.push(n);
1043
+ }
1044
+ });
1045
+ return nodes;
1046
+ };
1047
+
1048
+ forester.getAllNodes = function (phy) {
1049
+ let nodes = [];
1050
+ forester.preOrderTraversalAll(forester.getTreeRoot(phy), function (n) {
1051
+ nodes.push(n);
1052
+ });
1053
+ return nodes;
1054
+ };
1055
+
1056
+ forester.calcMaxDepth = function (node) {
1057
+ let max = 0;
1058
+ forester.preOrderTraversalAll(node, function (n) {
1059
+ if (!n.children && !n._children) {
1060
+ let steps = forester.calcDepth(n);
1061
+ if (steps > max) {
1062
+ max = steps;
1063
+ }
1064
+ }
1065
+ });
1066
+ return max;
1067
+ };
1068
+
1069
+ forester.calcDepth = function (node) {
1070
+
1071
+ let steps = 0;
1072
+ while (node.parent && node.parent.parent) {
1073
+ steps++;
1074
+ node = node.parent;
1075
+ }
1076
+ return steps;
1077
+ };
1078
+
1079
+
1080
+ forester.calcBranchLengthSimpleStatistics = function (node) {
1081
+ let stats = {};
1082
+ stats.mean = 0;
1083
+ stats.min = Number.MAX_VALUE;
1084
+ stats.max = 0;
1085
+ stats.n = 0;
1086
+ let sum = 0;
1087
+ forester.preOrderTraversalAll(node, function (n) {
1088
+ if (n !== node && n.branch_length && n.branch_length >= 0) {
1089
+ ++stats.n;
1090
+ sum += n.branch_length;
1091
+ if (n.branch_length < stats.min) {
1092
+ stats.min = n.branch_length;
1093
+ }
1094
+ if (n.branch_length > stats.max) {
1095
+ stats.max = n.branch_length;
1096
+ }
1097
+ }
1098
+ });
1099
+ if (stats.n > 0) {
1100
+ stats.mean = sum / stats.n;
1101
+ }
1102
+ return stats;
1103
+ };
1104
+
1105
+ forester.calcMaxBranchLength = function (node) {
1106
+ let max = 0;
1107
+ forester.preOrderTraversalAll(node, function (n) {
1108
+ if (n !== node && n.branch_length && (n.branch_length > max)) {
1109
+ max = n.branch_length;
1110
+ }
1111
+ });
1112
+ return max;
1113
+ };
1114
+
1115
+
1116
+ forester.isHasNodeData = function (node) {
1117
+ return ((node.name && node.name.length > 0) || (node.taxonomies && node.taxonomies.length > 0) || (node.sequences && node.sequences.length > 0) || (node.properties && node.properties.length > 0));
1118
+ };
1119
+
1120
+
1121
+ forester.removeMaxBranchLength = function (node) {
1122
+ forester.preOrderTraversalAll(node, function (n) {
1123
+ if (n.max) {
1124
+ n.max = undefined;
1125
+ }
1126
+ });
1127
+ };
1128
+
1129
+
1130
+ forester.collapseToBranchLength = function (root, branchLength) {
1131
+ if (root.children && root.children.length === 1) {
1132
+ collapseToBranchLengthHelper(root.children[0], branchLength);
1133
+ }
1134
+
1135
+ function collapseToBranchLengthHelper(n, branchLength) {
1136
+ if (!(n.children || n._children)) {
1137
+ return;
1138
+ }
1139
+
1140
+ if (!n.max) {
1141
+ n.max = forester.calcMaxBranchLength(n);
1142
+ }
1143
+ let max = n.max;
1144
+ if (max < branchLength) {
1145
+ forester.collapse(n);
1146
+ } else {
1147
+ forester.unCollapse(n);
1148
+ for (let i = n.children.length - 1; i >= 0; i--) {
1149
+ collapseToBranchLengthHelper(n.children[i], branchLength);
1150
+ }
1151
+ }
1152
+ }
1153
+ };
1154
+
1155
+ forester.collapseToDepth = function (root, depth) {
1156
+ if (root.children && root.children.length === 1) {
1157
+ collapseToDepthHelper(root.children[0], 0, depth);
1158
+ }
1159
+
1160
+ function collapseToDepthHelper(n, d, depth) {
1161
+ if (!n.children && !n._children) {
1162
+ return;
1163
+ }
1164
+ if (d >= depth) {
1165
+ forester.collapse(n);
1166
+ } else {
1167
+ forester.unCollapse(n);
1168
+ ++d;
1169
+ for (let i = n.children.length - 1; i >= 0; i--) {
1170
+ collapseToDepthHelper(n.children[i], d, depth);
1171
+ }
1172
+ }
1173
+ }
1174
+ };
1175
+
1176
+ forester.collapse = function (node) {
1177
+ if (node.children) {
1178
+ node._children = node.children;
1179
+ node.children = null;
1180
+ }
1181
+ };
1182
+
1183
+ forester.unCollapse = function (node) {
1184
+ if (node._children) {
1185
+ node.children = node._children;
1186
+ node._children = null;
1187
+ }
1188
+ };
1189
+
1190
+ /**
1191
+ * To parse a New Hampshire (Newick) formatted tree.
1192
+ *
1193
+ * @param nhStr - A New Hampshire (Newick) formatted string.
1194
+ * @param confidenceValuesInBrackets - Set to true if confidence values are in brackets (default: true)
1195
+ * Format is: name:distance[confidence]
1196
+ * Example: "bcl2:0.000393[95]"
1197
+ * @param confidenceValuesAsInternalNames - Set to true if confidence values are represented by internal names (default: false).
1198
+ * @returns {{}} - A phylogenetic tree object.
1199
+ */
1200
+ forester.parseNewHampshire = function (nhStr, confidenceValuesInBrackets, confidenceValuesAsInternalNames) {
1201
+
1202
+ let NH_FORMAT_ERR_OPEN_PARENS = NH_FORMAT_ERR + 'likely cause: number of open parentheses is larger than number of close parentheses';
1203
+ let NH_FORMAT_ERR_CLOSE_PARENS = NH_FORMAT_ERR + 'likely cause: number of close parentheses is larger than number of open parentheses';
1204
+
1205
+ if (confidenceValuesInBrackets === undefined) {
1206
+ confidenceValuesInBrackets = true;
1207
+ }
1208
+ if (confidenceValuesAsInternalNames === undefined) {
1209
+ confidenceValuesAsInternalNames = false;
1210
+ }
1211
+ if ((confidenceValuesInBrackets === true) && (confidenceValuesAsInternalNames === true)) {
1212
+ throw ("confidence values cannot be both in brackets and as internal node names");
1213
+ }
1214
+
1215
+ let ancs = [];
1216
+ let x = {};
1217
+
1218
+ let sss = nhStr.replace(/\[\s*&.+?\]/g, '');
1219
+
1220
+ let ss = sss.split(/(;|\(|\)|,|:|"|')/);
1221
+ let ssl = ss.length;
1222
+ let in_double_q = false;
1223
+ let in_single_q = false;
1224
+ let buffer = '';
1225
+ for (let i = 0; i < ssl; ++i) {
1226
+ let element = ss[i].replace(/\s+/g, '');
1227
+
1228
+ if (element === '"' && !in_single_q) {
1229
+ if (!in_double_q) {
1230
+ in_double_q = true;
1231
+ } else {
1232
+ in_double_q = false;
1233
+ if (x.name && x.name.length > 0) {
1234
+ x.name = x.name + buffer;
1235
+ } else {
1236
+ x.name = buffer;
1237
+ }
1238
+ buffer = '';
1239
+ }
1240
+ } else if (element === "'" && !in_double_q) {
1241
+ if (!in_single_q) {
1242
+ in_single_q = true;
1243
+ } else {
1244
+ in_single_q = false;
1245
+ if (x.name && x.name.length > 0) {
1246
+ x.name = x.name + buffer;
1247
+ } else {
1248
+ x.name = buffer;
1249
+ }
1250
+ buffer = '';
1251
+ }
1252
+ } else {
1253
+ if (in_double_q || in_single_q) {
1254
+ buffer += ss[i].replace(/\s+/g, ' ');
1255
+ } else {
1256
+ if (element === '(') {
1257
+ if (!x) {
1258
+ throw (NH_FORMAT_ERR_CLOSE_PARENS);
1259
+ }
1260
+ let subtree1 = {};
1261
+ x.children = [subtree1];
1262
+ ancs.push(x);
1263
+ x = subtree1;
1264
+ } else if (element === ',') {
1265
+ if (ancs.length === 0) {
1266
+ throw (NH_FORMAT_ERR_CLOSE_PARENS);
1267
+ }
1268
+ let subtree2 = {};
1269
+ ancs[ancs.length - 1].children.push(subtree2);
1270
+ x = subtree2;
1271
+ } else if (element === ')') {
1272
+ x = ancs.pop();
1273
+ } else if (element === ':') {
1274
+ } else {
1275
+ let e = ss[i - 1];
1276
+ if (e) {
1277
+ e = e.trim();
1278
+ if ((e === ')') || (e === '(') || (e === ',')) {
1279
+ if (element && element.length > 0) {
1280
+ if (element.charAt(element.length - 1) === "]") {
1281
+ let o = element.indexOf('[');
1282
+ if (o > -1) {
1283
+ if (confidenceValuesInBrackets === true) {
1284
+ addConfidence(x, element);
1285
+ }
1286
+ x.name = element.substring(0, o);
1287
+ } else {
1288
+ x.name = element;
1289
+ }
1290
+ } else {
1291
+ x.name = element;
1292
+ let op = x.name.indexOf('[');
1293
+ if (op > -1) {
1294
+ let cl = x.name.indexOf(']');
1295
+ if (cl > op) {
1296
+ x.name = x.name.substring(0, op) + x.name.substring(cl + 1, x.name.length);
1297
+ }
1298
+ }
1299
+ }
1300
+ }
1301
+ } else if (e === ':') {
1302
+ if (element && element.length > 0) {
1303
+ if (element.charAt(element.length - 1) === ']') {
1304
+ let o1 = element.indexOf('[');
1305
+ if (o1 > -1) {
1306
+ if (confidenceValuesInBrackets === true) {
1307
+ addConfidence(x, element);
1308
+ }
1309
+ let bl = parseFloat(element.substring(0, o1));
1310
+ if (forester.isNumber(bl)) {
1311
+ x.branch_length = bl;
1312
+ }
1313
+ }
1314
+ } else {
1315
+ let b = parseFloat(element);
1316
+ if (forester.isNumber(b)) {
1317
+ x.branch_length = b;
1318
+ } else {
1319
+ throw (NH_FORMAT_ERR + 'could not parse branch-length from "' + element + '"');
1320
+ }
1321
+ }
1322
+ }
1323
+ } else if (e === '"' || e === "'") {
1324
+ if ((element && element.length > 0) && (x.name && x.name.length > 0)) {
1325
+ if (element.charAt(element.length - 1) === "]") {
1326
+ let opp = element.indexOf('[');
1327
+ if (opp > -1) {
1328
+ if (confidenceValuesInBrackets === true) {
1329
+ addConfidence(x, element);
1330
+ }
1331
+ x.name = x.name + element.substring(0, opp);
1332
+ } else {
1333
+ x.name = x.name + element;
1334
+ }
1335
+ } else {
1336
+ x.name = x.name + element;
1337
+ }
1338
+ }
1339
+ }
1340
+ }
1341
+
1342
+ }
1343
+ }
1344
+ }
1345
+ }
1346
+ if (ancs.length !== 0) {
1347
+ throw (NH_FORMAT_ERR_OPEN_PARENS);
1348
+ }
1349
+ if (!x) {
1350
+ throw (NH_FORMAT_ERR_CLOSE_PARENS);
1351
+ }
1352
+
1353
+ let phy = {};
1354
+ phy.children = [x];
1355
+
1356
+ forester.addParents(phy);
1357
+
1358
+ if (confidenceValuesAsInternalNames === true) {
1359
+ moveInternalNodeNamesToConfidenceValues(phy);
1360
+ }
1361
+
1362
+ return phy;
1363
+
1364
+ function addConfidence(x, element) {
1365
+ let confValue = parseConfidence(element);
1366
+ if (confValue != null) {
1367
+ x.confidences = [];
1368
+ let conf = {};
1369
+ conf.value = confValue;
1370
+ conf.type = 'unknown';
1371
+ x.confidences.push(conf);
1372
+ }
1373
+ }
1374
+
1375
+ function parseConfidence(element) {
1376
+ let o = element.indexOf('[');
1377
+ if (o > -1) {
1378
+ let s = element.substring(o + 1, element.length - 1);
1379
+ if (NUMBERS_ONLY_PATTERN.test(s)) {
1380
+ let confValue = parseFloat(s);
1381
+ if (forester.isNumber(confValue)) {
1382
+ return confValue;
1383
+ } else {
1384
+ throw (NH_FORMAT_ERR + 'could not parse confidence value from "' + element + '"');
1385
+ }
1386
+ }
1387
+ }
1388
+ return null;
1389
+ }
1390
+
1391
+ function moveInternalNodeNamesToConfidenceValues(node) {
1392
+ forester.preOrderTraversalAll(node, function (n) {
1393
+ if (n.children || n._children) {
1394
+ if (n.name) {
1395
+ let s = n.name;
1396
+ if (NUMBERS_ONLY_PATTERN.test(s)) {
1397
+ let confValue = parseFloat(s);
1398
+ if ((confValue != null) && (forester.isNumber(confValue))) {
1399
+ n.confidences = [];
1400
+ let conf1 = {};
1401
+ conf1.value = confValue;
1402
+ conf1.type = 'unknown';
1403
+ n.confidences.push(conf1);
1404
+ n.name = undefined;
1405
+ }
1406
+ }
1407
+ }
1408
+ }
1409
+ });
1410
+ }
1411
+ };
1412
+
1413
+ forester.isNumber = function (v) {
1414
+ if (v === undefined || v === null) {
1415
+ return false;
1416
+ }
1417
+ if (v != v) {
1418
+ // This can only be true if the v is NaN
1419
+ return false;
1420
+ }
1421
+ return true;
1422
+ };
1423
+
1424
+ forester.getOneDistinctTaxonomy = function (node) {
1425
+ let id = null;
1426
+ let code = null;
1427
+ let sn = null;
1428
+ let cn = null;
1429
+ let result = true;
1430
+ let sawTax = false;
1431
+ forester.preOrderTraversalAll(node, function (n) {
1432
+ if (n.taxonomies && n.taxonomies.length === 1) {
1433
+ let tax = n.taxonomies[0];
1434
+ if (tax.code && tax.code.length > 0) {
1435
+ sawTax = true;
1436
+ if (code === null) {
1437
+ code = tax.code;
1438
+ } else if (code !== tax.code) {
1439
+ result = false;
1440
+ return;
1441
+ }
1442
+ }
1443
+ if (tax.scientific_name && tax.scientific_name.length > 0) {
1444
+ sawTax = true;
1445
+ if (sn === null) {
1446
+ sn = tax.scientific_name;
1447
+ } else if (sn !== tax.scientific_name) {
1448
+ result = false;
1449
+ return;
1450
+ }
1451
+ }
1452
+ if (tax.common_name && tax.common_name.length > 0) {
1453
+ sawTax = true;
1454
+ if (cn === null) {
1455
+ cn = tax.common_name;
1456
+ } else if (cn !== tax.common_name) {
1457
+ result = false;
1458
+ return;
1459
+ }
1460
+ }
1461
+ if (tax.id && tax.id.value && tax.id.value.length > 0) {
1462
+ sawTax = true;
1463
+ let myid;
1464
+ if (tax.id.provider && tax.id.provider.length > 0) {
1465
+ myid = tax.id.provider + ':' + tax.id.value;
1466
+ } else {
1467
+ myid = tax.id.value;
1468
+ }
1469
+ if (id === null) {
1470
+ id = myid;
1471
+ } else if (id !== myid) {
1472
+ result = false;
1473
+
1474
+ }
1475
+ }
1476
+ } else if (!n.children && !n._children) {
1477
+ // If an external node lacks taxonomy, return false.
1478
+ result = false;
1479
+ }
1480
+ });
1481
+ if (!sawTax) {
1482
+ return null;
1483
+ }
1484
+ if (result === true) {
1485
+
1486
+ if (sn) {
1487
+ return sn;
1488
+ } else if (code) {
1489
+ return code;
1490
+ } else if (cn) {
1491
+ return cn;
1492
+ } else if (id) {
1493
+ return id;
1494
+ }
1495
+ }
1496
+ return null;
1497
+ };
1498
+
1499
+ forester.getOneDistinctNodePropertyValue = function (node, propertyRef) {
1500
+ let propValue = null;
1501
+ let result = true;
1502
+ forester.preOrderTraversalAll(node, function (n) {
1503
+ if (n.properties && n.properties.length > 0) {
1504
+ let propertiesLength = n.properties.length;
1505
+ let gotIt = false;
1506
+ for (let i = 0; i < propertiesLength; ++i) {
1507
+ let property = n.properties[i];
1508
+ if (property.ref && property.value && (property.applies_to === 'node') && (property.ref === propertyRef) && (property.value.length > 0)) {
1509
+ if (propValue === null) {
1510
+ propValue = property.value;
1511
+ } else if (propValue !== property.value) {
1512
+ result = false;
1513
+ return;
1514
+ }
1515
+ gotIt = true;
1516
+ }
1517
+ }
1518
+ if (!gotIt && !n.children && !n._children) {
1519
+ // If an external node lacks propertyRef, return false.
1520
+ result = false;
1521
+
1522
+ }
1523
+ }
1524
+ });
1525
+ if (propValue === null) {
1526
+ return null;
1527
+ }
1528
+ if (result === true) {
1529
+ return propValue;
1530
+ } else {
1531
+ return null;
1532
+ }
1533
+ };
1534
+
1535
+ /**
1536
+ * To be deprecated!
1537
+ *
1538
+ * @param phy
1539
+ * @returns {{}}
1540
+ */
1541
+ forester.moveSimpleCharacteristicsToProperties = function (phy) {
1542
+ let apptype;
1543
+ if (phy.desc) {
1544
+ apptype = 'ird:'
1545
+ } else {
1546
+ apptype = 'vipr:'
1547
+ }
1548
+
1549
+ let HOST = apptype + 'Host';
1550
+ let COUNTRY = apptype + 'Country';
1551
+ let YEAR = apptype + 'Year';
1552
+ let HA = apptype + 'HA';
1553
+ let NA = apptype + 'NA';
1554
+ let NODE = 'node';
1555
+ let STRING = 'xsd:string';
1556
+ let INT = 'xsd:integer';
1557
+
1558
+ forester.preOrderTraversalAll(phy, function (n) {
1559
+ if (n.simple_characteristics) {
1560
+ let sc = n.simple_characteristics;
1561
+ let props;
1562
+ if (sc.country && sc.country.length > 0) {
1563
+ props = {};
1564
+ props.ref = COUNTRY;
1565
+ props.datatype = STRING;
1566
+ props.applies_to = NODE;
1567
+ props.value = sc.country;
1568
+ addProperties(n, props);
1569
+ }
1570
+ if (sc.host && sc.host.length > 0) {
1571
+ props = {};
1572
+ props.ref = HOST;
1573
+ props.datatype = STRING;
1574
+ props.applies_to = NODE;
1575
+ props.value = sc.host;
1576
+ addProperties(n, props);
1577
+ }
1578
+ if (sc.year && sc.year.length > 0) {
1579
+ props = {};
1580
+ props.ref = YEAR;
1581
+ props.datatype = INT;
1582
+ props.applies_to = NODE;
1583
+ props.value = parseInt(sc.year);
1584
+ addProperties(n, props);
1585
+ }
1586
+ if (sc.ha && sc.ha.length > 0) {
1587
+ props = {};
1588
+ props.ref = HA;
1589
+ props.datatype = INT;
1590
+ props.applies_to = NODE;
1591
+ props.value = parseInt(sc.ha);
1592
+ addProperties(n, props);
1593
+ }
1594
+ if (sc.na && sc.na.length > 0) {
1595
+ props = {};
1596
+ props.ref = NA;
1597
+ props.datatype = INT;
1598
+ props.applies_to = NODE;
1599
+ props.value = parseInt(sc.na);
1600
+ addProperties(n, props);
1601
+ }
1602
+ n.simple_characteristics = undefined;
1603
+ }
1604
+ });
1605
+
1606
+ function addProperties(n, props) {
1607
+ if (props) {
1608
+ if (!n.properties) {
1609
+ n.properties = [];
1610
+ }
1611
+ let alreadyHave = false;
1612
+ let l = n.properties.length;
1613
+ for (let i = 0; i < l; ++i) {
1614
+ if (n.properties[i].ref === props.ref) {
1615
+ alreadyHave = true;
1616
+ break;
1617
+ }
1618
+ }
1619
+ if (!alreadyHave) {
1620
+ n.properties.push(props);
1621
+ }
1622
+ }
1623
+ }
1624
+ };
1625
+
1626
+
1627
+ /**
1628
+ * To convert a phylogentic tree object to a New Hampshire (Newick) formatted string.
1629
+ *
1630
+ * @param phy - A phylogentic tree object.
1631
+ * @param decPointsMax - Maximal number of decimal points for branch lengths (optional)
1632
+ * @param replaceChars - To replace illegal characters (),:;"' instead of surrounding with quotation marks
1633
+ * @param writeConfidences - to write confidence values in brackets
1634
+ * @returns {*} - a New Hampshire (Newick) formatted string.
1635
+ */
1636
+ forester.toNewHampshire = function (phy, decPointsMax, replaceChars, writeConfidences) {
1637
+ let nh = "";
1638
+ if (phy.children && phy.children.length === 1) {
1639
+ toNewHampshireHelper(phy.children[0], true);
1640
+ }
1641
+ if (nh.length > 0) {
1642
+ return nh + ";";
1643
+ }
1644
+ return nh;
1645
+
1646
+ function toNewHampshireHelper(node, last) {
1647
+ if (node.children) {
1648
+ let l = node.children.length;
1649
+ nh += "(";
1650
+ for (let i = 0; i < l; ++i) {
1651
+ toNewHampshireHelper(node.children[i], i === l - 1);
1652
+ }
1653
+ nh += ")";
1654
+ } else if (node._children) {
1655
+ let ll = node._children.length;
1656
+ nh += "(";
1657
+ for (let ii = 0; ii < ll; ++ii) {
1658
+ toNewHampshireHelper(node._children[ii], ii === ll - 1);
1659
+ }
1660
+ nh += ")";
1661
+ }
1662
+ if (node.name && node.name.length > 0) {
1663
+ if (replaceChars === true) {
1664
+ nh += replaceUnsafeChars(node.name);
1665
+ } else {
1666
+ let myName = node.name.replace(/\s+/g, ' ');
1667
+ if (/[\s,():;'"\[\]]/.test(myName)) {
1668
+ if ((myName.indexOf('"') > -1) && (myName.indexOf("'") > -1)) {
1669
+ nh += '"' + myName.replace(/"/g, "'") + '"';
1670
+ } else if (myName.indexOf('"') > -1) {
1671
+ nh += "'" + myName + "'";
1672
+ } else {
1673
+ nh += '"' + myName + '"';
1674
+ }
1675
+ } else {
1676
+ nh += myName;
1677
+ }
1678
+ }
1679
+ }
1680
+ if (node.branch_length !== undefined && node.branch_length !== null) {
1681
+ if (decPointsMax && decPointsMax > 0) {
1682
+ nh += ":" + forester.roundNumber(node.branch_length, decPointsMax);
1683
+ } else {
1684
+ nh += ":" + node.branch_length;
1685
+ }
1686
+ }
1687
+ if (writeConfidences && node.confidences && node.confidences.length === 1 && node.confidences[0].value !== undefined && node.confidences[0].value !== null) {
1688
+ if (decPointsMax && decPointsMax > 0) {
1689
+ nh += "[" + forester.roundNumber(node.confidences[0].value, decPointsMax) + "]";
1690
+ } else {
1691
+ nh += "[" + node.confidences[0].value + "]";
1692
+ }
1693
+ }
1694
+ if (!last) {
1695
+ nh += ",";
1696
+ }
1697
+ }
1698
+
1699
+ function replaceUnsafeChars(str) {
1700
+ return str.replace(/[\s,():;'"\[\]]+/g, '_');
1701
+ }
1702
+ };
1703
+
1704
+ forester.getMolecularSequencesAsFasta = function (node, sep) {
1705
+ let fasta_all = '';
1706
+ let ext_nodes = forester.getAllExternalNodes(node).reverse();
1707
+ for (let j = 0, l = ext_nodes.length; j < l; ++j) {
1708
+ let n = ext_nodes[j];
1709
+ if (n.sequences) {
1710
+ for (let i = 0; i < n.sequences.length; ++i) {
1711
+ let s = n.sequences[i];
1712
+ if (s.mol_seq && s.mol_seq.value && s.mol_seq.value.length > 0) {
1713
+ let seq = s.mol_seq.value;
1714
+ let seqname = j;
1715
+ if (s.name && s.name.length > 0) {
1716
+ seqname = s.name
1717
+ } else if (n.name && n.name.length > 0) {
1718
+ seqname = n.name
1719
+ }
1720
+ let split_seq_ary = seq.match(/.{1,80}/g);
1721
+ let split_seq = '';
1722
+ for (let ii = 0; ii < split_seq_ary.length; ++ii) {
1723
+ split_seq += split_seq_ary[ii] + sep;
1724
+ }
1725
+
1726
+ let fasta = '>' + seqname + sep + split_seq;
1727
+ fasta_all += fasta;
1728
+ }
1729
+ }
1730
+ }
1731
+ }
1732
+ return fasta_all;
1733
+ }
1734
+
1735
+ forester.roundNumber = function (num, dec) {
1736
+ return Math.round(num * Math.pow(10, dec)) / Math.pow(10, dec);
1737
+ };
1738
+
1739
+ forester.isString = function (s) {
1740
+ return (typeof s === 'string' || s instanceof String);
1741
+ };
1742
+
1743
+
1744
+ // --------------------------------------------------------------
1745
+ // For exporting
1746
+ // --------------------------------------------------------------
1747
+ if (typeof module !== 'undefined' && module.exports && !global.xmldocAssumeBrowser) module.exports.forester = forester; else if (typeof window !== "undefined") window.forester = forester; else this.forester = forester;
1748
+ })();
1944
1749