@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,99 @@
|
|
|
1
|
+
import { SpringValue } from "@react-spring/web";
|
|
2
|
+
import type { NodeRef } from "../../evo";
|
|
3
|
+
|
|
4
|
+
// **Types for animated shapes that can accept either animated values or primitives */
|
|
5
|
+
export type numerical = number | SpringValue<number>;
|
|
6
|
+
export type stringy = string | SpringValue<string>;
|
|
7
|
+
|
|
8
|
+
/** Helper functions for unspringing types so we only have to define possible types once */
|
|
9
|
+
export type DeSpring<T> = T extends SpringValue<infer U> ? U : T;
|
|
10
|
+
export type StripSprings<T> = { [K in keyof T]: DeSpring<T[K]> };
|
|
11
|
+
|
|
12
|
+
/** Types of interactions that are accepted on rendered objects.
|
|
13
|
+
* These wrap functions provided by the users
|
|
14
|
+
*/
|
|
15
|
+
export type InternalInteractionType = {
|
|
16
|
+
onClick?: () => void;
|
|
17
|
+
OnMouseOver?: () => void;
|
|
18
|
+
onEnter?: () => void;
|
|
19
|
+
onExit?: () => void;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type InteractionType = {
|
|
23
|
+
onClick?: (n: NodeRef) => void;
|
|
24
|
+
OnMouseOver?: (n: NodeRef) => void;
|
|
25
|
+
onEnter?: (n: NodeRef) => void;
|
|
26
|
+
onExit?: (n: NodeRef) => void;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/** Attributes as they enter rendered possibly animated svg elements */
|
|
30
|
+
export type BaseAttrs = Record<string, numerical | stringy>;
|
|
31
|
+
export type BasePos = Record<string, numerical | stringy>;
|
|
32
|
+
/** Attribute as they are passed internally to possibly animated svg elements */
|
|
33
|
+
export type Attrs = Record<string, number | string>;
|
|
34
|
+
export type AttrsRecord<A extends object> = Record<string, A>;
|
|
35
|
+
/** Attributes provided by the user. They will be applied to elements prior to rendering */
|
|
36
|
+
export type UserAttrs = Record<
|
|
37
|
+
string,
|
|
38
|
+
number | string | ((n: NodeRef) => number | string)
|
|
39
|
+
>;
|
|
40
|
+
|
|
41
|
+
/** Strip x/y/d/attrs of their possible spring values
|
|
42
|
+
* This is used to infer the input types to animated values
|
|
43
|
+
*/
|
|
44
|
+
export type StripProps<P> = {
|
|
45
|
+
[K in keyof P]: K extends "attrs"
|
|
46
|
+
? StripSprings<P[K]>
|
|
47
|
+
: K extends "pos"
|
|
48
|
+
? StripSprings<P[K]>
|
|
49
|
+
: P[K];
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export type XYAttrs = { x: numerical; y: numerical } & BaseAttrs;
|
|
53
|
+
/** Wide type for all svg base baubles */
|
|
54
|
+
export type BaseBaubleProps<A extends BaseAttrs> = {
|
|
55
|
+
interactions?: InternalInteractionType;
|
|
56
|
+
attrs: A;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export type BaseXYProps<A extends BaseAttrs> = {
|
|
60
|
+
interactions?: InternalInteractionType;
|
|
61
|
+
x: numerical;
|
|
62
|
+
y: numerical;
|
|
63
|
+
attrs: A;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/** props for baubles that need x,y for positioning */
|
|
67
|
+
export type XYShape<A extends Attrs> = {
|
|
68
|
+
interactions?: InternalInteractionType;
|
|
69
|
+
attrs: A & { x: number; y: number };
|
|
70
|
+
animated?: boolean;
|
|
71
|
+
};
|
|
72
|
+
export type XYBaseShape<A extends BaseAttrs> = {
|
|
73
|
+
interactions?: InternalInteractionType;
|
|
74
|
+
attrs: A & { x: numerical; y: numerical };
|
|
75
|
+
animated?: boolean;
|
|
76
|
+
};
|
|
77
|
+
/** props for Baubles that need d for positioning */
|
|
78
|
+
export type DShape<A extends Attrs> = {
|
|
79
|
+
interactions?: InternalInteractionType;
|
|
80
|
+
d: string;
|
|
81
|
+
attrs: A;
|
|
82
|
+
animated?: boolean;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export type AttrAndInteractionApplier<A extends Attrs> = (n: NodeRef) => {
|
|
86
|
+
attrs: A;
|
|
87
|
+
interactions?: Record<string, () => void>;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// possibly redundant
|
|
91
|
+
export type Fn = (n: NodeRef) => number | string;
|
|
92
|
+
// resolves functions -> return type, leaves literals as-is
|
|
93
|
+
export const isSpringNumber = (v: numerical): v is SpringValue<number> =>
|
|
94
|
+
v instanceof SpringValue;
|
|
95
|
+
type NodeFn<T> = (n: NodeRef) => T;
|
|
96
|
+
/** A helper function map Attrs accepted by Baubles to those supplied by the user */
|
|
97
|
+
export type LiftToUser<A extends Attrs> = {
|
|
98
|
+
[K in keyof A]: A[K] | NodeFn<A[K]>;
|
|
99
|
+
};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { format } from "d3-format";
|
|
2
|
+
import type { ScaleContinuousNumeric } from "d3-scale";
|
|
3
|
+
import type { Attrs } from "../../baubles/types";
|
|
4
|
+
|
|
5
|
+
export type AxisOrientation = "horizontal" | "vertical" | "polar";
|
|
6
|
+
|
|
7
|
+
export type AxisTicksOptions = {
|
|
8
|
+
number?: number;
|
|
9
|
+
format?: (value: number) => string;
|
|
10
|
+
padding?: number;
|
|
11
|
+
style?: { [key: string]: number | string };
|
|
12
|
+
length?: number;
|
|
13
|
+
values?: number[];
|
|
14
|
+
};
|
|
15
|
+
// export type fullAxisTickOptions =
|
|
16
|
+
// optionals are filled by default below
|
|
17
|
+
export interface AxisProps {
|
|
18
|
+
offsetBy?: number;
|
|
19
|
+
scaleBy?: number;
|
|
20
|
+
reverse?: boolean;
|
|
21
|
+
gap?: number;
|
|
22
|
+
title?: { text: string; padding: number; style: Attrs };
|
|
23
|
+
ticks?: AxisTicksOptions;
|
|
24
|
+
direction?: AxisOrientation;
|
|
25
|
+
scale?: ScaleContinuousNumeric<number, number>; // context figure scale
|
|
26
|
+
// x: number, // optional?
|
|
27
|
+
// y: number,
|
|
28
|
+
// children?: React.ReactNode,
|
|
29
|
+
attrs: Attrs;
|
|
30
|
+
type?: "Polar" | "Rectangular"; //yuck
|
|
31
|
+
bars?: AxisBarOptions;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type DefaultAxisPropType = {
|
|
35
|
+
offsetBy: number;
|
|
36
|
+
scaleBy: number;
|
|
37
|
+
reverse: boolean;
|
|
38
|
+
gap: number;
|
|
39
|
+
title: { text: string; padding: number; style: Attrs };
|
|
40
|
+
ticks: {
|
|
41
|
+
number: number;
|
|
42
|
+
format: (n: number) => string;
|
|
43
|
+
padding: number;
|
|
44
|
+
style: Attrs;
|
|
45
|
+
length: number;
|
|
46
|
+
};
|
|
47
|
+
direction: AxisOrientation;
|
|
48
|
+
type: "Polar" | "Rectangular";
|
|
49
|
+
attrs: Record<string, number | string>;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const defaultAxisProps: DefaultAxisPropType = {
|
|
53
|
+
offsetBy: 0,
|
|
54
|
+
scaleBy: 1,
|
|
55
|
+
reverse: false,
|
|
56
|
+
gap: 5,
|
|
57
|
+
title: { text: "", padding: 40, style: {} },
|
|
58
|
+
ticks: {
|
|
59
|
+
number: 5,
|
|
60
|
+
format: format(".1f"),
|
|
61
|
+
padding: 20,
|
|
62
|
+
style: {},
|
|
63
|
+
length: 6,
|
|
64
|
+
},
|
|
65
|
+
direction: "horizontal",
|
|
66
|
+
attrs: { strokeWidth: 1 },
|
|
67
|
+
type: "Rectangular",
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export interface WorkingTipOptions extends AxisTicksOptions {
|
|
71
|
+
number: number;
|
|
72
|
+
format: (n: number) => string;
|
|
73
|
+
padding: number;
|
|
74
|
+
style: Attrs;
|
|
75
|
+
length: number;
|
|
76
|
+
values?: number[];
|
|
77
|
+
}
|
|
78
|
+
export interface WorkingAxisProps extends AxisProps {
|
|
79
|
+
offsetBy: number;
|
|
80
|
+
scaleBy: number;
|
|
81
|
+
reverse: boolean;
|
|
82
|
+
gap: number;
|
|
83
|
+
title: { text: string; padding: number; style: Attrs };
|
|
84
|
+
ticks: {
|
|
85
|
+
number: number;
|
|
86
|
+
format: (n: number) => string;
|
|
87
|
+
padding: number;
|
|
88
|
+
style: Attrs;
|
|
89
|
+
length: number;
|
|
90
|
+
values?: number[];
|
|
91
|
+
};
|
|
92
|
+
direction: AxisOrientation;
|
|
93
|
+
strokeWidth: number;
|
|
94
|
+
type: "Polar" | "Rectangular";
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export const defaultAxisBarsProps = {
|
|
98
|
+
evenFill: "#EDEDED",
|
|
99
|
+
oddFill: "none",
|
|
100
|
+
attrs: {
|
|
101
|
+
rx: 2,
|
|
102
|
+
ry: 2,
|
|
103
|
+
},
|
|
104
|
+
lift: 5,
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export interface AxisBarsProps {
|
|
108
|
+
evenFill?: string;
|
|
109
|
+
oddFill?: string;
|
|
110
|
+
attrs?: Attrs;
|
|
111
|
+
lift?: number;
|
|
112
|
+
type?: "Rectangular" | "Polar"; // type and layoutClass?
|
|
113
|
+
tickValues: number[];
|
|
114
|
+
scale: ScaleContinuousNumeric<number, number>;
|
|
115
|
+
// figureScale:scaleType, // context
|
|
116
|
+
axisY: number;
|
|
117
|
+
// layoutClass:layoutClass //context
|
|
118
|
+
}
|
|
119
|
+
//these are added by the axis
|
|
120
|
+
export type AxisBarOptions = Omit<
|
|
121
|
+
AxisBarsProps,
|
|
122
|
+
"axisY" | "type" | "tickValues" | "scale" | "figureScale" | "layoutClass"
|
|
123
|
+
>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import PolarAxis from "./polar-axis";
|
|
2
|
+
import RectangularAxis from "./rectangular-axis";
|
|
3
|
+
import type { AxisProps } from "./axis-types";
|
|
4
|
+
import { layoutClass } from "../../../layouts";
|
|
5
|
+
import { useContext } from "react";
|
|
6
|
+
import { DimensionContext } from "../../../context/dimension-context";
|
|
7
|
+
|
|
8
|
+
//TODO do things to scale and allow date as origin not maxD.
|
|
9
|
+
|
|
10
|
+
export default function Axis(props: AxisProps) {
|
|
11
|
+
const dimensions = useContext(DimensionContext);
|
|
12
|
+
const { layoutClass: layoutType } = dimensions;
|
|
13
|
+
if (layoutType === layoutClass.Polar) {
|
|
14
|
+
return <PolarAxis {...props} />;
|
|
15
|
+
} else if (layoutType === layoutClass.Rectangular) {
|
|
16
|
+
return <RectangularAxis {...props} />;
|
|
17
|
+
} else {
|
|
18
|
+
console.warn(`Axis not supported for ${layoutType}`);
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { AxisBarsProps } from "./axis-types";
|
|
2
|
+
import { defaultAxisBarsProps } from "./axis-types";
|
|
3
|
+
import type { PolarScaleType } from "../../../store/polar-scale";
|
|
4
|
+
import { BasePath } from "../../baubles";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* This component adds vertical bars to the backgound of a figure. It is used a child of an Axis component and gets
|
|
8
|
+
* it's size and position attributes from it's parent.
|
|
9
|
+
* @param props
|
|
10
|
+
* @return {*}
|
|
11
|
+
* @constructor
|
|
12
|
+
*/
|
|
13
|
+
// we are already rotated by the axis parent
|
|
14
|
+
export default function PolarAxisBars(props: AxisBarsProps) {
|
|
15
|
+
const {
|
|
16
|
+
attrs,
|
|
17
|
+
evenFill = defaultAxisBarsProps.evenFill,
|
|
18
|
+
oddFill = defaultAxisBarsProps.oddFill,
|
|
19
|
+
tickValues,
|
|
20
|
+
scale,
|
|
21
|
+
figureScale,
|
|
22
|
+
axisY,
|
|
23
|
+
} = props as { figureScale: PolarScaleType } & AxisBarsProps;
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<g className={"axisBars"}>
|
|
27
|
+
{tickValues
|
|
28
|
+
.filter((_t: number, i: number, all: number[]) => i < all.length - 1)
|
|
29
|
+
.map((t: number, i: number) => {
|
|
30
|
+
const start = figureScale({ x: scale(t), y: axisY });
|
|
31
|
+
const end = figureScale({ x: scale(t), y: 0 });
|
|
32
|
+
|
|
33
|
+
const secondStart = figureScale({
|
|
34
|
+
x: scale(tickValues[i + 1]),
|
|
35
|
+
y: 0,
|
|
36
|
+
});
|
|
37
|
+
const secondEnd = figureScale({
|
|
38
|
+
x: scale(tickValues[i + 1]),
|
|
39
|
+
y: axisY,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const arcBit =
|
|
43
|
+
start.theta === end.theta || start.r === 0
|
|
44
|
+
? ""
|
|
45
|
+
: `A${start.r},${start.r} 0 1 0 ${end.x},${end.y}`;
|
|
46
|
+
|
|
47
|
+
const secondArcBit =
|
|
48
|
+
secondStart.theta === secondEnd.theta || secondStart.r === 0
|
|
49
|
+
? ""
|
|
50
|
+
: `A${secondStart.r},${secondStart.r} 0 1 1 ${secondEnd.x},${secondEnd.y}`;
|
|
51
|
+
|
|
52
|
+
const shape = `M${start.x},${start.y} ${arcBit} L${end.x},${end.y} L${secondStart.x},${secondStart.y} ${secondArcBit} L ${start.x} ${start.y} Z`;
|
|
53
|
+
const fill = i % 2 === 0 ? evenFill : oddFill;
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<BasePath
|
|
57
|
+
key={i}
|
|
58
|
+
d={shape}
|
|
59
|
+
fill={fill}
|
|
60
|
+
{...attrs}
|
|
61
|
+
animated={false}
|
|
62
|
+
/>
|
|
63
|
+
);
|
|
64
|
+
})}
|
|
65
|
+
</g>
|
|
66
|
+
);
|
|
67
|
+
// const {
|
|
68
|
+
// attrs,
|
|
69
|
+
// evenFill=defaultAxisBarsProps.evenFill,
|
|
70
|
+
// oddFill=defaultAxisBarsProps.oddFill,
|
|
71
|
+
// lift=defaultAxisBarsProps.lift} = props;
|
|
72
|
+
|
|
73
|
+
// const {tickValues,scale,gap,direction} = useAxisContext();
|
|
74
|
+
// const {theta} = useLayout();
|
|
75
|
+
// //d3 starts with 0 at 12 o'clock and svg starts with 0 at 3 o'clock
|
|
76
|
+
|
|
77
|
+
// const angleRange = theta![0]>theta![1]+0.1?2*Math.PI-(theta![0]-(theta![1]+0.1)):(theta![1]+0.1)-theta![0];
|
|
78
|
+
|
|
79
|
+
// const startAngle = theta![0]+Math.PI/2 ;
|
|
80
|
+
// const endAngle = angleRange+startAngle;
|
|
81
|
+
|
|
82
|
+
// return(
|
|
83
|
+
// <g className={"axisBars"}>
|
|
84
|
+
// {tickValues.reduce((acc:JSX.Element[],curr,i)=>{
|
|
85
|
+
|
|
86
|
+
// const shape = arc(
|
|
87
|
+
// {
|
|
88
|
+
// innerRadius:scale(tickValues[i]),
|
|
89
|
+
// outerRadius:scale(tickValues[i+1]),
|
|
90
|
+
// startAngle: startAngle,
|
|
91
|
+
// endAngle:endAngle
|
|
92
|
+
// }
|
|
93
|
+
// )!
|
|
94
|
+
|
|
95
|
+
// const fill = i%2===0?evenFill:oddFill;
|
|
96
|
+
// acc.push(<path key={i} d={shape} fill={fill} {...attrs} />);
|
|
97
|
+
|
|
98
|
+
// return acc;
|
|
99
|
+
// },[])}
|
|
100
|
+
// </g>
|
|
101
|
+
// )
|
|
102
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { useContext } from "react";
|
|
2
|
+
|
|
3
|
+
import { mean } from "d3-array";
|
|
4
|
+
import { scaleLinear } from "d3-scale";
|
|
5
|
+
import type { AxisProps, WorkingTipOptions } from "./axis-types";
|
|
6
|
+
import { defaultAxisProps } from "./axis-types";
|
|
7
|
+
|
|
8
|
+
import { normalizeAngle } from "../../../store/polar-scale";
|
|
9
|
+
import type { dimensionType } from "../../figtree/figtree-types";
|
|
10
|
+
import { unNullify } from "../../../utils";
|
|
11
|
+
import { DimensionContext } from "../../../context/dimension-context";
|
|
12
|
+
import { ScaleContext } from "../../../context/scale-context";
|
|
13
|
+
import PolarAxisBars from "./polar-axis-bars";
|
|
14
|
+
import type { PolarVertex } from "../../../layouts/types";
|
|
15
|
+
|
|
16
|
+
//TODO do things to scale and allow date as origin not maxD.
|
|
17
|
+
|
|
18
|
+
export default function PolarAxis(props: AxisProps) {
|
|
19
|
+
const dimensions = useContext(DimensionContext);
|
|
20
|
+
const figureScale = useContext(ScaleContext);
|
|
21
|
+
const { bars, attrs } = props;
|
|
22
|
+
|
|
23
|
+
const ticks: WorkingTipOptions = props.ticks
|
|
24
|
+
? { ...defaultAxisProps.ticks, ...props.ticks }
|
|
25
|
+
: defaultAxisProps.ticks;
|
|
26
|
+
const title = props.title
|
|
27
|
+
? { ...defaultAxisProps.title, ...props.title }
|
|
28
|
+
: defaultAxisProps.title;
|
|
29
|
+
|
|
30
|
+
// todo options to provide tick values so can specify breaks
|
|
31
|
+
// we make the scale and then move it to the origin.
|
|
32
|
+
const scale = makeAxisScale(props, dimensions);
|
|
33
|
+
|
|
34
|
+
let tickValues: number[];
|
|
35
|
+
if (ticks.values != undefined) {
|
|
36
|
+
tickValues = ticks.values;
|
|
37
|
+
} else {
|
|
38
|
+
// if (!scale.ticks) {
|
|
39
|
+
// tickValues = range(ticks.number).map((i) =>
|
|
40
|
+
// quantile(scale.domain(), i / (ticks.number - 1)),
|
|
41
|
+
// ) as number[]
|
|
42
|
+
// } else {
|
|
43
|
+
tickValues = scale.ticks(ticks.number);
|
|
44
|
+
// }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// start at the root and go outwards
|
|
48
|
+
|
|
49
|
+
const theta = normalizeAngle(
|
|
50
|
+
figureScale({
|
|
51
|
+
x: dimensions.domainX[1],
|
|
52
|
+
y: dimensions.domainY[1],
|
|
53
|
+
} as PolarVertex).theta,
|
|
54
|
+
);
|
|
55
|
+
// const startAngle = figureScale({x:dimensions.domainX[1],y:dimensions.domainY[0]}).theta + Math.PI/2 ;
|
|
56
|
+
// const endAngle = startAngle + 0.05+ (figureScale({x:dimensions.domainX[1],y:dimensions.domainY[1]}).theta - figureScale({x:dimensions.domainX[1],y:dimensions.domainY[0]}).theta);
|
|
57
|
+
|
|
58
|
+
const axisY = dimensions.domainY[1] + dimensions.domainY[1] * 0.005;
|
|
59
|
+
const start = figureScale({ x: dimensions.domainX[0], y: axisY });
|
|
60
|
+
const end = figureScale({ x: dimensions.domainX[1], y: axisY });
|
|
61
|
+
const axisPath = `M${start.x},${start.y} L${end.x},${end.y}`;
|
|
62
|
+
|
|
63
|
+
// We draw the ticks in line with the axis then rotate them 90 degrees
|
|
64
|
+
const x2 = ticks.length * Math.cos(theta);
|
|
65
|
+
const y2 = ticks.length * Math.sin(theta);
|
|
66
|
+
|
|
67
|
+
const xPadding = ticks.padding * Math.cos(theta);
|
|
68
|
+
const yPadding = ticks.padding * Math.sin(theta);
|
|
69
|
+
|
|
70
|
+
// const rawBars = props.children
|
|
71
|
+
// ? Array.isArray(props.children)
|
|
72
|
+
// ? props.children
|
|
73
|
+
// : [props.children]
|
|
74
|
+
// : null
|
|
75
|
+
// const bars = rawBars
|
|
76
|
+
// ? rawBars.map((b: React.ReactElement,i:number) =>
|
|
77
|
+
// React.cloneElement(b, {
|
|
78
|
+
// key:i,
|
|
79
|
+
// figureScale,
|
|
80
|
+
// scale,
|
|
81
|
+
// axisY,
|
|
82
|
+
// direction,
|
|
83
|
+
// layoutClass,
|
|
84
|
+
// dimensions,
|
|
85
|
+
// tickValues,
|
|
86
|
+
// gap,
|
|
87
|
+
// reverse:props.reverse,
|
|
88
|
+
// startAngle,
|
|
89
|
+
// endAngle
|
|
90
|
+
// }),
|
|
91
|
+
// )
|
|
92
|
+
// : null
|
|
93
|
+
|
|
94
|
+
const xPos = unNullify(
|
|
95
|
+
mean(scale.range()),
|
|
96
|
+
`Error calculating x position for title`,
|
|
97
|
+
);
|
|
98
|
+
const titlePos = figureScale({ x: xPos, y: axisY });
|
|
99
|
+
const titleXPadding = title.padding * Math.cos(theta);
|
|
100
|
+
const titleYPadding = title.padding * Math.sin(theta);
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<g className={"axis"}>
|
|
104
|
+
<PolarAxisBars
|
|
105
|
+
{...bars}
|
|
106
|
+
tickValues={tickValues}
|
|
107
|
+
scale={scale}
|
|
108
|
+
axisY={axisY}
|
|
109
|
+
/>
|
|
110
|
+
:
|
|
111
|
+
<path d={axisPath} stroke={"black"} {...attrs} />
|
|
112
|
+
<g>
|
|
113
|
+
{tickValues.map((t, i) => {
|
|
114
|
+
const point = figureScale({ x: scale(t), y: axisY });
|
|
115
|
+
return (
|
|
116
|
+
<g
|
|
117
|
+
key={`tick-${i}`}
|
|
118
|
+
transform={`translate(${point.x},${point.y}) rotate(90)`}
|
|
119
|
+
>
|
|
120
|
+
<line x1={x2} y1={y2} x2={0} y2={0} stroke={"black"} {...attrs} />
|
|
121
|
+
<text
|
|
122
|
+
transform={`translate(${xPadding},${yPadding}) rotate(-90)`}
|
|
123
|
+
textAnchor={"middle"}
|
|
124
|
+
dominantBaseline={"central"}
|
|
125
|
+
{...ticks.style}
|
|
126
|
+
>
|
|
127
|
+
{ticks.format(t)}
|
|
128
|
+
</text>
|
|
129
|
+
</g>
|
|
130
|
+
);
|
|
131
|
+
})}
|
|
132
|
+
{/*TODO sometimes scale doesn't have a range*/}
|
|
133
|
+
<g transform={`translate(${titlePos.x},${titlePos.y}) rotate(90)`}>
|
|
134
|
+
<text
|
|
135
|
+
textAnchor={"middle"}
|
|
136
|
+
transform={`translate(${titleXPadding},${titleYPadding}) rotate(-90)`}
|
|
137
|
+
>
|
|
138
|
+
{title.text}
|
|
139
|
+
</text>
|
|
140
|
+
</g>
|
|
141
|
+
</g>
|
|
142
|
+
</g>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
//TODO can make maxR and height the same parameter and use this all axes
|
|
147
|
+
|
|
148
|
+
export function makeAxisScale(props: AxisProps, dimensions: dimensionType) {
|
|
149
|
+
const {
|
|
150
|
+
reverse = defaultAxisProps.reverse,
|
|
151
|
+
offsetBy = defaultAxisProps.offsetBy,
|
|
152
|
+
scaleBy = defaultAxisProps.scaleBy,
|
|
153
|
+
scale,
|
|
154
|
+
} = props;
|
|
155
|
+
const { domainX } = dimensions;
|
|
156
|
+
|
|
157
|
+
// just radius
|
|
158
|
+
// negative range to play nicely with transform above
|
|
159
|
+
const axisScale =
|
|
160
|
+
scale === undefined
|
|
161
|
+
? scaleLinear().domain(domainX).range(domainX)
|
|
162
|
+
: scale.copy();
|
|
163
|
+
if (scale === undefined) {
|
|
164
|
+
// assume domain goes 0 to max divergence make adjustments on this scale and then update min if it is not 0
|
|
165
|
+
const offset = domainX.map((d) => d + offsetBy);
|
|
166
|
+
const newDomain = offset.map((d) => (d - offsetBy) * scaleBy + offsetBy);
|
|
167
|
+
|
|
168
|
+
axisScale.domain(newDomain);
|
|
169
|
+
|
|
170
|
+
if (reverse) {
|
|
171
|
+
axisScale.domain([offsetBy - (newDomain[1] - newDomain[0]), offsetBy]);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return axisScale.nice();
|
|
175
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { useContext } from "react";
|
|
2
|
+
import { ScaleContext } from "../../../context/scale-context";
|
|
3
|
+
import type { AxisBarsProps } from "./axis-types";
|
|
4
|
+
import { defaultAxisBarsProps } from "./axis-types";
|
|
5
|
+
import { BaseRectangle } from "../../baubles";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* This component adds vertical bars to the backgound of a figure. It is used a child of an Axis component and gets
|
|
9
|
+
* it's size and position attributes from it's parent.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export default function AxisBars(props: AxisBarsProps) {
|
|
13
|
+
const {
|
|
14
|
+
attrs,
|
|
15
|
+
evenFill = defaultAxisBarsProps.evenFill,
|
|
16
|
+
oddFill = defaultAxisBarsProps.oddFill,
|
|
17
|
+
tickValues,
|
|
18
|
+
scale,
|
|
19
|
+
axisY,
|
|
20
|
+
} = props;
|
|
21
|
+
|
|
22
|
+
const figureScale = useContext(ScaleContext);
|
|
23
|
+
|
|
24
|
+
return (
|
|
25
|
+
<g className={"axisBars"} key="axisBars">
|
|
26
|
+
{tickValues
|
|
27
|
+
.filter((_t: number, i: number, all: number[]) => i < all.length - 1)
|
|
28
|
+
.map((t: number, i: number) => {
|
|
29
|
+
const start = figureScale({ x: scale(t), y: axisY });
|
|
30
|
+
const end = figureScale({ x: scale(t), y: -0.05 });
|
|
31
|
+
|
|
32
|
+
const secondStart = figureScale({
|
|
33
|
+
x: scale(tickValues[i + 1]),
|
|
34
|
+
y: 0,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const fill = i % 2 === 0 ? evenFill : oddFill;
|
|
38
|
+
return (
|
|
39
|
+
<BaseRectangle
|
|
40
|
+
key={`recBar-${i}`}
|
|
41
|
+
x={start.x}
|
|
42
|
+
width={secondStart.x - start.x} // to deal with negative scales
|
|
43
|
+
y={end.y}
|
|
44
|
+
height={start.y - end.y}
|
|
45
|
+
fill={fill}
|
|
46
|
+
{...{ rx: 2, ry: 2, ...attrs }}
|
|
47
|
+
animated={false} // animated can come from context in the shape not needed here
|
|
48
|
+
/>
|
|
49
|
+
);
|
|
50
|
+
}, [])}
|
|
51
|
+
</g>
|
|
52
|
+
);
|
|
53
|
+
}
|