@obitrain/charts 0.10.4 → 0.11.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 +4 -2
- package/lib/commonjs/Charts/BottomAxis/BottomAxis.js +53 -13
- package/lib/commonjs/Charts/BottomAxis/BottomAxis.js.map +1 -1
- package/lib/commonjs/Charts/BottomAxis/Tick.js +27 -11
- 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 +18 -6
- 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/YAxis.js +64 -0
- package/lib/commonjs/Charts/YAxis.js.map +1 -0
- package/lib/commonjs/Charts/gesture.js +67 -24
- package/lib/commonjs/Charts/gesture.js.map +1 -1
- package/lib/commonjs/Charts/graphUtils.js +4 -1
- package/lib/commonjs/Charts/graphUtils.js.map +1 -1
- package/lib/commonjs/Charts/index.js +33 -0
- package/lib/commonjs/Charts/index.js.map +1 -1
- package/lib/module/Charts/BottomAxis/BottomAxis.js +55 -14
- package/lib/module/Charts/BottomAxis/BottomAxis.js.map +1 -1
- package/lib/module/Charts/BottomAxis/Tick.js +28 -10
- 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 +19 -7
- 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/YAxis.js +59 -0
- package/lib/module/Charts/YAxis.js.map +1 -0
- package/lib/module/Charts/gesture.js +67 -25
- package/lib/module/Charts/gesture.js.map +1 -1
- package/lib/module/Charts/graphUtils.js +4 -1
- package/lib/module/Charts/graphUtils.js.map +1 -1
- package/lib/module/Charts/index.js +4 -1
- package/lib/module/Charts/index.js.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 +3 -0
- 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/YAxis.d.ts +22 -0
- package/lib/typescript/commonjs/src/Charts/YAxis.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/Charts/ZoomableLinechart/ScalablePath.d.ts +1 -1
- package/lib/typescript/commonjs/src/Charts/ZoomableLinechart/ScalablePath.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.map +1 -1
- package/lib/typescript/commonjs/src/Charts/index.d.ts +8 -2
- package/lib/typescript/commonjs/src/Charts/index.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 +3 -0
- 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/YAxis.d.ts +22 -0
- package/lib/typescript/module/src/Charts/YAxis.d.ts.map +1 -0
- package/lib/typescript/module/src/Charts/ZoomableLinechart/ScalablePath.d.ts +1 -1
- package/lib/typescript/module/src/Charts/ZoomableLinechart/ScalablePath.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.map +1 -1
- package/lib/typescript/module/src/Charts/index.d.ts +8 -2
- package/lib/typescript/module/src/Charts/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/Charts/BottomAxis/BottomAxis.tsx +100 -19
- package/src/Charts/BottomAxis/Tick.tsx +55 -11
- package/src/Charts/BottomAxis/index.tsx +5 -1
- package/src/Charts/Cursor.tsx +21 -1
- package/src/Charts/Dot.tsx +64 -0
- package/src/Charts/Dots.tsx +96 -0
- package/src/Charts/YAxis.tsx +96 -0
- package/src/Charts/ZoomableLinechart/ScalablePath.tsx +1 -1
- package/src/Charts/gesture.ts +109 -51
- package/src/Charts/graphUtils.ts +4 -1
- package/src/Charts/index.tsx +8 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { Path, Skia, type Color } from '@shopify/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.Path.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;
|
|
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 };
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Group,
|
|
3
|
+
Line,
|
|
4
|
+
Text,
|
|
5
|
+
vec,
|
|
6
|
+
type Color,
|
|
7
|
+
type SkFont,
|
|
8
|
+
} from '@shopify/react-native-skia';
|
|
9
|
+
import { 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
|
+
const values =
|
|
56
|
+
props.values ??
|
|
57
|
+
Array.from(
|
|
58
|
+
{ length: nbTicks },
|
|
59
|
+
(_, i) => minY + ((i + 1) * (maxY - minY)) / nbTicks
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<Group>
|
|
64
|
+
{values.map((value, i) => {
|
|
65
|
+
const y = height - ((value - minY) * height) / (maxY - minY);
|
|
66
|
+
const label = formatLabel(value);
|
|
67
|
+
const labelWidth = font
|
|
68
|
+
.getGlyphWidths(font.getGlyphIDs(label))
|
|
69
|
+
.reduce((a, b) => a + b, 0);
|
|
70
|
+
const lineWidth = showLabels ? width - 10 - labelWidth : width;
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<Group key={`YTick-${i}`} transform={[{ translateY: y }]}>
|
|
74
|
+
<Line
|
|
75
|
+
p1={vec(0, 0)}
|
|
76
|
+
p2={vec(lineWidth, 0)}
|
|
77
|
+
color={color}
|
|
78
|
+
strokeWidth={strokeWidth}
|
|
79
|
+
/>
|
|
80
|
+
{showLabels ? (
|
|
81
|
+
<Text
|
|
82
|
+
text={label}
|
|
83
|
+
x={width - labelWidth}
|
|
84
|
+
y={font.getSize() / 2.5}
|
|
85
|
+
font={font}
|
|
86
|
+
color={labelColor ?? color}
|
|
87
|
+
/>
|
|
88
|
+
) : null}
|
|
89
|
+
</Group>
|
|
90
|
+
);
|
|
91
|
+
})}
|
|
92
|
+
</Group>
|
|
93
|
+
);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export { YAxis };
|
|
@@ -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) {
|
package/src/Charts/gesture.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { clamp } from '@shopify/react-native-skia';
|
|
2
|
-
import { useCallback } from 'react';
|
|
2
|
+
import { useCallback, useMemo, useRef } from 'react';
|
|
3
3
|
import { Gesture, type PanGesture, type PinchGesture } from 'react-native-gesture-handler';
|
|
4
4
|
import {
|
|
5
5
|
useAnimatedReaction,
|
|
6
6
|
useSharedValue,
|
|
7
|
-
withTiming,
|
|
8
7
|
type SharedValue,
|
|
9
8
|
} from 'react-native-reanimated';
|
|
10
9
|
import { scheduleOnRN } from 'react-native-worklets';
|
|
@@ -24,6 +23,23 @@ export const getPositionWl = function (
|
|
|
24
23
|
return (position - focalX) * scale + focalX + offsetX;
|
|
25
24
|
};
|
|
26
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Offset range keeping the content edges pinned to the viewport:
|
|
28
|
+
* leftBound (position 0) <= startOffset and rightBound (position width)
|
|
29
|
+
* >= width + startOffset. Collapses to [startOffset, startOffset] at scale 1.
|
|
30
|
+
*/
|
|
31
|
+
export const getOffsetBoundsWl = function (
|
|
32
|
+
width: number,
|
|
33
|
+
startOffset: number,
|
|
34
|
+
focalX: number,
|
|
35
|
+
scale: number
|
|
36
|
+
): [min: number, max: number] {
|
|
37
|
+
'worklet';
|
|
38
|
+
const maxOffset = startOffset + focalX * (scale - 1);
|
|
39
|
+
const minOffset = startOffset + (1 - scale) * (width - focalX);
|
|
40
|
+
return [minOffset, maxOffset];
|
|
41
|
+
};
|
|
42
|
+
|
|
27
43
|
export type AxisGestureProps = {
|
|
28
44
|
width: number;
|
|
29
45
|
startOffset?: number;
|
|
@@ -55,6 +71,8 @@ export const useScalableGesture = (props: AxisGestureProps): ScalableGesture =>
|
|
|
55
71
|
// For panning
|
|
56
72
|
const offsetX = useSharedValue(startOffset);
|
|
57
73
|
const lastOffsetX = useSharedValue(startOffset);
|
|
74
|
+
// Pinch pivot bookkeeping (see pinchGesture)
|
|
75
|
+
const needsPivot = useSharedValue(true);
|
|
58
76
|
|
|
59
77
|
const reset = useCallback(() => {
|
|
60
78
|
scale.value = startScale;
|
|
@@ -74,54 +92,85 @@ export const useScalableGesture = (props: AxisGestureProps): ScalableGesture =>
|
|
|
74
92
|
startScale,
|
|
75
93
|
]);
|
|
76
94
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
95
|
+
// Memoized: handing a new gesture instance to GestureDetector mid-gesture
|
|
96
|
+
// resets the active pan and makes the chart jump
|
|
97
|
+
const panGesture = useMemo(
|
|
98
|
+
() =>
|
|
99
|
+
Gesture.Pan()
|
|
100
|
+
// Single-finger only: two fingers belong to the pinch, whose focal
|
|
101
|
+
// rebase would otherwise fight the pan over offsetX
|
|
102
|
+
.maxPointers(1)
|
|
103
|
+
// Incremental deltas (changeX) with a live clamp keep the content
|
|
104
|
+
// edges pinned: no overscroll, no snap-back to interrupt
|
|
105
|
+
.onChange((event) => {
|
|
106
|
+
const [minOffset, maxOffset] = getOffsetBoundsWl(
|
|
107
|
+
width,
|
|
108
|
+
startOffset,
|
|
109
|
+
focalX.value,
|
|
110
|
+
scale.value
|
|
111
|
+
);
|
|
112
|
+
offsetX.value = clamp(
|
|
113
|
+
offsetX.value + event.changeX,
|
|
114
|
+
minOffset,
|
|
115
|
+
maxOffset
|
|
116
|
+
);
|
|
117
|
+
})
|
|
118
|
+
.onEnd(() => {
|
|
119
|
+
lastScale.value = scale.value;
|
|
120
|
+
lastFocalX.value = focalX.value;
|
|
121
|
+
lastOffsetX.value = offsetX.value;
|
|
122
|
+
}),
|
|
123
|
+
[focalX, lastFocalX, lastOffsetX, lastScale, offsetX, scale, startOffset, width]
|
|
124
|
+
);
|
|
108
125
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
126
|
+
const pinchGesture = useMemo(
|
|
127
|
+
() =>
|
|
128
|
+
Gesture.Pinch()
|
|
129
|
+
.onStart(() => {
|
|
130
|
+
needsPivot.value = true;
|
|
131
|
+
})
|
|
132
|
+
.onUpdate((event) => {
|
|
133
|
+
// The pinch can keep firing in the single-finger tail of the
|
|
134
|
+
// gesture, tracking the remaining finger as its focal: ignore it
|
|
135
|
+
// (numberOfPointers is missing on web — let those through)
|
|
136
|
+
if ((event.numberOfPointers ?? 2) < 2) {
|
|
137
|
+
needsPivot.value = true;
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const prevScale = scale.value;
|
|
141
|
+
const newScale = clamp(lastScale.value * event.scale, 1, Infinity);
|
|
142
|
+
if (needsPivot.value) {
|
|
143
|
+
// New pivot: rebase offsetX so re-anchoring under the fingers
|
|
144
|
+
// doesn't shift the content
|
|
145
|
+
const rebased =
|
|
146
|
+
offsetX.value + (focalX.value - event.focalX) * (1 - prevScale);
|
|
147
|
+
offsetX.value = (rebased * newScale) / prevScale;
|
|
148
|
+
needsPivot.value = false;
|
|
149
|
+
} else {
|
|
150
|
+
// Keep the content glued to both fingers: the offset follows the
|
|
151
|
+
// focal translation and rescales with the zoom
|
|
152
|
+
offsetX.value =
|
|
153
|
+
(event.focalX - focalX.value + offsetX.value / prevScale) *
|
|
154
|
+
newScale;
|
|
155
|
+
}
|
|
156
|
+
focalX.value = event.focalX;
|
|
157
|
+
scale.value = newScale;
|
|
158
|
+
// Zooming moves the bounds: keep the content edges pinned
|
|
159
|
+
const [minOffset, maxOffset] = getOffsetBoundsWl(
|
|
160
|
+
width,
|
|
161
|
+
startOffset,
|
|
162
|
+
focalX.value,
|
|
163
|
+
scale.value
|
|
164
|
+
);
|
|
165
|
+
offsetX.value = clamp(offsetX.value, minOffset, maxOffset);
|
|
166
|
+
})
|
|
167
|
+
.onEnd(() => {
|
|
168
|
+
lastScale.value = scale.value;
|
|
169
|
+
lastFocalX.value = focalX.value;
|
|
170
|
+
lastOffsetX.value = offsetX.value;
|
|
171
|
+
}),
|
|
172
|
+
[focalX, lastFocalX, lastOffsetX, lastScale, needsPivot, offsetX, scale, startOffset, width]
|
|
173
|
+
);
|
|
125
174
|
|
|
126
175
|
return {
|
|
127
176
|
scale,
|
|
@@ -143,6 +192,14 @@ export const useUpdateAxis = function (props: UpdateAxisProps) {
|
|
|
143
192
|
const { scale, scales, onScaleChange } = props;
|
|
144
193
|
const currentIndex = useSharedValue(0);
|
|
145
194
|
|
|
195
|
+
// Stable dispatcher so an inline onScaleChange doesn't re-register the
|
|
196
|
+
// reaction (and disturb the UI thread) on every render
|
|
197
|
+
const onScaleChangeRef = useRef(onScaleChange);
|
|
198
|
+
onScaleChangeRef.current = onScaleChange;
|
|
199
|
+
const dispatchScaleChange = useCallback((index: number) => {
|
|
200
|
+
onScaleChangeRef.current?.(index);
|
|
201
|
+
}, []);
|
|
202
|
+
|
|
146
203
|
useAnimatedReaction(
|
|
147
204
|
() => scale.value,
|
|
148
205
|
(currentScale, _) => {
|
|
@@ -158,11 +215,12 @@ export const useUpdateAxis = function (props: UpdateAxisProps) {
|
|
|
158
215
|
currentIndex.value !== i
|
|
159
216
|
) {
|
|
160
217
|
currentIndex.value = i;
|
|
161
|
-
|
|
218
|
+
scheduleOnRN(dispatchScaleChange, i);
|
|
162
219
|
break;
|
|
163
220
|
}
|
|
164
221
|
}
|
|
165
|
-
}
|
|
222
|
+
},
|
|
223
|
+
[scales, dispatchScaleChange]
|
|
166
224
|
);
|
|
167
225
|
|
|
168
226
|
return { currentIndex };
|
package/src/Charts/graphUtils.ts
CHANGED
|
@@ -198,6 +198,9 @@ export const useDotsTransition = function (props: UseDotAnimationProps) {
|
|
|
198
198
|
}
|
|
199
199
|
_dot.opacity.value = opacityWl(1);
|
|
200
200
|
});
|
|
201
|
-
}
|
|
201
|
+
},
|
|
202
|
+
// Explicit deps: without them the reaction re-registers and re-fires on
|
|
203
|
+
// every parent render, restarting every dot animation on the UI thread
|
|
204
|
+
[currentGraph, path, dataPoints, dots, opacityWl, translateWl]
|
|
202
205
|
);
|
|
203
206
|
};
|
package/src/Charts/index.tsx
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
export { AxisLine, BottomAxis, Tick } from './BottomAxis';
|
|
2
2
|
export type {
|
|
3
3
|
AxisLineProps,
|
|
4
|
+
AxisTick,
|
|
4
5
|
BottomAxisProps,
|
|
5
6
|
TickProps,
|
|
6
7
|
} from './BottomAxis';
|
|
7
8
|
export { Cursor } from './Cursor';
|
|
8
9
|
export type { CursorProps } from './Cursor';
|
|
10
|
+
export { Dot } from './Dot';
|
|
11
|
+
export type { DotProps } from './Dot';
|
|
12
|
+
export { Dots } from './Dots';
|
|
13
|
+
export type { DotShape, DotsProps } from './Dots';
|
|
14
|
+
export { YAxis, getPaddedTicks } from './YAxis';
|
|
15
|
+
export type { YAxisProps } from './YAxis';
|
|
9
16
|
export { LineChart } from './Linechart';
|
|
10
17
|
export type { LineChartProps, LinePath } from './Linechart';
|
|
11
18
|
export { ScalablePath, ZoomableLineChart } from './ZoomableLinechart';
|
|
@@ -15,6 +22,7 @@ export type {
|
|
|
15
22
|
} from './ZoomableLinechart';
|
|
16
23
|
export {
|
|
17
24
|
getClosestPoint,
|
|
25
|
+
getOffsetBoundsWl,
|
|
18
26
|
getPositionWl,
|
|
19
27
|
useCursorGesture,
|
|
20
28
|
useScalableGesture,
|