@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.
- package/CHANGELOG.md +9 -0
- package/eslint.config.js +9 -0
- package/package.json +76 -0
- package/src/@custom-types/normalize-svg-path.d.ts +13 -0
- package/src/@custom-types/parse-svg-path.d.ts +8 -0
- package/src/@custom-types/svg-path-types.d.ts +37 -0
- package/src/bauble-makers/makers.ts +112 -0
- package/src/bauble-makers/set-up-baubles.ts +197 -0
- package/src/bauble-makers/utils.ts +61 -0
- package/src/components/baubles/bauble.tsx +61 -0
- package/src/components/baubles/branches.tsx +13 -0
- package/src/components/baubles/clades/cartoon.tsx +68 -0
- package/src/components/baubles/clades/highlight.tsx +96 -0
- package/src/components/baubles/clades/index.ts +1 -0
- package/src/components/baubles/clades.tsx +45 -0
- package/src/components/baubles/helpers.tsx +62 -0
- package/src/components/baubles/index.ts +16 -0
- package/src/components/baubles/labels.tsx +38 -0
- package/src/components/baubles/nodes.tsx +51 -0
- package/src/components/baubles/shapes/branch.tsx +53 -0
- package/src/components/baubles/shapes/circle.tsx +64 -0
- package/src/components/baubles/shapes/index.ts +9 -0
- package/src/components/baubles/shapes/label.tsx +104 -0
- package/src/components/baubles/shapes/rectangle.tsx +83 -0
- package/src/components/baubles/types.ts +99 -0
- package/src/components/decorations/axis/axis-types.ts +123 -0
- package/src/components/decorations/axis/axis.tsx +21 -0
- package/src/components/decorations/axis/index.ts +2 -0
- package/src/components/decorations/axis/polar-axis-bars.tsx +102 -0
- package/src/components/decorations/axis/polar-axis.tsx +175 -0
- package/src/components/decorations/axis/rectangular-axis-bars.tsx +53 -0
- package/src/components/decorations/axis/rectangular-axis.tsx +151 -0
- package/src/components/decorations/index.ts +2 -0
- package/src/components/decorations/legend/discrete-legend.tsx +93 -0
- package/src/components/decorations/legend/index.ts +1 -0
- package/src/components/decorations/legend/legend.tsx +1 -0
- package/src/components/figtree/figtree-types.ts +69 -0
- package/src/components/figtree/figtree.tsx +136 -0
- package/src/components/figtree/index.ts +3 -0
- package/src/components/hoc/index.ts +7 -0
- package/src/components/hoc/with-branch.tsx +148 -0
- package/src/components/hoc/with-branches.tsx +54 -0
- package/src/components/hoc/with-clades.tsx +47 -0
- package/src/components/hoc/with-node.tsx +183 -0
- package/src/components/hoc/with-nodes.tsx +45 -0
- package/src/components/index.ts +4 -0
- package/src/context/aminated-context.ts +3 -0
- package/src/context/dimension-context.ts +22 -0
- package/src/context/layout-context.ts +20 -0
- package/src/context/scale-context.ts +12 -0
- package/src/evo/index.ts +1 -0
- package/src/evo/tree/index.ts +5 -0
- package/src/evo/tree/mcc-tree.ts +0 -0
- package/src/evo/tree/normalized-tree/immutable-tree-helpers.ts +136 -0
- package/src/evo/tree/normalized-tree/immutable-tree.test.ts +158 -0
- package/src/evo/tree/normalized-tree/immutable-tree.ts +1365 -0
- package/src/evo/tree/normalized-tree/index.ts +3 -0
- package/src/evo/tree/parsers/annotation-parser.ts +276 -0
- package/src/evo/tree/parsers/index.ts +3 -0
- package/src/evo/tree/parsers/newick-character-parser.ts +246 -0
- package/src/evo/tree/parsers/newick-parsing.ts +22 -0
- package/src/evo/tree/parsers/nexus-parser.ts +12 -0
- package/src/evo/tree/parsers/nexus-parsing.ts +68 -0
- package/src/evo/tree/parsers/parsing.test.ts +289 -0
- package/src/evo/tree/parsers/stream-reader/index.ts +1 -0
- package/src/evo/tree/parsers/stream-reader/newick-importer.txt +395 -0
- package/src/evo/tree/parsers/stream-reader/nexus-importer.test.ts +99 -0
- package/src/evo/tree/parsers/stream-reader/nexus-importer.ts +293 -0
- package/src/evo/tree/parsers/stream-reader/nexus-tokenizer.ts +77 -0
- package/src/evo/tree/parsers/stream-reader/nexus-transform-stream.txt +109 -0
- package/src/evo/tree/taxa/helper-functions.ts +46 -0
- package/src/evo/tree/taxa/index.ts +1 -0
- package/src/evo/tree/taxa/taxon.ts +116 -0
- package/src/evo/tree/traversals/index.ts +1 -0
- package/src/evo/tree/traversals/preorder-traversal.ts +89 -0
- package/src/evo/tree/traversals/traversal-types.ts +6 -0
- package/src/evo/tree/tree-types.ts +197 -0
- package/src/evo/tree/utilities.ts +44 -0
- package/src/index.ts +6 -0
- package/src/layouts/functional/index.ts +2 -0
- package/src/layouts/functional/radial-layout.ts +150 -0
- package/src/layouts/functional/rectangular-layout.ts +71 -0
- package/src/layouts/index.ts +3 -0
- package/src/layouts/layout-interface.ts +90 -0
- package/src/layouts/types.ts +32 -0
- package/src/path.helpers.ts +81 -0
- package/src/store/polar-scale.ts +145 -0
- package/src/store/store.ts +144 -0
- package/src/tests/baubles/__snapshots__/branch-labels.test.tsx.snap +901 -0
- package/src/tests/baubles/__snapshots__/node-labels.test.tsx.snap +1516 -0
- package/src/tests/baubles/branch-labels.test.tsx +103 -0
- package/src/tests/baubles/label.svg +131 -0
- package/src/tests/baubles/node-labels.test.tsx +126 -0
- package/src/tests/clades/__snapshots__/cartoon.test.tsx.snap +327 -0
- package/src/tests/clades/__snapshots__/highlight.test.tsx.snap +337 -0
- package/src/tests/clades/cartoon.test.tsx +65 -0
- package/src/tests/clades/highlight.test.tsx +66 -0
- package/src/tests/figtree/__snapshots__/figtree.test.tsx.snap +761 -0
- package/src/tests/figtree/figtree.test.tsx +123 -0
- package/src/tests/figtree/simple.svg +47 -0
- package/src/tests/layouts/radiallayout.test.ts +23 -0
- package/src/tests/layouts/rectangularlayout.test.ts +65 -0
- package/src/tests/shapes/branch.test.tsx +40 -0
- package/src/tests/shapes/circle.test.tsx +47 -0
- package/src/tests/shapes/label.test.tsx +101 -0
- package/src/tests/shapes/rectangle.test.tsx +67 -0
- package/src/tests/shapes/types.ts +1 -0
- package/src/utils.ts +57 -0
- package/tsconfig.json +12 -0
- package/vite.config.ts +34 -0
- package/vitetest.config.ts +11 -0
package/CHANGELOG.md
ADDED
package/eslint.config.js
ADDED
|
@@ -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);
|