@figtreejs/core 0.0.1-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/eslint.config.js +9 -0
  3. package/package.json +76 -0
  4. package/src/@custom-types/normalize-svg-path.d.ts +13 -0
  5. package/src/@custom-types/parse-svg-path.d.ts +8 -0
  6. package/src/@custom-types/svg-path-types.d.ts +37 -0
  7. package/src/bauble-makers/makers.ts +112 -0
  8. package/src/bauble-makers/set-up-baubles.ts +197 -0
  9. package/src/bauble-makers/utils.ts +61 -0
  10. package/src/components/baubles/bauble.tsx +61 -0
  11. package/src/components/baubles/branches.tsx +13 -0
  12. package/src/components/baubles/clades/cartoon.tsx +68 -0
  13. package/src/components/baubles/clades/highlight.tsx +96 -0
  14. package/src/components/baubles/clades/index.ts +1 -0
  15. package/src/components/baubles/clades.tsx +45 -0
  16. package/src/components/baubles/helpers.tsx +62 -0
  17. package/src/components/baubles/index.ts +16 -0
  18. package/src/components/baubles/labels.tsx +38 -0
  19. package/src/components/baubles/nodes.tsx +51 -0
  20. package/src/components/baubles/shapes/branch.tsx +53 -0
  21. package/src/components/baubles/shapes/circle.tsx +64 -0
  22. package/src/components/baubles/shapes/index.ts +9 -0
  23. package/src/components/baubles/shapes/label.tsx +104 -0
  24. package/src/components/baubles/shapes/rectangle.tsx +83 -0
  25. package/src/components/baubles/types.ts +99 -0
  26. package/src/components/decorations/axis/axis-types.ts +123 -0
  27. package/src/components/decorations/axis/axis.tsx +21 -0
  28. package/src/components/decorations/axis/index.ts +2 -0
  29. package/src/components/decorations/axis/polar-axis-bars.tsx +102 -0
  30. package/src/components/decorations/axis/polar-axis.tsx +175 -0
  31. package/src/components/decorations/axis/rectangular-axis-bars.tsx +53 -0
  32. package/src/components/decorations/axis/rectangular-axis.tsx +151 -0
  33. package/src/components/decorations/index.ts +2 -0
  34. package/src/components/decorations/legend/discrete-legend.tsx +93 -0
  35. package/src/components/decorations/legend/index.ts +1 -0
  36. package/src/components/decorations/legend/legend.tsx +1 -0
  37. package/src/components/figtree/figtree-types.ts +69 -0
  38. package/src/components/figtree/figtree.tsx +136 -0
  39. package/src/components/figtree/index.ts +3 -0
  40. package/src/components/hoc/index.ts +7 -0
  41. package/src/components/hoc/with-branch.tsx +148 -0
  42. package/src/components/hoc/with-branches.tsx +54 -0
  43. package/src/components/hoc/with-clades.tsx +47 -0
  44. package/src/components/hoc/with-node.tsx +183 -0
  45. package/src/components/hoc/with-nodes.tsx +45 -0
  46. package/src/components/index.ts +4 -0
  47. package/src/context/aminated-context.ts +3 -0
  48. package/src/context/dimension-context.ts +22 -0
  49. package/src/context/layout-context.ts +20 -0
  50. package/src/context/scale-context.ts +12 -0
  51. package/src/evo/index.ts +1 -0
  52. package/src/evo/tree/index.ts +5 -0
  53. package/src/evo/tree/mcc-tree.ts +0 -0
  54. package/src/evo/tree/normalized-tree/immutable-tree-helpers.ts +136 -0
  55. package/src/evo/tree/normalized-tree/immutable-tree.test.ts +158 -0
  56. package/src/evo/tree/normalized-tree/immutable-tree.ts +1365 -0
  57. package/src/evo/tree/normalized-tree/index.ts +3 -0
  58. package/src/evo/tree/parsers/annotation-parser.ts +276 -0
  59. package/src/evo/tree/parsers/index.ts +3 -0
  60. package/src/evo/tree/parsers/newick-character-parser.ts +246 -0
  61. package/src/evo/tree/parsers/newick-parsing.ts +22 -0
  62. package/src/evo/tree/parsers/nexus-parser.ts +12 -0
  63. package/src/evo/tree/parsers/nexus-parsing.ts +68 -0
  64. package/src/evo/tree/parsers/parsing.test.ts +289 -0
  65. package/src/evo/tree/parsers/stream-reader/index.ts +1 -0
  66. package/src/evo/tree/parsers/stream-reader/newick-importer.txt +395 -0
  67. package/src/evo/tree/parsers/stream-reader/nexus-importer.test.ts +99 -0
  68. package/src/evo/tree/parsers/stream-reader/nexus-importer.ts +293 -0
  69. package/src/evo/tree/parsers/stream-reader/nexus-tokenizer.ts +77 -0
  70. package/src/evo/tree/parsers/stream-reader/nexus-transform-stream.txt +109 -0
  71. package/src/evo/tree/taxa/helper-functions.ts +46 -0
  72. package/src/evo/tree/taxa/index.ts +1 -0
  73. package/src/evo/tree/taxa/taxon.ts +116 -0
  74. package/src/evo/tree/traversals/index.ts +1 -0
  75. package/src/evo/tree/traversals/preorder-traversal.ts +89 -0
  76. package/src/evo/tree/traversals/traversal-types.ts +6 -0
  77. package/src/evo/tree/tree-types.ts +197 -0
  78. package/src/evo/tree/utilities.ts +44 -0
  79. package/src/index.ts +6 -0
  80. package/src/layouts/functional/index.ts +2 -0
  81. package/src/layouts/functional/radial-layout.ts +150 -0
  82. package/src/layouts/functional/rectangular-layout.ts +71 -0
  83. package/src/layouts/index.ts +3 -0
  84. package/src/layouts/layout-interface.ts +90 -0
  85. package/src/layouts/types.ts +32 -0
  86. package/src/path.helpers.ts +81 -0
  87. package/src/store/polar-scale.ts +145 -0
  88. package/src/store/store.ts +144 -0
  89. package/src/tests/baubles/__snapshots__/branch-labels.test.tsx.snap +901 -0
  90. package/src/tests/baubles/__snapshots__/node-labels.test.tsx.snap +1516 -0
  91. package/src/tests/baubles/branch-labels.test.tsx +103 -0
  92. package/src/tests/baubles/label.svg +131 -0
  93. package/src/tests/baubles/node-labels.test.tsx +126 -0
  94. package/src/tests/clades/__snapshots__/cartoon.test.tsx.snap +327 -0
  95. package/src/tests/clades/__snapshots__/highlight.test.tsx.snap +337 -0
  96. package/src/tests/clades/cartoon.test.tsx +65 -0
  97. package/src/tests/clades/highlight.test.tsx +66 -0
  98. package/src/tests/figtree/__snapshots__/figtree.test.tsx.snap +761 -0
  99. package/src/tests/figtree/figtree.test.tsx +123 -0
  100. package/src/tests/figtree/simple.svg +47 -0
  101. package/src/tests/layouts/radiallayout.test.ts +23 -0
  102. package/src/tests/layouts/rectangularlayout.test.ts +65 -0
  103. package/src/tests/shapes/branch.test.tsx +40 -0
  104. package/src/tests/shapes/circle.test.tsx +47 -0
  105. package/src/tests/shapes/label.test.tsx +101 -0
  106. package/src/tests/shapes/rectangle.test.tsx +67 -0
  107. package/src/tests/shapes/types.ts +1 -0
  108. package/src/utils.ts +57 -0
  109. package/tsconfig.json +12 -0
  110. package/vite.config.ts +34 -0
  111. package/vitetest.config.ts +11 -0
