@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
package/CHANGELOG.md ADDED
@@ -0,0 +1,9 @@
1
+ # @figtreejs/core
2
+
3
+ ## 0.0.1-alpha.0
4
+
5
+ ### Patch Changes
6
+
7
+ - This version marks the initial version of several packages, and the start of a new developement system
8
+ - Updated dependencies
9
+ - @figtreejs/maybe@0.0.1-alpha.0
@@ -0,0 +1,9 @@
1
+ import { defineConfig } from "eslint/config";
2
+ import defaultConfig from "@figtreejs/eslint-config";
3
+
4
+ export default defineConfig({
5
+ "extends": [defaultConfig],
6
+ "rules":{
7
+ "@typescript-eslint/restrict-template-expressions": ["error", { allowNumber: true }]
8
+ }
9
+ })
package/package.json ADDED
@@ -0,0 +1,76 @@
1
+ {
2
+ "name": "@figtreejs/core",
3
+ "version": "0.0.1-alpha.0",
4
+ "description": "A react component library for phylogenetic visualization and app building",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/figtreo/figtreejs.git"
8
+ },
9
+ "license": "MIT",
10
+ "author": "Andrew Rambaut and JT McCrone",
11
+ "type": "module",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "import": "./dist/index.mjs",
16
+ "require": "./dist/index.cjs"
17
+ }
18
+ },
19
+ "main": "./dist/index.cjs",
20
+ "module": "./dist/index.mjs",
21
+ "types": "./dist/index.d.ts",
22
+ "scripts": {
23
+ "build": "vite build",
24
+ "check-types": "tsc --noEmit",
25
+ "dev": "vite build --watch",
26
+ "lint": "eslint src/",
27
+ "test": "vitest run",
28
+ "vitest-preview": "vitest-preview"
29
+ },
30
+ "lint-staged": {
31
+ "src/**/*.{js,jsx,ts,tsx}": [
32
+ "eslint",
33
+ "prettier --write --ignore-unknown"
34
+ ],
35
+ "!(*.js|*.ts)": [
36
+ "prettier --write --ignore-unknown"
37
+ ]
38
+ },
39
+ "dependencies": {
40
+ "@figtreejs/maybe": "0.0.1-alpha.0",
41
+ "@react-spring/web": "^9.7.5",
42
+ "abs-svg-path": "^0.1.1",
43
+ "d3-array": "^3.2.4",
44
+ "d3-format": "^3.1.0",
45
+ "d3-scale": "^4.0.2",
46
+ "d3-shape": "^3.2.0",
47
+ "d3-time-format": "^4.1.0",
48
+ "immer": "^10.1.1",
49
+ "normalize-svg-path": "^1.1.0",
50
+ "parse-svg-path": "^0.1.2",
51
+ "uuid": "^13.0.0"
52
+ },
53
+ "devDependencies": {
54
+ "@figtreejs/eslint-config": "0.0.1-alpha.0",
55
+ "@figtreejs/typescript-config": "0.0.1-alpha.0",
56
+ "@microsoft/api-extractor": "^7.55.1",
57
+ "@testing-library/react": "^16.3.0",
58
+ "@types/abs-svg-path": "^0.1.3",
59
+ "@types/d3-array": "^3.2.2",
60
+ "@types/d3-format": "^3.0.4",
61
+ "@types/d3-scale": "^4.0.9",
62
+ "@types/d3-shape": "^3.1.7",
63
+ "@types/d3-time-format": "^4.0.3",
64
+ "@vitejs/plugin-react": "^5.1.0",
65
+ "jsdom": "^27.1.0",
66
+ "typescript": "^5.9.3",
67
+ "unplugin-dts": "^1.0.0-beta.0",
68
+ "vite": "^7.2.2",
69
+ "vite-plugin-dts": "^4.5.4",
70
+ "vitest": "^4.0.8"
71
+ },
72
+ "peerDependencies": {
73
+ "react": "^18.3.1",
74
+ "react-dom": "^18.3.1"
75
+ }
76
+ }
@@ -0,0 +1,13 @@
1
+ declare module 'normalize-svg-path' {
2
+ // Pull in the derived types (no value import here—types only)
3
+
4
+ type NormalizedCommand = import('./svg-path-types').NormalizedCommand; // eslint-disable-line @typescript-eslint/consistent-type-imports
5
+
6
+ // What normalize returns: path must be absolute; segments become curves (plus M/Z)
7
+
8
+
9
+ // The library exposes a single function; the upstream repo publishes CJS + ESM builds.
10
+ // We'll declare CJS style; with esModuleInterop you can default-import it if you like.
11
+ function normalize(path: AbsAnyCommand[]): NormalizedCommand[];
12
+ export = normalize;
13
+ }
@@ -0,0 +1,8 @@
1
+ // import type {AnyCommand} from 'abs-svg-path'
2
+ declare module 'parse-svg-path'{
3
+
4
+ // Derive the *input* element type of abs() commands:
5
+ type AnyCommand = import('./svg-path-types').AnyCommand; // eslint-disable-line @typescript-eslint/consistent-type-imports
6
+ const parse:(n:string)=>AnyCommand[],
7
+ export default parse;
8
+ }
@@ -0,0 +1,37 @@
1
+ // This file is a *module* (it exports types).
2
+ // It derives types from the value type of 'abs-svg-path'.
3
+
4
+ export type AbsFn = typeof import('abs-svg-path'); // eslint-disable-line @typescript-eslint/consistent-type-imports
5
+
6
+ // Array element type of the *input* to abs()
7
+ export type AnyCommand = Parameters<AbsFn>[0][number];
8
+
9
+ // Array element type of the *output* from abs() (absolute commands)
10
+ export type AbsAnyCommand = ReturnType<AbsFn>[number];
11
+
12
+ // Generic helper: pick the command union members whose first tuple entry matches C
13
+ type CommandOf<C extends string> = Extract<AnyCommand, [C, ...unknown[]]>;
14
+
15
+ // Common subtypes you might want:
16
+ export type RelMoveCommand = CommandOf<'m'>;
17
+ export type AbsMoveCommand = CommandOf<'M'>;
18
+ export type RelLineCommand = CommandOf<'l'>;
19
+ export type AbsLineCommand = CommandOf<'L'>;
20
+ export type RelHorizontalCommand = CommandOf<'h'>;
21
+ export type AbsHorizontalCommand = CommandOf<'H'>;
22
+ export type RelVerticalCommand = CommandOf<'v'>;
23
+ export type AbsVerticalCommand = CommandOf<'V'>;
24
+ export type RelClosePathCommand = CommandOf<'z'>;
25
+ export type AbsClosePathCommand = CommandOf<'Z'>;
26
+ export type RelBezierCurveCommand = CommandOf<'c'>;
27
+ export type AbsBezierCurveCommand = CommandOf<'C'>;
28
+ export type RelFollowingBezierCurveCommand = CommandOf<'s'>;
29
+ export type AbsFollowingBezierCurveCommand = CommandOf<'S'>;
30
+ export type RelQuadraticCurveCommand = CommandOf<'q'>;
31
+ export type AbsQuadraticCurveCommand = CommandOf<'Q'>;
32
+ export type RelFollowingQuadraticCurveCommand = CommandOf<'t'>;
33
+ export type AbsFollowingQuadraticCurveCommand = CommandOf<'T'>;
34
+ export type RelArcCommand = CommandOf<'a'>;
35
+ export type AbsArcCommand = CommandOf<'A'>;
36
+
37
+ export type NormalizedCommand = AbsMoveCommand | AbsBezierCurveCommand | AbsClosePathCommand;
@@ -0,0 +1,112 @@
1
+ // Nodes
2
+
3
+ import type {
4
+ CircleAttrs,
5
+ TextAttrs,
6
+ PathAttrs,
7
+ RectAttrs,
8
+ HighlightRectAttrs,
9
+ } from "../components";
10
+ import {
11
+ BaubleTarget,
12
+ CladeShapes,
13
+ NodeShapes,
14
+ } from "../components/baubles/bauble";
15
+ import type { BaseBaubleOptions, ExposedAttrs } from "./utils";
16
+
17
+ type RectangleNodeAttrs = ExposedAttrs<RectAttrs>;
18
+ type CircleNodeAttrs = ExposedAttrs<CircleAttrs>;
19
+
20
+ export type RectangleNodeOptions = BaseBaubleOptions<RectangleNodeAttrs>;
21
+ export type CircleNodeOptions = BaseBaubleOptions<CircleNodeAttrs>;
22
+ export type InternalNodeOptions = (
23
+ | (CircleNodeOptions & { shape: NodeShapes.Circle })
24
+ | (RectangleNodeOptions & { shape: NodeShapes.Rectangle })
25
+ ) & { target: BaubleTarget.Node };
26
+
27
+ export function CircleNodes(options: CircleNodeOptions): InternalNodeOptions {
28
+ return { ...options, shape: NodeShapes.Circle, target: BaubleTarget.Node };
29
+ }
30
+
31
+ export function RectangleNodes(
32
+ options: RectangleNodeOptions,
33
+ ): InternalNodeOptions {
34
+ return { ...options, shape: NodeShapes.Rectangle, target: BaubleTarget.Node };
35
+ }
36
+
37
+ // Branches
38
+
39
+ type BranchAttrs = ExposedAttrs<PathAttrs>;
40
+
41
+ export type BranchOptions = BaseBaubleOptions<BranchAttrs> & {
42
+ curvature?: number;
43
+ };
44
+ export type InternalBranchOptions = BranchOptions & {
45
+ target: BaubleTarget.Branch;
46
+ };
47
+
48
+ /**
49
+ * This function takes options from the user about the branches they would like in the figure
50
+ * and passes it to the figure. It is a nice functional API.
51
+ *
52
+ */
53
+
54
+ export function Branches(options: BranchOptions): InternalBranchOptions {
55
+ return { ...options, target: BaubleTarget.Branch };
56
+ }
57
+
58
+ // Labels
59
+
60
+ type LabelSpecificTextAttrs = Omit<TextAttrs, "text">; // we will provide this next the text attrs
61
+ type LabelAttrs = ExposedAttrs<LabelSpecificTextAttrs>;
62
+ type LabelOptions = BaseBaubleOptions<LabelAttrs> &
63
+ ExposedAttrs<{ text: string }>;
64
+ export type InternalLabelOptions = LabelOptions &
65
+ (
66
+ | { target: BaubleTarget.NodeLabel; aligned?: boolean }
67
+ | { target: BaubleTarget.BranchLabel }
68
+ );
69
+ export type ExternalLabelOptions = Omit<LabelOptions, "attrs"> & {
70
+ attrs?: LabelAttrs;
71
+ };
72
+
73
+ export function NodeLabels(
74
+ options: ExternalLabelOptions & { aligned?: boolean },
75
+ ): InternalLabelOptions {
76
+ return {
77
+ attrs: {},
78
+ aligned: false,
79
+ ...options,
80
+ target: BaubleTarget.NodeLabel,
81
+ };
82
+ }
83
+
84
+ export function BranchLabels(
85
+ options: ExternalLabelOptions,
86
+ ): InternalLabelOptions {
87
+ return { attrs: {}, ...options, target: BaubleTarget.BranchLabel };
88
+ }
89
+
90
+ type HighlightAttrs = ExposedAttrs<HighlightRectAttrs>;
91
+ type CartoonAttrs = ExposedAttrs<PathAttrs>;
92
+
93
+ export type HighlightOptions = BaseBaubleOptions<HighlightAttrs>;
94
+ export type CartoonOptions = BaseBaubleOptions<CartoonAttrs>;
95
+ export type InternalCladeOptions = (
96
+ | (HighlightOptions & { shape: CladeShapes.Highlight })
97
+ | (CartoonOptions & { shape: CladeShapes.Cartoon })
98
+ ) & { target: BaubleTarget.Clade };
99
+
100
+ export function HighlightClades(
101
+ options: HighlightOptions,
102
+ ): InternalCladeOptions {
103
+ return {
104
+ ...options,
105
+ shape: CladeShapes.Highlight,
106
+ target: BaubleTarget.Clade,
107
+ };
108
+ }
109
+
110
+ export function CartoonClades(options: CartoonOptions): InternalCladeOptions {
111
+ return { ...options, shape: CladeShapes.Cartoon, target: BaubleTarget.Clade };
112
+ }
@@ -0,0 +1,197 @@
1
+ import { maxIndex } from "d3-array";
2
+ import type {
3
+ CircleAttrs,
4
+ HighlightRectAttrs,
5
+ PathAttrs,
6
+ RectAttrs,
7
+ TextAttrs,
8
+ } from "../components";
9
+ import type { BaubleSpec } from "../components/baubles/bauble";
10
+ import {
11
+ BaubleTarget,
12
+ CladeShapes,
13
+ NodeShapes,
14
+ } from "../components/baubles/bauble";
15
+ import type { ImmutableTree, NodeRef } from "../evo";
16
+ import { tipIterator } from "../evo";
17
+ import type {
18
+ InternalBranchOptions,
19
+ InternalCladeOptions,
20
+ InternalLabelOptions,
21
+ InternalNodeOptions,
22
+ } from "./makers";
23
+ import { mapAttrsToProps, mapInteractionsToProps } from "./utils";
24
+ import type { Clade } from "../components/hoc/with-clades";
25
+ import { notNull } from "../utils";
26
+
27
+ export type InternalBaubleOptions =
28
+ | InternalNodeOptions
29
+ | InternalBranchOptions
30
+ | InternalLabelOptions
31
+ | InternalCladeOptions; // TODO fix redundancy and keep types happy
32
+ export function setupBaubles(
33
+ options: InternalBaubleOptions,
34
+ tree: ImmutableTree,
35
+ ): BaubleSpec {
36
+ const filterer = "filter" in options ? options.filter : () => true;
37
+
38
+ notNull(filterer, "Issue with filter option when making baubles");
39
+
40
+ const nodes: NodeRef[] =
41
+ "nodes" in options ? options.nodes : tree.getNodes().filter(filterer);
42
+
43
+ const interactionMapper = mapInteractionsToProps(options.interactions ?? {});
44
+
45
+ if (options.target === BaubleTarget.Node) {
46
+ if (options.shape === NodeShapes.Circle) {
47
+ const attrMapper = mapAttrsToProps(options.attrs); //fill is none unless we are told otherwise
48
+ const attrs = nodes.reduce((acc: Record<string, CircleAttrs>, n) => {
49
+ const nodeAttrs = attrMapper(n);
50
+ const nodeInteractions = interactionMapper(n);
51
+ acc[n._id] = { ...nodeAttrs, ...nodeInteractions };
52
+ return acc;
53
+ }, {});
54
+
55
+ return {
56
+ nodes,
57
+ attrs,
58
+ id: options.id,
59
+ target: options.target,
60
+ shape: options.shape,
61
+ };
62
+ } else {
63
+ const attrMapper = mapAttrsToProps(options.attrs); //fill is none unless we are told otherwise
64
+ const attrs = nodes.reduce((acc: Record<string, RectAttrs>, n) => {
65
+ const nodeAttrs = attrMapper(n);
66
+ const nodeInteractions = interactionMapper(n);
67
+ acc[n._id] = { ...nodeAttrs, ...nodeInteractions };
68
+ return acc;
69
+ }, {});
70
+ return {
71
+ nodes,
72
+ attrs,
73
+ id: options.id,
74
+ target: options.target,
75
+ shape: options.shape,
76
+ };
77
+ }
78
+ } else if (options.target === BaubleTarget.Branch) {
79
+ const branches = nodes
80
+ .filter((n) => !tree.isRoot(n))
81
+ .map((node) => ({ node, parent: tree.getParent(node) }));
82
+ const attrMapper = mapAttrsToProps({ fill: "none", ...options.attrs }); //fill is none unless we are told otherwise
83
+ const interactionMapper = mapInteractionsToProps(
84
+ options.interactions ?? {},
85
+ );
86
+ const attrs = nodes.reduce((acc: Record<string, PathAttrs>, n) => {
87
+ const nodeAttrs = attrMapper(n);
88
+ const nodeInteractions = interactionMapper(n);
89
+ acc[n._id] = { ...nodeAttrs, ...nodeInteractions };
90
+ return acc;
91
+ }, {});
92
+
93
+ return {
94
+ branches,
95
+ attrs,
96
+ id: options.id,
97
+ curvature: options.curvature,
98
+ target: options.target,
99
+ };
100
+ } else if (options.target === BaubleTarget.NodeLabel) {
101
+ const attrMapper = mapAttrsToProps(options.attrs);
102
+ const textMapper = mapAttrsToProps({ text: options.text });
103
+ const interactionMapper = mapInteractionsToProps(
104
+ options.interactions ?? {},
105
+ );
106
+ const attrs = nodes.reduce((acc: Record<string, TextAttrs>, n) => {
107
+ const nodeAttrs = attrMapper(n);
108
+ const nodeInteractions = interactionMapper(n);
109
+ const text = textMapper(n);
110
+ acc[n._id] = { ...nodeAttrs, ...nodeInteractions, ...text };
111
+ return acc;
112
+ }, {});
113
+
114
+ return {
115
+ nodes,
116
+ attrs,
117
+ id: options.id,
118
+ target: options.target,
119
+ aligned: options.aligned ?? false,
120
+ };
121
+ } else if (options.target === BaubleTarget.BranchLabel) {
122
+ const sineRoot = nodes.filter((n) => !tree.isRoot(n));
123
+ const branches = sineRoot
124
+ // .filter((n) => !tree.isRoot(n))
125
+ .map((node) => ({ node, parent: tree.getParent(node) }));
126
+ const attrMapper = mapAttrsToProps(options.attrs); //fill is none unless we are told otherwise
127
+ const interactionMapper = mapInteractionsToProps(
128
+ options.interactions ?? {},
129
+ );
130
+ const textMapper = mapAttrsToProps({ text: options.text });
131
+ const attrs = sineRoot.reduce((acc: Record<string, TextAttrs>, n) => {
132
+ const nodeAttrs = attrMapper(n);
133
+ const nodeInteractions = interactionMapper(n);
134
+ const text = textMapper(n);
135
+ acc[n._id] = { ...nodeAttrs, ...nodeInteractions, ...text };
136
+ return acc;
137
+ }, {});
138
+
139
+ return {
140
+ branches,
141
+ attrs,
142
+ id: options.id,
143
+ target: options.target,
144
+ };
145
+ } else {
146
+ // if(options.target===BaubleTarget.Clade){
147
+ const clades: Clade[] = nodes.map((n) => {
148
+ const tips = [...tipIterator(tree, n)];
149
+ const leftMost = tips[0];
150
+ const rightMost = tips[tips.length - 1];
151
+ const mostDiverged = tips[maxIndex(tips, (d) => tree.getDivergence(d))];
152
+ return {
153
+ root: n,
154
+ leftMost,
155
+ rightMost,
156
+ mostDiverged,
157
+ };
158
+ });
159
+
160
+ if (options.shape === CladeShapes.Highlight) {
161
+ const attrMapper = mapAttrsToProps(options.attrs); //fill is none unless we are told otherwise
162
+ const attrs = nodes.reduce(
163
+ (acc: Record<string, HighlightRectAttrs>, n) => {
164
+ const nodeAttrs = attrMapper(n);
165
+ const nodeInteractions = interactionMapper(n);
166
+ acc[n._id] = { ...nodeAttrs, ...nodeInteractions };
167
+ return acc;
168
+ },
169
+ {},
170
+ );
171
+ return {
172
+ clades,
173
+ attrs,
174
+ id: options.id,
175
+ target: options.target,
176
+ shape: options.shape,
177
+ };
178
+ } else {
179
+ const attrMapper = mapAttrsToProps(options.attrs); //fill is none unless we are told otherwise
180
+ const attrs = nodes.reduce((acc: Record<string, PathAttrs>, n) => {
181
+ const nodeAttrs = attrMapper(n);
182
+ const nodeInteractions = interactionMapper(n);
183
+ acc[n._id] = { ...nodeAttrs, ...nodeInteractions };
184
+ return acc;
185
+ }, {});
186
+ return {
187
+ clades,
188
+ attrs,
189
+ id: options.id,
190
+ target: options.target,
191
+ shape: options.shape,
192
+ };
193
+ }
194
+ }
195
+
196
+ throw new Error("not implemented the rest");
197
+ }
@@ -0,0 +1,61 @@
1
+ import type {
2
+ InteractionType,
3
+ InternalInteractionType,
4
+ } from "../components/baubles/types";
5
+ import type { NodeRef } from "../evo";
6
+
7
+ export type ExposedAttrs<A> = {
8
+ [K in keyof A]: A[K] | ((n: NodeRef) => A[K]);
9
+ };
10
+ export type filterOption = {
11
+ filter?: (n: NodeRef) => boolean;
12
+ };
13
+ export type nodeOption = {
14
+ nodes: NodeRef[];
15
+ };
16
+
17
+ export type BaseBaubleOptions<A> = {
18
+ id?: string;
19
+ attrs: A;
20
+ interactions?: InteractionType;
21
+ } & (filterOption | nodeOption);
22
+
23
+ // is T needed here?
24
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters
25
+ function isFn<T>(val: unknown): val is (n: NodeRef) => T {
26
+ return typeof val === "function";
27
+ }
28
+
29
+ export function mapAttrsToProps<A extends Record<string, unknown>>(
30
+ attrs: ExposedAttrs<A>,
31
+ ): (n: NodeRef) => A {
32
+ return function (node: NodeRef) {
33
+ const props = {} as A;
34
+ for (const k in attrs) {
35
+ const v = attrs[k];
36
+ if (isFn(v)) {
37
+ props[k as keyof A] = v(node) as A[typeof k];
38
+ } else {
39
+ props[k as keyof A] = v as A[typeof k];
40
+ }
41
+ }
42
+ return props;
43
+ };
44
+ }
45
+
46
+ export function mapInteractionsToProps(
47
+ interactions: InteractionType,
48
+ ): (n: NodeRef) => InternalInteractionType {
49
+ return function (node: NodeRef) {
50
+ const props: InternalInteractionType = {};
51
+ for (const k in interactions) {
52
+ const possibleInteraction = interactions[k as keyof InteractionType];
53
+ if (possibleInteraction !== undefined) {
54
+ props[k as keyof InteractionType] = () => {
55
+ possibleInteraction(node);
56
+ };
57
+ }
58
+ }
59
+ return props;
60
+ };
61
+ }
@@ -0,0 +1,61 @@
1
+ import type { NodeSpec } from "./nodes";
2
+ import { Nodes } from "./nodes";
3
+ import type { BranchLabelSpec, NodeLabelSpec } from "./labels";
4
+ import { BranchLabels, NodeLabels } from "./labels";
5
+ import type { BranchSpec } from "./branches";
6
+ import { Branches } from "./branches";
7
+ import type { CladeSpec } from "./clades";
8
+ import { Clades } from "./clades";
9
+
10
+ /**
11
+ * The main bauble which decides which baubles to renders base on the incoming specification
12
+ */
13
+ export function Bauble(props: BaubleSpec) {
14
+ // const{id} = props
15
+ switch (
16
+ props.target // switch before destructure so destructure is type aware
17
+ ) {
18
+ case BaubleTarget.Node: {
19
+ return <Nodes {...props} />;
20
+ }
21
+ case BaubleTarget.Branch: {
22
+ return <Branches {...props} />;
23
+ }
24
+ case BaubleTarget.NodeLabel: {
25
+ return <NodeLabels {...props} />;
26
+ }
27
+
28
+ case BaubleTarget.BranchLabel: {
29
+ return <BranchLabels {...props} />;
30
+ }
31
+
32
+ case BaubleTarget.Clade: {
33
+ return <Clades {...props} />;
34
+ }
35
+ }
36
+ }
37
+
38
+ export enum BaubleTarget {
39
+ Node,
40
+ Branch,
41
+ NodeLabel,
42
+ BranchLabel,
43
+ Clade,
44
+ Axis,
45
+ }
46
+
47
+ export enum NodeShapes {
48
+ Circle,
49
+ Rectangle,
50
+ }
51
+ export enum CladeShapes {
52
+ Cartoon,
53
+ Highlight,
54
+ }
55
+
56
+ export type BaubleSpec =
57
+ | NodeSpec
58
+ | BranchSpec
59
+ | NodeLabelSpec
60
+ | BranchLabelSpec
61
+ | CladeSpec;
@@ -0,0 +1,13 @@
1
+ import { withBranch } from "../hoc";
2
+ import type { BranchProps } from "../hoc/with-branches";
3
+ import { withBranches } from "../hoc/with-branches";
4
+ import type { BaubleTarget } from "./bauble";
5
+ import { BasePath } from "./shapes";
6
+ import type { PathAttrs } from "./shapes/branch";
7
+
8
+ export type BranchSpec = BranchProps<PathAttrs> & {
9
+ target: BaubleTarget.Branch;
10
+ id?: string;
11
+ };
12
+
13
+ export const Branches = withBranches<PathAttrs>(withBranch(BasePath));
@@ -0,0 +1,68 @@
1
+ import { layoutClass } from "../../../layouts";
2
+
3
+ import { normalizePath } from "../../../path.helpers";
4
+ import { BasePath } from "../shapes";
5
+ import type { PolarVertex } from "../../../layouts/types";
6
+ import type { PathProps } from "../shapes/branch";
7
+ import { useContext } from "react";
8
+ import { ScaleContext } from "../../../context/scale-context";
9
+ import { layoutContext } from "../../../context/layout-context";
10
+ import { animatedContext } from "../../../context/aminated-context";
11
+ import type { Clade } from "../../hoc/with-clades";
12
+ import { withClades } from "../../hoc/with-clades";
13
+
14
+ //TODO add padding
15
+ // const padding = 10;
16
+ //TODO make normalization part of an hoc or d animation.
17
+
18
+ /**
19
+ * A cartoon drawing of a clade in the tree.
20
+ * It will not yet render for radial layouts
21
+ */
22
+ type Injected = {
23
+ d: string;
24
+ animated: boolean;
25
+ };
26
+ export type CladeProps = Omit<PathProps, keyof Injected> & { clade: Clade };
27
+ function Cartoon(props: CladeProps) {
28
+ const { clade, ...rest } = props;
29
+ const scale = useContext(ScaleContext);
30
+ const layout = useContext(layoutContext);
31
+ const animated = useContext(animatedContext);
32
+ const { root, leftMost, rightMost, mostDiverged } = clade;
33
+ const v = scale(layout(root));
34
+
35
+ const { x, y } = v;
36
+ const lmv = scale(layout(leftMost)); // left most child v (top of highlight)
37
+ const rmv = scale(layout(rightMost)); // right most child v (top of highlight)
38
+ const mdv = scale(layout(mostDiverged)); // right most child v (top of highlight)
39
+ const { layoutClass: layoutType } = layout(root);
40
+ let d: string;
41
+ if (layoutType === layoutClass.Rectangular) {
42
+ const maxX = mdv.x;
43
+ const maxY = rmv.y;
44
+
45
+ const minY = lmv.y;
46
+
47
+ d = `M${x},${y}L${maxX},${maxY}L${maxX},${minY}Z`;
48
+ } else if (layoutType === layoutClass.Polar) {
49
+ //todo maybe swap lmv and rmv
50
+ // if we are here scale has returns a polarVertex
51
+ const top = lmv as PolarVertex;
52
+ const bottom = rmv as PolarVertex;
53
+
54
+ const arcBit =
55
+ top.theta === bottom.theta || top.r === 0
56
+ ? ""
57
+ : `A${top.r},${top.r} 0 0 ${top.theta < bottom.theta ? 1 : 0} ${bottom.x},${bottom.y}`;
58
+ d = `M${x},${y}L${top.x},${top.y} ${arcBit} Z`;
59
+ } else {
60
+ return null;
61
+ }
62
+
63
+ const normalized = normalizePath(d);
64
+
65
+ return <BasePath d={normalized} {...rest} animated={animated} />;
66
+ }
67
+
68
+ export const Cartoons = withClades(Cartoon);