@euphrasiologist/lwphylo 1.1.7 → 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,822 +0,0 @@
1
- // https://github.com/Euphrasiologist/lwPhylo#readme v1.1.7 Copyright 2025 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
- const pd = fortify(node);
473
-
474
- // Fast lookup from id -> pd index
475
- const idIndex = new Map(pd.map((d, i) => [d.thisId, i]));
476
-
477
- // 1) Leaf order from the INPUT TREE (respects your child order / ladderize)
478
- const leafIds = [];
479
- (function dfs(n) {
480
- if (!n.children || n.children.length === 0) { leafIds.push(n.id); return; }
481
- n.children.forEach(dfs);
482
- })(node);
483
-
484
- // Map each leaf id to a vertical slot (1..N)
485
- const tipSlot = new Map(leafIds.map((id, i) => [id, i + 1]));
486
-
487
- // 2) Set Y for tips directly from that order; compute Y for internal nodes via postorder
488
- (function setY(n) {
489
- const i = idIndex.get(n.id);
490
- if (!n.children || n.children.length === 0) {
491
- const y = tipSlot.get(n.id);
492
- pd[i].y0 = y; pd[i].y1 = y;
493
- return y;
494
- }
495
- const ys = n.children.map(setY);
496
- const y = mean(ys);
497
- pd[i].y0 = y; pd[i].y1 = y;
498
- return y;
499
- })(node);
500
-
501
- // 3) Set X by accumulating branch lengths down the tree (no sorting-by-id needed)
502
- (function setX(n, xParent) {
503
- const i = idIndex.get(n.id);
504
- const bl = pd[i].branchLength ?? 0;
505
- const x0 = xParent ?? 0;
506
- const x1 = x0 + bl;
507
- pd[i].x0 = x0; pd[i].x1 = x1;
508
- if (n.children && n.children.length) n.children.forEach(c => setX(c, x1));
509
- })(node, 0);
510
-
511
- // Clean up and return
512
- return pd.map(({ y, x, angle, ...item }) => item);
513
- }
514
-
515
- function getVertical (node) {
516
- const data = getHorizontal(node);
517
-
518
- // Group rows by parentId (children that share a parent)
519
- const byParent = new Map();
520
- for (const row of data) {
521
- if (row.parentId == null) continue;
522
- const a = byParent.get(row.parentId);
523
- if (a) a.push(row); else byParent.set(row.parentId, [row]);
524
- }
525
-
526
- const verticals = [];
527
- for (const [parentId, kids] of byParent.entries()) {
528
- if (!kids.length) continue;
529
- // Works for binary and multifurcations:
530
- const yvals = kids.map(d => d.y0);
531
- const y0 = Math.min(...yvals);
532
- const y1 = Math.max(...yvals);
533
- // All children share the same junction x (their x0)
534
- const x = kids[0].x0;
535
-
536
- verticals.push({
537
- parentId,
538
- x0: x,
539
- x1: x,
540
- y0,
541
- y1,
542
- heights: y1 - y0
543
- });
544
- }
545
-
546
- return verticals;
547
- }
548
-
549
- /**
550
- * Simple wrapper for rectangle layout functions
551
- */
552
-
553
- function rectangleLayout (node) {
554
- var data = {};
555
-
556
- data.data = getHorizontal(node); // horizontal_lines
557
- data.vertical_lines = getVertical(node);
558
-
559
- return data;
560
- }
561
-
562
- /**
563
- * Convert parsed Newick tree from fortify() into data frame of edges
564
- * this is akin to a "phylo" object in R, where thisID and parentId
565
- * are the $edge slot. I think.
566
- */
567
-
568
- function edges (df, rectangular = false) {
569
- var result = [],
570
- parent;
571
-
572
- // make sure data frame is sorted
573
- df.sort(function (a, b) {
574
- return a.thisId - b.thisId;
575
- });
576
-
577
- for (const row of df) {
578
- if (row.parentId === null) {
579
- continue; // skip the root
580
- }
581
- parent = df[row.parentId];
582
- if (parent === null || parent === undefined) continue;
583
-
584
- if (rectangular) {
585
- var pair1 = {
586
- x1: row.x,
587
- y1: row.y,
588
- id1: row.thisId,
589
- x2: parent.x,
590
- y2: row.y,
591
- id2: undefined
592
- };
593
- result.push(pair1);
594
- var pair2 = {
595
- x1: parent.x,
596
- y1: row.y,
597
- id1: undefined,
598
- x2: parent.x,
599
- y2: parent.y,
600
- id2: row.parentId
601
- };
602
- result.push(pair2);
603
- } else {
604
- var pair3 = {
605
- x1: row.x,
606
- y1: row.y,
607
- id1: row.thisId,
608
- x2: parent.x,
609
- y2: parent.y,
610
- id2: row.parentId
611
- };
612
- result.push(pair3);
613
- }
614
- }
615
- return result;
616
- }
617
-
618
- /**
619
- * Equal-angle layout algorithm for unrooted trees.
620
- * Populates the nodes of a tree object with information on
621
- * the angles to draw branches such that they do not
622
- * intersect.
623
- */
624
-
625
- function equalAngleLayout(node) {
626
- if (node.parent === null) {
627
- // node is root
628
- node.start = 0.; // guarantees no arcs overlap 0
629
- node.end = 2.; // *pi
630
- node.angle = 0.; // irrelevant
631
- node.ntips = numTips(node);
632
- node.x = 0;
633
- node.y = 0;
634
- }
635
-
636
- var child, arc, lastStart = node.start;
637
-
638
- for (var i = 0; i < node.children.length; i++) {
639
- // the child of the current node
640
- child = node.children[i];
641
- // the number of tips the child node has
642
- child.ntips = numTips(child);
643
-
644
- // assign proportion of arc to this child
645
- arc = (node.end - node.start) * child.ntips / node.ntips;
646
- child.start = lastStart;
647
- child.end = child.start + arc;
648
-
649
- // bisect the arc
650
- child.angle = child.start + (child.end - child.start) / 2.;
651
- lastStart = child.end;
652
-
653
- // map to coordinates
654
- child.x = node.x + child.branchLength * Math.sin(child.angle * Math.PI);
655
- child.y = node.y + child.branchLength * Math.cos(child.angle * Math.PI);
656
-
657
- // climb up
658
- equalAngleLayout(child);
659
- }
660
- // had to add this!
661
- return node;
662
- }
663
-
664
- /**
665
- * Simple wrapper function for equalAngleLayout()
666
- */
667
-
668
- function unrooted (node) {
669
- var data = {};
670
- // use the Felsenstein equal angle layout algorithm
671
- var eq = fortify(equalAngleLayout(node));
672
- data.data = eq;
673
- // make the edges dataset
674
- data.edges = edges(eq);
675
-
676
- return data;
677
- }
678
-
679
- // find the x & y coordinates of the parental species
680
- function parentFisheye (d, data /* e.g. lwPhylo.unrooted.data */) {
681
- for (let i = 0; i < data.length; i++) {
682
- if (d.parentId === data[i].thisId) {
683
- return {
684
- px: data[i].fisheye.x,
685
- py: data[i].fisheye.y
686
- };
687
- }
688
- }
689
- }
690
-
691
- /**
692
- * Parse a Newick tree string into a doubly-linked
693
- * list of JS Objects. Assigns node labels, branch
694
- * lengths and node IDs (numbering terminal before
695
- * internal nodes).
696
- */
697
-
698
- function readTree (text) {
699
- // remove whitespace
700
- text = text.replace(/ \t/g, '');
701
-
702
- var tokens = text.split(/(;|\(|\)|,)/),
703
- root = { 'parent': null, 'children': [] },
704
- curnode = root,
705
- nodeId = 0;
706
-
707
- for (const token of tokens) {
708
- if (token == "" || token == ';') {
709
- continue
710
- }
711
- if (token == '(') {
712
- // add a child to current node
713
- let child = {
714
- 'parent': curnode,
715
- 'children': []
716
- };
717
- curnode.children.push(child);
718
- curnode = child; // climb up
719
- }
720
- else if (token == ',') {
721
- // climb down, add another child to parent
722
- curnode = curnode.parent;
723
- let child = {
724
- 'parent': curnode,
725
- 'children': []
726
- };
727
- curnode.children.push(child);
728
- curnode = child; // climb up
729
- }
730
- else if (token == ')') {
731
- // climb down twice
732
- curnode = curnode.parent;
733
- if (curnode === null) {
734
- break;
735
- }
736
- }
737
- else {
738
- var nodeinfo = token.split(':');
739
-
740
- if (nodeinfo.length == 1) {
741
- if (token.startsWith(':')) {
742
- curnode.label = "";
743
- curnode.branchLength = parseFloat(nodeinfo[0]);
744
- } else {
745
- curnode.label = nodeinfo[0];
746
- curnode.branchLength = null;
747
- }
748
- }
749
- else if (nodeinfo.length == 2) {
750
- curnode.label = nodeinfo[0];
751
- curnode.branchLength = parseFloat(nodeinfo[1]);
752
- }
753
- else {
754
- // TODO: handle edge cases with >1 ":"
755
- console.warn(token, "I don't know what to do with two colons!");
756
- }
757
- curnode.id = nodeId++; // assign then increment
758
- }
759
- }
760
-
761
- curnode.id = nodeId;
762
-
763
- return (root);
764
- }
765
-
766
- /**
767
- * Subset a tree given a node - i.e. the node of interests and all the descendents
768
- */
769
-
770
- function subTree (tree, node) {
771
- // Thanks Richard Challis!
772
- let fullTree = {};
773
- tree.data.forEach(obj => {
774
- fullTree[obj.thisId] = { ...obj };
775
- });
776
-
777
- let subTree = {};
778
- const getDescendants = function (rootNodeId) {
779
- if (fullTree[rootNodeId]) {
780
- subTree[rootNodeId] = fullTree[rootNodeId];
781
- if (fullTree[rootNodeId].children) {
782
- fullTree[rootNodeId].children.forEach(childNodeId => {
783
- getDescendants(childNodeId);
784
- });
785
- }
786
- }
787
- };
788
- // call the recursive function
789
- getDescendants(node);
790
-
791
- // in each of the functions, data contains the children key
792
- const data = [["data", Object.values(subTree)]];
793
-
794
- const nodes = data[0][1].map(d => d.thisId);
795
-
796
- var res = [];
797
- // in all keys except data, push to new array
798
- for (const node in tree) {
799
- if (node === "data") continue;
800
- res.push([node, tree[node]]);
801
- }
802
-
803
- var filtered = res.map(d => [
804
- d[0],
805
- d[1].filter(d => nodes.includes(d.thisId))
806
- ]);
807
-
808
- return Object.fromEntries(data.concat(filtered));
809
- }
810
-
811
- exports.describeArc = describeArc;
812
- exports.parentFisheye = parentFisheye;
813
- exports.phisheye = phisheye;
814
- exports.radialLayout = radialLayout;
815
- exports.readTree = readTree;
816
- exports.rectangleLayout = rectangleLayout;
817
- exports.subTree = subTree;
818
- exports.unrooted = unrooted;
819
-
820
- Object.defineProperty(exports, '__esModule', { value: true });
821
-
822
- })));