@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
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { layoutClass } from "../../../layouts";
|
|
2
|
+
import { arc as arcgen } from "d3-shape";
|
|
3
|
+
import type { Clade } from "../../hoc/with-clades";
|
|
4
|
+
import { withClades } from "../../hoc/with-clades";
|
|
5
|
+
|
|
6
|
+
import { BasePath, BaseRectangle } from "../shapes";
|
|
7
|
+
import type { PolarVertex, simplePolarVertex } from "../../../layouts/types";
|
|
8
|
+
import { notNull } from "../../../utils";
|
|
9
|
+
import { ScaleContext } from "../../../context/scale-context";
|
|
10
|
+
import { layoutContext } from "../../../context/layout-context";
|
|
11
|
+
import { animatedContext } from "../../../context/aminated-context";
|
|
12
|
+
import { useContext } from "react";
|
|
13
|
+
|
|
14
|
+
const arc = arcgen();
|
|
15
|
+
//TODO add padding
|
|
16
|
+
// const padding = 10;
|
|
17
|
+
/**
|
|
18
|
+
* A highlight around a clade of interest.
|
|
19
|
+
* For Polar layouts this will be a shaded arc.
|
|
20
|
+
* In a rectangular figure this will be a rectangle around the clade
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
type BaseHighlightAttrs = {
|
|
24
|
+
d?: string;
|
|
25
|
+
x?: number;
|
|
26
|
+
y?: number;
|
|
27
|
+
width?: number;
|
|
28
|
+
height?: number;
|
|
29
|
+
transform?: string;
|
|
30
|
+
stroke?: string;
|
|
31
|
+
stokeWidth?: number;
|
|
32
|
+
fill?: string;
|
|
33
|
+
animated: boolean;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
type Injected = {
|
|
37
|
+
d?: string;
|
|
38
|
+
x?: number;
|
|
39
|
+
y?: number;
|
|
40
|
+
width?: number;
|
|
41
|
+
height?: number;
|
|
42
|
+
transform?: string;
|
|
43
|
+
animated: boolean;
|
|
44
|
+
};
|
|
45
|
+
type HighlightAttrs = Omit<BaseHighlightAttrs, keyof Injected>;
|
|
46
|
+
|
|
47
|
+
function Highlight(props: HighlightAttrs & { clade: Clade }) {
|
|
48
|
+
const { clade, ...rest } = props;
|
|
49
|
+
const scale = useContext(ScaleContext);
|
|
50
|
+
const layout = useContext(layoutContext);
|
|
51
|
+
const animated = useContext(animatedContext);
|
|
52
|
+
|
|
53
|
+
const { root, leftMost, rightMost, mostDiverged } = clade;
|
|
54
|
+
const v = scale(layout(root));
|
|
55
|
+
const lmv = scale(layout(leftMost)); // left most child v (top of highlight)
|
|
56
|
+
const rmv = scale(layout(rightMost)); // right most child v (top of highlight)
|
|
57
|
+
const mdv = scale(layout(mostDiverged)); // right most child v (top of highlight)
|
|
58
|
+
const { layoutClass: layoutType } = layout(root);
|
|
59
|
+
if (layoutType === layoutClass.Rectangular) {
|
|
60
|
+
const width = mdv.x - v.x;
|
|
61
|
+
const height = Math.abs(lmv.y - rmv.y);
|
|
62
|
+
return (
|
|
63
|
+
<BaseRectangle
|
|
64
|
+
width={width}
|
|
65
|
+
height={height}
|
|
66
|
+
x={v.x}
|
|
67
|
+
y={Math.min(lmv.y, rmv.y)}
|
|
68
|
+
{...rest}
|
|
69
|
+
animated={animated}
|
|
70
|
+
/>
|
|
71
|
+
);
|
|
72
|
+
} else if (layoutType === layoutClass.Polar) {
|
|
73
|
+
// if we are here then scale returned a polar vertex
|
|
74
|
+
const origin = scale({ x: 0, y: 0 }) as simplePolarVertex;
|
|
75
|
+
const transform = `translate(${origin.x},${origin.y})`;
|
|
76
|
+
const minR = (v as PolarVertex).r; //padding?
|
|
77
|
+
const maxR = (mdv as PolarVertex).r;
|
|
78
|
+
const maxTheta = (lmv as PolarVertex).theta;
|
|
79
|
+
const minTheta = (rmv as PolarVertex).theta;
|
|
80
|
+
const shape = arc({
|
|
81
|
+
innerRadius: minR,
|
|
82
|
+
outerRadius: maxR + 5,
|
|
83
|
+
startAngle: minTheta + Math.PI / 2,
|
|
84
|
+
endAngle: maxTheta + Math.PI / 2,
|
|
85
|
+
});
|
|
86
|
+
notNull(shape, `Error making arc shape for Clade Highlight`);
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<BasePath d={shape} transform={transform} {...rest} animated={animated} />
|
|
90
|
+
); //transform={transform}
|
|
91
|
+
} else {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export const Highlights = withClades(Highlight);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
// exporting for public use
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { BaubleTarget } from "./bauble";
|
|
2
|
+
import { CladeShapes } from "./bauble";
|
|
3
|
+
|
|
4
|
+
import type { InternalInteractionType } from "./types";
|
|
5
|
+
import type { NodeRef } from "../../evo";
|
|
6
|
+
import { Cartoons } from "./clades/cartoon";
|
|
7
|
+
import { Highlights } from "./clades/highlight";
|
|
8
|
+
import type { Clade } from "../hoc/with-clades";
|
|
9
|
+
import type { PathAttrs, RectAttrs } from "./shapes";
|
|
10
|
+
|
|
11
|
+
// Accessor for Node Shapes
|
|
12
|
+
|
|
13
|
+
export function Clades(props: CladeProps) {
|
|
14
|
+
switch (props.shape) {
|
|
15
|
+
case CladeShapes.Cartoon:
|
|
16
|
+
return <Cartoons {...props} />;
|
|
17
|
+
case CladeShapes.Highlight:
|
|
18
|
+
return <Highlights {...props} />;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type CartoonCladeProps = {
|
|
23
|
+
shape: CladeShapes.Cartoon;
|
|
24
|
+
clades: Clade[];
|
|
25
|
+
attrs: { [key: string]: PathAttrs };
|
|
26
|
+
interactions?: { [key: string]: InternalInteractionType }; // keyed by node id // check type
|
|
27
|
+
// shape:NodeShapes,
|
|
28
|
+
keyBy?: (n: NodeRef) => string;
|
|
29
|
+
};
|
|
30
|
+
type HighlighCladeProps = {
|
|
31
|
+
shape: CladeShapes.Highlight;
|
|
32
|
+
clades: Clade[];
|
|
33
|
+
attrs: { [key: string]: Omit<RectAttrs, "width" | "height"> };
|
|
34
|
+
interactions?: { [key: string]: InternalInteractionType }; // keyed by node id // check type
|
|
35
|
+
// shape:NodeShapes,
|
|
36
|
+
keyBy?: (n: NodeRef) => string;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export type HighlightRectAttrs = Omit<RectAttrs, "width" | "height">; //width and height provided by figtree
|
|
40
|
+
export type CladeProps = CartoonCladeProps | HighlighCladeProps;
|
|
41
|
+
|
|
42
|
+
export type CladeSpec = CladeProps & {
|
|
43
|
+
target: BaubleTarget.Clade;
|
|
44
|
+
id?: string;
|
|
45
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { NodeRef } from "../../evo";
|
|
2
|
+
import type { AttrAndInteractionApplier, Attrs, Fn, LiftToUser } from "./types";
|
|
3
|
+
|
|
4
|
+
export function isFn(x: unknown): x is Fn {
|
|
5
|
+
return typeof x === "function";
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function mapAttrsToProps<A extends Attrs>(
|
|
9
|
+
attrs: LiftToUser<A>,
|
|
10
|
+
): (n: NodeRef) => A {
|
|
11
|
+
return function (node: NodeRef) {
|
|
12
|
+
const props = {} as Record<keyof A, number | string>;
|
|
13
|
+
for (const key in attrs) {
|
|
14
|
+
const k = key as keyof A;
|
|
15
|
+
const v = attrs[k];
|
|
16
|
+
props[k] = isFn(v) ? (v as Fn)(node) : (v as number | string); // eslint-disable-line @typescript-eslint/no-unnecessary-type-assertion
|
|
17
|
+
}
|
|
18
|
+
return props as A;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function applyInteractions(
|
|
23
|
+
interactions: Record<string, (n: NodeRef) => void>,
|
|
24
|
+
): (n: NodeRef) => Record<string, () => void> {
|
|
25
|
+
return function (node: NodeRef) {
|
|
26
|
+
const props: Record<string, () => void> = {};
|
|
27
|
+
for (const [key, value] of Object.entries(interactions)) {
|
|
28
|
+
props[key] = () => {
|
|
29
|
+
value(node);
|
|
30
|
+
}; // wraps base interaction
|
|
31
|
+
}
|
|
32
|
+
return props;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** A function that takes user provided attrs and interactions and returns a function
|
|
37
|
+
* that takes a node and returns the interaction and Attrs in the form expected by baubles
|
|
38
|
+
*/
|
|
39
|
+
export function useAttributeMappers<A extends Attrs>(
|
|
40
|
+
attrs: LiftToUser<A>,
|
|
41
|
+
interactions?: Record<string, (n: NodeRef) => void>,
|
|
42
|
+
): AttrAndInteractionApplier<A> {
|
|
43
|
+
// const { attrs, interactions} = props;
|
|
44
|
+
|
|
45
|
+
//This memorizes the functions so they are not made each time - maybe overkill.
|
|
46
|
+
// const baseAttrMapper = useCallback(mapAttrsToProps((attrs?attrs:{})), [attrs]);
|
|
47
|
+
// const baseInteractionMapper = useCallback(applyInteractions((interactions?interactions:{})), [interactions]);
|
|
48
|
+
// const tooltipMapper = useCallback(mapAttrsToProps((tooltip?tooltip:{})),[tooltip]);
|
|
49
|
+
|
|
50
|
+
const baseAttrMapper = mapAttrsToProps(attrs);
|
|
51
|
+
|
|
52
|
+
const baseInteractionMapper = interactions
|
|
53
|
+
? applyInteractions(interactions)
|
|
54
|
+
: () => undefined;
|
|
55
|
+
|
|
56
|
+
return function shapeProps(node: NodeRef) {
|
|
57
|
+
return {
|
|
58
|
+
attrs: baseAttrMapper(node),
|
|
59
|
+
interactions: baseInteractionMapper(node),
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// public exports
|
|
2
|
+
//TODO only expose <baubles />
|
|
3
|
+
|
|
4
|
+
// pass on lower imports
|
|
5
|
+
export * from "./clades";
|
|
6
|
+
export * from "./shapes";
|
|
7
|
+
|
|
8
|
+
// exports from this directory
|
|
9
|
+
export { Branches } from "./branches";
|
|
10
|
+
export type { BranchSpec } from "./branches";
|
|
11
|
+
|
|
12
|
+
export { Nodes } from "./nodes";
|
|
13
|
+
export type { NodeSpec } from "./nodes";
|
|
14
|
+
|
|
15
|
+
export { NodeLabels, BranchLabels } from "./labels"; // Todo unify to Label
|
|
16
|
+
export type { LabelSpec } from "./labels";
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { NodeRef } from "../../evo";
|
|
2
|
+
import { withBranches } from "../hoc/with-branches";
|
|
3
|
+
import { withNodeLabel } from "../hoc/with-node";
|
|
4
|
+
import { withNodes } from "../hoc/with-nodes";
|
|
5
|
+
import type { BaubleTarget } from "./bauble";
|
|
6
|
+
import { BaseLabel } from "./shapes";
|
|
7
|
+
import type { TextAttrs } from "./shapes/label";
|
|
8
|
+
import type { AttrsRecord } from "./types";
|
|
9
|
+
|
|
10
|
+
export const BranchLabels = withBranches<TextAttrs>(
|
|
11
|
+
withNodeLabel<TextAttrs>(BaseLabel),
|
|
12
|
+
);
|
|
13
|
+
export const NodeLabels = withNodes<TextAttrs>(
|
|
14
|
+
withNodeLabel<TextAttrs>(BaseLabel),
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
export type NodeLabelProps = {
|
|
18
|
+
id?: string;
|
|
19
|
+
nodes: NodeRef[];
|
|
20
|
+
attrs: AttrsRecord<TextAttrs>;
|
|
21
|
+
aligned: boolean;
|
|
22
|
+
};
|
|
23
|
+
export type NodeLabelSpec = NodeLabelProps & {
|
|
24
|
+
target: BaubleTarget.NodeLabel;
|
|
25
|
+
id?: string;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type BranchLabelProps = {
|
|
29
|
+
id?: string;
|
|
30
|
+
branches: { node: NodeRef; parent: NodeRef }[];
|
|
31
|
+
attrs: AttrsRecord<TextAttrs>;
|
|
32
|
+
};
|
|
33
|
+
export type BranchLabelSpec = BranchLabelProps & {
|
|
34
|
+
target: BaubleTarget.BranchLabel;
|
|
35
|
+
id?: string;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type LabelSpec = BranchLabelSpec | NodeLabelSpec;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { CircleAttrs, RectAttrs } from "./shapes";
|
|
2
|
+
import { BaseCircle, CenteredRectangle } from "./shapes";
|
|
3
|
+
|
|
4
|
+
import { withNode } from "../hoc";
|
|
5
|
+
import type { BaubleTarget } from "./bauble";
|
|
6
|
+
import { NodeShapes } from "./bauble";
|
|
7
|
+
import { withNodes } from "../hoc/with-nodes";
|
|
8
|
+
import type { InternalInteractionType } from "./types";
|
|
9
|
+
import type { NodeRef } from "../../evo";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Add a Circle node Bauble to the figure.
|
|
13
|
+
*/
|
|
14
|
+
const CircleNodes = withNodes(withNode(BaseCircle));
|
|
15
|
+
/**
|
|
16
|
+
*
|
|
17
|
+
* Add a Rectangular node Bauble to the figure.
|
|
18
|
+
*/
|
|
19
|
+
const RectangleNodes = withNodes(withNode(CenteredRectangle));
|
|
20
|
+
|
|
21
|
+
// Accessor for Node Shapes
|
|
22
|
+
|
|
23
|
+
export function Nodes(props: NodeProps) {
|
|
24
|
+
switch (props.shape) {
|
|
25
|
+
case NodeShapes.Circle:
|
|
26
|
+
return <CircleNodes {...props} />;
|
|
27
|
+
case NodeShapes.Rectangle:
|
|
28
|
+
return <RectangleNodes {...props} />;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
type CircleNodeProps = {
|
|
33
|
+
shape: NodeShapes.Circle;
|
|
34
|
+
nodes: NodeRef[];
|
|
35
|
+
attrs: { [key: string]: CircleAttrs };
|
|
36
|
+
interactions?: { [key: string]: InternalInteractionType }; // keyed by node id // check type
|
|
37
|
+
// shape:NodeShapes,
|
|
38
|
+
keyBy?: (n: NodeRef) => string;
|
|
39
|
+
};
|
|
40
|
+
type RectangleNodeProps = {
|
|
41
|
+
shape: NodeShapes.Rectangle;
|
|
42
|
+
nodes: NodeRef[];
|
|
43
|
+
attrs: { [key: string]: RectAttrs };
|
|
44
|
+
interactions?: { [key: string]: InternalInteractionType }; // keyed by node id // check type
|
|
45
|
+
// shape:NodeShapes,
|
|
46
|
+
keyBy?: (n: NodeRef) => string;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
type NodeProps = CircleNodeProps | RectangleNodeProps;
|
|
50
|
+
|
|
51
|
+
export type NodeSpec = NodeProps & { target: BaubleTarget.Node; id?: string };
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { AnimatedProps } from "@react-spring/web";
|
|
2
|
+
import { animated, useSpring } from "@react-spring/web";
|
|
3
|
+
|
|
4
|
+
// update ref to work with animated expectations no old refs
|
|
5
|
+
export type BasePathDOMProps = Omit<React.ComponentProps<"path">, "ref"> & {
|
|
6
|
+
ref?: React.Ref<SVGPathElement>;
|
|
7
|
+
};
|
|
8
|
+
export type PathAttrs = Omit<BasePathDOMProps, "d">; // d will be calculated
|
|
9
|
+
|
|
10
|
+
export type PathProps = PathAttrs & { d: string; animated: boolean };
|
|
11
|
+
|
|
12
|
+
/** React component that renders a path.
|
|
13
|
+
* The fill is defaulted to 'none' but is overwritten by anything in attrs
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export const animatableBranchKeys = ["d", "stroke", "strokeWidth"] as const;
|
|
17
|
+
|
|
18
|
+
type AnimKey = (typeof animatableBranchKeys)[number];
|
|
19
|
+
|
|
20
|
+
// attribute get spread here interactions come in
|
|
21
|
+
function pickAnimatable(
|
|
22
|
+
props: Record<string, unknown>,
|
|
23
|
+
): Partial<Record<AnimKey, number | string>> {
|
|
24
|
+
const out: Partial<Record<AnimKey, number | string>> = {};
|
|
25
|
+
for (const k of animatableBranchKeys) {
|
|
26
|
+
const v = props[k];
|
|
27
|
+
// Keep 0; only exclude null/undefined
|
|
28
|
+
if (v != null && (typeof v === "number" || typeof v === "string")) {
|
|
29
|
+
out[k] = v;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function BasePath(props: PathProps) {
|
|
36
|
+
const { animated: a, ...rest } = props;
|
|
37
|
+
const aAttrs = pickAnimatable(rest);
|
|
38
|
+
// Keep hooks count constant
|
|
39
|
+
const animatedValues = useSpring({
|
|
40
|
+
...aAttrs,
|
|
41
|
+
config: { duration: 500 },
|
|
42
|
+
});
|
|
43
|
+
if (!a) {
|
|
44
|
+
return <animated.path {...rest} />;
|
|
45
|
+
} else {
|
|
46
|
+
return (
|
|
47
|
+
<animated.path
|
|
48
|
+
{...rest}
|
|
49
|
+
{...(animatedValues as AnimatedProps<BasePathDOMProps>)}
|
|
50
|
+
/>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { AnimatedProps } from "@react-spring/web";
|
|
2
|
+
import { animated, useSpring } from "@react-spring/web";
|
|
3
|
+
|
|
4
|
+
// update ref to work with animated expectations no old refs
|
|
5
|
+
type BaseCircleDOMProps = Omit<React.ComponentProps<"circle">, "ref"> & {
|
|
6
|
+
ref?: React.Ref<SVGCircleElement>;
|
|
7
|
+
};
|
|
8
|
+
export type CircleAttrs = Omit<BaseCircleDOMProps, "cx" | "cy"> & { r: number };
|
|
9
|
+
export type CircleProps = CircleAttrs & {
|
|
10
|
+
x: number;
|
|
11
|
+
y: number;
|
|
12
|
+
animated: boolean;
|
|
13
|
+
};
|
|
14
|
+
export const animatableCircleKeys = [
|
|
15
|
+
"cx",
|
|
16
|
+
"cy",
|
|
17
|
+
"r",
|
|
18
|
+
"stroke",
|
|
19
|
+
"strokeWidth",
|
|
20
|
+
"fill",
|
|
21
|
+
] as const;
|
|
22
|
+
|
|
23
|
+
type AnimKey = (typeof animatableCircleKeys)[number];
|
|
24
|
+
|
|
25
|
+
// attribute get spread here interactions come in
|
|
26
|
+
function pickAnimatable(
|
|
27
|
+
props: Record<string, unknown>,
|
|
28
|
+
): Partial<Record<AnimKey, number | string>> {
|
|
29
|
+
const out: Partial<Record<AnimKey, number | string>> = {};
|
|
30
|
+
for (const k of animatableCircleKeys) {
|
|
31
|
+
const v = props[k];
|
|
32
|
+
// Keep 0; only exclude null/undefined
|
|
33
|
+
if (v != null && (typeof v === "number" || typeof v === "string")) {
|
|
34
|
+
out[k] = v;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** React component that renders a circle */
|
|
41
|
+
export const BaseCircle = function (props: CircleProps) {
|
|
42
|
+
const { animated: a, x, y, ...attrs } = props;
|
|
43
|
+
// maybe could put animation logic in an HOC but that's causing type issues
|
|
44
|
+
const mappedProps = { ...attrs, cx: x, cy: y };
|
|
45
|
+
const aAttrs = pickAnimatable(mappedProps);
|
|
46
|
+
// Keep hooks count constant
|
|
47
|
+
const animatedValues = useSpring({
|
|
48
|
+
...aAttrs,
|
|
49
|
+
config: { duration: 500 },
|
|
50
|
+
});
|
|
51
|
+
if (!a) {
|
|
52
|
+
return <animated.circle className={"node-shape"} {...mappedProps} />;
|
|
53
|
+
} else {
|
|
54
|
+
return (
|
|
55
|
+
<animated.circle
|
|
56
|
+
className={"node-shape"}
|
|
57
|
+
{...mappedProps}
|
|
58
|
+
{...(animatedValues as AnimatedProps<BaseCircleDOMProps>)}
|
|
59
|
+
/>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// export const AnimatedCircle = withAnimation(BaseCircle)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Package exports
|
|
2
|
+
export type { CircleAttrs } from "./circle";
|
|
3
|
+
export { BaseCircle } from "./circle";
|
|
4
|
+
export type { PathAttrs } from "./branch";
|
|
5
|
+
export { BasePath } from "./branch";
|
|
6
|
+
export type { RectAttrs } from "./rectangle";
|
|
7
|
+
export { BaseRectangle, CenteredRectangle } from "./rectangle";
|
|
8
|
+
export type { TextAttrs } from "./label";
|
|
9
|
+
export { BaseLabel } from "./label";
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { SpringValue, Interpolation } from "@react-spring/web";
|
|
2
|
+
import { to, animated, useSpring } from "@react-spring/web";
|
|
3
|
+
import type { numerical } from "../types";
|
|
4
|
+
import { isSpringNumber } from "../types";
|
|
5
|
+
import type { LabelInjection } from "../../hoc/with-node";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Label attributes for styling and rendering labels.
|
|
9
|
+
* These will be stripped and trickle up to the user
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
type BaseTextDOMProps = Omit<React.ComponentProps<"text">, "ref"> & {
|
|
13
|
+
ref?: React.Ref<SVGTextElement>;
|
|
14
|
+
};
|
|
15
|
+
export type TextAttrs = BaseTextDOMProps & { text: string };
|
|
16
|
+
type LabelProps = TextAttrs & LabelInjection; // need these to match injected exactly for type inference
|
|
17
|
+
|
|
18
|
+
/** The props needed for rendering a label in the svg*/
|
|
19
|
+
|
|
20
|
+
// transform={to([animatedProperties.x, animatedProperties.y, animatedProperties.rotation], (x, y, rotation) => `translate(${x},${y}) rotate(${rotation})`)}
|
|
21
|
+
function getTransform(
|
|
22
|
+
x: numerical,
|
|
23
|
+
y: numerical,
|
|
24
|
+
rotation: numerical,
|
|
25
|
+
): string | SpringValue<string> | Interpolation<string> {
|
|
26
|
+
if (isSpringNumber(x) || isSpringNumber(y) || isSpringNumber(rotation)) {
|
|
27
|
+
return to(
|
|
28
|
+
[x, y, rotation],
|
|
29
|
+
(x, y, rotation) => `translate(${x},${y}) rotate(${rotation})`,
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
return `translate(${x},${y}) rotate(${rotation})`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function BaseLabel(props: LabelProps) {
|
|
36
|
+
const {
|
|
37
|
+
alignmentBaseline,
|
|
38
|
+
textAnchor,
|
|
39
|
+
rotation,
|
|
40
|
+
x,
|
|
41
|
+
y,
|
|
42
|
+
text,
|
|
43
|
+
d,
|
|
44
|
+
animated: a,
|
|
45
|
+
...other
|
|
46
|
+
} = props;
|
|
47
|
+
|
|
48
|
+
const animatedValues = useSpring({
|
|
49
|
+
x,
|
|
50
|
+
y,
|
|
51
|
+
rotation,
|
|
52
|
+
config: { duration: 500 },
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
if (!a) {
|
|
56
|
+
const transform = getTransform(x, y, rotation);
|
|
57
|
+
return (
|
|
58
|
+
<g>
|
|
59
|
+
<animated.text
|
|
60
|
+
alignmentBaseline={alignmentBaseline}
|
|
61
|
+
textAnchor={textAnchor}
|
|
62
|
+
transform={transform}
|
|
63
|
+
{...other}
|
|
64
|
+
>
|
|
65
|
+
{text}
|
|
66
|
+
</animated.text>
|
|
67
|
+
{d ? (
|
|
68
|
+
<animated.path
|
|
69
|
+
strokeWidth={1}
|
|
70
|
+
stroke="grey"
|
|
71
|
+
strokeDasharray="2"
|
|
72
|
+
d={d}
|
|
73
|
+
/>
|
|
74
|
+
) : null}
|
|
75
|
+
</g>
|
|
76
|
+
);
|
|
77
|
+
} else {
|
|
78
|
+
const animatedTransform = getTransform(
|
|
79
|
+
animatedValues.x,
|
|
80
|
+
animatedValues.y,
|
|
81
|
+
animatedValues.rotation,
|
|
82
|
+
);
|
|
83
|
+
return (
|
|
84
|
+
<g>
|
|
85
|
+
<animated.text
|
|
86
|
+
alignmentBaseline={alignmentBaseline}
|
|
87
|
+
textAnchor={textAnchor}
|
|
88
|
+
transform={animatedTransform}
|
|
89
|
+
{...other}
|
|
90
|
+
>
|
|
91
|
+
{text}
|
|
92
|
+
</animated.text>
|
|
93
|
+
{d ? (
|
|
94
|
+
<animated.path
|
|
95
|
+
strokeWidth={1}
|
|
96
|
+
stroke="grey"
|
|
97
|
+
strokeDasharray="2"
|
|
98
|
+
d={d}
|
|
99
|
+
/>
|
|
100
|
+
) : null}
|
|
101
|
+
</g>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { AnimatedProps } from "@react-spring/web";
|
|
2
|
+
import { animated, useSpring } from "@react-spring/web";
|
|
3
|
+
|
|
4
|
+
/** Find where to put a position so the center is the provided location
|
|
5
|
+
* used below for centered rectangles. Nodes provide the x,y of the center.
|
|
6
|
+
*/
|
|
7
|
+
function centerNumber(pos: number, size: number): number {
|
|
8
|
+
// plain numbers
|
|
9
|
+
return pos - size / 2;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Rectangle attributes for styling and rendering a rectangle.
|
|
14
|
+
* These will be stripped and trickle up to the user
|
|
15
|
+
*/
|
|
16
|
+
// update ref to work with animated expectations no old refs
|
|
17
|
+
type BaseRectangleDOMProps = Omit<React.ComponentProps<"rect">, "ref"> & {
|
|
18
|
+
ref?: React.Ref<SVGRectElement>;
|
|
19
|
+
};
|
|
20
|
+
export type RectAttrs = BaseRectangleDOMProps & {
|
|
21
|
+
width: number;
|
|
22
|
+
height: number;
|
|
23
|
+
};
|
|
24
|
+
export type RectProps = RectAttrs & { animated: boolean; x: number; y: number }; // need these to match injected exactly for type inference
|
|
25
|
+
export const animatableRectKeys = [
|
|
26
|
+
"rx",
|
|
27
|
+
"ry",
|
|
28
|
+
"x",
|
|
29
|
+
"y",
|
|
30
|
+
"width",
|
|
31
|
+
"height",
|
|
32
|
+
"stroke",
|
|
33
|
+
"strokeWidth",
|
|
34
|
+
] as const;
|
|
35
|
+
|
|
36
|
+
type AnimKey = (typeof animatableRectKeys)[number];
|
|
37
|
+
|
|
38
|
+
// attribute get spread here interactions come in
|
|
39
|
+
//TODO make this a generalized function
|
|
40
|
+
function pickAnimatable(
|
|
41
|
+
props: Record<string, unknown>,
|
|
42
|
+
): Partial<Record<AnimKey, number | string>> {
|
|
43
|
+
const out: Partial<Record<AnimKey, number | string>> = {};
|
|
44
|
+
for (const k of animatableRectKeys) {
|
|
45
|
+
const v = props[k];
|
|
46
|
+
// Keep 0; only exclude null/undefined
|
|
47
|
+
if (v != null && (typeof v === "number" || typeof v === "string")) {
|
|
48
|
+
out[k] = v;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** A Rectangle centered on the provided x,y */
|
|
55
|
+
export const CenteredRectangle = function (props: RectProps) {
|
|
56
|
+
const { x, y, width, height, ...rest } = props;
|
|
57
|
+
const xCentered = centerNumber(x, width);
|
|
58
|
+
const yCentered = centerNumber(y, height);
|
|
59
|
+
const newAttrs = { ...rest, x: xCentered, y: yCentered, width, height };
|
|
60
|
+
return <BaseRectangle {...newAttrs} />;
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* A rectangle rendered as expected in an svg
|
|
64
|
+
*/
|
|
65
|
+
export const BaseRectangle = function (props: RectProps) {
|
|
66
|
+
const { animated: a, ...attrs } = props;
|
|
67
|
+
const aAttrs = pickAnimatable(attrs);
|
|
68
|
+
// Keep hooks count constant
|
|
69
|
+
const animatedValues = useSpring({
|
|
70
|
+
...aAttrs,
|
|
71
|
+
config: { duration: 500 },
|
|
72
|
+
});
|
|
73
|
+
if (!a) {
|
|
74
|
+
return <animated.rect className={"node-shape"} {...attrs} />;
|
|
75
|
+
}
|
|
76
|
+
return (
|
|
77
|
+
<animated.rect
|
|
78
|
+
className={"node-shape"}
|
|
79
|
+
{...attrs}
|
|
80
|
+
{...(animatedValues as AnimatedProps<BaseRectangleDOMProps>)}
|
|
81
|
+
/>
|
|
82
|
+
);
|
|
83
|
+
};
|