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
|
@@ -3,16 +3,14 @@ import {
|
|
|
3
3
|
Group,
|
|
4
4
|
Path,
|
|
5
5
|
RoundedRect,
|
|
6
|
-
Skia,
|
|
7
6
|
Text as SkiaText,
|
|
8
7
|
type SkFont,
|
|
9
|
-
type SkPath,
|
|
10
8
|
} from "@shopify/react-native-skia";
|
|
11
|
-
import { useRef } from "react";
|
|
12
9
|
import { useDerivedValue } from "react-native-reanimated";
|
|
13
10
|
|
|
14
11
|
import type { ChartEngineLayout } from "../core/useLiveChartEngine";
|
|
15
12
|
import type { ChartPadding } from "../draw/line";
|
|
13
|
+
import { usePathBuilder } from "../hooks/usePathBuilder";
|
|
16
14
|
import { useReferenceLine } from "../hooks/useReferenceLine";
|
|
17
15
|
import { measureFontTextWidth } from "../lib/measureFontTextWidth";
|
|
18
16
|
import { referenceLineForm } from "../math/referenceLines";
|
|
@@ -65,131 +63,91 @@ export function ReferenceLineOverlay({
|
|
|
65
63
|
const badgeBorderColor = line.badgeBorderColor ?? color;
|
|
66
64
|
const badgeRadius = line.badgeRadius ?? OFF_AXIS_PILL_RADIUS;
|
|
67
65
|
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
bandB: SkPath;
|
|
74
|
-
bandT: boolean;
|
|
75
|
-
borderA: SkPath;
|
|
76
|
-
borderB: SkPath;
|
|
77
|
-
borderT: boolean;
|
|
78
|
-
offA: SkPath;
|
|
79
|
-
offB: SkPath;
|
|
80
|
-
offT: boolean;
|
|
81
|
-
chevA: SkPath;
|
|
82
|
-
chevB: SkPath;
|
|
83
|
-
chevT: boolean;
|
|
84
|
-
} | null>(null);
|
|
85
|
-
if (cacheRef.current === null) {
|
|
86
|
-
cacheRef.current = {
|
|
87
|
-
lineA: Skia.Path.Make(),
|
|
88
|
-
lineB: Skia.Path.Make(),
|
|
89
|
-
lineT: false,
|
|
90
|
-
bandA: Skia.Path.Make(),
|
|
91
|
-
bandB: Skia.Path.Make(),
|
|
92
|
-
bandT: false,
|
|
93
|
-
borderA: Skia.Path.Make(),
|
|
94
|
-
borderB: Skia.Path.Make(),
|
|
95
|
-
borderT: false,
|
|
96
|
-
offA: Skia.Path.Make(),
|
|
97
|
-
offB: Skia.Path.Make(),
|
|
98
|
-
offT: false,
|
|
99
|
-
chevA: Skia.Path.Make(),
|
|
100
|
-
chevB: Skia.Path.Make(),
|
|
101
|
-
chevT: false,
|
|
102
|
-
};
|
|
103
|
-
}
|
|
66
|
+
const lineBuilder = usePathBuilder();
|
|
67
|
+
const bandBuilder = usePathBuilder();
|
|
68
|
+
const borderBuilder = usePathBuilder();
|
|
69
|
+
const offBuilder = usePathBuilder();
|
|
70
|
+
const chevBuilder = usePathBuilder();
|
|
104
71
|
|
|
105
72
|
const linePath = useDerivedValue(() => {
|
|
106
|
-
const
|
|
107
|
-
cache.lineT = !cache.lineT;
|
|
108
|
-
const p = cache.lineT ? cache.lineA : cache.lineB;
|
|
109
|
-
p.reset();
|
|
73
|
+
const b = lineBuilder.value;
|
|
110
74
|
const l = layout.get();
|
|
111
|
-
if (
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
75
|
+
if (l.visible && !l.offAxis && !isBand) {
|
|
76
|
+
b.moveTo(l.x1, l.y);
|
|
77
|
+
b.lineTo(l.x2, l.y);
|
|
78
|
+
}
|
|
79
|
+
return b.detach();
|
|
115
80
|
});
|
|
116
81
|
|
|
117
82
|
const bandPath = useDerivedValue(() => {
|
|
118
|
-
const
|
|
119
|
-
cache.bandT = !cache.bandT;
|
|
120
|
-
const p = cache.bandT ? cache.bandA : cache.bandB;
|
|
121
|
-
p.reset();
|
|
83
|
+
const b = bandBuilder.value;
|
|
122
84
|
const l = layout.get();
|
|
123
|
-
if (
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
85
|
+
if (l.visible && isBand) {
|
|
86
|
+
b.moveTo(l.x1, l.y);
|
|
87
|
+
b.lineTo(l.x2, l.y);
|
|
88
|
+
b.lineTo(l.x2, l.yBottom);
|
|
89
|
+
b.lineTo(l.x1, l.yBottom);
|
|
90
|
+
b.close();
|
|
91
|
+
}
|
|
92
|
+
return b.detach();
|
|
130
93
|
});
|
|
131
94
|
|
|
132
95
|
const bandBorderPath = useDerivedValue(() => {
|
|
133
|
-
const
|
|
134
|
-
cache.borderT = !cache.borderT;
|
|
135
|
-
const p = cache.borderT ? cache.borderA : cache.borderB;
|
|
136
|
-
p.reset();
|
|
96
|
+
const b = borderBuilder.value;
|
|
137
97
|
const l = layout.get();
|
|
138
|
-
if (
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
98
|
+
if (l.visible && hasBandBorder) {
|
|
99
|
+
if (form === "time-band") {
|
|
100
|
+
// Vertical edges at the band's left / right.
|
|
101
|
+
b.moveTo(l.x1, l.y);
|
|
102
|
+
b.lineTo(l.x1, l.yBottom);
|
|
103
|
+
b.moveTo(l.x2, l.y);
|
|
104
|
+
b.lineTo(l.x2, l.yBottom);
|
|
105
|
+
} else {
|
|
106
|
+
// Horizontal edges at the band's top / bottom.
|
|
107
|
+
b.moveTo(l.x1, l.y);
|
|
108
|
+
b.lineTo(l.x2, l.y);
|
|
109
|
+
b.moveTo(l.x1, l.yBottom);
|
|
110
|
+
b.lineTo(l.x2, l.yBottom);
|
|
111
|
+
}
|
|
151
112
|
}
|
|
152
|
-
return
|
|
113
|
+
return b.detach();
|
|
153
114
|
});
|
|
154
115
|
|
|
155
116
|
const offLinePath = useDerivedValue(() => {
|
|
156
|
-
const
|
|
157
|
-
cache.offT = !cache.offT;
|
|
158
|
-
const p = cache.offT ? cache.offA : cache.offB;
|
|
159
|
-
p.reset();
|
|
117
|
+
const b = offBuilder.value;
|
|
160
118
|
const l = layout.get();
|
|
161
|
-
if (
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
119
|
+
if (l.visible && l.offAxis) {
|
|
120
|
+
// Start the connector just past the badge pill's right edge so the dashed
|
|
121
|
+
// line runs out to the chart edge rather than behind the badge.
|
|
122
|
+
const pillRight =
|
|
123
|
+
l.labelX + measureFontTextWidth(font, l.label) + OFF_AXIS_PILL_PAD_X;
|
|
124
|
+
const start = pillRight + 4;
|
|
125
|
+
if (start < l.x2) {
|
|
126
|
+
b.moveTo(start, l.y);
|
|
127
|
+
b.lineTo(l.x2, l.y);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return b.detach();
|
|
171
131
|
});
|
|
172
132
|
|
|
173
133
|
const chevronPath = useDerivedValue(() => {
|
|
174
|
-
const
|
|
175
|
-
cache.chevT = !cache.chevT;
|
|
176
|
-
const p = cache.chevT ? cache.chevA : cache.chevB;
|
|
177
|
-
p.reset();
|
|
134
|
+
const b = chevBuilder.value;
|
|
178
135
|
const l = layout.get();
|
|
179
|
-
if (
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
136
|
+
if (l.visible && l.offAxis) {
|
|
137
|
+
const cx = l.x1 + 6;
|
|
138
|
+
const cy = l.y;
|
|
139
|
+
const s = 4;
|
|
140
|
+
if (l.chevronUp) {
|
|
141
|
+
b.moveTo(cx - s, cy + s);
|
|
142
|
+
b.lineTo(cx, cy - s);
|
|
143
|
+
b.lineTo(cx + s, cy + s);
|
|
144
|
+
} else {
|
|
145
|
+
b.moveTo(cx - s, cy - s);
|
|
146
|
+
b.lineTo(cx, cy + s);
|
|
147
|
+
b.lineTo(cx + s, cy - s);
|
|
148
|
+
}
|
|
191
149
|
}
|
|
192
|
-
return
|
|
150
|
+
return b.detach();
|
|
193
151
|
});
|
|
194
152
|
|
|
195
153
|
const lineOpacity = useDerivedValue(() =>
|
|
@@ -1,14 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
DashPathEffect,
|
|
3
|
-
Path,
|
|
4
|
-
Skia,
|
|
5
|
-
type SkPath,
|
|
6
|
-
} from "@shopify/react-native-skia";
|
|
7
|
-
import { useRef } from "react";
|
|
1
|
+
import { DashPathEffect, Path } from "@shopify/react-native-skia";
|
|
8
2
|
import { useDerivedValue, type SharedValue } from "react-native-reanimated";
|
|
9
3
|
|
|
10
4
|
import type { ChartPadding } from "../draw/line";
|
|
11
5
|
import type { ChartEngineLayout } from "../core/useLiveChartEngine";
|
|
6
|
+
import { usePathBuilder } from "../hooks/usePathBuilder";
|
|
12
7
|
|
|
13
8
|
/**
|
|
14
9
|
* A dashed horizontal line that tracks the live display value — sitting at
|
|
@@ -29,30 +24,16 @@ export function ValueLineOverlay({
|
|
|
29
24
|
intervals: [number, number];
|
|
30
25
|
color: string;
|
|
31
26
|
}) {
|
|
32
|
-
|
|
33
|
-
const cacheRef = useRef<{
|
|
34
|
-
a: SkPath;
|
|
35
|
-
b: SkPath;
|
|
36
|
-
tick: boolean;
|
|
37
|
-
} | null>(null);
|
|
38
|
-
if (cacheRef.current === null) {
|
|
39
|
-
cacheRef.current = {
|
|
40
|
-
a: Skia.Path.Make(),
|
|
41
|
-
b: Skia.Path.Make(),
|
|
42
|
-
tick: false,
|
|
43
|
-
};
|
|
44
|
-
}
|
|
27
|
+
const builder = usePathBuilder();
|
|
45
28
|
|
|
46
29
|
const path = useDerivedValue(() => {
|
|
47
|
-
const
|
|
48
|
-
cache.tick = !cache.tick;
|
|
49
|
-
const p = cache.tick ? cache.a : cache.b;
|
|
50
|
-
p.reset();
|
|
30
|
+
const b = builder.value;
|
|
51
31
|
const y = dotY.get();
|
|
52
|
-
if (y
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
32
|
+
if (y >= 0) {
|
|
33
|
+
b.moveTo(padding.left, y);
|
|
34
|
+
b.lineTo(engine.canvasWidth.get() - padding.right, y);
|
|
35
|
+
}
|
|
36
|
+
return b.detach();
|
|
56
37
|
});
|
|
57
38
|
|
|
58
39
|
return (
|
|
@@ -1,15 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Group,
|
|
3
|
-
Path,
|
|
4
|
-
Skia,
|
|
5
|
-
type SkFont,
|
|
6
|
-
type SkPath,
|
|
7
|
-
} from "@shopify/react-native-skia";
|
|
8
|
-
import { useRef } from "react";
|
|
1
|
+
import { Group, Path, type SkFont } from "@shopify/react-native-skia";
|
|
9
2
|
import { useDerivedValue, type SharedValue } from "react-native-reanimated";
|
|
10
3
|
import type { ChartEngineLayout } from "../core/useLiveChartEngine";
|
|
11
4
|
import type { ChartPadding } from "../draw/line";
|
|
12
5
|
import type { XAxisEntry } from "../hooks/useXAxis";
|
|
6
|
+
import { usePathBuilder } from "../hooks/usePathBuilder";
|
|
13
7
|
import { measureFontTextWidth } from "../lib/measureFontTextWidth";
|
|
14
8
|
import type { LiveChartPalette } from "../types";
|
|
15
9
|
import { AnimatedLabel } from "./AnimatedLabel";
|
|
@@ -31,40 +25,26 @@ export function XAxisOverlay({
|
|
|
31
25
|
palette: LiveChartPalette;
|
|
32
26
|
font: SkFont;
|
|
33
27
|
}) {
|
|
34
|
-
const
|
|
35
|
-
a: SkPath;
|
|
36
|
-
b: SkPath;
|
|
37
|
-
tick: boolean;
|
|
38
|
-
} | null>(null);
|
|
39
|
-
if (axisCacheRef.current === null) {
|
|
40
|
-
axisCacheRef.current = {
|
|
41
|
-
a: Skia.Path.Make(),
|
|
42
|
-
b: Skia.Path.Make(),
|
|
43
|
-
tick: false,
|
|
44
|
-
};
|
|
45
|
-
}
|
|
28
|
+
const axisBuilder = usePathBuilder();
|
|
46
29
|
|
|
47
30
|
const axisPath = useDerivedValue(() => {
|
|
48
31
|
"worklet";
|
|
49
|
-
const
|
|
50
|
-
axisCache.tick = !axisCache.tick;
|
|
51
|
-
const path = axisCache.tick ? axisCache.a : axisCache.b;
|
|
52
|
-
path.reset();
|
|
32
|
+
const b = axisBuilder.value;
|
|
53
33
|
const w = engine.canvasWidth.get();
|
|
54
34
|
const h = engine.canvasHeight.get();
|
|
55
35
|
const lineY = h - padding.bottom;
|
|
56
36
|
|
|
57
37
|
// Bottom axis line
|
|
58
|
-
|
|
59
|
-
|
|
38
|
+
b.moveTo(padding.left, lineY);
|
|
39
|
+
b.lineTo(w - padding.right, lineY);
|
|
60
40
|
|
|
61
41
|
// Tick marks
|
|
62
42
|
const items = entries.get();
|
|
63
43
|
for (let i = 0; i < items.length; i++) {
|
|
64
|
-
|
|
65
|
-
|
|
44
|
+
b.moveTo(items[i].x, lineY);
|
|
45
|
+
b.lineTo(items[i].x, lineY + TICK_HEIGHT);
|
|
66
46
|
}
|
|
67
|
-
return
|
|
47
|
+
return b.detach();
|
|
68
48
|
});
|
|
69
49
|
|
|
70
50
|
// Transform XAxisEntry[] into { x, y, label, alpha } for AnimatedLabel
|
|
@@ -2,11 +2,8 @@ import {
|
|
|
2
2
|
DashPathEffect,
|
|
3
3
|
Group,
|
|
4
4
|
Path,
|
|
5
|
-
Skia,
|
|
6
5
|
type SkFont,
|
|
7
|
-
type SkPath,
|
|
8
6
|
} from "@shopify/react-native-skia";
|
|
9
|
-
import { useRef } from "react";
|
|
10
7
|
import { useDerivedValue, type SharedValue } from "react-native-reanimated";
|
|
11
8
|
import { BADGE_METRICS_DEFAULTS } from "../constants";
|
|
12
9
|
import type { ResolvedGridStyleConfig } from "../core/resolveConfig";
|
|
@@ -18,6 +15,7 @@ import {
|
|
|
18
15
|
pillTextLeftX,
|
|
19
16
|
type ChartPadding,
|
|
20
17
|
} from "../draw/line";
|
|
18
|
+
import { usePathBuilder } from "../hooks/usePathBuilder";
|
|
21
19
|
import { measureFontTextWidth } from "../lib/measureFontTextWidth";
|
|
22
20
|
import type { BadgeMetrics, LiveChartPalette } from "../types";
|
|
23
21
|
import type { ChartEngineLayout } from "../core/useLiveChartEngine";
|
|
@@ -57,31 +55,17 @@ export function YAxisOverlay({
|
|
|
57
55
|
const gridWidth = gridStyle?.strokeWidth ?? 1;
|
|
58
56
|
const gridIntervals = gridStyle?.intervals ?? [];
|
|
59
57
|
const gridOpacity = gridStyle?.opacity ?? 1;
|
|
60
|
-
const
|
|
61
|
-
a: SkPath;
|
|
62
|
-
b: SkPath;
|
|
63
|
-
tick: boolean;
|
|
64
|
-
} | null>(null);
|
|
65
|
-
if (gridCacheRef.current === null) {
|
|
66
|
-
gridCacheRef.current = {
|
|
67
|
-
a: Skia.Path.Make(),
|
|
68
|
-
b: Skia.Path.Make(),
|
|
69
|
-
tick: false,
|
|
70
|
-
};
|
|
71
|
-
}
|
|
58
|
+
const gridBuilder = usePathBuilder();
|
|
72
59
|
|
|
73
60
|
const gridLinesPath = useDerivedValue(() => {
|
|
74
|
-
const
|
|
75
|
-
gridCache.tick = !gridCache.tick;
|
|
76
|
-
const path = gridCache.tick ? gridCache.a : gridCache.b;
|
|
77
|
-
path.reset();
|
|
61
|
+
const b = gridBuilder.value;
|
|
78
62
|
const items = entries.get();
|
|
79
63
|
const w = engine.canvasWidth.get();
|
|
80
64
|
for (let i = 0; i < items.length; i++) {
|
|
81
|
-
|
|
82
|
-
|
|
65
|
+
b.moveTo(padding.left, items[i].y);
|
|
66
|
+
b.lineTo(w - padding.right, items[i].y);
|
|
83
67
|
}
|
|
84
|
-
return
|
|
68
|
+
return b.detach();
|
|
85
69
|
});
|
|
86
70
|
|
|
87
71
|
const leftInset =
|
package/src/draw/markerAtlas.ts
CHANGED
|
@@ -207,17 +207,17 @@ function cellSpec(m: Marker, palette: LiveChartPalette, font: SkFont): CellSpec
|
|
|
207
207
|
w: box,
|
|
208
208
|
h: box,
|
|
209
209
|
draw: (canvas, cx, cy) => {
|
|
210
|
-
const
|
|
210
|
+
const pb = Skia.PathBuilder.Make();
|
|
211
211
|
for (let k = 0; k < 10; k++) {
|
|
212
212
|
const ang = -Math.PI / 2 + (k * Math.PI) / 5;
|
|
213
213
|
const rad = k % 2 === 0 ? outer : inner;
|
|
214
214
|
const px = cx + rad * Math.cos(ang);
|
|
215
215
|
const py = cy + rad * Math.sin(ang);
|
|
216
|
-
if (k === 0)
|
|
217
|
-
else
|
|
216
|
+
if (k === 0) pb.moveTo(px, py);
|
|
217
|
+
else pb.lineTo(px, py);
|
|
218
218
|
}
|
|
219
|
-
|
|
220
|
-
canvas.drawPath(
|
|
219
|
+
pb.close();
|
|
220
|
+
canvas.drawPath(pb.detach(), fillPaint(color));
|
|
221
221
|
},
|
|
222
222
|
};
|
|
223
223
|
}
|
|
@@ -229,15 +229,15 @@ function cellSpec(m: Marker, palette: LiveChartPalette, font: SkFont): CellSpec
|
|
|
229
229
|
w: box,
|
|
230
230
|
h: box,
|
|
231
231
|
draw: (canvas, cx, cy) => {
|
|
232
|
-
const
|
|
232
|
+
const pb = Skia.PathBuilder.Make();
|
|
233
233
|
for (let k = 0; k < 4; k++) {
|
|
234
234
|
const ang = (k * Math.PI) / 4;
|
|
235
235
|
const dx = L * Math.cos(ang);
|
|
236
236
|
const dy = L * Math.sin(ang);
|
|
237
|
-
|
|
238
|
-
|
|
237
|
+
pb.moveTo(cx - dx, cy - dy);
|
|
238
|
+
pb.lineTo(cx + dx, cy + dy);
|
|
239
239
|
}
|
|
240
|
-
canvas.drawPath(
|
|
240
|
+
canvas.drawPath(pb.detach(), strokePaint(color, 1.5, true));
|
|
241
241
|
},
|
|
242
242
|
};
|
|
243
243
|
}
|
package/src/hooks/useBadge.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { useRef } from "react";
|
|
1
|
+
import { type SkFont } from "@shopify/react-native-skia";
|
|
3
2
|
import {
|
|
4
3
|
useDerivedValue,
|
|
5
4
|
useSharedValue,
|
|
@@ -25,6 +24,7 @@ import type {
|
|
|
25
24
|
LiveChartPalette,
|
|
26
25
|
Momentum,
|
|
27
26
|
} from "../types";
|
|
27
|
+
import { usePathBuilder } from "./usePathBuilder";
|
|
28
28
|
|
|
29
29
|
export function useBadge(
|
|
30
30
|
engine: ChartEngineWithLiveValue,
|
|
@@ -48,32 +48,17 @@ export function useBadge(
|
|
|
48
48
|
const downRgb = hexToRgb(palette.dotDown);
|
|
49
49
|
const accentRgb = hexToRgb(palette.badgeBg);
|
|
50
50
|
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
|
|
54
|
-
const cacheRef = useRef<{
|
|
55
|
-
a: SkPath;
|
|
56
|
-
b: SkPath;
|
|
57
|
-
tick: boolean;
|
|
58
|
-
} | null>(null);
|
|
59
|
-
if (cacheRef.current === null) {
|
|
60
|
-
cacheRef.current = {
|
|
61
|
-
a: Skia.Path.Make(),
|
|
62
|
-
b: Skia.Path.Make(),
|
|
63
|
-
tick: false,
|
|
64
|
-
};
|
|
65
|
-
}
|
|
51
|
+
// Pill path is built into a reused PathBuilder and finalized with detach()
|
|
52
|
+
// each frame — a fresh immutable SkPath, no per-frame Skia.Path.Make().
|
|
53
|
+
const badgeBuilder = usePathBuilder();
|
|
66
54
|
|
|
67
55
|
const badge = useDerivedValue(() => {
|
|
68
|
-
const
|
|
56
|
+
const b = badgeBuilder.value;
|
|
69
57
|
const w = engine.canvasWidth.get();
|
|
70
58
|
const h = engine.canvasHeight.get();
|
|
71
|
-
cache.tick = !cache.tick;
|
|
72
|
-
const path = cache.tick ? cache.a : cache.b;
|
|
73
|
-
path.reset();
|
|
74
59
|
if (w === 0 || h === 0) {
|
|
75
60
|
return {
|
|
76
|
-
path,
|
|
61
|
+
path: b.detach(),
|
|
77
62
|
textX: 0,
|
|
78
63
|
textY: 0,
|
|
79
64
|
text: "",
|
|
@@ -108,7 +93,7 @@ export function useBadge(
|
|
|
108
93
|
const bodyLeft = Math.max(badgeMetrics.marginEdge, bodyRight - pillW);
|
|
109
94
|
const pillBodyW = bodyRight - bodyLeft;
|
|
110
95
|
textX = (bodyLeft + bodyRight - textW) / 2;
|
|
111
|
-
|
|
96
|
+
b.addRRect({
|
|
112
97
|
rect: { x: bodyLeft, y: badgeY, width: pillBodyW, height: pillH },
|
|
113
98
|
rx: r,
|
|
114
99
|
ry: r,
|
|
@@ -132,16 +117,16 @@ export function useBadge(
|
|
|
132
117
|
const badgeX = w - padding.right + badgeMetrics.dotGap;
|
|
133
118
|
const cx = tl + pillW - r;
|
|
134
119
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
120
|
+
b.moveTo(badgeX + tl, badgeY);
|
|
121
|
+
b.lineTo(badgeX + cx, badgeY);
|
|
122
|
+
b.arcToOval(
|
|
138
123
|
{ x: badgeX + cx - r, y: badgeY, width: r * 2, height: pillH },
|
|
139
124
|
-90,
|
|
140
125
|
180,
|
|
141
126
|
false,
|
|
142
127
|
);
|
|
143
|
-
|
|
144
|
-
|
|
128
|
+
b.lineTo(badgeX + tl, badgeY + pillH);
|
|
129
|
+
b.cubicTo(
|
|
145
130
|
badgeX + badgeMetrics.tailLength + 2,
|
|
146
131
|
badgeY + pillH,
|
|
147
132
|
badgeX + 3,
|
|
@@ -149,7 +134,7 @@ export function useBadge(
|
|
|
149
134
|
badgeX,
|
|
150
135
|
badgeY + r,
|
|
151
136
|
);
|
|
152
|
-
|
|
137
|
+
b.cubicTo(
|
|
153
138
|
badgeX + 3,
|
|
154
139
|
badgeY + r - badgeMetrics.tailSpread,
|
|
155
140
|
badgeX + badgeMetrics.tailLength + 2,
|
|
@@ -157,9 +142,9 @@ export function useBadge(
|
|
|
157
142
|
badgeX + tl,
|
|
158
143
|
badgeY,
|
|
159
144
|
);
|
|
160
|
-
|
|
145
|
+
b.close();
|
|
161
146
|
} else {
|
|
162
|
-
|
|
147
|
+
b.addRRect({
|
|
163
148
|
rect: { x: bodyLeft, y: badgeY, width: pillW, height: pillH },
|
|
164
149
|
rx: r,
|
|
165
150
|
ry: r,
|
|
@@ -199,7 +184,7 @@ export function useBadge(
|
|
|
199
184
|
const textColor =
|
|
200
185
|
variant === "minimal" ? "rgba(100,100,100,1)" : palette.badgeText;
|
|
201
186
|
|
|
202
|
-
return { path, textX, textY, text, bgColor, textColor };
|
|
187
|
+
return { path: b.detach(), textX, textY, text, bgColor, textColor };
|
|
203
188
|
});
|
|
204
189
|
|
|
205
190
|
return badge;
|