@euphrasiologist/lwphylo 1.1.5 → 1.1.9

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.
@@ -1,867 +0,0 @@
1
- // https://github.com/Euphrasiologist/lwPhylo#readme v1.1.5 Copyright 2021 Max Brown
2
- (function (global, factory) {
3
- typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
4
- typeof define === 'function' && define.amd ? define(['exports'], factory) :
5
- (global = global || self, factory(global.lwphylo = global.lwphylo || {}));
6
- }(this, (function (exports) { 'use strict';
7
-
8
- // based on https://github.com/d3/d3-plugins/blob/master/fisheye/fisheye.js
9
- // now archived d3 code?
10
-
11
- const phisheye = {
12
- circular: () => {
13
- var radius = 200,
14
- distortion = 2,
15
- k0,
16
- k1,
17
- focus = [0, 0],
18
- scales = {};
19
-
20
- function fisheye(d) {
21
- var dx = scales.xscale(d.x) - focus[0],
22
- dy = scales.yscale(d.y) - focus[1],
23
- dd = Math.sqrt(dx * dx + dy * dy);
24
- if (!dd || dd >= radius)
25
- return {
26
- x: scales.xscale(d.x),
27
- y: scales.yscale(d.y),
28
- z: dd >= radius ? 1 : 10
29
- };
30
- var k = ((k0 * (1 - Math.exp(-dd * k1))) / dd) * .75 + .25;
31
- return {
32
- x: focus[0] + dx * k,
33
- y: focus[1] + dy * k,
34
- z: Math.min(k, 10)
35
- };
36
- }
37
-
38
- function rescale() {
39
- k0 = Math.exp(distortion);
40
- k0 = (k0 / (k0 - 1)) * radius;
41
- k1 = distortion / radius;
42
- return fisheye;
43
- }
44
-
45
- fisheye.radius = function (_) {
46
- if (!arguments.length) return radius;
47
- radius = +_;
48
- return rescale();
49
- };
50
-
51
- fisheye.distortion = function (_) {
52
- if (!arguments.length) return distortion;
53
- distortion = +_;
54
- return rescale();
55
- };
56
-
57
- fisheye.focus = function (_) {
58
- if (!arguments.length) return focus;
59
- focus = _;
60
- return fisheye;
61
- };
62
-
63
- fisheye.scales = function (_, __) {
64
- if (!arguments.length) return scales;
65
- scales = {
66
- xscale: _,
67
- yscale: __
68
- };
69
- return fisheye;
70
- };
71
-
72
- return rescale();
73
- }
74
- };
75
-
76
- /**
77
- * Convert polar co-ordinates to cartesian
78
- * https://stackoverflow.com/questions/5736398/how-to-calculate-the-svg-path-for-an-arc-of-a-circle
79
- */
80
-
81
- function polarToCartesian (centerX, centerY, radius, angleInRadians) {
82
- return {
83
- x: centerX + (radius * Math.cos(angleInRadians)),
84
- y: centerY + (radius * Math.sin(angleInRadians))
85
- };
86
- }
87
-
88
- /**
89
- * Describe the arc to draw
90
- * https://stackoverflow.com/questions/5736398/how-to-calculate-the-svg-path-for-an-arc-of-a-circle
91
- */
92
-
93
- function describeArc (x, y, radius, startAngle, endAngle) {
94
-
95
- var start = polarToCartesian(x, y, radius, startAngle);
96
- var end = polarToCartesian(x, y, radius, endAngle);
97
-
98
- // TODO: refers to middle zero below... but no large arcs needed..?
99
- // var largeArcFlag = endAngle - startAngle <= Math.PI ? "0" : "1";
100
-
101
- var d = [
102
- "M", start.x, start.y,
103
- "A", radius, radius, 0, 0, 0, end.x, end.y
104
- ].join(" ");
105
-
106
- return d;
107
- }
108
-
109
- /**
110
- * Recursive function for pre-order traversal of tree
111
- */
112
-
113
- function preorder(node, list = []) {
114
- list.push(node);
115
- for (var i = 0; i < node.children.length; i++) {
116
- list = preorder(node.children[i], list);
117
- }
118
- return (list);
119
- }
120
-
121
- /**
122
- * Convert parsed Newick tree from readTree() into data
123
- * frame.
124
- * this is akin to a "phylo" object in R.
125
- */
126
-
127
- function fortify (tree, sort = true) {
128
- var df = [];
129
-
130
- for (const node of preorder(tree)) {
131
- if (node.parent === null) {
132
- df.push({
133
- 'parentId': null,
134
- 'parentLabel': null,
135
- 'thisId': node.id,
136
- 'thisLabel': node.label,
137
- 'children': node.children.map(x => x.id),
138
- 'branchLength': 0.,
139
- 'isTip': false,
140
- 'x': node.x,
141
- 'y': node.y,
142
- 'angle': node.angle
143
- });
144
- }
145
- else {
146
- df.push({
147
- 'parentId': node.parent.id,
148
- 'parentLabel': node.parent.label,
149
- 'thisId': node.id,
150
- 'thisLabel': node.label,
151
- 'children': node.children.map(x => x.id),
152
- 'branchLength': node.branchLength,
153
- 'isTip': (node.children.length == 0),
154
- 'x': node.x,
155
- 'y': node.y,
156
- 'angle': node.angle
157
- });
158
- }
159
- }
160
-
161
- if (sort) {
162
- df = df.sort(function (a, b) {
163
- return a.thisId - b.thisId;
164
- });
165
- }
166
- return (df);
167
- }
168
-
169
- /**
170
- * Recursive function for breadth-first search of a tree
171
- * the root node is visited first.
172
- */
173
-
174
- function levelorder(root) {
175
- // aka breadth-first search
176
- var queue = [root],
177
- result = [],
178
- curnode;
179
-
180
- while (queue.length > 0) {
181
- curnode = queue.pop();
182
- result.push(curnode);
183
- for (const child of curnode.children) {
184
- queue.push(child);
185
- }
186
- }
187
- return (result);
188
- }
189
-
190
- /**
191
- * Count the number of tips that descend from this node
192
- */
193
-
194
- function numTips (thisnode) {
195
- var result = 0;
196
- for (const node of levelorder(thisnode)) {
197
- if (node.children.length == 0) result++;
198
- }
199
- return (result);
200
- }
201
-
202
- /**
203
- * Iterable mean
204
- * Poached from https://github.com/d3/d3-array/blob/master/src/mean.js
205
- * (Other array means buggered up the tree)
206
- */
207
-
208
- function mean (values, valueof) {
209
- let count = 0;
210
- let sum = 0;
211
- if (valueof === undefined) {
212
- for (let value of values) {
213
- if (value != null && (value = +value) >= value) {
214
- ++count, sum += value;
215
- }
216
- }
217
- } else {
218
- let index = -1;
219
- for (let value of values) {
220
- if ((value = valueof(value, ++index, values)) != null && (value = +value) >= value) {
221
- ++count, sum += value;
222
- }
223
- }
224
- }
225
- if (count) return sum / count;
226
- }
227
-
228
- /**
229
- * Take a parsed tree and get the important data from them (i.e. radii, arcs).
230
- */
231
-
232
- function radialData (node) {
233
- // should start out the same as get_horizontal
234
- // it's very similar in fact, but keeping separate for clarity.
235
- var pd = fortify(node);
236
-
237
- var tip_number = numTips(node);
238
-
239
- // make tip angles
240
- var tipID = 1;
241
- for (let i = 0; i < pd.length; i++) {
242
- if (pd[i].isTip == true) {
243
- pd[i].angle = (tipID / tip_number) * 2 * Math.PI;
244
- tipID += 1;
245
- }
246
- }
247
-
248
- // probably incredibly inefficient for large trees.
249
- // gets the y values of two child branches by looping through the whole tree...
250
- function internalNodeAngle(child_1, child_2) {
251
- for (var i = 0; i < pd.length; i++) {
252
- if (pd[i].thisId === child_1) {
253
- var angle_1 = pd[i].angle;
254
- }
255
- if (pd[i].thisId === child_2) {
256
- var angle_2 = pd[i].angle;
257
- }
258
- }
259
- return mean([angle_1, angle_2]); // see utils.js
260
- }
261
-
262
- // if the node is not a tip...
263
- for (let i = 0; i < pd.length; i++) {
264
- if (pd[i].isTip === false) {
265
- // then y0 === y1 and is the mean of the parental nodes
266
- pd[i].angle = internalNodeAngle(pd[i].children[0], pd[i].children[1]);
267
- }
268
- }
269
-
270
- // find root
271
- var root = pd.map(d => d.parentId === null ? d.thisId : null).filter(d => d != null)[0];
272
-
273
- // sort the data temporarily in decreasing parentId
274
- pd.sort((a, b) => b.thisId - a.thisId);
275
-
276
- // get the branchlength of the parentID
277
- function getParentBranchLength(current_parentId) {
278
- for (var i = 0; i < pd.length; i++) {
279
- if (pd[i].thisId === current_parentId) {
280
- var branchLength = pd[i].r;
281
- }
282
- }
283
- return branchLength;
284
- }
285
-
286
- // assign depths (branch lengths) to radii
287
- for (let i = 0; i < pd.length; i++) {
288
- // special cases where parent is the root.
289
- if (pd[i].parentId === root) {
290
-
291
- if (pd[i].isTip === true) {
292
- // radius is branch length at root?
293
- pd[i].r = pd[i].branchLength;
294
- pd[i].x = pd[i].branchLength;
295
- pd[i].y = 0;
296
-
297
- } else { // it's a node
298
- // radius is branch length at root?
299
- pd[i].r = pd[i].branchLength;
300
- pd[i].x = pd[i].branchLength * Math.cos(pd[i].angle);
301
- pd[i].y = pd[i].branchLength * Math.sin(pd[i].angle);
302
- }
303
-
304
- } else {
305
- // the x0 is that of the parent
306
- var parent_branchLength = getParentBranchLength(pd[i].parentId);
307
- // the x1 is the sum of parent and current branchlength
308
- pd[i].r = parent_branchLength + pd[i].branchLength;
309
- // at the same time we can make x and y from polar coordinates
310
- // this is creating some NaN's, may cause problems later.
311
- pd[i].x = (parent_branchLength + pd[i].branchLength) * Math.cos(pd[i].angle);
312
- pd[i].y = (parent_branchLength + pd[i].branchLength) * Math.sin(pd[i].angle);
313
- }
314
- }
315
-
316
- // at this point the first element is the root
317
- pd[0].r = 0;
318
- pd[0].x = 0;
319
- pd[0].y = 0;
320
-
321
- // return the original sorted data
322
- //pd.sort((a,b) => a.thisId - b.thisId)
323
-
324
- return pd;
325
- }
326
-
327
- /**
328
- * Take a parsed tree and get the radii of each of the nodes.
329
- */
330
-
331
- function getRadii (node) {
332
- var data = radialData(node);
333
-
334
- // for the current iteration of the loop find the matching parentId
335
- function getRadius(current_node) {
336
- for (var i = 0; i < data.length; i++) {
337
- if (data[i].thisId === current_node.parentId) {
338
- var radius = data[i].r;
339
- }
340
- }
341
- return radius;
342
- }
343
-
344
- var arcs = [];
345
- // find root
346
- var root = data.map(d => d.parentId === null ? d.thisId : null).filter(d => d != null)[0];
347
-
348
- for (var i = 0; i < data.length; i++) {
349
- if (data[i].thisId !== root) {
350
-
351
- arcs.push({
352
- 'thisId': data[i].thisId,
353
- 'thisLabel': data[i].thisLabel,
354
- 'x0': data[i].x,
355
- // radius of the parent * cos(angle)
356
- 'x1': getRadius(data[i]) * Math.cos(data[i].angle),
357
- 'y0': data[i].y,
358
- // radius of the parent * sin(angle)
359
- 'y1': getRadius(data[i]) * Math.sin(data[i].angle),
360
- 'isTip': data[i].isTip
361
- });
362
- }
363
- }
364
-
365
- return arcs;
366
-
367
- }
368
-
369
- /**
370
- * Takes parent data and returns a start value (radians),
371
- * end value (radians), and radius of circle to draw an
372
- * arc from.
373
- * thanks https://codereview.stackexchange.com/questions/187510/angle-reflection-function
374
- */
375
-
376
- function reflectAngle (rad, dir) {
377
- const c = Math.cos(rad), s = Math.sin(rad);
378
- const PI_sub = "3.1415";
379
-
380
- function checkSign(x) {
381
- if (x.toString().includes(PI_sub)) {
382
- x = Math.abs(x);
383
- }
384
- return x;
385
- }
386
-
387
- return checkSign(Math.atan2(...(dir === "X" ? [s, -c] : [-s, c])));
388
- }
389
-
390
- /**
391
- * Takes parent data and returns a start value (radians),
392
- * end value (radians), and radius of circle to draw an
393
- * arc from
394
- */
395
-
396
- function getArcs (pd) {
397
- // must return start of arc and end
398
- // these are pairs of edges that have the same parent
399
- // start is the min(angle) of the children and end is the max(angle)
400
- // radius is the radius of the parent
401
- // origin is 0, 0
402
-
403
- var data = [];
404
- var root = pd.map(d => d.parentId === null ? d.thisId : null).filter(d => d != null)[0];
405
-
406
-
407
- // get the branchlength of the parentID
408
- function sisterAngle(current_parentId) {
409
- for (var i = 0; i < pd.length; i++) {
410
- if (pd[i].parentId === current_parentId) {
411
- var sister_angle = pd[i].angle;
412
- }
413
- }
414
- return sister_angle;
415
- }
416
-
417
- function parentRadius(current_parentId) {
418
- for (var i = 0; i < pd.length; i++) {
419
- if (pd[i].thisId === current_parentId) {
420
- var parent_r = pd[i].r;
421
- }
422
- }
423
- return parent_r;
424
- }
425
-
426
- for (let i = 0; i < pd.length; i++) {
427
- if (pd[i].thisId !== root) {
428
- data.push({
429
- 'start': reflectAngle(Math.min(pd[i].angle, sisterAngle(pd[i].parentId)), "Y"),
430
- 'end': reflectAngle(Math.max(pd[i].angle, sisterAngle(pd[i].parentId)), "Y"),
431
- 'radius': parentRadius(pd[i].parentId),
432
- 'thisId': pd[i].thisId,
433
- 'parentId': pd[i].parentId
434
-
435
- });
436
- }
437
- }
438
- // TODO: understand why this works (and it may not in every case)... test!
439
- for (let i = 0; i < data.length; i++) {
440
- if (Math.sign(data[i].start) !== Math.sign(data[i].end)) {
441
- data[i].end = Math.abs(data[i].end);
442
- data[i].start = -Math.abs(data[i].start);
443
- }
444
- }
445
-
446
- return data.filter(d => d.start !== d.end & d.radius !== 0);
447
-
448
- }
449
-
450
- /**
451
- * Simple wrapper function for getting the data,
452
- * radii, and arcs.
453
- */
454
-
455
- function radialLayout (node) {
456
- var data = {};
457
-
458
- // TODO: does radial_data need to be printed out?
459
- data.data = radialData(node);
460
- data.radii = getRadii(node);
461
- data.arcs = getArcs(data.data);
462
-
463
- return data;
464
- }
465
-
466
- /**
467
- * Rectangle layout algorithm.
468
- * Attempted copy of .layout.rect() function here https://github.com/ArtPoon/ggfree/blob/master/R/tree.R
469
- */
470
-
471
- function getHorizontal (node) {
472
- // phylodata layout...
473
- var pd = fortify(node);
474
- // set y to null... not needed here.
475
- pd.map(d => d.y = null);
476
- // where y corresponds to a tip, return tip number
477
- // make y0 and y1 equal.
478
- var tipID = 1;
479
- for (let i = 0; i < pd.length; i++) {
480
- if (pd[i].isTip === true) {
481
- pd[i].y0 = tipID;
482
- pd[i].y1 = tipID;
483
- tipID += 1;
484
- }
485
- }
486
-
487
- // probably incredibly inefficient for large trees.
488
- // gets the y values of two child branches by looping through the whole tree...
489
- function yVals(child_1, child_2) {
490
- for (var i = 0; i < pd.length; i++) {
491
- if (pd[i].thisId === child_1) {
492
- var y1 = pd[i].y0;
493
- }
494
- if (pd[i].thisId === child_2) {
495
- var y2 = pd[i].y0;
496
- }
497
- }
498
- return mean(([y1, y2]))
499
- }
500
-
501
- // if the node is not a tip...
502
- for (let i = 0; i < pd.length; i++) {
503
- if (pd[i].isTip === false) {
504
- // then y0 === y1 and is the mean of the parental nodes
505
- pd[i].y0 = yVals(pd[i].children[0], pd[i].children[1]);
506
- pd[i].y1 = yVals(pd[i].children[0], pd[i].children[1]);
507
- }
508
- }
509
-
510
- // find root
511
- var root = pd.map(d => d.parentId === null ? d.thisId : null).filter(d => d != null)[0];
512
-
513
- // sort the data temporarily in decreasing parentId
514
- pd.sort((a, b) => b.thisId - a.thisId);
515
-
516
- // get the branchlength of the parentID
517
- function getParentBranchLength(current_parentId) {
518
- for (var i = 0; i < pd.length; i++) {
519
- if (pd[i].thisId === current_parentId) {
520
- var branchLength = pd[i].x1;
521
- }
522
- }
523
- return branchLength;
524
- }
525
-
526
- // last loop...
527
- // now get the x0 and x1 coordinates.
528
- for (let i = 0; i < pd.length; i++) {
529
- // special cases where parent is the root.
530
- if (pd[i].parentId === root) {
531
- // x0 = 0 and x1 is branch length
532
- pd[i].x0 = 0;
533
- pd[i].x1 = pd[i].branchLength;
534
- } else {
535
- // the x0 is that of the parent
536
- var parent_branchLength = getParentBranchLength(pd[i].parentId);
537
- pd[i].x0 = parent_branchLength;
538
- // the x1 is the sum of parent and current branchlength
539
- pd[i].x1 = parent_branchLength + pd[i].branchLength;
540
- }
541
- }
542
-
543
- // return the original sorted data
544
- pd.sort((a, b) => a.thisId - b.thisId);
545
-
546
- // remove root?
547
-
548
- // finally get rid of unwanted y, x and angle properties
549
- /* eslint-disable no-unused-vars */
550
- return pd.map(({ y, x, angle, ...item }) => item);
551
- /* eslint-enable no-unused-vars */
552
- }
553
-
554
- /**
555
- * Get the vertical lines to draw.
556
- */
557
-
558
- function getVertical (node) {
559
- var data = getHorizontal(node);
560
-
561
- // for the current iteration of the loop find the matching parentId
562
- // then take the difference
563
- function findPairs(current_node) {
564
- for (var i = 0; i < data.length; i++) {
565
- if (data[i].parentId === current_node.parentId) {
566
- var height = Math.abs(data[i].y0 - current_node.y0);
567
- }
568
- }
569
- return height;
570
- }
571
-
572
- var verticals = [];
573
- // find root
574
- var root = data.map(d => d.parentId === null ? d.thisId : null).filter(d => d != null)[0];
575
-
576
- for (var i = 0; i < data.length; i++) {
577
- if (data[i].thisId !== root) {
578
-
579
- verticals.push({
580
- 'thisId': data[i].thisId,
581
- 'x0': data[i].x0,
582
- 'x1': data[i].x0, // x values remain constant
583
- 'y0': data[i].y0,
584
- 'y1': data[i].y0 + findPairs(data[i]),
585
- 'heights': findPairs(data[i])
586
- });
587
-
588
- }
589
- }
590
-
591
- return verticals;
592
- }
593
-
594
- /**
595
- * Simple wrapper for rectangle layout functions
596
- */
597
-
598
- function rectangleLayout (node) {
599
- var data = {};
600
-
601
- data.data = getHorizontal(node); // horizontal_lines
602
- data.vertical_lines = getVertical(node);
603
-
604
- return data;
605
- }
606
-
607
- /**
608
- * Convert parsed Newick tree from fortify() into data frame of edges
609
- * this is akin to a "phylo" object in R, where thisID and parentId
610
- * are the $edge slot. I think.
611
- */
612
-
613
- function edges (df, rectangular = false) {
614
- var result = [],
615
- parent;
616
-
617
- // make sure data frame is sorted
618
- df.sort(function (a, b) {
619
- return a.thisId - b.thisId;
620
- });
621
-
622
- for (const row of df) {
623
- if (row.parentId === null) {
624
- continue; // skip the root
625
- }
626
- parent = df[row.parentId];
627
- if (parent === null || parent === undefined) continue;
628
-
629
- if (rectangular) {
630
- var pair1 = {
631
- x1: row.x,
632
- y1: row.y,
633
- id1: row.thisId,
634
- x2: parent.x,
635
- y2: row.y,
636
- id2: undefined
637
- };
638
- result.push(pair1);
639
- var pair2 = {
640
- x1: parent.x,
641
- y1: row.y,
642
- id1: undefined,
643
- x2: parent.x,
644
- y2: parent.y,
645
- id2: row.parentId
646
- };
647
- result.push(pair2);
648
- } else {
649
- var pair3 = {
650
- x1: row.x,
651
- y1: row.y,
652
- id1: row.thisId,
653
- x2: parent.x,
654
- y2: parent.y,
655
- id2: row.parentId
656
- };
657
- result.push(pair3);
658
- }
659
- }
660
- return result;
661
- }
662
-
663
- /**
664
- * Equal-angle layout algorithm for unrooted trees.
665
- * Populates the nodes of a tree object with information on
666
- * the angles to draw branches such that they do not
667
- * intersect.
668
- */
669
-
670
- function equalAngleLayout(node) {
671
- if (node.parent === null) {
672
- // node is root
673
- node.start = 0.; // guarantees no arcs overlap 0
674
- node.end = 2.; // *pi
675
- node.angle = 0.; // irrelevant
676
- node.ntips = numTips(node);
677
- node.x = 0;
678
- node.y = 0;
679
- }
680
-
681
- var child, arc, lastStart = node.start;
682
-
683
- for (var i = 0; i < node.children.length; i++) {
684
- // the child of the current node
685
- child = node.children[i];
686
- // the number of tips the child node has
687
- child.ntips = numTips(child);
688
-
689
- // assign proportion of arc to this child
690
- arc = (node.end - node.start) * child.ntips / node.ntips;
691
- child.start = lastStart;
692
- child.end = child.start + arc;
693
-
694
- // bisect the arc
695
- child.angle = child.start + (child.end - child.start) / 2.;
696
- lastStart = child.end;
697
-
698
- // map to coordinates
699
- child.x = node.x + child.branchLength * Math.sin(child.angle * Math.PI);
700
- child.y = node.y + child.branchLength * Math.cos(child.angle * Math.PI);
701
-
702
- // climb up
703
- equalAngleLayout(child);
704
- }
705
- // had to add this!
706
- return node;
707
- }
708
-
709
- /**
710
- * Simple wrapper function for equalAngleLayout()
711
- */
712
-
713
- function unrooted (node) {
714
- var data = {};
715
- // use the Felsenstein equal angle layout algorithm
716
- var eq = fortify(equalAngleLayout(node));
717
- data.data = eq;
718
- // make the edges dataset
719
- data.edges = edges(eq);
720
-
721
- return data;
722
- }
723
-
724
- // find the x & y coordinates of the parental species
725
- function parentFisheye (d, data /* e.g. lwPhylo.unrooted.data */) {
726
- for (let i = 0; i < data.length; i++) {
727
- if (d.parentId === data[i].thisId) {
728
- return {
729
- px: data[i].fisheye.x,
730
- py: data[i].fisheye.y
731
- };
732
- }
733
- }
734
- }
735
-
736
- /**
737
- * Parse a Newick tree string into a doubly-linked
738
- * list of JS Objects. Assigns node labels, branch
739
- * lengths and node IDs (numbering terminal before
740
- * internal nodes).
741
- */
742
-
743
- function readTree (text) {
744
- // remove whitespace
745
- text = text.replace(/ \t/g, '');
746
-
747
- var tokens = text.split(/(;|\(|\)|,)/),
748
- root = { 'parent': null, 'children': [] },
749
- curnode = root,
750
- nodeId = 0;
751
-
752
- for (const token of tokens) {
753
- if (token == "" || token == ';') {
754
- continue
755
- }
756
- if (token == '(') {
757
- // add a child to current node
758
- let child = {
759
- 'parent': curnode,
760
- 'children': []
761
- };
762
- curnode.children.push(child);
763
- curnode = child; // climb up
764
- }
765
- else if (token == ',') {
766
- // climb down, add another child to parent
767
- curnode = curnode.parent;
768
- let child = {
769
- 'parent': curnode,
770
- 'children': []
771
- };
772
- curnode.children.push(child);
773
- curnode = child; // climb up
774
- }
775
- else if (token == ')') {
776
- // climb down twice
777
- curnode = curnode.parent;
778
- if (curnode === null) {
779
- break;
780
- }
781
- }
782
- else {
783
- var nodeinfo = token.split(':');
784
-
785
- if (nodeinfo.length == 1) {
786
- if (token.startsWith(':')) {
787
- curnode.label = "";
788
- curnode.branchLength = parseFloat(nodeinfo[0]);
789
- } else {
790
- curnode.label = nodeinfo[0];
791
- curnode.branchLength = null;
792
- }
793
- }
794
- else if (nodeinfo.length == 2) {
795
- curnode.label = nodeinfo[0];
796
- curnode.branchLength = parseFloat(nodeinfo[1]);
797
- }
798
- else {
799
- // TODO: handle edge cases with >1 ":"
800
- console.warn(token, "I don't know what to do with two colons!");
801
- }
802
- curnode.id = nodeId++; // assign then increment
803
- }
804
- }
805
-
806
- curnode.id = nodeId;
807
-
808
- return (root);
809
- }
810
-
811
- /**
812
- * Subset a tree given a node - i.e. the node of interests and all the descendents
813
- */
814
-
815
- function subTree (tree, node) {
816
- // Thanks Richard Challis!
817
- let fullTree = {};
818
- tree.data.forEach(obj => {
819
- fullTree[obj.thisId] = { ...obj };
820
- });
821
-
822
- let subTree = {};
823
- const getDescendants = function (rootNodeId) {
824
- if (fullTree[rootNodeId]) {
825
- subTree[rootNodeId] = fullTree[rootNodeId];
826
- if (fullTree[rootNodeId].children) {
827
- fullTree[rootNodeId].children.forEach(childNodeId => {
828
- getDescendants(childNodeId);
829
- });
830
- }
831
- }
832
- };
833
- // call the recursive function
834
- getDescendants(node);
835
-
836
- // in each of the functions, data contains the children key
837
- const data = [["data", Object.values(subTree)]];
838
-
839
- const nodes = data[0][1].map(d => d.thisId);
840
-
841
- var res = [];
842
- // in all keys except data, push to new array
843
- for (const node in tree) {
844
- if (node === "data") continue;
845
- res.push([node, tree[node]]);
846
- }
847
-
848
- var filtered = res.map(d => [
849
- d[0],
850
- d[1].filter(d => nodes.includes(d.thisId))
851
- ]);
852
-
853
- return Object.fromEntries(data.concat(filtered));
854
- }
855
-
856
- exports.describeArc = describeArc;
857
- exports.parentFisheye = parentFisheye;
858
- exports.phisheye = phisheye;
859
- exports.radialLayout = radialLayout;
860
- exports.readTree = readTree;
861
- exports.rectangleLayout = rectangleLayout;
862
- exports.subTree = subTree;
863
- exports.unrooted = unrooted;
864
-
865
- Object.defineProperty(exports, '__esModule', { value: true });
866
-
867
- })));