@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,116 @@
1
+ import { MaybeType } from "@figtreejs/maybe/maybe";
2
+ import { maybeGetNameFromIndex, maybeGetTaxonByName } from "./helper-functions";
3
+
4
+ export interface Taxon {
5
+ name: string;
6
+ number: number;
7
+ annotations: { [annotation: string]: string | string[] | number | number[] };
8
+ }
9
+
10
+ function newTaxon(name: string, number: number): Taxon {
11
+ return {
12
+ name,
13
+ number,
14
+ annotations: {},
15
+ };
16
+ }
17
+
18
+ export interface TaxonSetInterface {
19
+ addTaxon(name: string): TaxonSetInterface;
20
+ getTaxon(id: number): Taxon | undefined;
21
+ getTaxonByName(name: string): Taxon;
22
+ getTaxonCount(): number;
23
+ lockTaxa(): TaxonSetInterface;
24
+ }
25
+
26
+ export interface TaxonSetData {
27
+ allNames: string[];
28
+ byName: { [taxon: string]: Taxon };
29
+ finalized: boolean;
30
+ }
31
+ export class TaxonSet implements TaxonSetInterface {
32
+ _data: TaxonSetData;
33
+ constructor(taxonSetData?: TaxonSetData) {
34
+ this._data = taxonSetData
35
+ ? taxonSetData
36
+ : {
37
+ allNames: [],
38
+ byName: {},
39
+ finalized: false,
40
+ };
41
+ }
42
+ lockTaxa(): TaxonSetInterface {
43
+ if (!this._data.finalized) {
44
+ this._data.finalized = true;
45
+ }
46
+ return this;
47
+ }
48
+ addTaxon(taxonOrName: string | Taxon): this {
49
+ if (this._data.finalized) {
50
+ throw new Error("Cannot add taxon to finalized set");
51
+ }
52
+ let taxon: Taxon;
53
+ if (typeof taxonOrName === "string") {
54
+ const name = taxonOrName;
55
+
56
+ if (Object.prototype.hasOwnProperty.call(this._data.byName, name)) {
57
+ throw new Error(
58
+ `taxon ${name} already exists in the set. Names must be unique`,
59
+ );
60
+ }
61
+
62
+ taxon = newTaxon(name, this._data.allNames.length);
63
+ } else {
64
+ taxon = taxonOrName;
65
+ if (Object.prototype.hasOwnProperty.call(this._data.byName, taxon.name)) {
66
+ throw new Error(
67
+ `taxon ${taxon.name} already exists in the set. Names must be unique`,
68
+ );
69
+ }
70
+ if (
71
+ this._data.allNames[taxon.number] &&
72
+ this._data.allNames[taxon.number] !== taxon.name
73
+ ) {
74
+ throw new Error(
75
+ `taxon number ${taxon.number} already exists in the set with name ${this._data.allNames[taxon.number]}. Taxon numbers must be unique`,
76
+ );
77
+ }
78
+ console.log("Adding existing taxon:", taxon.name);
79
+ }
80
+ this._data.allNames[taxon.number] = taxon.name;
81
+ this._data.byName[taxon.name] = taxon;
82
+ return this;
83
+ }
84
+
85
+ getTaxon(id: number): Taxon {
86
+ const taxon = maybeGetTaxonByName(
87
+ this._data,
88
+ maybeGetNameFromIndex(this._data, id),
89
+ );
90
+ switch (taxon.type) {
91
+ case MaybeType.Some:
92
+ return taxon.value;
93
+ case MaybeType.Nothing:
94
+ throw new Error(`Taxon by name ${id} not found`); // won't get here I dont' think
95
+ }
96
+ }
97
+ getTaxonByName(name: string): Taxon {
98
+ const taxon = maybeGetTaxonByName(this._data, name);
99
+ switch (taxon.type) {
100
+ case MaybeType.Some:
101
+ return taxon.value;
102
+ case MaybeType.Nothing:
103
+ throw new Error(`Taxon by name ${name} not found`);
104
+ }
105
+ }
106
+ hasTaxon(id: string): boolean {
107
+ return Object.prototype.hasOwnProperty.call(this._data.byName, id);
108
+ }
109
+
110
+ getTaxonCount(): number {
111
+ return this._data.allNames.length;
112
+ }
113
+ get isFinalized() {
114
+ return this._data.finalized;
115
+ }
116
+ }
@@ -0,0 +1 @@
1
+ export { PreOrderTraversalCache } from "./preorder-traversal";
@@ -0,0 +1,89 @@
1
+ import type { Tree, NodeRef } from "../tree-types";
2
+ import type { TreeTraversal } from "./traversal-types";
3
+ export class PreOrderTraversalCache implements TreeTraversal {
4
+ _forwardCache: Map<NodeRef, NodeRef>;
5
+ _reverseCache: Map<NodeRef, NodeRef>;
6
+ constructor() {
7
+ this._forwardCache = new Map();
8
+ this._reverseCache = new Map();
9
+ }
10
+
11
+ *traverse(tree: Tree, node?: NodeRef): Generator<NodeRef> {
12
+ const traverse = function* (node: NodeRef): Generator<NodeRef> {
13
+ const childCount = tree.getChildCount(node);
14
+
15
+ if (childCount > 0) {
16
+ for (let i = 0; i < childCount; i++) {
17
+ const child = tree.getChild(node, i);
18
+ yield* traverse(child);
19
+ }
20
+ }
21
+ yield node;
22
+ };
23
+ if (node === undefined) {
24
+ node = tree.getRoot();
25
+ }
26
+ yield* traverse(node);
27
+ }
28
+
29
+ //Check we've been to next otherwise we need to update again.
30
+ getNext(tree: Tree, node: NodeRef): NodeRef | undefined {
31
+ const n = this._forwardCache.get(node);
32
+ if (n !== undefined) {
33
+ if (this._forwardCache.get(n) !== undefined) {
34
+ return n; //if this node hasn't changed nor the next
35
+ }
36
+ }
37
+
38
+ if (tree.isRoot(node)) {
39
+ //start over
40
+ return undefined;
41
+ }
42
+ const parent = tree.getParent(node);
43
+ if (tree.hasRightSibling(node)) {
44
+ const rs = tree.getRightSibling(node);
45
+ this._forwardCache.set(node, rs);
46
+ this._reverseCache.set(rs, node);
47
+ } else {
48
+ this._forwardCache.set(node, parent);
49
+ this._reverseCache.set(parent, node);
50
+ }
51
+
52
+ return this._forwardCache.get(node);
53
+ }
54
+ getPrevious(tree: Tree, node: NodeRef): NodeRef | undefined {
55
+ const n = this._reverseCache.get(node);
56
+ if (n !== undefined) {
57
+ if (this._reverseCache.get(n) !== undefined) {
58
+ return n; //if this node hasn't changed nor the next
59
+ }
60
+ }
61
+ if (node === this.traverse(tree).next().value) {
62
+ return undefined;
63
+ }
64
+ if (tree.isInternal(node)) {
65
+ const childCount = tree.getChildCount(node) - 1;
66
+ const lastChild = tree.getChild(node, childCount);
67
+ this._reverseCache.set(node, lastChild);
68
+ this._forwardCache.set(lastChild, node);
69
+ } else {
70
+ if (tree.hasLeftSibling(node)) {
71
+ const ls = tree.getLeftSibling(node);
72
+ this._reverseCache.set(node, ls);
73
+ this._forwardCache.set(ls, node);
74
+ } else {
75
+ let n = node;
76
+ while (!tree.hasLeftSibling(n)) {
77
+ // aunt = tree.getLeftSibling(n);
78
+ n = tree.getParent(n);
79
+ }
80
+ const aunt = tree.getLeftSibling(n);
81
+ this._reverseCache.set(node, aunt);
82
+ this._forwardCache.set(aunt, node);
83
+ // look for parent's left sibling if none try again until root
84
+ // if at and no ls error we are at first tip and should have caught this above.
85
+ }
86
+ }
87
+ return this._reverseCache.get(node);
88
+ }
89
+ }
@@ -0,0 +1,6 @@
1
+ import type { NodeRef, Tree } from "../tree-types";
2
+
3
+ export interface TreeTraversal {
4
+ getNext(tree: Tree, node: NodeRef): NodeRef | undefined;
5
+ getPrevious(tree: Tree, node: NodeRef): NodeRef | undefined;
6
+ }
@@ -0,0 +1,197 @@
1
+ import type { Taxon, TaxonSet } from "./taxa/taxon";
2
+
3
+ export interface NodeRef {
4
+ number: number;
5
+ _id: string;
6
+ }
7
+ export enum BaseAnnotationType {
8
+ DISCRETE = "DISCRETE", // string could also be stringy numbers
9
+ BOOLEAN = "BOOLEAN", // true false
10
+ NUMERICAL = "NUMERICAL", // float
11
+
12
+ NUMERICAL_SET = "NUMERICAL_SET", // number[]
13
+ DISCRETE_SET = "DISCRETE_SET", // string[]
14
+
15
+ MARKOV_JUMPS = "MARKOV_JUMPS", // {from: to: time:}
16
+ DENSITIES = "DENSITIES", // Record<string, number>
17
+ }
18
+ export type MarkovJumpValue = { from: string; to: string; time?: number };
19
+
20
+ export type ValueOf<T extends BaseAnnotationType> =
21
+ T extends BaseAnnotationType.DISCRETE
22
+ ? string
23
+ : T extends BaseAnnotationType.BOOLEAN
24
+ ? boolean
25
+ : T extends BaseAnnotationType.NUMERICAL
26
+ ? number
27
+ : T extends BaseAnnotationType.NUMERICAL_SET
28
+ ? number[]
29
+ : T extends BaseAnnotationType.DISCRETE_SET
30
+ ? string[]
31
+ : T extends BaseAnnotationType.MARKOV_JUMPS
32
+ ? MarkovJumpValue[]
33
+ : T extends BaseAnnotationType.DENSITIES
34
+ ? Record<string, number>
35
+ : never;
36
+
37
+ export type RawValueOf<T extends BaseAnnotationType> =
38
+ T extends BaseAnnotationType.DISCRETE
39
+ ? string
40
+ : T extends BaseAnnotationType.BOOLEAN
41
+ ? boolean
42
+ : T extends BaseAnnotationType.NUMERICAL
43
+ ? number
44
+ : T extends BaseAnnotationType.NUMERICAL_SET
45
+ ? number[]
46
+ : T extends BaseAnnotationType.DISCRETE_SET
47
+ ? string[]
48
+ : T extends BaseAnnotationType.MARKOV_JUMPS
49
+ ? [number, string, string][] | [string, string, string][]
50
+ : T extends BaseAnnotationType.DENSITIES
51
+ ? Record<string, number>
52
+ : never;
53
+
54
+ export type DomainOf<T extends BaseAnnotationType> =
55
+ T extends BaseAnnotationType.DISCRETE
56
+ ? string[]
57
+ : T extends BaseAnnotationType.BOOLEAN
58
+ ? [boolean, boolean]
59
+ : T extends BaseAnnotationType.NUMERICAL
60
+ ? [number, number]
61
+ : T extends BaseAnnotationType.NUMERICAL_SET
62
+ ? [number, number]
63
+ : T extends BaseAnnotationType.DISCRETE_SET
64
+ ? string[] | number[]
65
+ : T extends BaseAnnotationType.MARKOV_JUMPS
66
+ ? string[] // just locations
67
+ : T extends BaseAnnotationType.DENSITIES
68
+ ? string[] // just states
69
+ : never;
70
+
71
+ /** A single, generic annotation, discriminated by `type`. */
72
+ export type AbstractAnnotation<T extends BaseAnnotationType> = {
73
+ id: string;
74
+ type: T;
75
+ value: ValueOf<T>;
76
+ };
77
+
78
+ export type Annotation = {
79
+ [K in BaseAnnotationType]: AbstractAnnotation<K>;
80
+ }[BaseAnnotationType];
81
+
82
+ export interface AbstractAnnotationSummary<T extends BaseAnnotationType> {
83
+ id: string;
84
+ type: T;
85
+ domain: DomainOf<T>;
86
+ }
87
+
88
+ export type AnnotationDomain = {
89
+ [K in BaseAnnotationType]: DomainOf<K>;
90
+ }[BaseAnnotationType];
91
+ export type AnnotationValue = {
92
+ [K in BaseAnnotationType]: ValueOf<K>;
93
+ }[BaseAnnotationType];
94
+ export type AnnotationSummary = {
95
+ [K in BaseAnnotationType]: AbstractAnnotationSummary<K>;
96
+ }[BaseAnnotationType];
97
+ export type RawAnnotationValue = {
98
+ [K in BaseAnnotationType]: RawValueOf<K>;
99
+ }[BaseAnnotationType];
100
+
101
+ export interface newickParsingOptions {
102
+ dateFormat?: string;
103
+ datePrefix?: string;
104
+ labelName?: string;
105
+ parseAnnotations?: boolean;
106
+ tipNameMap?: Map<string, string>;
107
+ taxonSet?: TaxonSet;
108
+ }
109
+ export interface Tree {
110
+ getRoot(): NodeRef;
111
+ isRooted(): boolean;
112
+ getNodeCount(): number;
113
+ getInternalNodeCount(): number;
114
+ getExternalNodeCount(): number;
115
+ getNode(i: string | Taxon | number): NodeRef;
116
+ getInternalNodes(): NodeRef[];
117
+ getExternalNodes(): NodeRef[];
118
+ getNodes(): NodeRef[];
119
+ getTaxon(id: number | NodeRef): Taxon;
120
+
121
+ isExternal(node: NodeRef): boolean;
122
+ isInternal(node: NodeRef): boolean;
123
+ isRoot(node: NodeRef): boolean;
124
+
125
+ getChildCount(node: NodeRef): number;
126
+ getChild(node: NodeRef, i: number): NodeRef;
127
+
128
+ getNodeByTaxon(taxon: Taxon): NodeRef;
129
+ getNodeByLabel(label: string): NodeRef;
130
+ // getLevel(node:NodeRef):number;
131
+
132
+ getDivergence(node: NodeRef): number;
133
+ getHeight(node: NodeRef): number;
134
+ getLength(node: NodeRef): number;
135
+
136
+ getParent(node: NodeRef): NodeRef;
137
+ getChildren(node: NodeRef): NodeRef[];
138
+
139
+ getAnnotation(
140
+ node: NodeRef,
141
+ name: string,
142
+ d?: AnnotationValue,
143
+ ): AnnotationValue; // what about or undefined?
144
+ // getAnnotation<T extends BaseAnnotationType>(node: NodeRef, name: string, d:T): T
145
+
146
+ // getAnnotationValue<T extends BaseAnnotationType>(node:NodeRef, name:string,type:T):ValueOf<T>|undefined
147
+ // annotateNode<T extends BaseAnnotationType>(node:NodeRef,annotation:{name:string,value:RawValueOf<T>}):Tree
148
+ annotateNode(node: NodeRef, name: string, value: RawAnnotationValue): Tree;
149
+ annotateNode(
150
+ node: NodeRef,
151
+ annotation: Record<string, RawAnnotationValue>,
152
+ ): Tree;
153
+
154
+ getLabel(node: NodeRef): string;
155
+ hasLabel(node: NodeRef): boolean;
156
+
157
+ getAnnotationKeys(): string[];
158
+ getAnnotationType(name: string): BaseAnnotationType;
159
+
160
+ getAnnotations(): AnnotationSummary[];
161
+ getAnnotationSummary(name: string): AnnotationSummary;
162
+
163
+ addNodes(n?: number): { tree: Tree; nodes: NodeRef[] };
164
+ deleteNode(n: NodeRef): Tree;
165
+ removeChild(parent: NodeRef, child: NodeRef): Tree;
166
+ deleteClade(n: NodeRef): Tree;
167
+ // hasNextSibling(node:Node)
168
+ getNextSibling(node: NodeRef): NodeRef;
169
+ hasRightSibling(node: NodeRef): boolean;
170
+ getRightSibling(node: NodeRef): NodeRef;
171
+ hasLeftSibling(node: NodeRef): boolean;
172
+ getLeftSibling(node: NodeRef): NodeRef;
173
+
174
+ setHeight(node: NodeRef, height: number): Tree;
175
+ setDivergence(node: NodeRef, divergence: number): Tree;
176
+ setLength(node: NodeRef, length: number): Tree;
177
+ setTaxon(node: NodeRef, taxon: Taxon): Tree;
178
+ setLabel(node: NodeRef, label: string): Tree;
179
+
180
+ addChild(parent: NodeRef, child: NodeRef): Tree;
181
+ // root(node: NodeRef,portion:number): Tree
182
+ unroot(node: NodeRef): Tree;
183
+ toNewick(node?: NodeRef, options?: { includeAnnotations: boolean }): string;
184
+ orderNodesByDensity(down: boolean): Tree;
185
+ sortChildren(
186
+ node: NodeRef,
187
+ compare: (a: NodeRef, b: NodeRef) => number,
188
+ ): Tree;
189
+ isRoot(node: NodeRef): boolean;
190
+ getMRCA(node1: NodeRef, node2: NodeRef): NodeRef;
191
+ getMRCA(nodes: NodeRef[]): NodeRef;
192
+ rotate(node: NodeRef, recursive: boolean): Tree;
193
+ reroot(node: NodeRef, proportion: number): Tree;
194
+ }
195
+ export type TreeListener = (tree: Tree, node: NodeRef) => void;
196
+
197
+ //TODO abstact Tree with parsing implementations
@@ -0,0 +1,44 @@
1
+ //https://stackoverflow.com/questions/27936772/how-to-deep-merge-instead-of-shallow-merge
2
+ import { timeParse, timeFormat } from "d3-time-format";
3
+
4
+ //https://stackoverflow.com/questions/29400171/how-do-i-convert-a-decimal-year-value-into-a-date-in-javascript
5
+ /**
6
+ * Helper function to determine if the provided year is a leap year
7
+ * @param year
8
+ * @return {boolean}
9
+ */
10
+ export function leapYear(year: number) {
11
+ return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
12
+ }
13
+
14
+ /**
15
+ * A function which converts a decimal float into a date object
16
+ * @param decimalDate
17
+ * @return {Date}
18
+ */
19
+ export function decimalToDate(decimal: number) {
20
+ const year = Math.trunc(decimal);
21
+ const totalNumberOfDays = leapYear(year) ? 366 : 365;
22
+ const day = Math.round((decimal - year) * totalNumberOfDays);
23
+ return timeParse("%Y-%j")(`${year}-${day}`);
24
+ }
25
+
26
+ /**
27
+ * A function that converts a date into a decimal.
28
+ * @param date
29
+ * @return {number}
30
+ */
31
+ export function dateToDecimal(date: Date) {
32
+ const year = parseInt(timeFormat("%Y")(date));
33
+ const day = parseInt(timeFormat("%j")(date));
34
+ const totalNumberOfDays = leapYear(year) ? 366 : 365;
35
+ return year + day / totalNumberOfDays;
36
+ }
37
+
38
+ //https://stackoverflow.com/questions/526559/testing-if-something-is-a-class-in-javascript
39
+ export function isFunction(funcOrClass: unknown) {
40
+ const propertyNames = Object.getOwnPropertyNames(funcOrClass);
41
+ return (
42
+ !propertyNames.includes("prototype") || propertyNames.includes("arguments")
43
+ );
44
+ }
package/src/index.ts ADDED
@@ -0,0 +1,6 @@
1
+ export { FigTree } from "./components";
2
+ export type { FigtreeProps } from "./components";
3
+ export * from "./bauble-makers/makers";
4
+ export * from "./evo";
5
+ export * from "./layouts";
6
+ export * from "./utils";
@@ -0,0 +1,2 @@
1
+ export { rectangularLayout, polarLayout } from "./rectangular-layout";
2
+ export { radialLayout } from "./radial-layout";
@@ -0,0 +1,150 @@
1
+ // export function layout (tree:ImmutableTree,node?:NodeRef):Map<NodeRef,FunctionalVertex>{
2
+
3
+ import type { ImmutableTree, NodeRef } from "../../evo";
4
+ import { preOrderIterator, tipIterator } from "../../evo";
5
+ import type { NodeLabelType } from "../types";
6
+ import { notNull } from "../../utils";
7
+ import type { FunctionalVertex } from "../types";
8
+ import { layoutClass } from "../types";
9
+
10
+ // //todo set some map for fixing the traversal of the tree.
11
+ // const vertexMap = new Map();
12
+ type data = {
13
+ angleStart: number;
14
+ angleEnd: number;
15
+ xpos: number;
16
+ ypos: number;
17
+ level: number;
18
+ number: number;
19
+ };
20
+
21
+ export function radialLayout(
22
+ tree: ImmutableTree,
23
+ options: { spread?: number } = {},
24
+ ): (node: NodeRef) => FunctionalVertex {
25
+ const { spread = 1 } = options;
26
+ console.log("radial layout with spread", spread);
27
+ const map = new Map<NodeRef, FunctionalVertex>();
28
+
29
+ const dataStack: data[] = [
30
+ {
31
+ angleStart: 0,
32
+ angleEnd: 2 * Math.PI,
33
+ xpos: 0,
34
+ ypos: 0,
35
+ level: 0,
36
+ number: tree.getRoot().number,
37
+ },
38
+ ]; // TODO start tree.
39
+ for (const node of preOrderIterator(tree)) {
40
+ const data = dataStack.pop();
41
+ notNull(data, `Internal Error, hit the end of the data stack unexpectedly`);
42
+ const { angleStart, angleEnd, xpos, ypos, level } = data;
43
+
44
+ const branchAngle = (angleStart + angleEnd) / 2.0;
45
+
46
+ const length = !tree.isRoot(node) ? tree.getLength(node) : 0;
47
+
48
+ const directionX = Math.cos(branchAngle);
49
+ const directionY = Math.sin(branchAngle);
50
+ const x = xpos + length * directionX;
51
+ const y = ypos + length * directionY;
52
+
53
+ const leftLabel = tree.getChildCount(node) > 0;
54
+ let dx, dy;
55
+ if (!leftLabel) {
56
+ dx = Math.cos(branchAngle);
57
+ dy = Math.sin(branchAngle);
58
+ } else {
59
+ dx = Math.cos(branchAngle);
60
+ dy = Math.sin(branchAngle);
61
+ }
62
+ const vertex = {
63
+ x,
64
+ y,
65
+ layoutClass: layoutClass.Radial,
66
+ theta: branchAngle,
67
+ nodeLabel: {
68
+ dxFactor: dx,
69
+ dyFactor: dy,
70
+ alignmentBaseline: "middle",
71
+ textAnchor:
72
+ normalizeAngle(branchAngle) > Math.PI / 2 &&
73
+ normalizeAngle(branchAngle) < (3 * Math.PI) / 2
74
+ ? "end"
75
+ : "start",
76
+ rotation: 0, // textSafeDegrees(normalizeAngle(branchAngle))
77
+ } as NodeLabelType,
78
+ };
79
+
80
+ if (tree.getChildCount(node) > 0) {
81
+ const childLeafs: number[] = [];
82
+ let totalLeafs = 0;
83
+ for (let i = 0; i < tree.getChildCount(node); i++) {
84
+ const leafCount = [...tipIterator(tree, tree.getChild(node, i))].length;
85
+ childLeafs[i] = leafCount;
86
+ totalLeafs += leafCount;
87
+ }
88
+
89
+ let span = angleEnd - angleStart;
90
+ let updatedAngleStart = angleStart;
91
+
92
+ if (tree.getRoot() !== node) {
93
+ // span *= 1.0 + ((safeOpts.spread * Math.PI / 180) / 10.0);
94
+ span *= 1.0 + (spread * Math.PI) / 180 / 10.0;
95
+ updatedAngleStart = branchAngle - span / 2.0;
96
+ }
97
+
98
+ let a2 = updatedAngleStart;
99
+
100
+ for (let i = tree.getChildCount(node) - 1; i > -1; i--) {
101
+ // i think we need to go in reverse order here
102
+ const a1 = a2;
103
+ a2 = a1 + (span * childLeafs[i]) / totalLeafs;
104
+ dataStack.push({
105
+ angleStart: a1,
106
+ angleEnd: a2,
107
+ xpos: x,
108
+ ypos: y,
109
+ level: level + 1,
110
+ number: tree.getChild(node, i).number,
111
+ });
112
+ }
113
+ }
114
+ map.set(node, vertex);
115
+ }
116
+
117
+ return function (node: NodeRef): FunctionalVertex {
118
+ if (map.has(node)) {
119
+ return map.get(node) as FunctionalVertex; // must be here since we check it above
120
+ } else {
121
+ throw new Error("Node not found in layout - has the tree changed");
122
+ }
123
+ };
124
+ }
125
+
126
+ //this function converts radians to degrees and adjusts degrees
127
+ // so the text is not fliped
128
+ export function textSafeDegrees(radians: number) {
129
+ const d = degrees(radians);
130
+ //trial and error - must be a better way
131
+ if (d > 90 && d < 270) {
132
+ return (d - 180) / 2;
133
+ } else if (d > 0 && d < 88) {
134
+ return d / 2;
135
+ } else if (d < 360 && d > 272) {
136
+ return (360 + d) / 2;
137
+ } else {
138
+ return d;
139
+ }
140
+ }
141
+ export function normalizeAngle(theta: number) {
142
+ while (theta > 2 * Math.PI) {
143
+ theta -= 2 * Math.PI;
144
+ }
145
+ return theta;
146
+ }
147
+
148
+ export function degrees(theta: number) {
149
+ return (normalizeAngle(theta) * 180) / Math.PI;
150
+ }
@@ -0,0 +1,71 @@
1
+ import { mean } from "d3-array";
2
+ import type { ImmutableTree, NodeRef } from "../../evo";
3
+ import { postOrderIterator } from "../../evo";
4
+ import type { NodeLabelType, FunctionalVertex } from "../types";
5
+ import { layoutClass } from "../types";
6
+ import { unNullify } from "../../utils";
7
+
8
+ export function baseLayout(lc: layoutClass) {
9
+ function layout(tree: ImmutableTree): (node: NodeRef) => FunctionalVertex {
10
+ const map = new Map<NodeRef, FunctionalVertex>();
11
+
12
+ let currentY = 0;
13
+ for (const node of postOrderIterator(tree)) {
14
+ let protoVertex: { x: number; y: number };
15
+ const x = tree.getDivergence(node);
16
+ const leftLabel = tree.getChildCount(node) > 0;
17
+ const hasParent = !tree.isRoot(node);
18
+ const labelBelow =
19
+ tree.getChildCount(node) > 0 &&
20
+ (!hasParent || tree.getChild(tree.getParent(node), 0) !== node);
21
+
22
+ if (tree.isExternal(node)) {
23
+ protoVertex = { x, y: currentY };
24
+ currentY++;
25
+ } else {
26
+ const kidPositions = tree
27
+ .getChildren(node)
28
+ .map((child) =>
29
+ unNullify(
30
+ map.get(child),
31
+ `Internal Error: child not yet found in layout`,
32
+ ),
33
+ );
34
+ const y = unNullify(
35
+ mean(kidPositions, (d) => d.y),
36
+ `Error taking the mean of child positions`,
37
+ );
38
+ protoVertex = { x, y };
39
+ }
40
+ const vertex = {
41
+ ...protoVertex,
42
+ layoutClass: lc,
43
+ nodeLabel: {
44
+ alignmentBaseline: leftLabel
45
+ ? labelBelow
46
+ ? "bottom"
47
+ : "hanging"
48
+ : "middle", // todo calc on the fly
49
+ textAnchor: leftLabel ? "end" : "start",
50
+ dxFactor: leftLabel ? -1 : 1,
51
+ dyFactor: leftLabel ? (labelBelow ? -1 : 1) : 0,
52
+ rotation: 0,
53
+ } as NodeLabelType,
54
+ };
55
+ map.set(node, vertex);
56
+ }
57
+
58
+ return function (node: NodeRef): FunctionalVertex {
59
+ if (map.has(node)) {
60
+ return map.get(node) as FunctionalVertex; // check above so
61
+ } else {
62
+ console.log(node);
63
+ throw new Error("Node not found in layout - has the tree changed");
64
+ }
65
+ };
66
+ }
67
+ return layout;
68
+ }
69
+
70
+ export const rectangularLayout = baseLayout(layoutClass.Rectangular);
71
+ export const polarLayout = baseLayout(layoutClass.Polar);
@@ -0,0 +1,3 @@
1
+ export * from "./layout-interface";
2
+ export * from "./functional";
3
+ export * from "./types";