@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,123 @@
|
|
|
1
|
+
import { describe, test } from "vitest";
|
|
2
|
+
import { render } from "@testing-library/react";
|
|
3
|
+
import { ImmutableTree } from "../../evo";
|
|
4
|
+
import { FigTree } from "../../components";
|
|
5
|
+
import { rectangularLayout } from "../../layouts";
|
|
6
|
+
import {
|
|
7
|
+
Branches,
|
|
8
|
+
CircleNodes,
|
|
9
|
+
RectangleNodes,
|
|
10
|
+
} from "../../bauble-makers/makers";
|
|
11
|
+
|
|
12
|
+
const tree = ImmutableTree.fromNewick("((A:1,B:1):2,C:1);");
|
|
13
|
+
|
|
14
|
+
describe("Figures", () => {
|
|
15
|
+
test("renders a simple figure", () => {
|
|
16
|
+
const fig = render(
|
|
17
|
+
<svg
|
|
18
|
+
width="400px"
|
|
19
|
+
height="400px"
|
|
20
|
+
data-testid="figure"
|
|
21
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
22
|
+
>
|
|
23
|
+
<FigTree
|
|
24
|
+
width={400}
|
|
25
|
+
height={400}
|
|
26
|
+
tree={tree}
|
|
27
|
+
layout={rectangularLayout}
|
|
28
|
+
/>
|
|
29
|
+
</svg>,
|
|
30
|
+
);
|
|
31
|
+
//fig.debug();
|
|
32
|
+
expect(fig).toMatchSnapshot();
|
|
33
|
+
// expect(fig).toBeInTheDocument();
|
|
34
|
+
});
|
|
35
|
+
test("renders a tree with branch baubles", () => {
|
|
36
|
+
const tree = ImmutableTree.fromNewick("((A:1,B:1):2,C:1);");
|
|
37
|
+
const fig = render(
|
|
38
|
+
<svg
|
|
39
|
+
width="400px"
|
|
40
|
+
height="400px"
|
|
41
|
+
data-testid="figure"
|
|
42
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
43
|
+
>
|
|
44
|
+
<FigTree
|
|
45
|
+
width={400}
|
|
46
|
+
height={400}
|
|
47
|
+
tree={tree}
|
|
48
|
+
layout={rectangularLayout}
|
|
49
|
+
baubles={[Branches({ attrs: { strokeWidth: 2, stroke: "black" } })]}
|
|
50
|
+
/>
|
|
51
|
+
</svg>,
|
|
52
|
+
);
|
|
53
|
+
//fig.debug();
|
|
54
|
+
expect(fig).toMatchSnapshot();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("renders a tree with branch baubles and nodes", () => {
|
|
58
|
+
const tree = ImmutableTree.fromNewick("((A:1,B:1):2,C:1);");
|
|
59
|
+
const fig = render(
|
|
60
|
+
<svg
|
|
61
|
+
width="400px"
|
|
62
|
+
height="400px"
|
|
63
|
+
data-testid="figure"
|
|
64
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
65
|
+
>
|
|
66
|
+
<FigTree
|
|
67
|
+
width={400}
|
|
68
|
+
height={400}
|
|
69
|
+
tree={tree}
|
|
70
|
+
layout={rectangularLayout}
|
|
71
|
+
baubles={[
|
|
72
|
+
Branches({ attrs: { strokeWidth: 2, stroke: "black" } }),
|
|
73
|
+
CircleNodes({
|
|
74
|
+
filter: (n) => tree.isExternal(n),
|
|
75
|
+
attrs: { fill: "black", r: 10 },
|
|
76
|
+
}),
|
|
77
|
+
RectangleNodes({
|
|
78
|
+
filter: (n) => !tree.isExternal(n),
|
|
79
|
+
attrs: { fill: "black", width: 10, height: 4 },
|
|
80
|
+
}),
|
|
81
|
+
]}
|
|
82
|
+
/>
|
|
83
|
+
</svg>,
|
|
84
|
+
);
|
|
85
|
+
//fig.debug();
|
|
86
|
+
expect(fig).toMatchSnapshot();
|
|
87
|
+
// screen.debug()
|
|
88
|
+
// expect(fig).toBeInTheDocument();
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("renders a tree with branch baubles and and functions", () => {
|
|
92
|
+
const tree = ImmutableTree.fromNewick("((A:1,B:1):2,C:1);");
|
|
93
|
+
const fig = render(
|
|
94
|
+
<svg
|
|
95
|
+
width="400px"
|
|
96
|
+
height="400px"
|
|
97
|
+
data-testid="figure"
|
|
98
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
99
|
+
>
|
|
100
|
+
<FigTree
|
|
101
|
+
width={400}
|
|
102
|
+
height={400}
|
|
103
|
+
tree={tree}
|
|
104
|
+
layout={rectangularLayout}
|
|
105
|
+
baubles={[
|
|
106
|
+
Branches({ attrs: { strokeWidth: 2, stroke: () => "black" } }),
|
|
107
|
+
CircleNodes({
|
|
108
|
+
filter: (n) => tree.isExternal(n),
|
|
109
|
+
attrs: { fill: "black", r: (n) => n.number },
|
|
110
|
+
}),
|
|
111
|
+
RectangleNodes({
|
|
112
|
+
filter: (n) => !tree.isExternal(n),
|
|
113
|
+
attrs: { fill: "black", width: 10, height: 4 },
|
|
114
|
+
}),
|
|
115
|
+
]}
|
|
116
|
+
/>
|
|
117
|
+
</svg>,
|
|
118
|
+
);
|
|
119
|
+
//fig.debug();
|
|
120
|
+
expect(fig).toMatchSnapshot();
|
|
121
|
+
// expect(fig).toBeInTheDocument();
|
|
122
|
+
});
|
|
123
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<svg
|
|
2
|
+
data-testid="figure"
|
|
3
|
+
height="400px"
|
|
4
|
+
width="400px"
|
|
5
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
6
|
+
>
|
|
7
|
+
<g>
|
|
8
|
+
<g
|
|
9
|
+
transform="translate(10,10)"
|
|
10
|
+
>
|
|
11
|
+
<g
|
|
12
|
+
class="branch-layer"
|
|
13
|
+
>
|
|
14
|
+
<path
|
|
15
|
+
d="M 0.001 237.5 C0.001,237.5 0,237.5 0,237.5 C0,237.5 0,237.5005 0,237.50074999999998 C0,237.50074999999998 0,237.500875 0,237.50093750000002 C0,237.50093750000002 0,237.50096875000003 0,237.50098437500003 C0,237.50098437500003 0,237.5009921875 0,237.50099609375002 C0,237.50099609375002 0,237.501 0,237.501 "
|
|
16
|
+
fill="none"
|
|
17
|
+
stroke="black"
|
|
18
|
+
stroke-width="1"
|
|
19
|
+
/>
|
|
20
|
+
<path
|
|
21
|
+
d="M 0.001 237.5 C0.001,237.5 0,95 0,95 C0,95 126.66666666666666,95.0005 190,95.00075000000001 C190,95.00075000000001 221.66666666666666,95.00087500000001 237.5,95.0009375 C237.5,95.0009375 245.41666666666666,95.00096875 249.375,95.000984375 C249.375,95.000984375 251.35416666666666,95.0009921875 252.34375,95.00099609375 C252.34375,95.00099609375 253.33333333333331,95.001 253.33333333333331,95.001 "
|
|
22
|
+
fill="none"
|
|
23
|
+
stroke="black"
|
|
24
|
+
stroke-width="1"
|
|
25
|
+
/>
|
|
26
|
+
<path
|
|
27
|
+
d="M 253.33433333333332 95 C253.33433333333332,95 253.33333333333331,0 253.33333333333331,0 C253.33333333333331,0 316.66666666666663,0.0005 348.3333333333333,0.00075 C348.3333333333333,0.00075 364.16666666666663,0.000875 372.0833333333333,0.0009375 C372.0833333333333,0.0009375 376.04166666666663,0.00096875 378.0208333333333,0.000984375 C378.0208333333333,0.000984375 379.01041666666663,0.0009921875 379.5052083333333,0.00099609375 C379.5052083333333,0.00099609375 380,0.001 380,0.001 "
|
|
28
|
+
fill="none"
|
|
29
|
+
stroke="black"
|
|
30
|
+
stroke-width="1"
|
|
31
|
+
/>
|
|
32
|
+
<path
|
|
33
|
+
d="M 253.33433333333332 95 C253.33433333333332,95 253.33333333333331,190 253.33333333333331,190 C253.33333333333331,190 316.66666666666663,190.0005 348.3333333333333,190.00074999999998 C348.3333333333333,190.00074999999998 364.16666666666663,190.000875 372.0833333333333,190.00093750000002 C372.0833333333333,190.00093750000002 376.04166666666663,190.00096875000003 378.0208333333333,190.00098437500003 C378.0208333333333,190.00098437500003 379.01041666666663,190.0009921875 379.5052083333333,190.00099609375002 C379.5052083333333,190.00099609375002 380,190.001 380,190.001 "
|
|
34
|
+
fill="none"
|
|
35
|
+
stroke="black"
|
|
36
|
+
stroke-width="1"
|
|
37
|
+
/>
|
|
38
|
+
<path
|
|
39
|
+
d="M 0.001 237.5 C0.001,237.5 0,380 0,380 C0,380 63.33333333333333,380.0005 95,380.00075 C95,380.00075 110.83333333333333,380.00087499999995 118.75,380.00093749999996 C118.75,380.00093749999996 122.70833333333333,380.00096874999997 124.6875,380.000984375 C124.6875,380.000984375 125.67708333333333,380.00099218749995 126.171875,380.00099609374996 C126.171875,380.00099609374996 126.66666666666666,380.001 126.66666666666666,380.001 "
|
|
40
|
+
fill="none"
|
|
41
|
+
stroke="black"
|
|
42
|
+
stroke-width="1"
|
|
43
|
+
/>
|
|
44
|
+
</g>
|
|
45
|
+
</g>
|
|
46
|
+
</g>
|
|
47
|
+
</svg>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { ImmutableTree } from "../../evo";
|
|
3
|
+
import { radialLayout } from "../../layouts";
|
|
4
|
+
describe("Test rectangular layout", () => {
|
|
5
|
+
it("check x and y on root", function () {
|
|
6
|
+
const tree = ImmutableTree.fromNewick("((a:1,b:1):1,c:1);");
|
|
7
|
+
const layout = radialLayout(tree);
|
|
8
|
+
const root = layout(tree.getRoot());
|
|
9
|
+
expect(root.x).toBeCloseTo(0);
|
|
10
|
+
expect(root.y).toBeCloseTo(0);
|
|
11
|
+
|
|
12
|
+
// //a
|
|
13
|
+
const a = tree.getNodeByTaxon(tree.getTaxonByName("a"));
|
|
14
|
+
const aV = layout(a);
|
|
15
|
+
expect(aV.x).toBeCloseTo(0.0015820025368573631);
|
|
16
|
+
expect(aV.y).toBeCloseTo(-1.7311355093381278);
|
|
17
|
+
|
|
18
|
+
const c = tree.getNodeByTaxon(tree.getTaxonByName("c"));
|
|
19
|
+
const cV = layout(c);
|
|
20
|
+
expect(cV.x).toBeCloseTo(0.5);
|
|
21
|
+
expect(cV.y).toBeCloseTo(0.8660254037844386);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { ImmutableTree } from "../../evo";
|
|
3
|
+
import { rectangularLayout } from "../../layouts";
|
|
4
|
+
import { u } from "../../utils";
|
|
5
|
+
|
|
6
|
+
describe("Test rectangular layout", () => {
|
|
7
|
+
it("check x and y on root", function () {
|
|
8
|
+
const tree = ImmutableTree.fromNewick("((a:1,b:1):1,c:1);");
|
|
9
|
+
const layout = rectangularLayout(tree);
|
|
10
|
+
const root = layout(tree.getRoot());
|
|
11
|
+
expect(root.x).toBeCloseTo(0);
|
|
12
|
+
expect(root.y).toBeCloseTo(1.25);
|
|
13
|
+
|
|
14
|
+
// //a
|
|
15
|
+
const a = tree.getNodeByTaxon(tree.getTaxonByName("a"));
|
|
16
|
+
const aV = layout(a);
|
|
17
|
+
expect(aV.x).toBeCloseTo(2);
|
|
18
|
+
expect(aV.y).toBeCloseTo(0);
|
|
19
|
+
|
|
20
|
+
// //c
|
|
21
|
+
const c = tree.getNodeByTaxon(tree.getTaxonByName("c"));
|
|
22
|
+
const cV = layout(c);
|
|
23
|
+
expect(cV.x).toBeCloseTo(1);
|
|
24
|
+
expect(cV.y).toBeCloseTo(2);
|
|
25
|
+
});
|
|
26
|
+
it("check x and y on root - tree", function () {
|
|
27
|
+
const tree = ImmutableTree.fromNewick("((a:1,b:1):1,c:1);");
|
|
28
|
+
|
|
29
|
+
const layout = rectangularLayout(tree);
|
|
30
|
+
const root = layout(tree.getRoot());
|
|
31
|
+
const c = tree.getNodeByTaxon(tree.getTaxonByName("c"));
|
|
32
|
+
const cV = layout(c);
|
|
33
|
+
const tree2 = tree.setDivergence(
|
|
34
|
+
u(tree.getNodeByTaxon(tree.getTaxonByName("a"))),
|
|
35
|
+
3.0,
|
|
36
|
+
);
|
|
37
|
+
const layout2 = rectangularLayout(tree2);
|
|
38
|
+
const root2 = layout2(tree2.getRoot());
|
|
39
|
+
const c2 = tree2.getNodeByTaxon(tree.getTaxonByName("c"));
|
|
40
|
+
const cV2 = layout2(c2);
|
|
41
|
+
|
|
42
|
+
expect(c2).toStrictEqual(c);
|
|
43
|
+
expect(cV2).toStrictEqual(cV);
|
|
44
|
+
|
|
45
|
+
expect(root2).not.toBe(root); //these are vertexes
|
|
46
|
+
expect(root2.x).toBeCloseTo(0);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("check after reordering", function () {
|
|
50
|
+
// check above but with rotations
|
|
51
|
+
|
|
52
|
+
const tree = ImmutableTree.fromNewick("((a:1,b:1):1,c:1);");
|
|
53
|
+
|
|
54
|
+
const layout = rectangularLayout(tree);
|
|
55
|
+
const reordered = tree.orderNodesByDensity(true);
|
|
56
|
+
const reorderedLayout = rectangularLayout(reordered);
|
|
57
|
+
const reordededC = reordered.getNodeByTaxon(tree.getTaxonByName("c"));
|
|
58
|
+
const ogC = tree.getNodeByTaxon(tree.getTaxonByName("c"));
|
|
59
|
+
|
|
60
|
+
const ogCvertext = layout(ogC);
|
|
61
|
+
const reorderedCVertex = reorderedLayout(reordededC);
|
|
62
|
+
|
|
63
|
+
expect(ogCvertext).not.toBe(reorderedCVertex);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
|
+
import { BasePath } from "../../components/baubles/shapes";
|
|
4
|
+
|
|
5
|
+
describe("BaseBranch", () => {
|
|
6
|
+
test("renders BaseBranch, SVG path which has the right attributes", () => {
|
|
7
|
+
const d =
|
|
8
|
+
"M 10,30 A 20,20 0,0,1 50,30 A 20,20 0,0,1 90,30Q 90,60 50,90 Q 10,60 10,30 z";
|
|
9
|
+
render(
|
|
10
|
+
<svg>
|
|
11
|
+
<BasePath
|
|
12
|
+
d={d}
|
|
13
|
+
{...{ stroke: "blue", "data-testid": "branch", animated: false }}
|
|
14
|
+
/>
|
|
15
|
+
</svg>,
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
const branch = screen.getByTestId("branch");
|
|
19
|
+
expect(branch.getAttribute("d")).toBe(d);
|
|
20
|
+
expect(branch.getAttribute("stroke")).toBe("blue");
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("Renders an animated branch , SVG path which has the right attributes", () => {
|
|
24
|
+
const d =
|
|
25
|
+
"M 10,30 A 20,20 0,0,1 50,30 A 20,20 0,0,1 90,30Q 90,60 50,90 Q 10,60 10,30 z";
|
|
26
|
+
render(
|
|
27
|
+
<svg>
|
|
28
|
+
<BasePath
|
|
29
|
+
d={d}
|
|
30
|
+
animated={true}
|
|
31
|
+
{...{ stroke: "blue", "data-testid": "branch" }}
|
|
32
|
+
/>
|
|
33
|
+
</svg>,
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const branch = screen.getByTestId("branch");
|
|
37
|
+
expect(branch.getAttribute("d")).toBe(d);
|
|
38
|
+
expect(branch.getAttribute("stroke")).toBe("rgba(0, 0, 255, 1)"); // animation used rga
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
|
+
import { BaseCircle } from "../../components/baubles/shapes";
|
|
4
|
+
|
|
5
|
+
describe("BaseCircle", () => {
|
|
6
|
+
test("renders an SVG circle with the right attributes", () => {
|
|
7
|
+
render(
|
|
8
|
+
<svg>
|
|
9
|
+
<BaseCircle
|
|
10
|
+
x={0}
|
|
11
|
+
y={0}
|
|
12
|
+
{...{ r: 1, fill: "blue", "data-testid": "circle" }}
|
|
13
|
+
animated={false}
|
|
14
|
+
/>
|
|
15
|
+
</svg>,
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
const circle = screen.getByTestId("circle"); // const circle = container.querySelector("circle");
|
|
19
|
+
|
|
20
|
+
expect(circle).not.toBeNull();
|
|
21
|
+
expect(circle.getAttribute("cx")).toBe("0");
|
|
22
|
+
expect(circle.getAttribute("cy")).toBe("0");
|
|
23
|
+
expect(circle.getAttribute("r")).toBe("1");
|
|
24
|
+
expect(circle.getAttribute("fill")).toBe("blue");
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("renders an animated circle with the right attributes", () => {
|
|
28
|
+
render(
|
|
29
|
+
<svg>
|
|
30
|
+
<BaseCircle
|
|
31
|
+
x={0}
|
|
32
|
+
y={0}
|
|
33
|
+
{...{ r: 1, fill: "blue", "data-testid": "circle" }}
|
|
34
|
+
animated={true}
|
|
35
|
+
/>
|
|
36
|
+
</svg>,
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const circle = screen.getByTestId("circle"); // const circle = container.querySelector("circle");
|
|
40
|
+
|
|
41
|
+
expect(circle).not.toBeNull();
|
|
42
|
+
expect(circle.getAttribute("cx")).toBe("0");
|
|
43
|
+
expect(circle.getAttribute("cy")).toBe("0");
|
|
44
|
+
expect(circle.getAttribute("r")).toBe("1");
|
|
45
|
+
expect(circle.getAttribute("fill")).toBe("rgba(0, 0, 255, 1)");
|
|
46
|
+
});
|
|
47
|
+
});
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
|
+
import { BaseLabel } from "../../components/baubles/shapes/label";
|
|
4
|
+
import { unNullify } from "../../utils";
|
|
5
|
+
|
|
6
|
+
describe("BaseLabel", () => {
|
|
7
|
+
test("renders an SVG text element that uses transform to position", () => {
|
|
8
|
+
render(
|
|
9
|
+
<svg>
|
|
10
|
+
<BaseLabel
|
|
11
|
+
x={2}
|
|
12
|
+
y={2}
|
|
13
|
+
{...{ alignmentBaseline: "central", textAnchor: "end", rotation: 0 }}
|
|
14
|
+
text={"TEST"}
|
|
15
|
+
animated={false}
|
|
16
|
+
/>
|
|
17
|
+
</svg>,
|
|
18
|
+
);
|
|
19
|
+
const textEl = screen.getByText("TEST");
|
|
20
|
+
expect(textEl.tagName.toLowerCase()).toBe("text");
|
|
21
|
+
expect(textEl.getAttribute("transform")).toBe("translate(2,2) rotate(0)");
|
|
22
|
+
});
|
|
23
|
+
test("renders an SVG text element an alignment line", () => {
|
|
24
|
+
const d =
|
|
25
|
+
"M 10,30 A 20,20 0,0,1 50,30 A 20,20 0,0,1 90,30Q 90,60 50,90 Q 10,60 10,30 z";
|
|
26
|
+
const { container } = render(
|
|
27
|
+
<svg>
|
|
28
|
+
<BaseLabel
|
|
29
|
+
x={2}
|
|
30
|
+
y={2}
|
|
31
|
+
{...{ alignmentBaseline: "central", textAnchor: "end", rotation: 0 }}
|
|
32
|
+
text={"TEST"}
|
|
33
|
+
animated={false}
|
|
34
|
+
d={d}
|
|
35
|
+
/>
|
|
36
|
+
</svg>,
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const path = unNullify(container.querySelector("path"), `path was null`);
|
|
40
|
+
expect(path).not.toBeNull();
|
|
41
|
+
expect(path.getAttribute("d")).toBe(d);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("renders a label element an alignment line", () => {
|
|
45
|
+
const d =
|
|
46
|
+
"M 10,30 A 20,20 0,0,1 50,30 A 20,20 0,0,1 90,30Q 90,60 50,90 Q 10,60 10,30 z";
|
|
47
|
+
const { container } = render(
|
|
48
|
+
<svg>
|
|
49
|
+
<BaseLabel
|
|
50
|
+
x={2}
|
|
51
|
+
y={2}
|
|
52
|
+
{...{ alignmentBaseline: "central", textAnchor: "end", rotation: 0 }}
|
|
53
|
+
text={"TEST"}
|
|
54
|
+
animated={false}
|
|
55
|
+
d={d}
|
|
56
|
+
/>
|
|
57
|
+
</svg>,
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const path = unNullify(container.querySelector("path"), `path was null`);
|
|
61
|
+
expect(path).not.toBeNull();
|
|
62
|
+
expect(path.getAttribute("d")).toBe(d);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("renders an animated label element that uses transform to position", () => {
|
|
66
|
+
render(
|
|
67
|
+
<svg>
|
|
68
|
+
<BaseLabel
|
|
69
|
+
x={2}
|
|
70
|
+
y={2}
|
|
71
|
+
{...{ alignmentBaseline: "central", textAnchor: "end", rotation: 0 }}
|
|
72
|
+
text={"TEST"}
|
|
73
|
+
animated={true}
|
|
74
|
+
/>
|
|
75
|
+
</svg>,
|
|
76
|
+
);
|
|
77
|
+
const textEl = screen.getByText("TEST");
|
|
78
|
+
expect(textEl.tagName.toLowerCase()).toBe("text");
|
|
79
|
+
expect(textEl.getAttribute("transform")).toBe("translate(2,2) rotate(0)");
|
|
80
|
+
});
|
|
81
|
+
test("renders an animated label element an alignment line", () => {
|
|
82
|
+
const d =
|
|
83
|
+
"M 10,30 A 20,20 0,0,1 50,30 A 20,20 0,0,1 90,30Q 90,60 50,90 Q 10,60 10,30 z";
|
|
84
|
+
const { container } = render(
|
|
85
|
+
<svg>
|
|
86
|
+
<BaseLabel
|
|
87
|
+
x={2}
|
|
88
|
+
y={2}
|
|
89
|
+
{...{ alignmentBaseline: "central", textAnchor: "end", rotation: 0 }}
|
|
90
|
+
text={"TEST"}
|
|
91
|
+
animated={true}
|
|
92
|
+
d={d}
|
|
93
|
+
/>
|
|
94
|
+
</svg>,
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
const path = unNullify(container.querySelector("path"), `path was null`);
|
|
98
|
+
expect(path).not.toBeNull();
|
|
99
|
+
expect(path.getAttribute("d")).toBe(d);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { render, screen } from "@testing-library/react";
|
|
3
|
+
import {
|
|
4
|
+
BaseRectangle,
|
|
5
|
+
CenteredRectangle,
|
|
6
|
+
} from "../../components/baubles/shapes";
|
|
7
|
+
|
|
8
|
+
describe("BaseRectangle", () => {
|
|
9
|
+
test("renders an SVG rect which is centered and has the right attributes", () => {
|
|
10
|
+
render(
|
|
11
|
+
<svg>
|
|
12
|
+
<CenteredRectangle
|
|
13
|
+
x={2}
|
|
14
|
+
y={2}
|
|
15
|
+
{...{ width: 2, height: 3, "data-testid": "rect" }}
|
|
16
|
+
animated={false}
|
|
17
|
+
/>
|
|
18
|
+
</svg>,
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
const rect = screen.getByTestId("rect");
|
|
22
|
+
// const circle = container.querySelector("circle");
|
|
23
|
+
|
|
24
|
+
// expect(rect).toBeInTheDocument();
|
|
25
|
+
expect(rect.getAttribute("x")).toBe("1");
|
|
26
|
+
expect(rect.getAttribute("y")).toBe("0.5");
|
|
27
|
+
expect(rect.getAttribute("width")).toBe("2");
|
|
28
|
+
expect(rect.getAttribute("height")).toBe("3");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("renders a rect which is not centered and has the right attributes", () => {
|
|
32
|
+
render(
|
|
33
|
+
<svg>
|
|
34
|
+
<BaseRectangle
|
|
35
|
+
x={2}
|
|
36
|
+
y={2}
|
|
37
|
+
{...{ width: 2, height: 3, "data-testid": "rect" }}
|
|
38
|
+
animated={false}
|
|
39
|
+
/>
|
|
40
|
+
</svg>,
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
const rect = screen.getByTestId("rect");
|
|
44
|
+
expect(rect.getAttribute("x")).toBe("2");
|
|
45
|
+
expect(rect.getAttribute("y")).toBe("2");
|
|
46
|
+
expect(rect.getAttribute("width")).toBe("2");
|
|
47
|
+
expect(rect.getAttribute("height")).toBe("3");
|
|
48
|
+
});
|
|
49
|
+
test("renders an animated rect which is centered and has the right attributes", () => {
|
|
50
|
+
render(
|
|
51
|
+
<svg>
|
|
52
|
+
<BaseRectangle
|
|
53
|
+
x={2}
|
|
54
|
+
y={2}
|
|
55
|
+
{...{ width: 2, height: 3, "data-testid": "rect" }}
|
|
56
|
+
animated={true}
|
|
57
|
+
/>
|
|
58
|
+
</svg>,
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const rect = screen.getByTestId("rect");
|
|
62
|
+
expect(rect.getAttribute("x")).toBe("2");
|
|
63
|
+
expect(rect.getAttribute("y")).toBe("2");
|
|
64
|
+
expect(rect.getAttribute("width")).toBe("2");
|
|
65
|
+
expect(rect.getAttribute("height")).toBe("3");
|
|
66
|
+
});
|
|
67
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type WithTestId<T> = T & { "data-testid"?: string };
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import {timeParse, timeFormat} from "d3-time-format";
|
|
2
|
+
|
|
3
|
+
//https://stackoverflow.com/questions/29400171/how-do-i-convert-a-decimal-year-value-into-a-date-in-javascript
|
|
4
|
+
/**
|
|
5
|
+
* Helper function to determine if the provided year is a leap year
|
|
6
|
+
* @param year
|
|
7
|
+
* @return {boolean}
|
|
8
|
+
*/
|
|
9
|
+
export function leapYear(year:number):boolean {
|
|
10
|
+
return ((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A function which converts a decimal float into a date object
|
|
15
|
+
* @param decimalDate
|
|
16
|
+
* @return {Date}
|
|
17
|
+
*/
|
|
18
|
+
export function decimalToDate(decimal:number):Date{
|
|
19
|
+
const year = Math.trunc(decimal);
|
|
20
|
+
const totalNumberOfDays = leapYear(year)? 366:365;
|
|
21
|
+
const day = Math.round(((decimal-year)*totalNumberOfDays));
|
|
22
|
+
const date = timeParse("%Y-%j")(`${year}-${day}`)
|
|
23
|
+
// notNull(date,`Could not convert ${decimal} to date tried (year:${year} - day: ${day})`)
|
|
24
|
+
return unNullify(date,`Could not convert ${decimal} to date tried (year:${year} - day: ${day})`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* A function that converts a date into a decimal.
|
|
29
|
+
* @param date
|
|
30
|
+
* @return {number}
|
|
31
|
+
*/
|
|
32
|
+
export function dateToDecimal(date:Date){
|
|
33
|
+
const year = parseInt(timeFormat("%Y")(date));
|
|
34
|
+
const day = parseInt(timeFormat("%j")(date));
|
|
35
|
+
const totalNumberOfDays = leapYear(year)? 366:365;
|
|
36
|
+
return year+day/totalNumberOfDays
|
|
37
|
+
}
|
|
38
|
+
export function panic(message: string): never { throw new Error(message); }
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
export function u<T>(x: T | undefined): NonNullable<T> {
|
|
42
|
+
if (x === undefined) {
|
|
43
|
+
throw new Error('internal bug! unhandled undefined');
|
|
44
|
+
}
|
|
45
|
+
return x as NonNullable<T>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
export function notNull<T>(x: T,message:string): asserts x is NonNullable<T> {
|
|
50
|
+
if (x === undefined) throw new Error(message);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function unNullify<T>(x: T,message:string): NonNullable<T> {
|
|
54
|
+
if (x === null) throw new Error(message);
|
|
55
|
+
return x as NonNullable<T>
|
|
56
|
+
}
|
|
57
|
+
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "@figtreejs/typescript-config/vite.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"allowSyntheticDefaultImports": true,
|
|
5
|
+
"noEmit": true,
|
|
6
|
+
"noUncheckedIndexedAccess": false,
|
|
7
|
+
"jsx": "react-jsx",
|
|
8
|
+
"types": ["vitest/globals"], // gives types for describe/test/expect
|
|
9
|
+
"lib": ["ES2020", "DOM", "DOM.Iterable"] // DOM types for `document`
|
|
10
|
+
},
|
|
11
|
+
"include": ["src/**/*.ts", "src/**/*.tsx", "*.js", "*.ts"] // /src files and config files
|
|
12
|
+
}
|
package/vite.config.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import dts from 'vite-plugin-dts'
|
|
2
|
+
import packageJson from "./package.json";
|
|
3
|
+
import { defineConfig } from 'vitest/config';
|
|
4
|
+
|
|
5
|
+
import react from '@vitejs/plugin-react';
|
|
6
|
+
// https://vitejs.dev/config/
|
|
7
|
+
export default defineConfig({
|
|
8
|
+
plugins: [react(),dts({ rollupTypes: true })],
|
|
9
|
+
build: {
|
|
10
|
+
lib: {
|
|
11
|
+
entry: 'src/index.ts',
|
|
12
|
+
name: packageJson.name, // used only for UMD/IIFE
|
|
13
|
+
formats: ['es', 'cjs'], // add 'umd' only if you need it
|
|
14
|
+
fileName: (format) => (format === 'es' ? 'index.mjs' : `index.${format}`)
|
|
15
|
+
},
|
|
16
|
+
rollupOptions: {
|
|
17
|
+
// mark peer deps external to avoid bundling react, etc.
|
|
18
|
+
external: ['react', 'react-dom','react/jsx-runtime'],
|
|
19
|
+
output: {
|
|
20
|
+
globals: {
|
|
21
|
+
react: 'React',
|
|
22
|
+
'react-dom': 'ReactDOM' //react jsx runtime?
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
test: {
|
|
28
|
+
globals: true,
|
|
29
|
+
environment: 'jsdom', // makes `document` available at runtime
|
|
30
|
+
include: ['src/**/*.{test,spec}.{ts,tsx}'],
|
|
31
|
+
// setupFiles: ['./vitest.setup.ts'], // optional
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
|
|
2
|
+
import { defineConfig } from 'vitest/config';
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
test: {
|
|
6
|
+
globals: true,
|
|
7
|
+
environment: 'jsdom', // makes `document` available at runtime
|
|
8
|
+
include: ['src/**/*.{test,spec}.{ts,tsx}'],
|
|
9
|
+
// setupFiles: ['./vitest.setup.ts'], // optional
|
|
10
|
+
},
|
|
11
|
+
});
|