react-native-livechart 2.0.1 → 3.0.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/dist/components/LoadingOverlay.d.ts.map +1 -1
- package/dist/components/MarkerOverlay.d.ts.map +1 -1
- package/dist/components/MultiSeriesValueLines.d.ts.map +1 -1
- package/dist/components/ReferenceLineOverlay.d.ts.map +1 -1
- package/dist/components/ValueLineOverlay.d.ts.map +1 -1
- package/dist/components/XAxisOverlay.d.ts.map +1 -1
- package/dist/components/YAxisOverlay.d.ts.map +1 -1
- package/dist/hooks/useBadge.d.ts +2 -2
- package/dist/hooks/useBadge.d.ts.map +1 -1
- package/dist/hooks/useCandlePaths.d.ts +8 -10
- package/dist/hooks/useCandlePaths.d.ts.map +1 -1
- package/dist/hooks/useChartPaths.d.ts +8 -7
- package/dist/hooks/useChartPaths.d.ts.map +1 -1
- package/dist/hooks/useMultiSeriesLinePaths.d.ts +9 -8
- package/dist/hooks/useMultiSeriesLinePaths.d.ts.map +1 -1
- package/dist/hooks/usePathBuilder.d.ts +27 -0
- package/dist/hooks/usePathBuilder.d.ts.map +1 -0
- package/dist/math/spline.d.ts +10 -2
- package/dist/math/spline.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/components/LoadingOverlay.tsx +15 -29
- package/src/components/MarkerOverlay.tsx +23 -28
- package/src/components/MultiSeriesValueLines.tsx +20 -40
- package/src/components/ReferenceLineOverlay.tsx +65 -107
- package/src/components/ValueLineOverlay.tsx +9 -28
- package/src/components/XAxisOverlay.tsx +9 -29
- package/src/components/YAxisOverlay.tsx +6 -22
- package/src/draw/markerAtlas.ts +9 -9
- package/src/hooks/useBadge.ts +17 -32
- package/src/hooks/useCandlePaths.ts +24 -63
- package/src/hooks/useChartPaths.ts +27 -39
- package/src/hooks/useMultiSeriesLinePaths.ts +26 -28
- package/src/hooks/usePathBuilder.ts +42 -0
- package/src/math/spline.ts +22 -2
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { Skia
|
|
2
|
-
import { useRef } from "react";
|
|
1
|
+
import { Skia } from "@shopify/react-native-skia";
|
|
3
2
|
import {
|
|
4
3
|
useDerivedValue,
|
|
5
4
|
useFrameCallback,
|
|
@@ -12,15 +11,15 @@ import { buildCandleGeometry } from "../draw/candle";
|
|
|
12
11
|
import type { ChartPadding } from "../draw/line";
|
|
13
12
|
import { lerp } from "../math/lerp";
|
|
14
13
|
import type { CandleMetrics, CandlePoint } from "../types";
|
|
14
|
+
import { usePathBuilder } from "./usePathBuilder";
|
|
15
15
|
|
|
16
16
|
const CANDLE_WIDTH_LERP_SPEED = 0.08;
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* so
|
|
22
|
-
*
|
|
23
|
-
* ticking).
|
|
19
|
+
* Candle paths (up/down bodies + up/down wicks). Each is built into a reused
|
|
20
|
+
* `Skia.PathBuilder` and finalized with `detach()` each frame — a fresh
|
|
21
|
+
* immutable `SkPath`, so Skia repaints without a per-curve ping-pong and no
|
|
22
|
+
* mutable `SkPath` is retained across frames.
|
|
24
23
|
*/
|
|
25
24
|
export function useCandlePaths(
|
|
26
25
|
engine: SingleEngineState,
|
|
@@ -34,36 +33,10 @@ export function useCandlePaths(
|
|
|
34
33
|
const targetCandleWidth = useDerivedValue(() => candleWidthSecs);
|
|
35
34
|
const displayCandleWidth = useSharedValue(candleWidthSecs);
|
|
36
35
|
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
downBodiesB: SkPath;
|
|
42
|
-
upWicksA: SkPath;
|
|
43
|
-
upWicksB: SkPath;
|
|
44
|
-
downWicksA: SkPath;
|
|
45
|
-
downWicksB: SkPath;
|
|
46
|
-
ubTick: boolean;
|
|
47
|
-
dbTick: boolean;
|
|
48
|
-
uwTick: boolean;
|
|
49
|
-
dwTick: boolean;
|
|
50
|
-
} | null>(null);
|
|
51
|
-
if (cacheRef.current === null) {
|
|
52
|
-
cacheRef.current = {
|
|
53
|
-
upBodiesA: Skia.Path.Make(),
|
|
54
|
-
upBodiesB: Skia.Path.Make(),
|
|
55
|
-
downBodiesA: Skia.Path.Make(),
|
|
56
|
-
downBodiesB: Skia.Path.Make(),
|
|
57
|
-
upWicksA: Skia.Path.Make(),
|
|
58
|
-
upWicksB: Skia.Path.Make(),
|
|
59
|
-
downWicksA: Skia.Path.Make(),
|
|
60
|
-
downWicksB: Skia.Path.Make(),
|
|
61
|
-
ubTick: false,
|
|
62
|
-
dbTick: false,
|
|
63
|
-
uwTick: false,
|
|
64
|
-
dwTick: false,
|
|
65
|
-
};
|
|
66
|
-
}
|
|
36
|
+
const upBodiesBuilder = usePathBuilder();
|
|
37
|
+
const downBodiesBuilder = usePathBuilder();
|
|
38
|
+
const upWicksBuilder = usePathBuilder();
|
|
39
|
+
const downWicksBuilder = usePathBuilder();
|
|
67
40
|
|
|
68
41
|
useFrameCallback((frameInfo) => {
|
|
69
42
|
"worklet";
|
|
@@ -99,68 +72,56 @@ export function useCandlePaths(
|
|
|
99
72
|
|
|
100
73
|
/* istanbul ignore next -- worklet */
|
|
101
74
|
const upBodiesPath = useDerivedValue(() => {
|
|
102
|
-
const
|
|
103
|
-
cache.ubTick = !cache.ubTick;
|
|
104
|
-
const path = cache.ubTick ? cache.upBodiesA : cache.upBodiesB;
|
|
105
|
-
path.reset();
|
|
75
|
+
const b = upBodiesBuilder.value;
|
|
106
76
|
const { bodies } = geometry.value;
|
|
107
77
|
for (let i = 0; i < bodies.length; i++) {
|
|
108
78
|
if (bodies[i].up) {
|
|
109
|
-
|
|
79
|
+
b.addRect(
|
|
110
80
|
Skia.XYWHRect(bodies[i].x, bodies[i].y, bodies[i].w, bodies[i].h),
|
|
111
81
|
);
|
|
112
82
|
}
|
|
113
83
|
}
|
|
114
|
-
return
|
|
84
|
+
return b.detach();
|
|
115
85
|
});
|
|
116
86
|
|
|
117
87
|
/* istanbul ignore next -- worklet */
|
|
118
88
|
const downBodiesPath = useDerivedValue(() => {
|
|
119
|
-
const
|
|
120
|
-
cache.dbTick = !cache.dbTick;
|
|
121
|
-
const path = cache.dbTick ? cache.downBodiesA : cache.downBodiesB;
|
|
122
|
-
path.reset();
|
|
89
|
+
const b = downBodiesBuilder.value;
|
|
123
90
|
const { bodies } = geometry.value;
|
|
124
91
|
for (let i = 0; i < bodies.length; i++) {
|
|
125
92
|
if (!bodies[i].up) {
|
|
126
|
-
|
|
93
|
+
b.addRect(
|
|
127
94
|
Skia.XYWHRect(bodies[i].x, bodies[i].y, bodies[i].w, bodies[i].h),
|
|
128
95
|
);
|
|
129
96
|
}
|
|
130
97
|
}
|
|
131
|
-
return
|
|
98
|
+
return b.detach();
|
|
132
99
|
});
|
|
133
100
|
|
|
134
101
|
/* istanbul ignore next -- worklet */
|
|
135
102
|
const upWicksPath = useDerivedValue(() => {
|
|
136
|
-
const
|
|
137
|
-
cache.uwTick = !cache.uwTick;
|
|
138
|
-
const path = cache.uwTick ? cache.upWicksA : cache.upWicksB;
|
|
139
|
-
path.reset();
|
|
103
|
+
const b = upWicksBuilder.value;
|
|
140
104
|
const { wicks } = geometry.value;
|
|
141
105
|
for (let i = 0; i < wicks.length; i++) {
|
|
142
106
|
if (wicks[i].up) {
|
|
143
|
-
|
|
144
|
-
|
|
107
|
+
b.moveTo(wicks[i].x, wicks[i].y1);
|
|
108
|
+
b.lineTo(wicks[i].x, wicks[i].y2);
|
|
145
109
|
}
|
|
146
110
|
}
|
|
147
|
-
return
|
|
111
|
+
return b.detach();
|
|
148
112
|
});
|
|
149
113
|
|
|
150
114
|
/* istanbul ignore next -- worklet */
|
|
151
115
|
const downWicksPath = useDerivedValue(() => {
|
|
152
|
-
const
|
|
153
|
-
cache.dwTick = !cache.dwTick;
|
|
154
|
-
const path = cache.dwTick ? cache.downWicksA : cache.downWicksB;
|
|
155
|
-
path.reset();
|
|
116
|
+
const b = downWicksBuilder.value;
|
|
156
117
|
const { wicks } = geometry.value;
|
|
157
118
|
for (let i = 0; i < wicks.length; i++) {
|
|
158
119
|
if (!wicks[i].up) {
|
|
159
|
-
|
|
160
|
-
|
|
120
|
+
b.moveTo(wicks[i].x, wicks[i].y1);
|
|
121
|
+
b.lineTo(wicks[i].x, wicks[i].y2);
|
|
161
122
|
}
|
|
162
123
|
}
|
|
163
|
-
return
|
|
124
|
+
return b.detach();
|
|
164
125
|
});
|
|
165
126
|
|
|
166
127
|
return { upBodiesPath, downBodiesPath, upWicksPath, downWicksPath } as const;
|
|
@@ -5,30 +5,29 @@ import type { SingleEngineState } from "../core/useLiveChartEngine";
|
|
|
5
5
|
import { buildLinePoints, type ChartPadding } from "../draw/line";
|
|
6
6
|
import { drawSpline, makeSplineScratch } from "../math/spline";
|
|
7
7
|
import { blendPtsY, squigglifyPts } from "../math/squiggly";
|
|
8
|
+
import { usePathBuilder } from "./usePathBuilder";
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
|
-
*
|
|
11
|
+
* Builds the `linePath` / `fillPath` with `Skia.PathBuilder`s reused across
|
|
12
|
+
* frames (one per curve, held in a SharedValue) and finalized with `detach()` —
|
|
13
|
+
* which returns a fresh immutable `SkPath` each frame and resets the builder.
|
|
14
|
+
* The fresh reference makes Reanimated notify subscribers (re-record + repaint)
|
|
15
|
+
* without the two-SkPath ping-pong the mutable-path pool needed.
|
|
11
16
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* two paths per curve once and ping-pong the reference each frame so Reanimated's
|
|
16
|
-
* value-equality early-return still fires its subscribers (i.e. the Skia reanimated
|
|
17
|
-
* container re-records and repaints).
|
|
17
|
+
* The flat point buffer still ping-pongs (ptsA/ptsB) so the intermediate
|
|
18
|
+
* `flatPts` derived value changes reference each frame and re-runs linePath /
|
|
19
|
+
* fillPath.
|
|
18
20
|
*/
|
|
19
21
|
export function useChartPaths(
|
|
20
22
|
engine: SingleEngineState,
|
|
21
23
|
padding: ChartPadding,
|
|
22
24
|
morphT?: SharedValue<number>,
|
|
23
25
|
) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
const lineBuilder = usePathBuilder();
|
|
27
|
+
const fillBuilder = usePathBuilder();
|
|
28
|
+
|
|
26
29
|
const cacheRef = useRef<{
|
|
27
|
-
|
|
28
|
-
lineB: SkPath;
|
|
29
|
-
fillA: SkPath;
|
|
30
|
-
fillB: SkPath;
|
|
31
|
-
tick: boolean;
|
|
30
|
+
emptyPath: SkPath;
|
|
32
31
|
ptsA: number[];
|
|
33
32
|
ptsB: number[];
|
|
34
33
|
ptsTick: boolean;
|
|
@@ -36,13 +35,7 @@ export function useChartPaths(
|
|
|
36
35
|
} | null>(null);
|
|
37
36
|
if (cacheRef.current === null) {
|
|
38
37
|
cacheRef.current = {
|
|
39
|
-
|
|
40
|
-
lineB: Skia.Path.Make(),
|
|
41
|
-
fillA: Skia.Path.Make(),
|
|
42
|
-
fillB: Skia.Path.Make(),
|
|
43
|
-
tick: false,
|
|
44
|
-
// Ping-pong point buffers: reused across frames but the returned reference
|
|
45
|
-
// still alternates, so Reanimated keeps notifying linePath/fillPath.
|
|
38
|
+
emptyPath: Skia.Path.Make(),
|
|
46
39
|
ptsA: [] as number[],
|
|
47
40
|
ptsB: [] as number[],
|
|
48
41
|
ptsTick: false,
|
|
@@ -89,32 +82,27 @@ export function useChartPaths(
|
|
|
89
82
|
const linePath = useDerivedValue(() => {
|
|
90
83
|
const cache = cacheRef.current!;
|
|
91
84
|
const pts = flatPts.get();
|
|
92
|
-
cache.tick = !cache.tick;
|
|
93
|
-
const path = cache.tick ? cache.lineA : cache.lineB;
|
|
94
|
-
path.reset();
|
|
95
85
|
const n = pts.length >> 1;
|
|
96
|
-
if (n < 2) return
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
86
|
+
if (n < 2) return cache.emptyPath;
|
|
87
|
+
const b = lineBuilder.value;
|
|
88
|
+
b.moveTo(pts[0], pts[1]);
|
|
89
|
+
drawSpline(b, pts, cache.scratch);
|
|
90
|
+
return b.detach();
|
|
100
91
|
});
|
|
101
92
|
|
|
102
93
|
const fillPath = useDerivedValue(() => {
|
|
103
94
|
const cache = cacheRef.current!;
|
|
104
95
|
const pts = flatPts.get();
|
|
105
|
-
// Use the same tick flag flipped by linePath — flipping again here would
|
|
106
|
-
// keep both paths in sync, so just read the current parity.
|
|
107
|
-
const path = cache.tick ? cache.fillA : cache.fillB;
|
|
108
|
-
path.reset();
|
|
109
96
|
const n = pts.length >> 1;
|
|
110
|
-
if (n < 2) return
|
|
111
|
-
|
|
112
|
-
|
|
97
|
+
if (n < 2) return cache.emptyPath;
|
|
98
|
+
const b = fillBuilder.value;
|
|
99
|
+
b.moveTo(pts[0], pts[1]);
|
|
100
|
+
drawSpline(b, pts, cache.scratch);
|
|
113
101
|
const bottom = engine.canvasHeight.get() - padding.bottom;
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
return
|
|
102
|
+
b.lineTo(pts[(n - 1) * 2], bottom);
|
|
103
|
+
b.lineTo(pts[0], bottom);
|
|
104
|
+
b.close();
|
|
105
|
+
return b.detach();
|
|
118
106
|
});
|
|
119
107
|
|
|
120
108
|
return { linePath, fillPath } as const;
|
|
@@ -5,51 +5,46 @@ import { MAX_MULTI_SERIES } from "../constants";
|
|
|
5
5
|
import type { MultiEngineState } from "../core/useLiveChartEngine";
|
|
6
6
|
import { buildLinePoints, type ChartPadding } from "../draw/line";
|
|
7
7
|
import { drawSpline, makeSplineScratch } from "../math/spline";
|
|
8
|
+
import { usePathBuilders } from "./usePathBuilder";
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
|
-
* One derived shared value holding up to `MAX_MULTI_SERIES` Skia paths (unused
|
|
11
|
+
* One derived shared value holding up to `MAX_MULTI_SERIES` Skia paths (unused
|
|
12
|
+
* slots reuse one cached empty path).
|
|
11
13
|
*
|
|
12
|
-
* Each
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* Each visible series' path is built into a per-slot `Skia.PathBuilder` (reused
|
|
15
|
+
* across frames via a SharedValue) and finalized with `detach()` — a fresh
|
|
16
|
+
* immutable `SkPath` per frame, so `MultiSeriesStroke`'s per-slot
|
|
17
|
+
* `useDerivedValue(() => paths.value[index])` repaints without the two-SkPath
|
|
18
|
+
* ping-pong. Only visible series allocate a path.
|
|
17
19
|
*/
|
|
18
20
|
export function useMultiSeriesLinePaths(
|
|
19
21
|
engine: MultiEngineState,
|
|
20
22
|
padding: ChartPadding,
|
|
21
|
-
): SharedValue<
|
|
23
|
+
): SharedValue<SkPath[]> {
|
|
24
|
+
const builders = usePathBuilders(MAX_MULTI_SERIES);
|
|
25
|
+
|
|
22
26
|
const poolRef = useRef<{
|
|
23
|
-
|
|
24
|
-
b: SkPath[];
|
|
25
|
-
tick: boolean;
|
|
27
|
+
empty: SkPath;
|
|
26
28
|
ptsBuf: number[];
|
|
27
29
|
scratch: ReturnType<typeof makeSplineScratch>;
|
|
28
30
|
} | null>(null);
|
|
29
31
|
if (poolRef.current === null) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
-
// One point buffer + spline scratch, reused across slots (built sequentially)
|
|
37
|
-
// and across frames — no per-frame, per-series array allocation.
|
|
38
|
-
poolRef.current = { a, b, tick: false, ptsBuf: [] as number[], scratch: makeSplineScratch() };
|
|
32
|
+
poolRef.current = {
|
|
33
|
+
empty: Skia.Path.Make(),
|
|
34
|
+
ptsBuf: [] as number[],
|
|
35
|
+
scratch: makeSplineScratch(),
|
|
36
|
+
};
|
|
39
37
|
}
|
|
40
38
|
|
|
41
39
|
return useDerivedValue(() => {
|
|
42
40
|
const pool = poolRef.current!;
|
|
43
|
-
|
|
44
|
-
const slots = pool.tick ? pool.a : pool.b;
|
|
41
|
+
const slots = builders.value;
|
|
45
42
|
const s = engine.series.get();
|
|
46
43
|
const displays = engine.displaySeriesValues.get();
|
|
47
|
-
const out:
|
|
44
|
+
const out: SkPath[] = [];
|
|
48
45
|
for (let i = 0; i < MAX_MULTI_SERIES; i++) {
|
|
49
|
-
const path = slots[i];
|
|
50
|
-
path.reset();
|
|
51
46
|
if (i >= s.length) {
|
|
52
|
-
out.push(
|
|
47
|
+
out.push(pool.empty);
|
|
53
48
|
continue;
|
|
54
49
|
}
|
|
55
50
|
const pts = buildLinePoints(
|
|
@@ -66,10 +61,13 @@ export function useMultiSeriesLinePaths(
|
|
|
66
61
|
);
|
|
67
62
|
const n = pts.length >> 1;
|
|
68
63
|
if (n >= 2) {
|
|
69
|
-
|
|
70
|
-
|
|
64
|
+
const b = slots[i];
|
|
65
|
+
b.moveTo(pts[0], pts[1]);
|
|
66
|
+
drawSpline(b, pts, pool.scratch);
|
|
67
|
+
out.push(b.detach());
|
|
68
|
+
} else {
|
|
69
|
+
out.push(pool.empty);
|
|
71
70
|
}
|
|
72
|
-
out.push(path);
|
|
73
71
|
}
|
|
74
72
|
return out;
|
|
75
73
|
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Skia } from "@shopify/react-native-skia";
|
|
2
|
+
import { useMemo } from "react";
|
|
3
|
+
import { useSharedValue, type SharedValue } from "react-native-reanimated";
|
|
4
|
+
|
|
5
|
+
/** A `Skia.PathBuilder` instance (typed without depending on a named export). */
|
|
6
|
+
export type ReanimatedPathBuilder = ReturnType<typeof Skia.PathBuilder.Make>;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A `Skia.PathBuilder` made accessible inside Reanimated worklets.
|
|
10
|
+
*
|
|
11
|
+
* Unlike `SkPath`, an `SkPathBuilder` is **not** a Reanimated-shareable host
|
|
12
|
+
* object: stashing one in a `useRef` and reading it from a `useDerivedValue`
|
|
13
|
+
* worklet yields `undefined`. Wrapping it in a `SharedValue` is the supported
|
|
14
|
+
* way to cross the JS→UI boundary, so the builder is created once on the JS
|
|
15
|
+
* thread and reused every frame on the UI thread.
|
|
16
|
+
*
|
|
17
|
+
* Per-frame usage in a worklet:
|
|
18
|
+
*
|
|
19
|
+
* const b = builder.value;
|
|
20
|
+
* b.moveTo(x, y); b.lineTo(...); // emit verbs
|
|
21
|
+
* return b.detach(); // fresh immutable SkPath; resets the builder
|
|
22
|
+
*
|
|
23
|
+
* `detach()` returns a new `SkPath` and resets the builder for the next frame,
|
|
24
|
+
* so the returned reference changes every frame and Reanimated notifies
|
|
25
|
+
* subscribers without the two-path ping-pong the mutable-`SkPath` pool needs.
|
|
26
|
+
*/
|
|
27
|
+
export function usePathBuilder(): SharedValue<ReanimatedPathBuilder> {
|
|
28
|
+
const builder = useMemo(() => Skia.PathBuilder.Make(), []);
|
|
29
|
+
return useSharedValue(builder);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Like {@link usePathBuilder} but a fixed-length array of builders (e.g. one per series). */
|
|
33
|
+
export function usePathBuilders(
|
|
34
|
+
count: number,
|
|
35
|
+
): SharedValue<ReanimatedPathBuilder[]> {
|
|
36
|
+
const builders = useMemo(() => {
|
|
37
|
+
const arr: ReanimatedPathBuilder[] = [];
|
|
38
|
+
for (let i = 0; i < count; i++) arr.push(Skia.PathBuilder.Make());
|
|
39
|
+
return arr;
|
|
40
|
+
}, [count]);
|
|
41
|
+
return useSharedValue(builders);
|
|
42
|
+
}
|
package/src/math/spline.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { SkPath } from "@shopify/react-native-skia";
|
|
2
1
|
import type { LiveChartPoint } from "../types";
|
|
3
2
|
|
|
4
3
|
/**
|
|
@@ -33,7 +32,28 @@ export function makeSplineScratch(): SplineScratch {
|
|
|
33
32
|
*
|
|
34
33
|
* @see https://github.com/benjitaylor/liveline
|
|
35
34
|
*/
|
|
36
|
-
|
|
35
|
+
/**
|
|
36
|
+
* Minimal structural sink for {@link drawSpline}: just the verb-emitting methods
|
|
37
|
+
* it calls. Both `SkPath` and `SkPathBuilder` satisfy it, so the spline can be
|
|
38
|
+
* built into either a mutable `SkPath` or a `Skia.PathBuilder`.
|
|
39
|
+
*/
|
|
40
|
+
export type SplinePathSink = {
|
|
41
|
+
lineTo: (x: number, y: number) => unknown;
|
|
42
|
+
cubicTo: (
|
|
43
|
+
c1x: number,
|
|
44
|
+
c1y: number,
|
|
45
|
+
c2x: number,
|
|
46
|
+
c2y: number,
|
|
47
|
+
x: number,
|
|
48
|
+
y: number,
|
|
49
|
+
) => unknown;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export function drawSpline(
|
|
53
|
+
path: SplinePathSink,
|
|
54
|
+
pts: number[],
|
|
55
|
+
scratch?: SplineScratch,
|
|
56
|
+
) {
|
|
37
57
|
"worklet";
|
|
38
58
|
const n = pts.length >> 1;
|
|
39
59
|
if (n < 2) return;
|