@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,289 @@
1
+ import { u } from "../../../utils";
2
+ import { ImmutableTree } from "../normalized-tree/immutable-tree";
3
+ import { processAnnotationValue } from "./annotation-parser";
4
+ import { describe, it, expect } from "vitest";
5
+ describe("Test annotation parsing", () => {
6
+ it("integer", function () {
7
+ const annotation = processAnnotationValue("1");
8
+ expect(annotation).toEqual({ type: "NUMERICAL", value: 1 });
9
+ });
10
+ it("integer array", function () {
11
+ const annotation = processAnnotationValue(["1", "2", "3"]);
12
+ expect(annotation).toEqual({ type: "NUMERICAL_SET", value: [1, 2, 3] });
13
+ });
14
+
15
+ it("continuous array", function () {
16
+ const annotation = processAnnotationValue(["1", "2.5", "3"]);
17
+ expect(annotation).toEqual({ type: "NUMERICAL_SET", value: [1, 2.5, 3] });
18
+ });
19
+
20
+ it("continuous range", function () {
21
+ const annotation = processAnnotationValue([0.0, 1]);
22
+ expect(annotation).toEqual({ type: "NUMERICAL_SET", value: [0, 1] });
23
+ });
24
+
25
+ it("continuous", function () {
26
+ const annotation = processAnnotationValue("1.3");
27
+ expect(annotation).toEqual({ type: "NUMERICAL", value: 1.3 });
28
+ });
29
+ it("discrete", function () {
30
+ const annotation = processAnnotationValue("a");
31
+ expect(annotation).toEqual({ type: "DISCRETE", value: "a" });
32
+ });
33
+ it("discrete array", function () {
34
+ const annotation = processAnnotationValue(["a", "b", "c"]);
35
+ expect(annotation).toEqual({
36
+ type: "DISCRETE_SET",
37
+ value: ["a", "b", "c"],
38
+ });
39
+ });
40
+ it("markov jump", function () {
41
+ const annotation = processAnnotationValue([["0.1", "U", "me"]]);
42
+ expect(annotation).toEqual({
43
+ type: "MARKOV_JUMPS",
44
+ value: [{ time: 0.1, from: "U", to: "me" }],
45
+ });
46
+ });
47
+ it("markov jump array", function () {
48
+ const annotation = processAnnotationValue([
49
+ ["0.1", "U", "me"],
50
+ ["0.2", "me", "U"],
51
+ ]);
52
+ expect(annotation).toEqual({
53
+ type: "MARKOV_JUMPS",
54
+ value: [
55
+ { time: 0.1, from: "U", to: "me" },
56
+ { time: 0.2, from: "me", to: "U" },
57
+ ],
58
+ });
59
+ });
60
+ it("probabilities", function () {
61
+ const annotation = processAnnotationValue({ HERE: 0.3, THERE: 0.7 });
62
+ expect(annotation).toEqual({
63
+ type: "DENSITIES",
64
+ value: { HERE: 0.3, THERE: 0.7 },
65
+ });
66
+ });
67
+ });
68
+
69
+ describe("Test tree parsing and normalized Tree", () => {
70
+ it("simpleParse", function () {
71
+ const newickString = `((((((virus1:0.1,virus2:0.12):0.08,(virus3:0.011,virus4:0.0087):0.15):0.03,virus5:0.21):0.2,(virus6:0.45,virus7:0.4):0.02):0.1,virus8:0.4):0.1,(virus9:0.04,virus10:0.03):0.6);`;
72
+
73
+ const tree = ImmutableTree.fromNewick(newickString, {
74
+ parseAnnotations: false,
75
+ labelName: "probability",
76
+ });
77
+ expect(tree.toNewick()).toEqual(newickString);
78
+ });
79
+
80
+ it("height", function () {
81
+ const newickString = `((((((virus1:0.1,virus2:0.12):0.08,(virus3:0.011,virus4:0.0087):0.15):0.03,virus5:0.21):0.2,(virus6:0.45,virus7:0.4):0.02):0.1,virus8:0.4):0.1,(virus9:0.04,virus10:0.03):0.6);`;
82
+ const tree = ImmutableTree.fromNewick(newickString, { labelName: "prob" });
83
+ const virus1 = tree.getTaxonByName("virus1");
84
+ const virus1Node = tree.getNodeByTaxon(virus1);
85
+ expect(tree.getHeight(virus1Node)).toBeCloseTo(0.06, 1e-6);
86
+ });
87
+ it("divergence", function () {
88
+ const newickString = `((((((virus1:0.1,virus2:0.12):0.08,(virus3:0.011,virus4:0.0087):0.15):0.03,virus5:0.21):0.2,(virus6:0.45,virus7:0.4):0.02):0.1,virus8:0.4):0.1,(virus9:0.04,virus10:0.03):0.6);`;
89
+ const tree = ImmutableTree.fromNewick(newickString, { labelName: "prob" });
90
+ const virus6 = tree.getTaxonByName("virus6");
91
+ const virus6Node = tree.getNodeByTaxon(virus6);
92
+ expect(tree.getDivergence(virus6Node)).toBeCloseTo(0.67, 1e-6);
93
+ });
94
+ it("general_parse", function () {
95
+ const tree = ImmutableTree.fromNewick("(a:1,b:4)#l;");
96
+ const root = tree.getRoot();
97
+ const label = tree.getLabel(root);
98
+ expect(label).toEqual("l");
99
+
100
+ const r = tree.getNodeByLabel("l");
101
+ expect(r).toEqual(root);
102
+
103
+ const names = [];
104
+ const bl = [];
105
+
106
+ const count = tree.getChildCount(root);
107
+
108
+ for (let i = 0; i < count; i++) {
109
+ const child = tree.getChild(root, i);
110
+ names.push(tree.getTaxonFromNode(child).name);
111
+ bl.push(tree.getLength(child));
112
+ }
113
+ expect(names).toEqual(["a", "b"]);
114
+
115
+ expect(bl).toEqual([1, 4]);
116
+ });
117
+
118
+ it("scientific notation", function () {
119
+ const tree = ImmutableTree.fromNewick("(a:1E1,b:2e-5);");
120
+ const root = tree.getRoot();
121
+ const bl = [];
122
+ const count = tree.getChildCount(root);
123
+
124
+ for (let i = 0; i < count; i++) {
125
+ const child = tree.getChild(root, i);
126
+ bl.push(tree.getLength(child));
127
+ }
128
+ expect(bl[0]).toBeCloseTo(10.0, 1e-6);
129
+ expect(bl[1]).toBeCloseTo(0.00002, 1e-6);
130
+ });
131
+
132
+ it("quoted taxa", function () {
133
+ const tree = ImmutableTree.fromNewick("('234] ':1,'here a *':1);");
134
+ const names = [...tree.getExternalNodes()].map(
135
+ (node) => tree.getTaxonFromNode(node).name,
136
+ );
137
+ expect(names).toEqual(["234]", "here a *"]);
138
+ });
139
+
140
+ it("whitespace", function () {
141
+ const tree = ImmutableTree.fromNewick(" (a,b:1);\t");
142
+ expect(tree.toNewick()).toEqual("(a,b:1);");
143
+ });
144
+ it("node id", function () {
145
+ const tree = ImmutableTree.fromNewick("((A,T)#Node_1:1,(a,b:1));");
146
+ const A = tree.getTaxonByName("A");
147
+ const B = tree.getTaxonByName("b");
148
+ const Anode = tree.getNodeByTaxon(A);
149
+ const node1 = tree.getNodeByLabel("Node_1");
150
+ const parent = tree.getParent(Anode);
151
+
152
+ expect(parent).toEqual(node1);
153
+
154
+ expect(tree.getLength(u(tree.getNodeByTaxon(B)))).toEqual(1);
155
+ expect(tree.toNewick()).toEqual("((A,T)#Node_1:1,(a,b:1));");
156
+ });
157
+ it("root length and label", function () {
158
+ const tree = ImmutableTree.fromNewick("((A,T)#Node_1:1,(a,b:1))#root:0.1;");
159
+ const root = tree.getRoot();
160
+ const rootLength = tree.getLength(root);
161
+ expect(rootLength).toEqual(0.1);
162
+ const label = tree.getLabel(root);
163
+ expect(label).toEqual("root");
164
+ expect(tree.toNewick()).toEqual("((A,T)#Node_1:1,(a,b:1))#root:0.1;");
165
+ });
166
+
167
+ it("fail no ;", function () {
168
+ expect(() => ImmutableTree.fromNewick("('234] ','here a *')")).toThrow(
169
+ "expecting a semi-colon at the end of the newick string",
170
+ );
171
+ });
172
+
173
+ it("fail unbalanced )", function () {
174
+ expect(() => ImmutableTree.fromNewick("(a,b));")).toThrow(
175
+ "the brackets in the newick file are not balanced: too many closed",
176
+ );
177
+ });
178
+
179
+ it("fail unbalanced (", function () {
180
+ expect(() => ImmutableTree.fromNewick("((a,b);")).toThrow(
181
+ "unexpected semi-colon in tree did not reach the root yet",
182
+ );
183
+ });
184
+
185
+ it("comment", function () {
186
+ const tree = ImmutableTree.fromNewick("(a[&test=ok],b:1);", {
187
+ parseAnnotations: true,
188
+ });
189
+ const a = tree.getTaxonByName("a");
190
+ const aNode = tree.getNodeByTaxon(a);
191
+ const testAnnotation = tree.getAnnotation(aNode, "test");
192
+ expect(testAnnotation).toEqual("ok");
193
+ });
194
+
195
+ it("markov jump comment", function () {
196
+ const tree = ImmutableTree.fromNewick(
197
+ "(a[&test=ok],b[&jump={{0.1,U,me}}]);",
198
+ { parseAnnotations: true },
199
+ );
200
+ const a = tree.getNodeByTaxon(tree.getTaxonByName("a"));
201
+ const b = tree.getNodeByTaxon(tree.getTaxonByName("b"));
202
+ const testAnnotation = tree.getAnnotation(a, "test");
203
+ expect(testAnnotation).toEqual("ok");
204
+ const jumpAnnotation = tree.getAnnotation(b, "jump");
205
+ expect(jumpAnnotation).toEqual([{ time: 0.1, from: "U", to: "me" }]);
206
+ });
207
+
208
+ it("double comment", function () {
209
+ const tree = ImmutableTree.fromNewick("(a[&test=ok,other test = 1],b:1);", {
210
+ parseAnnotations: true,
211
+ });
212
+ const a = tree.getNodeByTaxon(tree.getTaxonByName("a"));
213
+ const testAnnotation = tree.getFullNodeAnnotation(a, "test");
214
+
215
+ expect(testAnnotation).toEqual({
216
+ id: "test",
217
+ type: "DISCRETE",
218
+ value: "ok",
219
+ });
220
+ const otherTestAnnotation = tree.getFullNodeAnnotation(a, "other test");
221
+ expect(otherTestAnnotation).toEqual({
222
+ id: "other test",
223
+ type: "NUMERICAL",
224
+ value: 1,
225
+ });
226
+ });
227
+
228
+ it("SET comment", function () {
229
+ const tree = ImmutableTree.fromNewick(
230
+ "(a[&test={ok,Not ok},other test = {0,1}],b:1);",
231
+ {
232
+ parseAnnotations: true,
233
+ },
234
+ );
235
+ const a = tree.getNodeByTaxon(tree.getTaxonByName("a"));
236
+ const testAnnotation = tree.getFullNodeAnnotation(a, "test");
237
+ expect(testAnnotation).toEqual({
238
+ id: "test",
239
+ type: "DISCRETE_SET",
240
+ value: ["ok", "Not ok"],
241
+ });
242
+
243
+ const otestAnnotation = tree.getFullNodeAnnotation(a, "other test");
244
+ expect(otestAnnotation).toEqual({
245
+ id: "other test",
246
+ type: "NUMERICAL_SET",
247
+ value: [0, 1],
248
+ });
249
+ });
250
+
251
+ it("SET comment writing", function () {
252
+ const treeString = "(a[&test={ok, Not ok}, other test={0, 1}],b:1);";
253
+ const tree = ImmutableTree.fromNewick(treeString, {
254
+ parseAnnotations: true,
255
+ });
256
+ expect(tree.toNewick(undefined, { includeAnnotations: true })).toEqual(
257
+ treeString,
258
+ );
259
+ });
260
+ it("label annotation", function () {
261
+ const tree = ImmutableTree.fromNewick(
262
+ "((((((virus1:0.1,virus2:0.12)0.95:0.08,(virus3:0.011,virus4:0.0087)1.0:0.15)0.65:0.03,virus5:0.21)1.0:0.2,(virus6:0.45,virus7:0.4)0.51:0.02)1.0:0.1,virus8:0.4)1.0:0.1,(virus9:0.04,virus10:0.03)1.0:0.6);",
263
+ { parseAnnotations: true, labelName: "probability" },
264
+ );
265
+ const virus1Node = tree.getNodeByTaxon(tree.getTaxonByName("virus1"));
266
+
267
+ const probability = tree.getAnnotation(
268
+ tree.getParent(virus1Node),
269
+ "probability",
270
+ );
271
+
272
+ expect(probability).toEqual(0.95);
273
+ });
274
+ it("nexus", function () {
275
+ const nexusString = `#NEXUS
276
+ BEGIN TAXA;
277
+ DIMENSIONS NTAX=10;
278
+ TAXLABELS virus1 virus10 virus2 virus3 virus4 virus5 virus6 virus7 virus8 virus9;
279
+ END;
280
+ BEGIN TREES;
281
+ TREE tree0 = ((((((virus1:0.1,virus2:0.12):0.08,(virus3:0.011,virus4:0.0087):0.15):0.03,virus5:0.21):0.2,(virus6:0.45,virus7:0.4):0.02):0.1,virus8:0.4):0.1,(virus9:0.04,virus10:0.03):0.6);
282
+ END;
283
+ `;
284
+ const tree = ImmutableTree.fromNexus(nexusString);
285
+ const newickString = `((((((virus1:0.1,virus2:0.12):0.08,(virus3:0.011,virus4:0.0087):0.15):0.03,virus5:0.21):0.2,(virus6:0.45,virus7:0.4):0.02):0.1,virus8:0.4):0.1,(virus9:0.04,virus10:0.03):0.6);`;
286
+
287
+ expect(tree.toNewick()).toEqual(newickString);
288
+ });
289
+ });
@@ -0,0 +1 @@
1
+ export * from "./nexus-importer";
@@ -0,0 +1,395 @@
1
+ import { ImmutableTree } from "../../NormalizedTree"
2
+ import { TaxonSet } from "../../Taxa/Taxon"
3
+
4
+ class newickImporter {
5
+ reader: ReadableStreamDefaultReader<string>
6
+ utf8Decoder: TextDecoder
7
+ taxonSet: TaxonSet | undefined
8
+ currentBlock: string | undefined
9
+ hasTree: boolean | undefined
10
+ options: { labelName?: string }
11
+
12
+ //TODO update the options here to just read in trees.
13
+
14
+
15
+ constructor(
16
+ stream: ReadableStream<Uint8Array>,
17
+ options: { labelName?: string } = {},
18
+ ) {
19
+ this.utf8Decoder = new TextDecoder("utf-8")
20
+ const newickTransformer = new TransformStream(transformerOptions())
21
+
22
+ this.reader = stream
23
+ .pipeThrough(new TextDecoderStream())
24
+ .pipeThrough(newickTransformer)
25
+ .getReader()
26
+
27
+ this.taxonSet = undefined
28
+ this.currentBlock = undefined
29
+ this.options = options
30
+ }
31
+
32
+ async *getNextTree(taxa:TaxonSet): AsyncIterableIterator<ImmutableTree> {
33
+ yield* this.parseNextTree()
34
+ }
35
+
36
+ async parseNextBlock() {
37
+ const blockName = await this.getNextBlockName()
38
+ switch (blockName) {
39
+ case "taxa":
40
+ this.currentBlock = "taxa"
41
+ await this.parseTaxaBlock()
42
+ break
43
+ case "trees":
44
+ this.currentBlock = "trees"
45
+ break
46
+ default:
47
+ console.log(
48
+ `skipping block ${blockName}. Only parsing blocks are taxa and trees for now.`,
49
+ )
50
+ await this.readToEndOfBlock()
51
+ }
52
+ }
53
+
54
+ async nextToken() {
55
+ const { done, value } = await this.reader.read()
56
+ if (done) {
57
+ throw "unexpectedly hit the end of the stream"
58
+ }
59
+ return value
60
+ }
61
+ //make skip and then a list of regex to skip
62
+ async skipSemiColon() {
63
+ const { done, value } = await this.reader.read()
64
+ if (done) {
65
+ throw "unexpectedly hit the end of the stream"
66
+ }
67
+ if (!value.match(/;$/)) {
68
+ throw `expected ";" got ${value}`
69
+ }
70
+ }
71
+
72
+ async getNextBlockName() {
73
+ while (true) {
74
+ const value = await this.nextToken()
75
+
76
+ if (value.match(/\bbegin/i)) {
77
+ const token = await this.nextToken()
78
+ this.skipSemiColon()
79
+ return token
80
+ }
81
+ }
82
+ }
83
+
84
+ async readToEndOfBlock() {
85
+ while (true) {
86
+ const value = await this.nextToken()
87
+ if (value.match(/\bend;/i)) {
88
+ break
89
+ }
90
+ }
91
+ }
92
+
93
+ async getNextCommand(command: RegExp) {
94
+ while (true) {
95
+ const value = await this.nextToken()
96
+ if (value === ";") {
97
+ throw `Hit ; looking for ${command}`
98
+ }
99
+ if (command.test(value)) {
100
+ return value
101
+ break
102
+ }
103
+ }
104
+ }
105
+ // skip until match and return match
106
+ async skipUntil(stopper: RegExp) {
107
+ while (true) {
108
+ const value = await this.nextToken()
109
+ if (stopper.test(value)) {
110
+ return value
111
+ }
112
+ }
113
+ }
114
+ // read up to match return everything up to including the match
115
+ async readUntil(stopper: RegExp) {
116
+ let buffer = ""
117
+ while (true) {
118
+ const value = await this.nextToken()
119
+ if (stopper.test(value)) {
120
+ return buffer + value
121
+ }
122
+ buffer += value
123
+ }
124
+ }
125
+ async parseTaxaBlock() {
126
+ let ntax
127
+ let command = await this.skipUntil(/dimensions|taxlabels|end/i)
128
+ while (true) {
129
+ switch (true) {
130
+ case /dimensions/i.test(command):
131
+ const taxaLine = await this.readUntil(/;/)
132
+ const ntaxa = taxaLine.match(/ntax=(\d+);/)
133
+ if (ntaxa) {
134
+ ntax = parseInt(ntaxa[1])
135
+ } else {
136
+ throw `Expected dimension in form of ntax=(\d+);. Got ${taxaLine}`
137
+ }
138
+ break
139
+ case /taxlabels/i.test(command):
140
+ let token = await this.nextToken()
141
+ this.taxonSet = new TaxonSet()
142
+ while (token !== ";") {
143
+ this.taxonSet.addTaxon(token)
144
+ token = await this.nextToken()
145
+ }
146
+ if (ntax) {
147
+ if (ntax != this.taxonSet.getTaxonCount()) {
148
+ throw `found ${this.taxonSet.getTaxonCount()} taxa. Expected: ${ntax}}`
149
+ }
150
+ }
151
+ break
152
+ case /end/i.test(command):
153
+ if (this.taxonSet!.getTaxonCount() === 0) {
154
+ throw "hit end of taxa section but didn't find any taxa"
155
+ }
156
+ this.skipSemiColon()
157
+ default:
158
+ throw `Reached impossible code looking for dimensions or taxlabels in taxa block "${command}"`
159
+ }
160
+ command = await this.skipUntil(/dimensions|taxlabels|end/i)
161
+ }
162
+ }
163
+
164
+ async *parseNextTree() {
165
+ let command = await this.skipUntil(/translate|tree|end/i)
166
+ let token
167
+ while (true) {
168
+ switch (true) {
169
+ case /translate/i.test(command):
170
+ // all white space removed by tranformStream so will be
171
+ // ['key','taxon,'] but may be ['key','taxon',','] if space tween taxa and ,
172
+ this.translateTaxonMap = new Map()
173
+ let newTaxonSet = false
174
+ if (!this.taxonSet) {
175
+ this.taxonSet = new TaxonSet()
176
+ newTaxonSet = true
177
+ }
178
+ let i = 0
179
+ let key
180
+ token = await this.nextToken()
181
+ while (token !== ";") {
182
+ if (i % 2 == 0) {
183
+ key = token
184
+ } else {
185
+ if (token[token.length - 1] === ",") {
186
+ token = token.slice(0, -1)
187
+ }
188
+ // todo get taxa to add here.
189
+ if (!newTaxonSet) {
190
+ if (this.taxonSet.getTaxonByName(token) === undefined) {
191
+ throw `Taxon ${token} not found in taxa block - but found in translate block`
192
+ }
193
+ } else {
194
+ //new taxon set so add it;
195
+ this.taxonSet.addTaxon(token)
196
+ }
197
+ const taxon = this.taxonSet.getTaxonByName(token)
198
+ this.translateTaxonMap.set(key!, taxon)
199
+ }
200
+ token = await this.nextToken()
201
+ while (token === ",") {
202
+ token = await this.nextToken()
203
+ } //incase some white space in there
204
+ i++
205
+ }
206
+ break
207
+ case /tree/i.test(command):
208
+ //parse tree
209
+ // put this in loop so the next call parses the next tree;
210
+
211
+ // first token will be tree id
212
+ // then possible annotation
213
+ // Then =
214
+ // then possible annotations
215
+ // then tree
216
+ const treeId = await this.nextToken()
217
+ // read to first '(';
218
+ token = await this.skipUntil(/\(/)
219
+ let buffer = token
220
+ .split(newickDeliminators)
221
+ .filter((n) => n.length > 0)
222
+ .reverse()
223
+
224
+ let level = 0
225
+ let currentNode: NodeRef | undefined = undefined
226
+ let nodeStack: NodeRef[] = []
227
+ let labelNext = false
228
+ let lengthNext = false
229
+
230
+ let tree = new ImmutableTree({ taxonSet: this.taxonSet })
231
+
232
+ while (token !== ";") {
233
+ while (buffer.length > 0) {
234
+ const t = buffer.pop()!
235
+ if (t.length > 2 && t.substring(0, 2) === "[&") {
236
+ const annotations = parseAnnotation(token)
237
+
238
+ tree = tree.annotateNode(currentNode!, annotations)
239
+ } else if (token === "(") {
240
+ // an internal node
241
+
242
+ if (labelNext) {
243
+ // if labelNext is set then the last bracket has just closed
244
+ // so there shouldn't be an open bracket.
245
+ throw new Error("expecting a comma")
246
+ }
247
+ let node
248
+ level += 1
249
+ if (currentNode !== undefined) {
250
+ const added = tree.addNodes(1)
251
+ tree = added.tree
252
+ node = added.nodes[0]
253
+ nodeStack.push(currentNode)
254
+ } else {
255
+ node = tree.getRoot()
256
+ }
257
+ currentNode = node
258
+ } else if (token === ",") {
259
+ // another branch in an internal node
260
+
261
+ labelNext = false // labels are optional
262
+ if (lengthNext) {
263
+ throw new Error("branch length missing")
264
+ }
265
+
266
+ let parent = nodeStack.pop()! as NodeRef
267
+ tree = tree.addChild(parent, currentNode!)
268
+ // tree.setParent(currentNode!,parent)
269
+
270
+ currentNode = parent
271
+ } else if (token === ")") {
272
+ if (level === 0) {
273
+ throw new Error(
274
+ "the brackets in the newick file are not balanced: too many closed",
275
+ )
276
+ }
277
+ // finished an internal node
278
+
279
+ labelNext = false // labels are optional
280
+ if (lengthNext) {
281
+ throw new Error("branch length missing")
282
+ }
283
+
284
+ // the end of an internal node
285
+ let parent = nodeStack.pop()! as NodeRef
286
+ tree = tree.addChild(parent, currentNode!)
287
+ // tree.setParent(currentNode!,parent)
288
+
289
+ level -= 1
290
+ currentNode = parent
291
+
292
+ labelNext = true
293
+ } else if (token === ":") {
294
+ labelNext = false // labels are optional
295
+ lengthNext = true
296
+ } else {
297
+ // not any specific token so may be a label, a length, or an external node name
298
+ if (lengthNext) {
299
+ tree = tree.setLength(currentNode!, parseFloat(token))
300
+ lengthNext = false
301
+ } else if (labelNext) {
302
+ if (!token.startsWith("#")) {
303
+ let value: number | any = parseFloat(token)
304
+ if (isNaN(value)) {
305
+ value = token
306
+ }
307
+ if (this.options.labelName) {
308
+ let label_annotation = {
309
+ name: this.options.labelName,
310
+ value: value,
311
+ }
312
+ tree = tree.annotateNode(currentNode!, label_annotation)
313
+ } else {
314
+ console.warn(
315
+ `No label name provided to newick parser but found label ${token}. It will be ignored`,
316
+ )
317
+ }
318
+ } else {
319
+ tree = tree.setLabel(currentNode!, token.slice(1)) //remove the # todo put it back when writing to newick
320
+ }
321
+ labelNext = false
322
+ } else {
323
+ let name = token // TODO tree needs be a map that's not the ID
324
+
325
+ // remove any quoting and then trim whitespace
326
+ // TODO add to bit that parses taxa block
327
+ if (name.startsWith('"') || name.startsWith("'")) {
328
+ name = name.substr(1)
329
+ }
330
+ if (name.endsWith('"') || name.endsWith("'")) {
331
+ name = name.substr(0, name.length - 1)
332
+ }
333
+ name = name.trim()
334
+
335
+ const added = tree.addNodes(1)
336
+ tree = added.tree
337
+ const externalNode = added.nodes[0]
338
+ let taxon: Taxon
339
+ if (this.translateTaxonMap) {
340
+ if (this.translateTaxonMap.has(name)) {
341
+ taxon = this.translateTaxonMap.get(name)!
342
+ } else {
343
+ throw `No mapping found for ${name} in tipNameMap. It's name will not be updated`
344
+ }
345
+ } else if (this.taxonSet) {
346
+ taxon = this.taxonSet.getTaxonByName(name)
347
+ if (taxon === undefined) {
348
+ throw `Taxon ${name} not found in taxa - but found in tree`
349
+ }
350
+ } else {
351
+ tree.addTaxon(name) //does this affect immutability - using the first tree as a taxon set.
352
+ taxon = tree.getTaxonByName(name)!
353
+ }
354
+
355
+ tree = tree.setTaxon(externalNode, taxon)
356
+
357
+ if (currentNode) {
358
+ nodeStack.push(currentNode)
359
+ }
360
+ currentNode = externalNode
361
+ }
362
+ }
363
+ }
364
+ // get next token from reader
365
+ token = await this.nextToken()
366
+ buffer = token
367
+ .split(newickDeliminators)
368
+ .filter((n) => n.length > 0)
369
+ .reverse()
370
+ }
371
+ if (level > 0) {
372
+ throw new Error(`unexpected semi-colon in tree: ${treeId}`)
373
+ }
374
+ if (this.taxonSet === undefined) {
375
+ this.taxonSet === tree
376
+ }
377
+ yield tree
378
+ break
379
+ case /end/i.test(command):
380
+ this.skipSemiColon()
381
+ this.hasTree = false
382
+ // Give up the reader.
383
+ //TODO read to the end of the file.
384
+ this.reader.releaseLock()
385
+ break
386
+ default:
387
+ throw `Reached impossible code in treeblock block "${command}"`
388
+ }
389
+ command = await this.skipUntil(/translate|tree|end/i)
390
+ }
391
+ }
392
+ }
393
+
394
+
395
+