@@ -0,0 +1,1365 @@
1
+ import type {
2
+ Annotation,
3
+ AnnotationDomain,
4
+ AnnotationSummary,
5
+ AnnotationValue,
6
+ DomainOf,
7
+ MarkovJumpValue,
8
+ NodeRef,
9
+ RawAnnotationValue,
10
+ Tree,
11
+ newickParsingOptions,
12
+ } from "../tree-types";
13
+ import { BaseAnnotationType } from "../tree-types";
14
+ import {
15
+ parseNewick,
16
+ parseNexus,
17
+ processAnnotationValue,
18
+ writeAnnotationValue,
19
+ } from "../parsers";
20
+ import { immerable, produce } from "immer";
21
+ import type { Taxon, TaxonSetInterface } from "../taxa/taxon";
22
+ import { TaxonSet } from "../taxa/taxon";
23
+ import { format } from "d3-format";
24
+ import { extent } from "d3-array";
25
+
26
+ import {
27
+ MaybeType,
28
+ type Undefinable,
29
+ UnwrapErr,
30
+ type Maybe,
31
+ } from "@figtreejs/maybe/maybe";
32
+ import {
33
+ maybeGetAnnotation,
34
+ maybeGetNode,
35
+ maybeGetNodeByTaxon,
36
+ maybeGetParent,
37
+ maybeGetTaxon,
38
+ maybeGetTaxonFromNode,
39
+ } from "./immutable-tree-helpers";
40
+ import { notNull, unNullify } from "../../../utils";
41
+ import { v4 as uuidv4 } from "uuid";
42
+
43
+ export type nodeIndex = string | number | Taxon;
44
+ export type maybeNodeIndex = Maybe<nodeIndex>;
45
+
46
+ //TODO will need to think about taxonsets and immutability.
47
+ export interface Node extends NodeRef {
48
+ number: number;
49
+ taxon: number | undefined;
50
+ label: string | undefined;
51
+ children: number[];
52
+ parent: number | undefined;
53
+ length: number | undefined;
54
+ annotations: { [annotation: string]: Annotation };
55
+ _id: string; // an id so we can mark updates to root when topology changes
56
+ }
57
+
58
+ export interface ImmutableTreeData {
59
+ nodes: {
60
+ allNodes: Node[];
61
+ byTaxon: number[];
62
+ byLabel: {
63
+ [label: string]: number;
64
+ };
65
+ };
66
+ heights: [];
67
+ nodeToTaxon: number[];
68
+ rootNode: number;
69
+ is_rooted: boolean;
70
+ hasLengths: boolean;
71
+ annotations: { [annotation: string]: AnnotationSummary };
72
+ }
73
+
74
+ export class ImmutableTree implements Tree, TaxonSetInterface {
75
+ //TODO remove the TaxonSetInterface implementation.
76
+ [immerable] = true;
77
+
78
+ _data: ImmutableTreeData;
79
+ taxonSet: TaxonSet;
80
+ constructor(input: { data?: ImmutableTreeData; taxonSet?: TaxonSet } = {}) {
81
+ const { data: _data, taxonSet } = input;
82
+ let data = _data;
83
+ if (taxonSet) {
84
+ this.taxonSet = taxonSet;
85
+ } else {
86
+ this.taxonSet = new TaxonSet();
87
+ }
88
+ if (data === undefined) {
89
+ data = {
90
+ nodes: {
91
+ allNodes: [
92
+ {
93
+ number: 0,
94
+ children: [],
95
+ parent: undefined,
96
+ label: undefined,
97
+ length: undefined,
98
+ taxon: undefined,
99
+ annotations: {},
100
+ _id: uuidv4(),
101
+ },
102
+ ],
103
+ byTaxon: [],
104
+ byLabel: {},
105
+ },
106
+ nodeToTaxon: [],
107
+ rootNode: 0,
108
+ is_rooted: true,
109
+ annotations: {},
110
+ heights: [],
111
+ hasLengths: false,
112
+ };
113
+ }
114
+ this._data = data;
115
+ }
116
+ lockTaxa(): TaxonSetInterface {
117
+ this.taxonSet.lockTaxa();
118
+ return this;
119
+ }
120
+
121
+ addTaxon(taxonOrName: string | Taxon): this {
122
+ this.taxonSet.addTaxon(taxonOrName);
123
+ return this;
124
+ }
125
+
126
+ getTaxonCount(): number {
127
+ return this.taxonSet.getTaxonCount();
128
+ }
129
+
130
+ getTaxonSet(): TaxonSetInterface {
131
+ return this.taxonSet;
132
+ }
133
+ // Parsers and constructors
134
+
135
+ static fromNewick(
136
+ newick: string,
137
+ options: newickParsingOptions = {},
138
+ ): ImmutableTree {
139
+ // const tree = new this()
140
+ return parseNewick(newick, options);
141
+ }
142
+ static fromNexus(
143
+ nexus: string,
144
+ options?: newickParsingOptions,
145
+ ): ImmutableTree {
146
+ const tree = new this();
147
+ return parseNexus(tree, nexus, options);
148
+ // throw new Error("Nexus parsing not implemented")
149
+ }
150
+ static fromString(
151
+ string: string,
152
+ options?: newickParsingOptions,
153
+ ): ImmutableTree {
154
+ if (string.toLowerCase().includes("#nexus")) {
155
+ return this.fromNexus(string, options);
156
+ } else {
157
+ return this.fromNewick(string, options);
158
+ }
159
+ }
160
+
161
+ static fromTree(tree: ImmutableTree, rootNode: NodeRef): ImmutableTree {
162
+ // make a new tree.
163
+ let newTree = new this();
164
+
165
+ const traverseAndCopy = (tree: ImmutableTree, node: NodeRef): NodeRef => {
166
+ const children = [];
167
+ let newNode: NodeRef;
168
+ for (const child of tree.getChildren(node)) {
169
+ children.push(traverseAndCopy(tree, child));
170
+ }
171
+
172
+ if (node !== rootNode) {
173
+ newTree = this._addNodeWithMetadata(tree, node, newTree);
174
+ newNode = newTree.getNode(newTree.getNodeCount() - 1);
175
+ } else {
176
+ // already have the node
177
+ newNode = newTree.getRoot();
178
+ // add metadata
179
+ this._copyNodeMetadata(tree, node, newTree, newNode);
180
+ }
181
+ for (const child of children) {
182
+ newTree = newTree.addChild(newNode, child);
183
+ }
184
+ return newNode;
185
+ };
186
+
187
+ traverseAndCopy(tree, rootNode);
188
+ newTree = newTree.deleteLength(newTree.getRoot()); //
189
+
190
+ return newTree;
191
+ }
192
+
193
+ static _addNodeWithMetadata(
194
+ tree: ImmutableTree,
195
+ node: NodeRef,
196
+ newTree: ImmutableTree,
197
+ ): ImmutableTree {
198
+ const added = newTree.addNodes(1);
199
+ const newNode = added.nodes[0];
200
+ newTree = added.tree;
201
+ newTree = this._copyNodeMetadata(tree, node, newTree, newNode);
202
+ return newTree;
203
+ }
204
+
205
+ static _copyNodeMetadata(
206
+ tree: ImmutableTree,
207
+ node: NodeRef,
208
+ newTree: ImmutableTree,
209
+ newNode: NodeRef,
210
+ ): ImmutableTree {
211
+ if (tree.hasTaxon(node)) {
212
+ const taxon = tree.getTaxonFromNode(node);
213
+ newTree = newTree.addTaxon(taxon);
214
+ console.log("Current taxa:", newTree.taxonSet);
215
+ newTree = newTree.setTaxon(newNode, taxon);
216
+ }
217
+
218
+ if (tree.hasLabel(node)) {
219
+ const label = tree.getLabel(node);
220
+ newTree = newTree.setLabel(newNode, label);
221
+ }
222
+ for (const key of tree.getAnnotationKeys()) {
223
+ if (tree.hasAnnotation(node, key)) {
224
+ // access directly to get the full type
225
+ const annotation = tree.getFullNodeAnnotation(node, key);
226
+
227
+ if (annotation.type === BaseAnnotationType.MARKOV_JUMPS) {
228
+ const value = annotation.value.map((j) => [
229
+ Number(j.time),
230
+ j.from,
231
+ j.to,
232
+ ]) as [[number, string, string]]; // todo infer type from above
233
+ newTree = newTree.annotateNode(newNode, key, value) as ImmutableTree; // TODO make tree interface genaric and return the same type of tree
234
+ } else {
235
+ newTree = newTree.annotateNode(
236
+ newNode,
237
+ key,
238
+ annotation.value,
239
+ ) as ImmutableTree;
240
+ }
241
+ }
242
+ }
243
+
244
+ if (tree.hasBranchLength(node)) {
245
+ const length = tree.getLength(node);
246
+ newTree = newTree.setLength(newNode, length);
247
+ }
248
+
249
+ return newTree;
250
+ }
251
+ // ------------------ Getters ----------------------
252
+
253
+ isRooted(): boolean {
254
+ return this._data.is_rooted;
255
+ }
256
+ getAnnotationType(name: string): BaseAnnotationType {
257
+ if (
258
+ (this._data.annotations[name] as Undefinable<AnnotationSummary>) ===
259
+ undefined
260
+ ) {
261
+ throw new Error(`No annotation found with name: ${name}`);
262
+ }
263
+ return this._data.annotations[name].type;
264
+ }
265
+
266
+ getAnnotationKeys(): string[] {
267
+ return Object.keys(this._data.annotations);
268
+ }
269
+
270
+ getRoot(): NodeRef {
271
+ return this._data.nodes.allNodes[this._data.rootNode];
272
+ }
273
+ getNodeCount(): number {
274
+ return this._data.nodes.allNodes.length;
275
+ }
276
+ getInternalNodeCount(): number {
277
+ return this._data.nodes.allNodes.filter((n) => n.children.length > 0)
278
+ .length;
279
+ }
280
+ getExternalNodeCount(): number {
281
+ return this._data.nodes.allNodes.filter((n) => n.children.length == 0)
282
+ .length;
283
+ }
284
+ getInternalNodes(): NodeRef[] {
285
+ return this._data.nodes.allNodes.filter((n) => n.children.length > 0);
286
+ }
287
+ getExternalNodes(): NodeRef[] {
288
+ return this._data.nodes.allNodes.filter((n) => n.children.length == 0);
289
+ }
290
+ getNodes(): NodeRef[] {
291
+ return this._data.nodes.allNodes;
292
+ }
293
+
294
+ getNode(i: nodeIndex): NodeRef {
295
+ const node = maybeGetNode(this, i);
296
+ switch (node.type) {
297
+ case MaybeType.Some:
298
+ return node.value;
299
+ case MaybeType.Nothing:
300
+ throw new Error(`No node found`);
301
+ }
302
+ }
303
+
304
+ getNodeByTaxon(taxon: Taxon): NodeRef {
305
+ const n = maybeGetNodeByTaxon(this, taxon);
306
+ switch (n.type) {
307
+ case MaybeType.Nothing:
308
+ throw new Error(`No node found for Taxon ${taxon.name}`);
309
+ case MaybeType.Some:
310
+ return n.value;
311
+ }
312
+ }
313
+
314
+ getTaxonByName(name: string): Taxon {
315
+ return this.taxonSet.getTaxonByName(name);
316
+ }
317
+
318
+ getNodeByLabel(label: string): NodeRef {
319
+ return this.getNode(this._data.nodes.byLabel[label]);
320
+ }
321
+
322
+ hasTaxon(node: NodeRef): boolean {
323
+ const t = maybeGetTaxonFromNode(this, node);
324
+
325
+ switch (t.type) {
326
+ case MaybeType.Some:
327
+ return true;
328
+ case MaybeType.Nothing:
329
+ return false;
330
+ }
331
+ }
332
+
333
+ getTaxonFromNode(node: NodeRef): Taxon {
334
+ const t = maybeGetTaxonFromNode(this, node);
335
+ switch (t.type) {
336
+ case MaybeType.Some:
337
+ return t.value;
338
+ case MaybeType.Nothing:
339
+ throw new Error(`Node taxon found for the provided node`);
340
+ }
341
+ }
342
+ //TODO overload as above.
343
+
344
+ getTaxon(id: number | NodeRef | string): Taxon {
345
+ const t = maybeGetTaxon(this, id);
346
+ switch (t.type) {
347
+ case MaybeType.Some:
348
+ return t.value;
349
+ case MaybeType.Nothing:
350
+ throw new Error(`Node taxon found that matched the provided id`);
351
+ }
352
+ }
353
+
354
+ hasNodeHeights(): boolean {
355
+ throw new Error("hasNodeHeights not implemented.");
356
+ }
357
+ //todo cache
358
+ getHeight(node: NodeRef): number {
359
+ let maxDiv = -1;
360
+ for (const t of tipIterator(this)) {
361
+ const d = this.getDivergence(t);
362
+ if (d > maxDiv) {
363
+ maxDiv = d;
364
+ }
365
+ }
366
+ return maxDiv - this.getDivergence(node);
367
+ }
368
+ hasBranchLength(node: NodeRef): boolean {
369
+ const n = this.getNode(node.number) as Node;
370
+ return n.length !== undefined;
371
+ }
372
+
373
+ getLength(node: NodeRef): number {
374
+ const thisNode = this.getNode(node.number);
375
+ const length = (thisNode as Node).length;
376
+ if (length === undefined) {
377
+ if (this.hasLengths()) {
378
+ throw new Error(
379
+ `The tree has lengths but, no length was found for node ${node.number}`,
380
+ );
381
+ }
382
+ console.warn(
383
+ `The tree does not have branchlengths so a length of 1 is used as default`,
384
+ );
385
+ return 1.0;
386
+ }
387
+ return length;
388
+ }
389
+ hasLengths(): boolean {
390
+ return this._data.hasLengths;
391
+ }
392
+
393
+ _toString(
394
+ node: NodeRef,
395
+ options?: {
396
+ blFormat: (value: number) => string;
397
+ includeAnnotations: boolean;
398
+ },
399
+ ): string {
400
+ if (options === undefined) {
401
+ options = { blFormat: format("0.2"), includeAnnotations: false };
402
+ }
403
+ return (
404
+ (this.getChildCount(node) > 0
405
+ ? `(${this.getChildren(node)
406
+ .map((child) => this._toString(child, options))
407
+ .join(",")})${this.hasLabel(node) ? "#" + this.getLabel(node) : ""}`
408
+ : this.hasTaxon(node)
409
+ ? this.getTaxonFromNode(node).name
410
+ : "") +
411
+ (options.includeAnnotations ? this._writeAnnotations(node) : "") +
412
+ (this.hasBranchLength(node)
413
+ ? `:${options.blFormat(this.getLength(node))}`
414
+ : "")
415
+ );
416
+ }
417
+
418
+ _writeAnnotations(node: NodeRef): string {
419
+ const annotations = this._data.nodes.allNodes[node.number].annotations;
420
+ if (Object.keys(annotations).length === 0) {
421
+ return "";
422
+ }
423
+ let annotationString = "[&";
424
+ let i = 0;
425
+ for (const [key, annotation] of Object.entries(annotations)) {
426
+ if (i > 0) {
427
+ annotationString += ", "; // add a comma before the next annotation
428
+ }
429
+ annotationString += `${key}=${writeAnnotationValue(annotation)}`;
430
+ i += 1;
431
+ }
432
+ annotationString += "]";
433
+ return annotationString;
434
+ }
435
+
436
+ toNewick(
437
+ node?: NodeRef,
438
+ options?: {
439
+ blFormat?: (value: number) => string;
440
+ includeAnnotations?: boolean;
441
+ },
442
+ ): string {
443
+ const ops = {
444
+ blFormat: format("0.2"),
445
+ includeAnnotations: false,
446
+ ...options,
447
+ };
448
+ if (node === undefined) {
449
+ node = this.getRoot();
450
+ }
451
+ return this._toString(node, ops) + ";";
452
+ }
453
+
454
+ getMRCA(node1: NodeRef | NodeRef[], node2?: NodeRef): NodeRef {
455
+ if (Array.isArray(node1)) {
456
+ const nodes = node1;
457
+ if (nodes.length === 0) {
458
+ throw new Error("No nodes provided to get MRCA");
459
+ }
460
+ let mrca = nodes[0];
461
+ for (let i = 1; i < nodes.length; i++) {
462
+ mrca = this.getMRCA(mrca, nodes[i]);
463
+ if (this.isRoot(mrca)) {
464
+ return mrca;
465
+ }
466
+ }
467
+ return mrca;
468
+ } else {
469
+ if (node2 === undefined) {
470
+ throw new Error(
471
+ `No second node provided. A node must be provided if the first value is not an array`,
472
+ );
473
+ }
474
+ const path1 = [...this.getPathToRoot(node1)];
475
+ let mrca = null;
476
+
477
+ for (const ancestor of this.getPathToRoot(node2)) {
478
+ if (path1.includes(ancestor)) {
479
+ mrca = ancestor;
480
+ break;
481
+ }
482
+ }
483
+ if (mrca === null) {
484
+ throw new Error("No MRCA found");
485
+ }
486
+ return mrca;
487
+ }
488
+ }
489
+
490
+ getPath(node1: NodeRef, node2: NodeRef): NodeRef[] {
491
+ const path = [];
492
+ const mrca = this.getMRCA(node1, node2);
493
+ for (let node of [node1, node2]) {
494
+ while (node != mrca) {
495
+ path.push(node);
496
+ const parent = this.getParent(node);
497
+ node = parent;
498
+ }
499
+ }
500
+ return path;
501
+ }
502
+
503
+ getPathLength(node1: NodeRef, node2: NodeRef): number {
504
+ let sum = 0;
505
+ const mrca = this.getMRCA(node1, node2);
506
+ for (let node of [node1, node2]) {
507
+ while (node != mrca) {
508
+ const length = this.getLength(node);
509
+ sum += length;
510
+ const parent = this.getParent(node);
511
+ node = parent;
512
+ }
513
+ }
514
+
515
+ return sum;
516
+ }
517
+
518
+ *getPathToRoot(node: NodeRef): Generator<NodeRef> {
519
+ let n: NodeRef = node;
520
+ while (!this.isRoot(n)) {
521
+ yield n;
522
+ n = this.getParent(n);
523
+ }
524
+ yield n; // yield root
525
+ }
526
+
527
+ getNextSibling(node: NodeRef): NodeRef {
528
+ if (!this.hasLeftSibling(node) && !this.hasRightSibling(node)) {
529
+ throw new Error(`Node ${node.number} has no sibling`);
530
+ }
531
+ const parent = this.getParent(node);
532
+ const index = (parent as Node).children.map((c) => c).indexOf(node.number);
533
+ return this.getChild(parent, (index + 1) % this.getChildCount(parent));
534
+ }
535
+
536
+ hasRightSibling(node: NodeRef): boolean {
537
+ const parent = maybeGetParent(this, node);
538
+ switch (parent.type) {
539
+ case MaybeType.Nothing:
540
+ return false;
541
+ case MaybeType.Some:
542
+ return (
543
+ (parent.value as Node).children.map((c) => c).indexOf(node.number) <
544
+ this.getChildCount(parent.value) - 1
545
+ );
546
+ }
547
+ }
548
+ getRightSibling(node: NodeRef): NodeRef {
549
+ if (!this.hasRightSibling(node)) {
550
+ throw new Error(`node ${node.number} does not have a right sibling`);
551
+ }
552
+ const parent = this.getParent(node);
553
+ const index = (parent as Node).children.map((c) => c).indexOf(node.number);
554
+ return this.getChild(parent, index + 1);
555
+ }
556
+ hasLeftSibling(node: NodeRef): boolean {
557
+ const parent = maybeGetParent(this, node);
558
+ switch (parent.type) {
559
+ case MaybeType.Nothing:
560
+ return false;
561
+ case MaybeType.Some:
562
+ return (
563
+ this.getChildCount(parent.value) > 1 &&
564
+ (parent.value as Node).children.map((c) => c).indexOf(node.number) > 0
565
+ );
566
+ }
567
+ }
568
+
569
+ getLeftSibling(node: NodeRef): NodeRef {
570
+ if (!this.hasLeftSibling(node)) {
571
+ throw new Error(`node ${node.number} does not have a left sibling`);
572
+ }
573
+ const parent = this.getParent(node);
574
+ const index = (parent as Node).children.map((c) => c).indexOf(node.number);
575
+ return this.getChild(parent, index - 1);
576
+ }
577
+
578
+ getDivergence(node: NodeRef): number {
579
+ let divergence = 0;
580
+ for (const n of this.getPathToRoot(node)) {
581
+ if (this.isRoot(n)) {
582
+ // the root does not need a length but may have one
583
+ if (this.hasBranchLength(n)) {
584
+ divergence += this.getLength(n);
585
+ }
586
+ } else {
587
+ divergence += this.getLength(n);
588
+ }
589
+ }
590
+ return divergence;
591
+ }
592
+
593
+ getChildCount(node: NodeRef): number {
594
+ if (!this._data.nodes.allNodes[node.number]) {
595
+ throw new Error(`Node ${node.number} not found`);
596
+ }
597
+ return this._data.nodes.allNodes[node.number].children.length;
598
+ }
599
+ getChild(node: NodeRef, index: number): NodeRef {
600
+ return this._data.nodes.allNodes[
601
+ this._data.nodes.allNodes[node.number].children[index]
602
+ ];
603
+ }
604
+
605
+ getParent(node: NodeRef): NodeRef {
606
+ const parent = maybeGetParent(this, node);
607
+ switch (parent.type) {
608
+ case MaybeType.Some:
609
+ return parent.value;
610
+ case MaybeType.Nothing:
611
+ throw new Error(`No parent for node ${node.number}`);
612
+ }
613
+ }
614
+ getChildren(node: NodeRef): NodeRef[] {
615
+ return this._data.nodes.allNodes[node.number].children.map((n) =>
616
+ this.getNode(n),
617
+ );
618
+ }
619
+
620
+ hasLabel(node: NodeRef): boolean {
621
+ return this._data.nodes.allNodes[node.number].label !== undefined;
622
+ }
623
+ getLabel(node: NodeRef): string {
624
+ const l = this._data.nodes.allNodes[node.number].label;
625
+ if (l === undefined) {
626
+ throw new Error(`no label for node ${node.number}`);
627
+ }
628
+ return l;
629
+ }
630
+
631
+ isExternal(node: NodeRef): boolean {
632
+ return (this.getNode(node.number) as Node).children.length === 0;
633
+ }
634
+ isInternal(node: NodeRef): boolean {
635
+ return (this.getNode(node.number) as Node).children.length > 0;
636
+ }
637
+ isRoot(node: NodeRef) {
638
+ return this._data.rootNode === node.number;
639
+ }
640
+
641
+ addNodes(n: number = 1): { tree: ImmutableTree; nodes: NodeRef[] } {
642
+ const newNodes: NodeRef[] = [];
643
+
644
+ return {
645
+ tree: produce(this, (draft) => {
646
+ const number = draft._data.nodes.allNodes.length;
647
+ for (let i = 0; i < n; i++) {
648
+ const newNode: Node = {
649
+ number: number + i,
650
+ children: [],
651
+ parent: undefined,
652
+ label: undefined,
653
+ length: undefined,
654
+ taxon: undefined,
655
+ annotations: {},
656
+ _id: uuidv4(),
657
+ };
658
+ newNodes.push(newNode);
659
+ draft._data.nodes.allNodes.push(newNode);
660
+ }
661
+ }),
662
+ nodes: newNodes,
663
+ };
664
+ }
665
+
666
+ setTaxon(node: NodeRef, taxon: Taxon): this {
667
+ // check we know about this taxon;
668
+ if (taxon !== this.taxonSet.getTaxonByName(taxon.name)) {
669
+ throw new Error(
670
+ `Taxon ${taxon.name} is either not in the taxon set. Has it been copied?`,
671
+ );
672
+ }
673
+ return produce(this, (draft) => {
674
+ const n = draft.getNode(node.number) as Node;
675
+ n.taxon = taxon.number;
676
+ draft._data.nodes.byTaxon[taxon.number] = node.number;
677
+ draft._data.nodeToTaxon[node.number] = taxon.number;
678
+ });
679
+ }
680
+ getAnnotationSummary(name: string): AnnotationSummary {
681
+ if (
682
+ (this._data.annotations[name] as Undefinable<AnnotationSummary>) ===
683
+ undefined
684
+ ) {
685
+ throw new Error(`No annotation with name ${name} found in tree`);
686
+ }
687
+ return this._data.annotations[name];
688
+ }
689
+
690
+ getAnnotations(): AnnotationSummary[] {
691
+ return Object.values(this._data.annotations);
692
+ }
693
+
694
+ getAnnotation(
695
+ node: NodeRef,
696
+ name: string,
697
+ d?: AnnotationValue,
698
+ ): AnnotationValue {
699
+ const a = maybeGetAnnotation(this, this.getNode(node.number), name);
700
+ if (d === undefined) {
701
+ const { value } = UnwrapErr(
702
+ a,
703
+ `Node ${node.number} is not annotated with ${name}`,
704
+ );
705
+ return value;
706
+ } else {
707
+ switch (a.type) {
708
+ case MaybeType.Some:
709
+ return a.value.value; // value of the maybe = annotation . value of the annotation
710
+ case MaybeType.Nothing:
711
+ return d;
712
+ }
713
+ }
714
+ }
715
+
716
+ getFullNodeAnnotation(node: NodeRef, name: string): Annotation {
717
+ const a = maybeGetAnnotation(this, this.getNode(node.number), name);
718
+ return UnwrapErr(a, `Node ${node.number} is not annotated with ${name}`);
719
+ }
720
+
721
+ hasAnnotation(node: NodeRef, name: string): boolean {
722
+ const a = maybeGetAnnotation(this, this.getNode(node.number), name);
723
+ switch (a.type) {
724
+ case MaybeType.Some:
725
+ return true;
726
+ case MaybeType.Nothing:
727
+ return false;
728
+ }
729
+ }
730
+
731
+ // ---------------- Setters ---------------------
732
+
733
+ annotateNode(node: NodeRef, name: string, value: RawAnnotationValue): Tree;
734
+ annotateNode(
735
+ node: NodeRef,
736
+ annotation: Record<string, RawAnnotationValue>,
737
+ ): Tree;
738
+ annotateNode(
739
+ node: NodeRef,
740
+ a: string | Record<string, RawAnnotationValue>,
741
+ b?: RawAnnotationValue,
742
+ ): Tree {
743
+ if (typeof a === "string") {
744
+ const name = a;
745
+ const value = b as RawAnnotationValue;
746
+ const classifiedAnnotation = processAnnotationValue(value);
747
+ const currentSummary = this._data.annotations[
748
+ name
749
+ ] as Undefinable<AnnotationSummary>;
750
+ if (currentSummary !== undefined) {
751
+ if (currentSummary.type !== classifiedAnnotation.type) {
752
+ throw new Error(
753
+ `Tried annotation ${name} was parsed as ${classifiedAnnotation.type} - but is ${currentSummary.type} in tree.`,
754
+ );
755
+ }
756
+ }
757
+ return produce(this, (draft) => {
758
+ const currentDomain = currentSummary
759
+ ? currentSummary.domain
760
+ : undefined;
761
+ const domain = updateDomain(classifiedAnnotation, currentDomain);
762
+ draft._data.nodes.allNodes[node.number].annotations[name] = {
763
+ id: name,
764
+ type: classifiedAnnotation.type,
765
+ value: classifiedAnnotation.value,
766
+ } as Annotation;
767
+ draft._data.annotations[name] = {
768
+ id: name,
769
+ type: classifiedAnnotation.type,
770
+ domain: domain,
771
+ } as AnnotationSummary;
772
+ });
773
+ } else {
774
+ // loop over entries
775
+ let t: Tree = this as Tree;
776
+ for (const [k, v] of Object.entries(a)) {
777
+ t = t.annotateNode(node, k, v);
778
+ }
779
+ return t;
780
+ }
781
+ }
782
+
783
+ //TODO handle height and divergence changes still not very happy with how these are handled.
784
+
785
+ setHeight(node: NodeRef, height: number): this {
786
+ if (!this.hasLengths()) {
787
+ throw new Error(
788
+ "Can not set the heights of nodes in a tree without branch lengths",
789
+ );
790
+ }
791
+ return produce(this, (draft) => {
792
+ const n = draft.getNode(node.number) as Node;
793
+ if (height < 0) {
794
+ throw new Error("Height must be non-negative");
795
+ }
796
+
797
+ const currentHeight = draft.getHeight(node);
798
+ const change = currentHeight - height; // positive change increases length
799
+ // height goes 2 to 3 length should decease new length = length + (-1) childe lengths must increase
800
+
801
+ if (n.length === undefined) {
802
+ if (!draft.isRoot(node)) {
803
+ throw new Error("Cannot set height on a node without length");
804
+ }
805
+ } else {
806
+ n.length = n.length + change;
807
+ }
808
+ // update length of children
809
+ for (const child of draft.getChildren(node)) {
810
+ const childNode = draft.getNode(child.number) as Node;
811
+ const newLength = draft.getLength(childNode) - change;
812
+
813
+ childNode.length = newLength;
814
+ }
815
+ });
816
+ }
817
+
818
+ setLength(node: NodeRef, length: number): this {
819
+ return produce(this, (draft) => {
820
+ const n = draft.getNode(node.number) as Node;
821
+ n.length = length;
822
+ draft._data.hasLengths = true;
823
+ });
824
+ }
825
+ deleteLength(node: NodeRef): this {
826
+ return produce(this, (draft) => {
827
+ const n = draft.getNode(node.number) as Node;
828
+ n.length = undefined;
829
+ });
830
+ }
831
+ // can only be called once heights are known.
832
+ setDivergence(node: NodeRef, divergence: number): this {
833
+ if (!this.hasLengths()) {
834
+ throw new Error(
835
+ "Can not set the divergences of nodes in a tree without branch lengths",
836
+ );
837
+ }
838
+ return produce(this, (draft) => {
839
+ const n = draft.getNode(node.number) as Node;
840
+ const height = draft.getHeight(node);
841
+ const rootHeight = draft.getHeight(draft.getRoot());
842
+ const currentDivergence = rootHeight - height;
843
+ const change = currentDivergence - divergence; // a negative change increases the length
844
+ const l = draft.getLength(node);
845
+ n.length = l - change;
846
+ });
847
+ }
848
+
849
+ setLabel(node: NodeRef, label: string): this {
850
+ if (
851
+ (this._data.nodes.byLabel[label] as Undefinable<number>) !== undefined
852
+ ) {
853
+ throw new Error(`Duplicate node label ${label}`);
854
+ }
855
+
856
+ return produce(this, (draft) => {
857
+ const n = draft.getNode(node.number) as Node;
858
+ n.label = label;
859
+ draft._data.nodes.byLabel[label] = node.number;
860
+ });
861
+ }
862
+
863
+ insertNode(n: NodeRef, proportion: number = 0.5): this {
864
+ return produce(this, (draft) => {
865
+ const newNode: Node = {
866
+ number: draft._data.nodes.allNodes.length,
867
+ children: [],
868
+ parent: undefined,
869
+ label: "",
870
+ length: undefined,
871
+ taxon: undefined,
872
+ annotations: {}, // todo copy annotations from root
873
+ _id: uuidv4(),
874
+ };
875
+ draft._data.nodes.allNodes.push(newNode);
876
+ draft._data.nodes.byTaxon.length += 1;
877
+ // insert halfway on first child's branch.
878
+
879
+ const node = draft.getNode(n.number) as Node;
880
+ const parentNode = draft.getParent(node) as Node;
881
+
882
+ const index = parentNode.children.indexOf(node.number);
883
+
884
+ parentNode.children.splice(index, 1, newNode.number);
885
+ newNode.parent = parentNode.number;
886
+ const l = draft.getLength(node);
887
+ const oldLength = l;
888
+ node.length = oldLength * (1 - proportion);
889
+ newNode.length = oldLength * proportion;
890
+ newNode.children = [node.number];
891
+
892
+ node.parent = newNode.number;
893
+ });
894
+ }
895
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
896
+ unroot(_n: NodeRef): ImmutableTree {
897
+ throw new Error("unroot not implemented in immutable tree");
898
+ }
899
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
900
+ deleteNode(_n: NodeRef): ImmutableTree {
901
+ throw new Error("deleteNode not implemented in immutable tree");
902
+ }
903
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
904
+ deleteClade(_n: NodeRef): ImmutableTree {
905
+ throw new Error("deleteClade not implemented in immutable tree");
906
+ }
907
+
908
+ orderNodesByDensity(down: boolean, node?: NodeRef): this {
909
+ return produce(this, (draft) => {
910
+ if (node === undefined) {
911
+ node = draft._data.nodes.allNodes[draft._data.rootNode];
912
+ }
913
+ const factor = down ? 1 : -1;
914
+ orderNodes(draft._data, node, (_nodeA, countA, _nodeB, countB) => {
915
+ return (countA - countB) * factor;
916
+ });
917
+ });
918
+ }
919
+
920
+ rotate(nodeRef: NodeRef, recursive: boolean = false): this {
921
+ return produce(this, (draft) => {
922
+ const node = draft.getNode(nodeRef.number) as Node;
923
+ node.children = node.children.reverse();
924
+ if (recursive) {
925
+ for (const child of node.children.map((n) => draft.getNode(n))) {
926
+ draft.rotate(child, recursive);
927
+ }
928
+ }
929
+ });
930
+ }
931
+ //TODO handle lengthless tree
932
+ reroot(node: NodeRef, proportion: number = 0.5): this {
933
+ return produce(this, (draft) => {
934
+ if (node.number === draft._data.rootNode) {
935
+ // the node is the root - nothing to do
936
+ return;
937
+ }
938
+ const rootNode = draft.getRoot() as Node;
939
+ if (rootNode.children.length !== 2) {
940
+ console.warn(
941
+ "Root node has more than two children and we are rerooting! There be dragons!",
942
+ );
943
+ }
944
+ let rootLength = 0;
945
+
946
+ if (rootNode.children.length == 2) {
947
+ // just sum them.
948
+ rootLength = rootNode.children
949
+ .map((n) => draft.getNode(n))
950
+ .map((n) => draft.getLength(n))
951
+ .reduce((acc, l) => l + acc, 0);
952
+ } else {
953
+ // we need the length of the incoming root branch
954
+ const pathToRoot = [...draft.getPathToRoot(node)];
955
+
956
+ const rootChild = pathToRoot[pathToRoot.length - 2]; // last one is the root
957
+ notNull(rootChild, `Index error when looking for the root child`);
958
+ if (!rootNode.children.includes(rootChild.number)) {
959
+ throw new Error(
960
+ "Root child not in path to root - likely an internal error",
961
+ );
962
+ }
963
+ rootLength = draft.getLength(rootChild);
964
+ }
965
+
966
+ const treeNode = draft.getNode(node.number) as Node;
967
+ if (draft.getParent(node) !== rootNode) {
968
+ // the node is not a child of the existing root so the root is actually changing
969
+
970
+ let node0 = treeNode;
971
+ let parent = draft.getParent(treeNode) as Node;
972
+
973
+ // was the node the first child in the parent's children?
974
+ const nodeAtTop = draft.getChild(parent, 0).number === node.number;
975
+
976
+ const rootChild1 = treeNode;
977
+ const rootChild2 = parent;
978
+
979
+ let oldLength = draft.getLength(parent);
980
+
981
+ while (!draft.isRoot(parent)) {
982
+ // remove the node that will becoming the parent from the children
983
+
984
+ parent.children = parent.children.filter((n) => n !== node0.number);
985
+
986
+ if (draft.getParent(parent).number === rootNode.number) {
987
+ // at the root
988
+ if (rootNode.children.length == 2) {
989
+ if (
990
+ !draft.hasLeftSibling(parent) &&
991
+ !draft.hasRightSibling(parent)
992
+ ) {
993
+ throw new Error("no sibling in rerooting");
994
+ }
995
+ const ls = draft.hasLeftSibling(parent)
996
+ ? draft.getLeftSibling(parent)
997
+ : draft.getRightSibling(parent);
998
+ // const ls = draft.getLeftSibling(parent)
999
+ const sibling = ls as Node;
1000
+ parent.children.push(sibling.number);
1001
+ sibling.parent = parent.number;
1002
+ sibling.length = rootLength;
1003
+ } else {
1004
+ // need to add a new node here.
1005
+ const newNode: Node = {
1006
+ number: draft._data.nodes.allNodes.length,
1007
+ children: [],
1008
+ parent: undefined,
1009
+ label: "",
1010
+ length: undefined,
1011
+ taxon: undefined,
1012
+ annotations: {},
1013
+ _id: uuidv4(),
1014
+ };
1015
+ draft._data.nodes.allNodes.push(newNode);
1016
+ newNode.length = rootLength;
1017
+ parent.children.push(newNode.number);
1018
+ newNode.parent = parent.number;
1019
+ for (const childNumber of rootNode.children) {
1020
+ const child = draft.getNode(childNumber) as Node;
1021
+ if (child.number !== parent.number) {
1022
+ child.parent = newNode.number;
1023
+ newNode.children.push(child.number);
1024
+ }
1025
+ }
1026
+ }
1027
+ } else {
1028
+ // swap the parent and parent's parent's length around
1029
+ const nan = draft.getParent(parent) as Node; // your mammy's mammy
1030
+ const nanLength = draft.getLength(nan);
1031
+ nan.length = oldLength;
1032
+ oldLength = nanLength;
1033
+
1034
+ //
1035
+
1036
+ // add the new child don't update the parent yet - need for loop.
1037
+ // nan.parent = parent.number;
1038
+ parent.children.push(nan.number);
1039
+ }
1040
+
1041
+ node0 = parent;
1042
+
1043
+ parent = draft.getParent(parent) as Node;
1044
+ }
1045
+
1046
+ // Reuse the root node as root...
1047
+
1048
+ // Set the order of the children to be the same as for the original parent of the node.
1049
+ // This makes for a more visually consistent rerooting graphically.
1050
+ rootChild1.parent = rootNode.number;
1051
+ rootChild2.parent = rootNode.number;
1052
+ rootNode.children = [rootChild1.number, rootChild2.number];
1053
+
1054
+ if (!nodeAtTop) {
1055
+ rootNode.children = rootNode.children.reverse();
1056
+ }
1057
+ // connect all the children to their parents which we put off above
1058
+ this.getInternalNodes().forEach((node) => {
1059
+ for (const child of draft.getChildren(node) as Node[]) {
1060
+ child.parent = node.number;
1061
+ }
1062
+ });
1063
+
1064
+ const l = draft.getLength(rootChild1) * proportion;
1065
+ rootChild2.length = l;
1066
+ notNull(
1067
+ rootChild1.length,
1068
+ `Expected the root's new child to have a length`,
1069
+ );
1070
+ rootChild1.length -= l;
1071
+ } else {
1072
+ // the root is staying the same, just the position of the root changing
1073
+ const l = draft.getLength(node) * (1.0 - proportion);
1074
+ treeNode.length = l;
1075
+ const sibling = draft.getNextSibling(node) as Node;
1076
+ sibling.length = rootLength - l;
1077
+ }
1078
+ // todo reset heights.
1079
+ // traverse and get max height;
1080
+ // set all heights to max height - divergence
1081
+ });
1082
+ }
1083
+ removeChild(parent: NodeRef, child: NodeRef): this {
1084
+ return produce(this, (draft) => {
1085
+ draft._data.nodes.allNodes[parent.number].children =
1086
+ draft._data.nodes.allNodes[parent.number].children.filter(
1087
+ (n) => n !== child.number,
1088
+ );
1089
+ draft._data.nodes.allNodes[child.number].parent = -1;
1090
+ });
1091
+ }
1092
+ sortChildren(
1093
+ node: NodeRef,
1094
+ compare: (a: NodeRef, b: NodeRef) => number,
1095
+ ): this {
1096
+ return produce(this, (draft) => {
1097
+ draft._data.nodes.allNodes[node.number].children =
1098
+ this._data.nodes.allNodes[node.number].children
1099
+ .map((n) => draft.getNode(n))
1100
+ .sort(compare)
1101
+ .map((n) => n.number);
1102
+ });
1103
+ }
1104
+
1105
+ addChild(parent: NodeRef, child: NodeRef): this {
1106
+ return produce(this, (draft) => {
1107
+ const c = draft.getNode(child.number) as Node;
1108
+ const p = draft.getNode(parent.number) as Node;
1109
+ p.children.push(c.number);
1110
+ c.parent = parent.number;
1111
+ });
1112
+ }
1113
+
1114
+ setRoot(node: NodeRef): this {
1115
+ return produce(this, (draft) => {
1116
+ draft._data.rootNode = node.number;
1117
+ });
1118
+ }
1119
+ }
1120
+
1121
+ /**
1122
+ * A private recursive function that rotates nodes to give an ordering provided
1123
+ * by a function.
1124
+ * @param node
1125
+ * @param ordering function that takes (a,number of tips form a, b, number of tips from b) and sorts a and be by the output.
1126
+ * @param callback an optional callback that is called each rotate
1127
+ * @returns {number}
1128
+ */
1129
+ //I don't think this needs to update to the root because it is a preorder traversal or is it postorder anyway it's a traversal that probably works
1130
+ function orderNodes(
1131
+ treeData: ImmutableTreeData,
1132
+ node: NodeRef,
1133
+ ordering: (
1134
+ a: NodeRef,
1135
+ numberOfATips: number,
1136
+ b: NodeRef,
1137
+ numberOfBTips: number,
1138
+ ) => number,
1139
+ ): number {
1140
+ let count = 0;
1141
+ if (treeData.nodes.allNodes[node.number].children.length > 0) {
1142
+ // count the number of descendents for each child
1143
+ const counts = new Map<number, number>();
1144
+ for (const child of treeData.nodes.allNodes[node.number].children.map(
1145
+ (n) => treeData.nodes.allNodes[n],
1146
+ )) {
1147
+ const value = orderNodes(treeData, child, ordering);
1148
+ counts.set(child.number, value);
1149
+ count += value;
1150
+ }
1151
+ // sort the children using the provided function
1152
+ const reorderedChildren = treeData.nodes.allNodes[node.number].children
1153
+ .slice()
1154
+ .sort((a, b) => {
1155
+ // sort updates the array in place so changed is always false
1156
+ return ordering(
1157
+ treeData.nodes.allNodes[a],
1158
+ unNullify(
1159
+ counts.get(a),
1160
+ `Internal error when ordering. Counts not defined.`,
1161
+ ),
1162
+ treeData.nodes.allNodes[b],
1163
+ unNullify(
1164
+ counts.get(b),
1165
+ `Internal error when ordering. Counts not defined.`,
1166
+ ),
1167
+ );
1168
+ });
1169
+
1170
+ const changed = reorderedChildren.reduce(
1171
+ (acc: boolean, n: number, i: number) =>
1172
+ acc || n !== treeData.nodes.allNodes[node.number].children[i],
1173
+ true,
1174
+ );
1175
+ if (changed) {
1176
+ treeData.nodes.allNodes[node.number].children = reorderedChildren;
1177
+ }
1178
+ } else {
1179
+ count = 1;
1180
+ }
1181
+ return count;
1182
+ }
1183
+
1184
+ export function* preOrderIterator(
1185
+ tree: Tree,
1186
+ node: NodeRef | undefined = undefined,
1187
+ ): Generator<NodeRef> {
1188
+ const traverse = function* (node: NodeRef): Generator<NodeRef> {
1189
+ yield tree.getNode(node.number); // get from tree so we keep proxy when used in draft
1190
+ const childCount = tree.getChildCount(node);
1191
+ if (childCount > 0) {
1192
+ for (let i = 0; i < childCount; i++) {
1193
+ const child = tree.getChild(node, i);
1194
+ yield* traverse(child);
1195
+ }
1196
+ }
1197
+ };
1198
+ if (node === undefined) {
1199
+ node = tree.getRoot();
1200
+ }
1201
+
1202
+ yield* traverse(node);
1203
+ }
1204
+
1205
+ //TODO return the node and edge length of direction
1206
+ export function* psuedoRootPreOrderIterator(
1207
+ tree: Tree,
1208
+ node: NodeRef | undefined = undefined,
1209
+ sort: (a: NodeRef, b: NodeRef) => number = (a, b) => a.number - b.number,
1210
+ ): Generator<NodeRef> {
1211
+ const traverse = function* (
1212
+ node: NodeRef,
1213
+ visited: number | undefined = undefined,
1214
+ ): Generator<NodeRef> {
1215
+ yield tree.getNode(node.number); // get from tree so we keep proxy when used in draft
1216
+ const branches = [...tree.getChildren(node), tree.getParent(node)].filter(
1217
+ (n) => n.number !== visited,
1218
+ ); //
1219
+ branches.sort(sort);
1220
+ for (const branch of branches) {
1221
+ yield* traverse(branch, node.number);
1222
+ }
1223
+ };
1224
+
1225
+ if (node === undefined) {
1226
+ node = tree.getRoot();
1227
+ }
1228
+ yield* traverse(node);
1229
+ }
1230
+
1231
+ export function* psuedoRootPostOrderIterator(
1232
+ tree: Tree,
1233
+ node: NodeRef | undefined = undefined,
1234
+ sort: (a: NodeRef, b: NodeRef) => number = (a, b) => a.number - b.number,
1235
+ ): Generator<NodeRef> {
1236
+ const traverse = function* (
1237
+ node: NodeRef,
1238
+ visited: number | undefined = undefined,
1239
+ ): Generator<NodeRef> {
1240
+ // get from tree so we keep proxy when used in draft
1241
+ const branches = [...tree.getChildren(node), tree.getParent(node)].filter(
1242
+ (n) => n.number !== visited,
1243
+ );
1244
+ branches.sort(sort);
1245
+ for (const branch of branches) {
1246
+ yield* traverse(branch, node.number);
1247
+ }
1248
+ yield tree.getNode(node.number);
1249
+ };
1250
+
1251
+ if (node === undefined) {
1252
+ node = tree.getRoot();
1253
+ }
1254
+ yield* traverse(node);
1255
+ }
1256
+ export function* postOrderIterator(
1257
+ tree: Tree,
1258
+ node: NodeRef | undefined = undefined,
1259
+ ): Generator<NodeRef> {
1260
+ const traverse = function* (node: NodeRef): Generator<NodeRef> {
1261
+ const childCount = tree.getChildCount(node);
1262
+ if (childCount > 0) {
1263
+ for (let i = 0; i < childCount; i++) {
1264
+ const child = tree.getChild(node, i);
1265
+ yield* traverse(child);
1266
+ }
1267
+ }
1268
+ yield node;
1269
+ };
1270
+ if (node === undefined) {
1271
+ node = tree.getRoot();
1272
+ }
1273
+
1274
+ yield* traverse(node);
1275
+ }
1276
+
1277
+ export function* tipIterator(tree: Tree, node?: NodeRef): Generator<NodeRef> {
1278
+ if (node === undefined) {
1279
+ node = tree.getRoot();
1280
+ }
1281
+ const traverse = function* (node: NodeRef): Generator<NodeRef> {
1282
+ const childCount = tree.getChildCount(node);
1283
+ if (childCount > 0) {
1284
+ for (let i = 0; i < childCount; i++) {
1285
+ const child = tree.getChild(node, i);
1286
+ yield* traverse(child);
1287
+ }
1288
+ } else {
1289
+ yield node;
1290
+ }
1291
+ };
1292
+ yield* traverse(node);
1293
+ }
1294
+
1295
+ export function* pathToRootIterator(
1296
+ tree: Tree,
1297
+ node: NodeRef,
1298
+ ): Generator<NodeRef> {
1299
+ let n: NodeRef = node;
1300
+ while (!tree.isRoot(n)) {
1301
+ yield n;
1302
+ n = tree.getParent(n);
1303
+ }
1304
+ yield n; // gibe the root at the end
1305
+ }
1306
+
1307
+ function updateDomain(
1308
+ annotation: { type: BaseAnnotationType; value: AnnotationValue },
1309
+ currentDomain: AnnotationDomain | undefined,
1310
+ ): AnnotationDomain {
1311
+ switch (annotation.type) {
1312
+ case BaseAnnotationType.BOOLEAN:
1313
+ return [true, false];
1314
+
1315
+ case BaseAnnotationType.DISCRETE: {
1316
+ const value = annotation.value as string;
1317
+ if (currentDomain !== undefined) {
1318
+ const curr = currentDomain as DomainOf<BaseAnnotationType.DISCRETE>;
1319
+ return [...new Set([...curr, value])].sort();
1320
+ } else {
1321
+ return [value];
1322
+ }
1323
+ }
1324
+ case BaseAnnotationType.NUMERICAL: {
1325
+ const value = annotation.value as number;
1326
+ const curr = currentDomain ? (currentDomain as [number, number]) : [];
1327
+ return extent([...curr, value]) as [number, number];
1328
+ }
1329
+
1330
+ case BaseAnnotationType.DISCRETE_SET: {
1331
+ const value = annotation.value as string[];
1332
+ const curr = currentDomain ? (currentDomain as string[]) : [];
1333
+ return [...new Set([...curr, ...value])].sort();
1334
+ }
1335
+
1336
+ case BaseAnnotationType.NUMERICAL_SET: {
1337
+ const value = annotation.value as number[];
1338
+ const curr = currentDomain ? (currentDomain as [number, number]) : [];
1339
+ return extent([...curr, ...value]) as [number, number];
1340
+ }
1341
+
1342
+ case BaseAnnotationType.DENSITIES: {
1343
+ if (currentDomain !== undefined) {
1344
+ const curr = currentDomain as DomainOf<BaseAnnotationType.DENSITIES>;
1345
+ return [...new Set([...curr, ...Object.keys(annotation.value)])].sort();
1346
+ } else {
1347
+ return [...new Set(Object.keys(annotation.value))]
1348
+ .sort()
1349
+ .filter((d) => d);
1350
+ }
1351
+ }
1352
+
1353
+ case BaseAnnotationType.MARKOV_JUMPS: {
1354
+ const value = (annotation.value as MarkovJumpValue[]).reduce<string[]>(
1355
+ (acc: string[], d: MarkovJumpValue) => acc.concat([d.to, d.from]),
1356
+ [],
1357
+ );
1358
+ const curr = currentDomain ? (currentDomain as string[]) : [];
1359
+ return [...new Set([...curr, ...value])].sort();
1360
+ }
1361
+
1362
+ default:
1363
+ throw new Error(`Unrecognized type when updating domain`);
1364
+ }
1365
+ }