@obitrain/charts 0.10.4 → 0.12.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/README.md +6 -4
- package/lib/commonjs/Charts/BottomAxis/AxisLine.js +1 -1
- package/lib/commonjs/Charts/BottomAxis/BottomAxis.js +54 -14
- package/lib/commonjs/Charts/BottomAxis/BottomAxis.js.map +1 -1
- package/lib/commonjs/Charts/BottomAxis/Tick.js +29 -13
- package/lib/commonjs/Charts/BottomAxis/Tick.js.map +1 -1
- package/lib/commonjs/Charts/BottomAxis/index.js.map +1 -1
- package/lib/commonjs/Charts/Cursor.js +33 -13
- package/lib/commonjs/Charts/Cursor.js.map +1 -1
- package/lib/commonjs/Charts/Dot.js +51 -0
- package/lib/commonjs/Charts/Dot.js.map +1 -0
- package/lib/commonjs/Charts/Dots.js +74 -0
- package/lib/commonjs/Charts/Dots.js.map +1 -0
- package/lib/commonjs/Charts/Linechart/index.js +1 -1
- package/lib/commonjs/Charts/YAxis.js +77 -0
- package/lib/commonjs/Charts/YAxis.js.map +1 -0
- package/lib/commonjs/Charts/ZoomableLinechart/ScalablePath.js +5 -4
- package/lib/commonjs/Charts/ZoomableLinechart/ScalablePath.js.map +1 -1
- package/lib/commonjs/Charts/ZoomableLinechart/index.js +1 -1
- package/lib/commonjs/Charts/gesture.js +90 -38
- package/lib/commonjs/Charts/gesture.js.map +1 -1
- package/lib/commonjs/Charts/graphUtils.js +69 -16
- package/lib/commonjs/Charts/graphUtils.js.map +1 -1
- package/lib/commonjs/Charts/index.js +39 -0
- package/lib/commonjs/Charts/index.js.map +1 -1
- package/lib/commonjs/Charts/maths.js +107 -16
- package/lib/commonjs/Charts/maths.js.map +1 -1
- package/lib/module/Charts/BottomAxis/AxisLine.js +1 -1
- package/lib/module/Charts/BottomAxis/AxisLine.js.map +1 -1
- package/lib/module/Charts/BottomAxis/BottomAxis.js +56 -15
- package/lib/module/Charts/BottomAxis/BottomAxis.js.map +1 -1
- package/lib/module/Charts/BottomAxis/Tick.js +29 -11
- package/lib/module/Charts/BottomAxis/Tick.js.map +1 -1
- package/lib/module/Charts/BottomAxis/index.js.map +1 -1
- package/lib/module/Charts/Cursor.js +35 -15
- package/lib/module/Charts/Cursor.js.map +1 -1
- package/lib/module/Charts/Dot.js +47 -0
- package/lib/module/Charts/Dot.js.map +1 -0
- package/lib/module/Charts/Dots.js +70 -0
- package/lib/module/Charts/Dots.js.map +1 -0
- package/lib/module/Charts/Linechart/index.js +1 -1
- package/lib/module/Charts/Linechart/index.js.map +1 -1
- package/lib/module/Charts/YAxis.js +73 -0
- package/lib/module/Charts/YAxis.js.map +1 -0
- package/lib/module/Charts/ZoomableLinechart/ScalablePath.js +5 -4
- package/lib/module/Charts/ZoomableLinechart/ScalablePath.js.map +1 -1
- package/lib/module/Charts/ZoomableLinechart/index.js +1 -1
- package/lib/module/Charts/ZoomableLinechart/index.js.map +1 -1
- package/lib/module/Charts/gesture.js +90 -39
- package/lib/module/Charts/gesture.js.map +1 -1
- package/lib/module/Charts/graphUtils.js +71 -18
- package/lib/module/Charts/graphUtils.js.map +1 -1
- package/lib/module/Charts/index.js +5 -2
- package/lib/module/Charts/index.js.map +1 -1
- package/lib/module/Charts/maths.js +101 -14
- package/lib/module/Charts/maths.js.map +1 -1
- package/lib/typescript/commonjs/src/Charts/BottomAxis/AxisLine.d.ts +1 -1
- package/lib/typescript/commonjs/src/Charts/BottomAxis/AxisLine.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/Charts/BottomAxis/BottomAxis.d.ts +26 -3
- package/lib/typescript/commonjs/src/Charts/BottomAxis/BottomAxis.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/Charts/BottomAxis/Tick.d.ts +16 -3
- package/lib/typescript/commonjs/src/Charts/BottomAxis/Tick.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/Charts/BottomAxis/index.d.ts +1 -1
- package/lib/typescript/commonjs/src/Charts/BottomAxis/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/Charts/Cursor.d.ts +5 -2
- package/lib/typescript/commonjs/src/Charts/Cursor.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/Charts/Dot.d.ts +22 -0
- package/lib/typescript/commonjs/src/Charts/Dot.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/Charts/Dots.d.ts +28 -0
- package/lib/typescript/commonjs/src/Charts/Dots.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/Charts/Linechart/index.d.ts +1 -1
- package/lib/typescript/commonjs/src/Charts/Linechart/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/Charts/YAxis.d.ts +21 -0
- package/lib/typescript/commonjs/src/Charts/YAxis.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/Charts/ZoomableLinechart/ScalablePath.d.ts +2 -2
- package/lib/typescript/commonjs/src/Charts/ZoomableLinechart/ScalablePath.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/Charts/ZoomableLinechart/index.d.ts +1 -1
- package/lib/typescript/commonjs/src/Charts/ZoomableLinechart/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/Charts/gesture.d.ts +6 -0
- package/lib/typescript/commonjs/src/Charts/gesture.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/Charts/graphUtils.d.ts +1 -1
- package/lib/typescript/commonjs/src/Charts/graphUtils.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/Charts/index.d.ts +9 -3
- package/lib/typescript/commonjs/src/Charts/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/Charts/maths.d.ts +15 -1
- package/lib/typescript/commonjs/src/Charts/maths.d.ts.map +1 -1
- package/lib/typescript/module/src/Charts/BottomAxis/AxisLine.d.ts +1 -1
- package/lib/typescript/module/src/Charts/BottomAxis/AxisLine.d.ts.map +1 -1
- package/lib/typescript/module/src/Charts/BottomAxis/BottomAxis.d.ts +26 -3
- package/lib/typescript/module/src/Charts/BottomAxis/BottomAxis.d.ts.map +1 -1
- package/lib/typescript/module/src/Charts/BottomAxis/Tick.d.ts +16 -3
- package/lib/typescript/module/src/Charts/BottomAxis/Tick.d.ts.map +1 -1
- package/lib/typescript/module/src/Charts/BottomAxis/index.d.ts +1 -1
- package/lib/typescript/module/src/Charts/BottomAxis/index.d.ts.map +1 -1
- package/lib/typescript/module/src/Charts/Cursor.d.ts +5 -2
- package/lib/typescript/module/src/Charts/Cursor.d.ts.map +1 -1
- package/lib/typescript/module/src/Charts/Dot.d.ts +22 -0
- package/lib/typescript/module/src/Charts/Dot.d.ts.map +1 -0
- package/lib/typescript/module/src/Charts/Dots.d.ts +28 -0
- package/lib/typescript/module/src/Charts/Dots.d.ts.map +1 -0
- package/lib/typescript/module/src/Charts/Linechart/index.d.ts +1 -1
- package/lib/typescript/module/src/Charts/Linechart/index.d.ts.map +1 -1
- package/lib/typescript/module/src/Charts/YAxis.d.ts +21 -0
- package/lib/typescript/module/src/Charts/YAxis.d.ts.map +1 -0
- package/lib/typescript/module/src/Charts/ZoomableLinechart/ScalablePath.d.ts +2 -2
- package/lib/typescript/module/src/Charts/ZoomableLinechart/ScalablePath.d.ts.map +1 -1
- package/lib/typescript/module/src/Charts/ZoomableLinechart/index.d.ts +1 -1
- package/lib/typescript/module/src/Charts/ZoomableLinechart/index.d.ts.map +1 -1
- package/lib/typescript/module/src/Charts/gesture.d.ts +6 -0
- package/lib/typescript/module/src/Charts/gesture.d.ts.map +1 -1
- package/lib/typescript/module/src/Charts/graphUtils.d.ts +1 -1
- package/lib/typescript/module/src/Charts/graphUtils.d.ts.map +1 -1
- package/lib/typescript/module/src/Charts/index.d.ts +9 -3
- package/lib/typescript/module/src/Charts/index.d.ts.map +1 -1
- package/lib/typescript/module/src/Charts/maths.d.ts +15 -1
- package/lib/typescript/module/src/Charts/maths.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/Charts/BottomAxis/AxisLine.tsx +1 -1
- package/src/Charts/BottomAxis/BottomAxis.tsx +100 -19
- package/src/Charts/BottomAxis/Tick.tsx +61 -15
- package/src/Charts/BottomAxis/index.tsx +5 -1
- package/src/Charts/Cursor.tsx +38 -10
- package/src/Charts/Dot.tsx +64 -0
- package/src/Charts/Dots.tsx +96 -0
- package/src/Charts/Linechart/index.tsx +1 -1
- package/src/Charts/YAxis.tsx +103 -0
- package/src/Charts/ZoomableLinechart/ScalablePath.tsx +8 -9
- package/src/Charts/ZoomableLinechart/index.tsx +1 -1
- package/src/Charts/gesture.ts +132 -71
- package/src/Charts/graphUtils.ts +78 -16
- package/src/Charts/index.tsx +9 -0
- package/src/Charts/maths.ts +107 -16
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
|
+
DashPathEffect,
|
|
2
3
|
Group,
|
|
3
4
|
Line,
|
|
4
5
|
Text,
|
|
5
6
|
vec,
|
|
7
|
+
type Color,
|
|
6
8
|
type SkFont,
|
|
7
|
-
} from '
|
|
8
|
-
import
|
|
9
|
+
} from 'react-native-skia';
|
|
10
|
+
import { memo, useMemo } from 'react';
|
|
9
11
|
import { useDerivedValue, type SharedValue } from 'react-native-reanimated';
|
|
10
12
|
import { getPositionWl } from '../gesture';
|
|
11
13
|
|
|
@@ -17,10 +19,37 @@ export type TickProps = {
|
|
|
17
19
|
offsetX: SharedValue<number>;
|
|
18
20
|
offsetY?: number;
|
|
19
21
|
font: SkFont;
|
|
22
|
+
color?: Color;
|
|
23
|
+
labelColor?: Color;
|
|
24
|
+
/** Length of the tick line. Use a negative value (e.g. -chartHeight) to draw a vertical gridline above the axis. */
|
|
25
|
+
tickLength?: number;
|
|
26
|
+
strokeWidth?: number;
|
|
27
|
+
/** Dash/gap lengths for a dashed tick line. */
|
|
28
|
+
dash?: [number, number];
|
|
29
|
+
showLabel?: boolean;
|
|
30
|
+
/** `center` puts the label under the tick, `left` starts it right after the tick. */
|
|
31
|
+
labelAlign?: 'center' | 'left';
|
|
32
|
+
/** Gap between the tick and a left-aligned label. */
|
|
33
|
+
labelGap?: number;
|
|
20
34
|
};
|
|
21
35
|
|
|
22
|
-
|
|
23
|
-
const {
|
|
36
|
+
const TickComponent = function (props: TickProps) {
|
|
37
|
+
const {
|
|
38
|
+
label,
|
|
39
|
+
scale,
|
|
40
|
+
focalX,
|
|
41
|
+
offsetX,
|
|
42
|
+
initPosition,
|
|
43
|
+
font,
|
|
44
|
+
color = 'black',
|
|
45
|
+
labelColor,
|
|
46
|
+
tickLength = 10,
|
|
47
|
+
strokeWidth,
|
|
48
|
+
dash,
|
|
49
|
+
showLabel = true,
|
|
50
|
+
labelAlign = 'center',
|
|
51
|
+
labelGap = 6,
|
|
52
|
+
} = props;
|
|
24
53
|
const offsetY = props.offsetY ?? 0;
|
|
25
54
|
const transform = useDerivedValue(() => [
|
|
26
55
|
{
|
|
@@ -32,20 +61,37 @@ export const Tick: React.FC<TickProps> = (props) => {
|
|
|
32
61
|
),
|
|
33
62
|
},
|
|
34
63
|
]);
|
|
35
|
-
const width =
|
|
36
|
-
|
|
37
|
-
|
|
64
|
+
const width = useMemo(
|
|
65
|
+
() =>
|
|
66
|
+
font.getGlyphWidths(font.getGlyphIDs(label)).reduce((a, b) => a + b, 0),
|
|
67
|
+
[font, label]
|
|
68
|
+
);
|
|
69
|
+
const labelX = labelAlign === 'left' ? labelGap : -width / 2;
|
|
38
70
|
|
|
39
71
|
return (
|
|
40
72
|
<Group transform={transform}>
|
|
41
|
-
<Line
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
73
|
+
<Line
|
|
74
|
+
color={color}
|
|
75
|
+
strokeWidth={strokeWidth}
|
|
76
|
+
p1={vec(0, offsetY)}
|
|
77
|
+
p2={vec(0, offsetY + tickLength)}
|
|
78
|
+
>
|
|
79
|
+
{dash !== undefined ? <DashPathEffect intervals={dash} /> : null}
|
|
80
|
+
</Line>
|
|
81
|
+
{showLabel ? (
|
|
82
|
+
<Text
|
|
83
|
+
text={label}
|
|
84
|
+
color={labelColor ?? color}
|
|
85
|
+
x={labelX}
|
|
86
|
+
y={offsetY + 23}
|
|
87
|
+
font={font}
|
|
88
|
+
/>
|
|
89
|
+
) : null}
|
|
49
90
|
</Group>
|
|
50
91
|
);
|
|
51
92
|
};
|
|
93
|
+
|
|
94
|
+
// Memoized: zoom/pan flows through shared values, so a Tick only needs to
|
|
95
|
+
// re-render when its label or base position changes
|
|
96
|
+
export const Tick = memo(TickComponent);
|
|
97
|
+
Tick.displayName = 'Tick';
|
package/src/Charts/Cursor.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { Circle, type PathCommand } from '
|
|
2
|
-
import type
|
|
1
|
+
import { Circle, type PathCommand } from 'react-native-skia';
|
|
2
|
+
import { useEffect, type FC } from 'react';
|
|
3
3
|
import { useDerivedValue, type SharedValue } from 'react-native-reanimated';
|
|
4
|
-
import {
|
|
4
|
+
import { commandsToBezier, getYForXOnBeziers } from './maths';
|
|
5
5
|
|
|
6
6
|
const CURSOR_SIZE = 10;
|
|
7
7
|
|
|
@@ -10,6 +10,9 @@ export type CursorProps = {
|
|
|
10
10
|
positionX: SharedValue<number>;
|
|
11
11
|
size?: number;
|
|
12
12
|
color?: string;
|
|
13
|
+
/** When set, draws a ring of this color around the cursor. */
|
|
14
|
+
strokeColor?: string;
|
|
15
|
+
strokeWidth?: number;
|
|
13
16
|
currentValue?: SharedValue<number> | SharedValue<number | undefined>;
|
|
14
17
|
translateY?: SharedValue<number | undefined>;
|
|
15
18
|
};
|
|
@@ -18,20 +21,30 @@ const Cursor: FC<CursorProps> = function ({
|
|
|
18
21
|
commands,
|
|
19
22
|
positionX,
|
|
20
23
|
color,
|
|
24
|
+
strokeColor,
|
|
25
|
+
strokeWidth = 2,
|
|
21
26
|
currentValue,
|
|
22
27
|
translateY,
|
|
23
28
|
size = CURSOR_SIZE,
|
|
24
29
|
}) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
30
|
+
const conflictingProps = currentValue !== undefined && translateY !== undefined;
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (conflictingProps)
|
|
33
|
+
console.warn('currentValue has no effect when translateY is set');
|
|
34
|
+
}, [conflictingProps]);
|
|
28
35
|
|
|
29
|
-
|
|
36
|
+
// Normalized once per path change, not on every cursor move
|
|
37
|
+
const beziers = useDerivedValue(() => {
|
|
30
38
|
const _commands = commands?.value;
|
|
31
|
-
|
|
39
|
+
return _commands === undefined ? undefined : commandsToBezier(_commands);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const derivedTranslateY = useDerivedValue(() => {
|
|
43
|
+
const _beziers = beziers.value;
|
|
44
|
+
if (_beziers === undefined) {
|
|
32
45
|
return 0;
|
|
33
46
|
}
|
|
34
|
-
const _value =
|
|
47
|
+
const _value = getYForXOnBeziers(_beziers, positionX.value) ?? 0;
|
|
35
48
|
if (translateY === undefined && currentValue !== undefined) {
|
|
36
49
|
currentValue.value = _value;
|
|
37
50
|
}
|
|
@@ -44,7 +57,22 @@ const Cursor: FC<CursorProps> = function ({
|
|
|
44
57
|
{ translateX: positionX.value },
|
|
45
58
|
{ translateY: _translateY.value ?? 0 },
|
|
46
59
|
]);
|
|
47
|
-
return
|
|
60
|
+
return (
|
|
61
|
+
<>
|
|
62
|
+
<Circle transform={transform} cx={0} cy={0} r={size} color={color} />
|
|
63
|
+
{strokeColor !== undefined ? (
|
|
64
|
+
<Circle
|
|
65
|
+
transform={transform}
|
|
66
|
+
cx={0}
|
|
67
|
+
cy={0}
|
|
68
|
+
r={size}
|
|
69
|
+
color={strokeColor}
|
|
70
|
+
style="stroke"
|
|
71
|
+
strokeWidth={strokeWidth}
|
|
72
|
+
/>
|
|
73
|
+
) : null}
|
|
74
|
+
</>
|
|
75
|
+
);
|
|
48
76
|
};
|
|
49
77
|
|
|
50
78
|
export { Cursor };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Circle, Group, type Color } from 'react-native-skia';
|
|
2
|
+
import { memo } from 'react';
|
|
3
|
+
import { useDerivedValue, type SharedValue } from 'react-native-reanimated';
|
|
4
|
+
import { getPositionWl } from './gesture';
|
|
5
|
+
|
|
6
|
+
export type DotProps = {
|
|
7
|
+
x: SharedValue<number> | number;
|
|
8
|
+
y: SharedValue<number> | number;
|
|
9
|
+
opacity?: SharedValue<number> | number;
|
|
10
|
+
r?: number;
|
|
11
|
+
color?: Color;
|
|
12
|
+
/**
|
|
13
|
+
* When set, the dot is drawn as a ring: `fillColor` inside, `color` as the
|
|
14
|
+
* stroke. Use the chart background color to get a "hollow" marker.
|
|
15
|
+
*/
|
|
16
|
+
fillColor?: Color;
|
|
17
|
+
strokeWidth?: number;
|
|
18
|
+
// Optional zoom/pan awareness (see useScalableGesture)
|
|
19
|
+
scale?: SharedValue<number>;
|
|
20
|
+
focalX?: SharedValue<number>;
|
|
21
|
+
offsetX?: SharedValue<number>;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
function DotComponent(props: DotProps) {
|
|
25
|
+
const {
|
|
26
|
+
x,
|
|
27
|
+
y,
|
|
28
|
+
opacity = 1,
|
|
29
|
+
r = 4,
|
|
30
|
+
color = 'black',
|
|
31
|
+
fillColor,
|
|
32
|
+
strokeWidth = 2,
|
|
33
|
+
scale,
|
|
34
|
+
focalX,
|
|
35
|
+
offsetX,
|
|
36
|
+
} = props;
|
|
37
|
+
|
|
38
|
+
const cx = useDerivedValue(() => {
|
|
39
|
+
const _x = typeof x === 'number' ? x : x.value;
|
|
40
|
+
if (scale === undefined || focalX === undefined || offsetX === undefined)
|
|
41
|
+
return _x;
|
|
42
|
+
return getPositionWl(_x, focalX.value, scale.value, offsetX.value);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<Group opacity={opacity}>
|
|
47
|
+
<Circle cx={cx} cy={y} r={r} color={fillColor ?? color} />
|
|
48
|
+
{fillColor !== undefined ? (
|
|
49
|
+
<Circle
|
|
50
|
+
cx={cx}
|
|
51
|
+
cy={y}
|
|
52
|
+
r={r}
|
|
53
|
+
style="stroke"
|
|
54
|
+
color={color}
|
|
55
|
+
strokeWidth={strokeWidth}
|
|
56
|
+
/>
|
|
57
|
+
) : null}
|
|
58
|
+
</Group>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Memoized: charts render many dots and the position updates flow through
|
|
63
|
+
// shared values, so parent re-renders don't need to reach them
|
|
64
|
+
export const Dot = memo(DotComponent);
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { Path, Skia, type Color } from 'react-native-skia';
|
|
2
|
+
import { type FC } from 'react';
|
|
3
|
+
import { useDerivedValue, type SharedValue } from 'react-native-reanimated';
|
|
4
|
+
import { getPositionWl } from './gesture';
|
|
5
|
+
import type { AnimatedDot } from './types';
|
|
6
|
+
|
|
7
|
+
export type DotShape = 'circle' | 'diamond';
|
|
8
|
+
|
|
9
|
+
export type DotsProps = {
|
|
10
|
+
dots: AnimatedDot[];
|
|
11
|
+
r?: number;
|
|
12
|
+
shape?: DotShape;
|
|
13
|
+
color?: Color;
|
|
14
|
+
/** When set, dots are drawn as rings: `fillColor` inside, `color` as the stroke. */
|
|
15
|
+
fillColor?: Color;
|
|
16
|
+
strokeWidth?: number;
|
|
17
|
+
/** Viewport width used to cull off-screen dots. */
|
|
18
|
+
width?: number;
|
|
19
|
+
// Optional zoom/pan awareness (see useScalableGesture)
|
|
20
|
+
scale?: SharedValue<number>;
|
|
21
|
+
focalX?: SharedValue<number>;
|
|
22
|
+
offsetX?: SharedValue<number>;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Draw a whole series of dots as a single Skia path (two with `fillColor`).
|
|
27
|
+
* Unlike mapping over {@link Dot}, this runs one worklet per frame instead of
|
|
28
|
+
* one per dot, which keeps large series smooth during zoom/pan gestures.
|
|
29
|
+
* Per-dot opacity is binary here: dots below 0.5 are hidden.
|
|
30
|
+
*/
|
|
31
|
+
const Dots: FC<DotsProps> = function (props) {
|
|
32
|
+
const {
|
|
33
|
+
dots,
|
|
34
|
+
r = 4,
|
|
35
|
+
shape = 'circle',
|
|
36
|
+
color = 'black',
|
|
37
|
+
fillColor,
|
|
38
|
+
strokeWidth = 2,
|
|
39
|
+
width,
|
|
40
|
+
scale,
|
|
41
|
+
focalX,
|
|
42
|
+
offsetX,
|
|
43
|
+
} = props;
|
|
44
|
+
|
|
45
|
+
const path = useDerivedValue(() => {
|
|
46
|
+
const p = Skia.PathBuilder.Make();
|
|
47
|
+
const margin = r + strokeWidth;
|
|
48
|
+
for (const dot of dots) {
|
|
49
|
+
if (dot.opacity.value < 0.5) continue;
|
|
50
|
+
const x =
|
|
51
|
+
scale === undefined || focalX === undefined || offsetX === undefined
|
|
52
|
+
? dot.x.value
|
|
53
|
+
: getPositionWl(
|
|
54
|
+
dot.x.value,
|
|
55
|
+
focalX.value,
|
|
56
|
+
scale.value,
|
|
57
|
+
offsetX.value
|
|
58
|
+
);
|
|
59
|
+
if (width !== undefined && (x < -margin || x > width + margin)) continue;
|
|
60
|
+
const y = dot.y.value;
|
|
61
|
+
if (shape === 'diamond') {
|
|
62
|
+
// One addPoly beats four moveTo/lineTo calls: each is a JSI hop, and
|
|
63
|
+
// this worklet rebuilds every dot on every frame of a gesture
|
|
64
|
+
p.addPoly(
|
|
65
|
+
[
|
|
66
|
+
{ x, y: y - r },
|
|
67
|
+
{ x: x + r, y },
|
|
68
|
+
{ x, y: y + r },
|
|
69
|
+
{ x: x - r, y },
|
|
70
|
+
],
|
|
71
|
+
true
|
|
72
|
+
);
|
|
73
|
+
} else {
|
|
74
|
+
p.addCircle(x, y, r);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return p.build();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<>
|
|
82
|
+
<Path path={path} color={fillColor ?? color} />
|
|
83
|
+
{fillColor !== undefined ? (
|
|
84
|
+
<Path
|
|
85
|
+
path={path}
|
|
86
|
+
style="stroke"
|
|
87
|
+
color={color}
|
|
88
|
+
strokeWidth={strokeWidth}
|
|
89
|
+
strokeJoin="round"
|
|
90
|
+
/>
|
|
91
|
+
) : null}
|
|
92
|
+
</>
|
|
93
|
+
);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export { Dots };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Canvas, Group, Path, type SkPath } from '
|
|
1
|
+
import { Canvas, Group, Path, type SkPath } from 'react-native-skia';
|
|
2
2
|
import type { FC } from 'react';
|
|
3
3
|
import React from 'react';
|
|
4
4
|
import type { StyleProp, ViewStyle } from 'react-native';
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Group,
|
|
3
|
+
Line,
|
|
4
|
+
Text,
|
|
5
|
+
vec,
|
|
6
|
+
type Color,
|
|
7
|
+
type SkFont,
|
|
8
|
+
} from 'react-native-skia';
|
|
9
|
+
import { memo, useMemo, type FC } from 'react';
|
|
10
|
+
import { StyleSheet } from 'react-native';
|
|
11
|
+
|
|
12
|
+
export type YAxisProps = {
|
|
13
|
+
width: number;
|
|
14
|
+
height: number;
|
|
15
|
+
font: SkFont;
|
|
16
|
+
minY: number;
|
|
17
|
+
maxY: number;
|
|
18
|
+
/** Values to draw a gridline at. Defaults to `nbTicks` evenly spaced values. */
|
|
19
|
+
values?: number[];
|
|
20
|
+
nbTicks?: number;
|
|
21
|
+
color?: Color;
|
|
22
|
+
labelColor?: Color;
|
|
23
|
+
strokeWidth?: number;
|
|
24
|
+
showLabels?: boolean;
|
|
25
|
+
formatLabel?: (value: number) => string;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const defaultFormatLabel = (value: number) => `${Math.round(value)}`;
|
|
29
|
+
|
|
30
|
+
/** Evenly spaced integer ticks from 0 to a padded maximum, e.g. 0, 26, 52, 78, 104. */
|
|
31
|
+
export const getPaddedTicks = function (
|
|
32
|
+
maxValue: number,
|
|
33
|
+
nbTicks = 4,
|
|
34
|
+
padding = 1.15
|
|
35
|
+
): number[] {
|
|
36
|
+
const step = Math.max(1, Math.ceil((maxValue * padding) / nbTicks));
|
|
37
|
+
return Array.from({ length: nbTicks + 1 }, (_, i) => i * step);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const YAxis: FC<YAxisProps> = function (props) {
|
|
41
|
+
const {
|
|
42
|
+
width,
|
|
43
|
+
height,
|
|
44
|
+
font,
|
|
45
|
+
minY,
|
|
46
|
+
maxY,
|
|
47
|
+
nbTicks = 4,
|
|
48
|
+
color = 'black',
|
|
49
|
+
labelColor,
|
|
50
|
+
strokeWidth = StyleSheet.hairlineWidth,
|
|
51
|
+
showLabels = true,
|
|
52
|
+
formatLabel = defaultFormatLabel,
|
|
53
|
+
} = props;
|
|
54
|
+
|
|
55
|
+
// Measured once per input change: parents re-render while panning
|
|
56
|
+
const rows = useMemo(() => {
|
|
57
|
+
// A flat range would divide by zero
|
|
58
|
+
const span = maxY - minY || 1;
|
|
59
|
+
const values =
|
|
60
|
+
props.values ??
|
|
61
|
+
Array.from(
|
|
62
|
+
{ length: nbTicks },
|
|
63
|
+
(_, i) => minY + ((i + 1) * (maxY - minY)) / nbTicks
|
|
64
|
+
);
|
|
65
|
+
return values.map((value) => {
|
|
66
|
+
const label = formatLabel(value);
|
|
67
|
+
const labelWidth = font
|
|
68
|
+
.getGlyphWidths(font.getGlyphIDs(label))
|
|
69
|
+
.reduce((a, b) => a + b, 0);
|
|
70
|
+
return {
|
|
71
|
+
label,
|
|
72
|
+
labelWidth,
|
|
73
|
+
transform: [{ translateY: height - ((value - minY) * height) / span }],
|
|
74
|
+
p1: vec(0, 0),
|
|
75
|
+
p2: vec(showLabels ? width - 10 - labelWidth : width, 0),
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
}, [props.values, nbTicks, minY, maxY, height, width, font, showLabels, formatLabel]);
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<Group>
|
|
82
|
+
{rows.map((row, i) => (
|
|
83
|
+
<Group key={`YTick-${i}`} transform={row.transform}>
|
|
84
|
+
<Line p1={row.p1} p2={row.p2} color={color} strokeWidth={strokeWidth} />
|
|
85
|
+
{showLabels ? (
|
|
86
|
+
<Text
|
|
87
|
+
text={row.label}
|
|
88
|
+
x={width - row.labelWidth}
|
|
89
|
+
y={font.getSize() / 2.5}
|
|
90
|
+
font={font}
|
|
91
|
+
color={labelColor ?? color}
|
|
92
|
+
/>
|
|
93
|
+
) : null}
|
|
94
|
+
</Group>
|
|
95
|
+
))}
|
|
96
|
+
</Group>
|
|
97
|
+
);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const MemoYAxis = memo(YAxis);
|
|
101
|
+
MemoYAxis.displayName = 'YAxis';
|
|
102
|
+
|
|
103
|
+
export { MemoYAxis as YAxis };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Path, Skia, type SkPath } from '
|
|
1
|
+
import { Path, Skia, type SkPath } from 'react-native-skia';
|
|
2
2
|
import { type FC } from 'react';
|
|
3
3
|
import { useDerivedValue, type SharedValue } from 'react-native-reanimated';
|
|
4
4
|
|
|
@@ -8,7 +8,7 @@ export type ScalablePathProps = {
|
|
|
8
8
|
focalX: SharedValue<number>;
|
|
9
9
|
offsetX: SharedValue<number>;
|
|
10
10
|
color?: string;
|
|
11
|
-
pathProps?: Omit<React.ComponentProps<typeof Path>, 'color'>;
|
|
11
|
+
pathProps?: Omit<React.ComponentProps<typeof Path>, 'color' | 'path'>;
|
|
12
12
|
};
|
|
13
13
|
|
|
14
14
|
const ScalablePath: FC<ScalablePathProps> = function (props) {
|
|
@@ -16,13 +16,12 @@ const ScalablePath: FC<ScalablePathProps> = function (props) {
|
|
|
16
16
|
|
|
17
17
|
const animatedPath = useDerivedValue(() => {
|
|
18
18
|
// Affine equivalent of getPositionWl: x' = scale * x + focalX * (1 - scale) + offsetX
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
return _path;
|
|
19
|
+
// Plain 3x3 matrix: a Skia.Matrix() would allocate a native object per frame
|
|
20
|
+
const s = scale.value;
|
|
21
|
+
const tx = focalX.value * (1 - s) + offsetX.value;
|
|
22
|
+
return Skia.PathBuilder.MakeFromPath(path.value)
|
|
23
|
+
.transform([s, 0, tx, 0, 1, 0, 0, 0, 1])
|
|
24
|
+
.build();
|
|
26
25
|
});
|
|
27
26
|
|
|
28
27
|
return (
|