react-native-livechart 4.21.0 → 4.21.1
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/AxisLabelOverlay.d.ts +5 -1
- package/dist/components/AxisLabelOverlay.d.ts.map +1 -1
- package/dist/components/CrosshairOverlay.d.ts +4 -2
- package/dist/components/CrosshairOverlay.d.ts.map +1 -1
- package/dist/components/CustomMarkerOverlay.d.ts.map +1 -1
- package/dist/components/DegenParticlesOverlay.d.ts.map +1 -1
- package/dist/components/ExtremaConnectorOverlay.d.ts +5 -1
- package/dist/components/ExtremaConnectorOverlay.d.ts.map +1 -1
- package/dist/components/LiveChart.d.ts.map +1 -1
- package/dist/components/LiveChartSeries.d.ts.map +1 -1
- package/dist/components/MarkerOverlay.d.ts +4 -2
- package/dist/components/MarkerOverlay.d.ts.map +1 -1
- package/dist/components/ReferenceLineOverlay.d.ts +1 -1
- package/dist/components/ReferenceLineOverlay.d.ts.map +1 -1
- package/dist/core/chartDataPresence.d.ts +8 -0
- package/dist/core/chartDataPresence.d.ts.map +1 -0
- package/dist/core/useLiveChartEngine.d.ts.map +1 -1
- package/dist/core/useLiveChartSeriesEngine.d.ts.map +1 -1
- package/dist/draw/markerAtlas.d.ts +3 -2
- package/dist/draw/markerAtlas.d.ts.map +1 -1
- package/dist/hooks/useChartReveal.d.ts +1 -1
- package/dist/hooks/useChartReveal.d.ts.map +1 -1
- package/dist/hooks/useCrosshair.d.ts.map +1 -1
- package/dist/hooks/useDegen.d.ts.map +1 -1
- package/dist/hooks/useLiveChartHasData.d.ts +2 -2
- package/dist/hooks/useLiveChartHasData.d.ts.map +1 -1
- package/dist/hooks/useReverseMorphEngineInputs.d.ts.map +1 -1
- package/dist/types.d.ts +3 -3
- package/package.json +1 -1
- package/src/components/AxisLabelOverlay.tsx +162 -81
- package/src/components/CrosshairOverlay.tsx +394 -147
- package/src/components/CustomMarkerOverlay.tsx +9 -6
- package/src/components/DegenParticlesOverlay.tsx +34 -23
- package/src/components/ExtremaConnectorOverlay.tsx +43 -3
- package/src/components/LiveChart.tsx +1692 -1025
- package/src/components/LiveChartSeries.tsx +445 -296
- package/src/components/MarkerOverlay.tsx +215 -156
- package/src/components/ReferenceLineOverlay.tsx +77 -50
- package/src/core/chartDataPresence.ts +24 -0
- package/src/core/useLiveChartEngine.ts +6 -3
- package/src/core/useLiveChartSeriesEngine.ts +2 -0
- package/src/draw/markerAtlas.ts +8 -3
- package/src/hooks/useChartReveal.ts +1 -1
- package/src/hooks/useCrosshair.ts +69 -46
- package/src/hooks/useDegen.ts +82 -53
- package/src/hooks/useLiveChartHasData.ts +8 -4
- package/src/hooks/useReverseMorphEngineInputs.ts +11 -20
- package/src/types.ts +3 -3
|
@@ -17,41 +17,7 @@ import type { ResolvedSelectionDotConfig } from "../core/resolveConfig";
|
|
|
17
17
|
import type { ChartEngineLayout } from "../core/useLiveChartEngine";
|
|
18
18
|
import { SelectionDotSlot } from "./SelectionDot";
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
scrubX,
|
|
22
|
-
crosshairOpacity,
|
|
23
|
-
tooltipLayout,
|
|
24
|
-
engine,
|
|
25
|
-
padding,
|
|
26
|
-
palette,
|
|
27
|
-
font,
|
|
28
|
-
showTooltip = true,
|
|
29
|
-
children,
|
|
30
|
-
renderTooltip,
|
|
31
|
-
lineTop,
|
|
32
|
-
showLine = true,
|
|
33
|
-
selectionDot,
|
|
34
|
-
selectionY,
|
|
35
|
-
scrubActive,
|
|
36
|
-
selectionColor,
|
|
37
|
-
dimOpacity = 0.3,
|
|
38
|
-
liveDotExtent = 0,
|
|
39
|
-
crosshairLineColor,
|
|
40
|
-
crosshairStrokeWidth = 1,
|
|
41
|
-
crosshairOvershoot = 0,
|
|
42
|
-
crosshairFade = true,
|
|
43
|
-
crosshairFadeDistance = 4,
|
|
44
|
-
crosshairLineCap,
|
|
45
|
-
crosshairDash,
|
|
46
|
-
crosshairDimColor,
|
|
47
|
-
tooltipBackground,
|
|
48
|
-
tooltipColor,
|
|
49
|
-
tooltipBorderColor,
|
|
50
|
-
tooltipBorderRadius = 5,
|
|
51
|
-
tooltipShowValue = true,
|
|
52
|
-
tooltipShowTime = true,
|
|
53
|
-
opaqueCanvas = false,
|
|
54
|
-
}: {
|
|
20
|
+
type CrosshairOverlayProps = {
|
|
55
21
|
scrubX: SharedValue<number>;
|
|
56
22
|
crosshairOpacity: SharedValue<number>;
|
|
57
23
|
tooltipLayout: SharedValue<TooltipLayout>;
|
|
@@ -116,32 +82,62 @@ export function CrosshairOverlay({
|
|
|
116
82
|
tooltipShowTime?: boolean;
|
|
117
83
|
/** Paint the owned background instead of erasing destination alpha. */
|
|
118
84
|
opaqueCanvas?: boolean;
|
|
119
|
-
}
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export function CrosshairOverlay({
|
|
88
|
+
scrubX,
|
|
89
|
+
crosshairOpacity,
|
|
90
|
+
tooltipLayout,
|
|
91
|
+
engine,
|
|
92
|
+
padding,
|
|
93
|
+
palette,
|
|
94
|
+
font,
|
|
95
|
+
showTooltip = true,
|
|
96
|
+
children,
|
|
97
|
+
renderTooltip,
|
|
98
|
+
lineTop,
|
|
99
|
+
showLine = true,
|
|
100
|
+
selectionDot,
|
|
101
|
+
selectionY,
|
|
102
|
+
scrubActive,
|
|
103
|
+
selectionColor,
|
|
104
|
+
dimOpacity = 0.3,
|
|
105
|
+
liveDotExtent = 0,
|
|
106
|
+
crosshairLineColor,
|
|
107
|
+
crosshairStrokeWidth = 1,
|
|
108
|
+
crosshairOvershoot = 0,
|
|
109
|
+
crosshairFade = true,
|
|
110
|
+
crosshairFadeDistance = 4,
|
|
111
|
+
crosshairLineCap,
|
|
112
|
+
crosshairDash,
|
|
113
|
+
crosshairDimColor,
|
|
114
|
+
tooltipBackground,
|
|
115
|
+
tooltipColor,
|
|
116
|
+
tooltipBorderColor,
|
|
117
|
+
tooltipBorderRadius = 5,
|
|
118
|
+
tooltipShowValue = true,
|
|
119
|
+
tooltipShowTime = true,
|
|
120
|
+
opaqueCanvas = false,
|
|
121
|
+
}: CrosshairOverlayProps) {
|
|
120
122
|
// Explicit dependency arrays: with React Compiler enabled, Reanimated's
|
|
121
123
|
// auto-detected worklet dependencies can change array size between renders
|
|
122
124
|
// (e.g. when `liveDotExtent` flips 0 → the live-dot extent), which trips
|
|
123
125
|
// React's "final argument changed size between renders" error. Listing the
|
|
124
126
|
// captured plain values keeps the dependency array a constant size. SharedValue
|
|
125
127
|
// reads stay reactive regardless of this list.
|
|
126
|
-
const p1 = useDerivedValue(
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
},
|
|
136
|
-
[scrubX, padding.top, lineTop, crosshairOvershoot],
|
|
137
|
-
);
|
|
128
|
+
const p1 = useDerivedValue(() => {
|
|
129
|
+
// A top-pinned custom tooltip pushes the line's start down to its measured
|
|
130
|
+
// bottom (lineTop) so the line stops at the label; -1 → no top tooltip.
|
|
131
|
+
const lt = lineTop?.value ?? -1;
|
|
132
|
+
return {
|
|
133
|
+
x: scrubX.value,
|
|
134
|
+
y: lt >= 0 ? lt : padding.top - crosshairOvershoot,
|
|
135
|
+
};
|
|
136
|
+
}, [scrubX, padding.top, lineTop, crosshairOvershoot]);
|
|
138
137
|
const p2 = useDerivedValue(
|
|
139
138
|
() => ({
|
|
140
139
|
x: scrubX.value,
|
|
141
|
-
y:
|
|
142
|
-
engine.canvasHeight.value -
|
|
143
|
-
padding.bottom +
|
|
144
|
-
crosshairOvershoot,
|
|
140
|
+
y: engine.canvasHeight.value - padding.bottom + crosshairOvershoot,
|
|
145
141
|
}),
|
|
146
142
|
[scrubX, engine.canvasHeight, padding.bottom, crosshairOvershoot],
|
|
147
143
|
);
|
|
@@ -197,111 +193,362 @@ export function CrosshairOverlay({
|
|
|
197
193
|
|
|
198
194
|
return (
|
|
199
195
|
<>
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
196
|
+
<CrosshairDimLayer
|
|
197
|
+
scrubX={scrubX}
|
|
198
|
+
paddingTop={padding.top}
|
|
199
|
+
dimWidth={dimWidth}
|
|
200
|
+
dimHeight={dimHeight}
|
|
201
|
+
crosshairOpacity={crosshairOpacity}
|
|
202
|
+
crosshairDimColor={crosshairDimColor}
|
|
203
|
+
dimOpacity={dimOpacity}
|
|
204
|
+
opaqueCanvas={opaqueCanvas}
|
|
205
|
+
backgroundColor={backgroundColor}
|
|
206
|
+
opaqueDimOpacity={opaqueDimOpacity}
|
|
207
|
+
dimErase={dimErase}
|
|
208
|
+
/>
|
|
209
|
+
<CrosshairVisibleLayer
|
|
210
|
+
opacity={visibleOpacity}
|
|
211
|
+
p1={p1}
|
|
212
|
+
p2={p2}
|
|
213
|
+
palette={palette}
|
|
214
|
+
showLine={showLine}
|
|
215
|
+
crosshairLineColor={crosshairLineColor}
|
|
216
|
+
crosshairStrokeWidth={crosshairStrokeWidth}
|
|
217
|
+
crosshairLineCap={crosshairLineCap}
|
|
218
|
+
crosshairDash={crosshairDash}
|
|
219
|
+
selectionDot={selectionDot}
|
|
220
|
+
scrubX={scrubX}
|
|
221
|
+
selectionY={selectionY}
|
|
222
|
+
scrubActive={scrubActive}
|
|
223
|
+
selectionColor={selectionColor}
|
|
224
|
+
showTooltip={showTooltip}
|
|
225
|
+
tipX={tipX}
|
|
226
|
+
tipY={tipY}
|
|
227
|
+
tipW={tipW}
|
|
228
|
+
tipH={tipH}
|
|
229
|
+
tooltipBorderRadius={tooltipBorderRadius}
|
|
230
|
+
tooltipBackground={tooltipBackground}
|
|
231
|
+
tooltipBorderColor={tooltipBorderColor}
|
|
232
|
+
renderTooltip={renderTooltip}
|
|
233
|
+
tooltipShowValue={tooltipShowValue}
|
|
234
|
+
effectiveTooltipShowTime={effectiveTooltipShowTime}
|
|
235
|
+
valueTextX={valueTextX}
|
|
236
|
+
timeTextX={timeTextX}
|
|
237
|
+
line1Y={line1Y}
|
|
238
|
+
line2Y={line2Y}
|
|
239
|
+
valueStr={valueStr}
|
|
240
|
+
timeStr={timeStr}
|
|
241
|
+
font={font}
|
|
242
|
+
tooltipColor={tooltipColor}
|
|
243
|
+
>
|
|
244
|
+
{children}
|
|
245
|
+
</CrosshairVisibleLayer>
|
|
246
|
+
</>
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function CrosshairDimLayer({
|
|
251
|
+
scrubX,
|
|
252
|
+
paddingTop,
|
|
253
|
+
dimWidth,
|
|
254
|
+
dimHeight,
|
|
255
|
+
crosshairOpacity,
|
|
256
|
+
crosshairDimColor,
|
|
257
|
+
dimOpacity,
|
|
258
|
+
opaqueCanvas,
|
|
259
|
+
backgroundColor,
|
|
260
|
+
opaqueDimOpacity,
|
|
261
|
+
dimErase,
|
|
262
|
+
}: {
|
|
263
|
+
scrubX: SharedValue<number>;
|
|
264
|
+
paddingTop: number;
|
|
265
|
+
dimWidth: SharedValue<number>;
|
|
266
|
+
dimHeight: SharedValue<number>;
|
|
267
|
+
crosshairOpacity: SharedValue<number>;
|
|
268
|
+
crosshairDimColor?: string;
|
|
269
|
+
dimOpacity: number;
|
|
270
|
+
opaqueCanvas: boolean;
|
|
271
|
+
backgroundColor: string;
|
|
272
|
+
opaqueDimOpacity: SharedValue<number>;
|
|
273
|
+
dimErase: SharedValue<string>;
|
|
274
|
+
}) {
|
|
275
|
+
if (crosshairDimColor !== undefined) {
|
|
276
|
+
return (
|
|
277
|
+
<Group opacity={crosshairOpacity}>
|
|
212
278
|
<Rect
|
|
213
279
|
x={scrubX}
|
|
214
|
-
y={
|
|
280
|
+
y={paddingTop}
|
|
215
281
|
width={dimWidth}
|
|
216
282
|
height={dimHeight}
|
|
217
|
-
color={
|
|
218
|
-
opacity={opaqueDimOpacity}
|
|
283
|
+
color={crosshairDimColor}
|
|
219
284
|
/>
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
285
|
+
</Group>
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
if (dimOpacity >= 1) return null;
|
|
289
|
+
if (opaqueCanvas) {
|
|
290
|
+
return (
|
|
291
|
+
<Rect
|
|
292
|
+
x={scrubX}
|
|
293
|
+
y={paddingTop}
|
|
294
|
+
width={dimWidth}
|
|
295
|
+
height={dimHeight}
|
|
296
|
+
color={backgroundColor}
|
|
297
|
+
opacity={opaqueDimOpacity}
|
|
298
|
+
/>
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
return (
|
|
302
|
+
<Group blendMode="dstOut">
|
|
303
|
+
<Rect
|
|
304
|
+
x={scrubX}
|
|
305
|
+
y={paddingTop}
|
|
306
|
+
width={dimWidth}
|
|
307
|
+
height={dimHeight}
|
|
308
|
+
color={dimErase}
|
|
309
|
+
/>
|
|
310
|
+
</Group>
|
|
311
|
+
);
|
|
312
|
+
}
|
|
233
313
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
314
|
+
function CrosshairVisibleLayer({
|
|
315
|
+
opacity,
|
|
316
|
+
p1,
|
|
317
|
+
p2,
|
|
318
|
+
palette,
|
|
319
|
+
showLine,
|
|
320
|
+
crosshairLineColor,
|
|
321
|
+
crosshairStrokeWidth,
|
|
322
|
+
crosshairLineCap,
|
|
323
|
+
crosshairDash,
|
|
324
|
+
selectionDot,
|
|
325
|
+
scrubX,
|
|
326
|
+
selectionY,
|
|
327
|
+
scrubActive,
|
|
328
|
+
selectionColor,
|
|
329
|
+
showTooltip,
|
|
330
|
+
tipX,
|
|
331
|
+
tipY,
|
|
332
|
+
tipW,
|
|
333
|
+
tipH,
|
|
334
|
+
tooltipBorderRadius,
|
|
335
|
+
tooltipBackground,
|
|
336
|
+
tooltipBorderColor,
|
|
337
|
+
children,
|
|
338
|
+
renderTooltip,
|
|
339
|
+
tooltipShowValue,
|
|
340
|
+
effectiveTooltipShowTime,
|
|
341
|
+
valueTextX,
|
|
342
|
+
timeTextX,
|
|
343
|
+
line1Y,
|
|
344
|
+
line2Y,
|
|
345
|
+
valueStr,
|
|
346
|
+
timeStr,
|
|
347
|
+
font,
|
|
348
|
+
tooltipColor,
|
|
349
|
+
}: {
|
|
350
|
+
opacity: SharedValue<number>;
|
|
351
|
+
p1: SharedValue<{ x: number; y: number }>;
|
|
352
|
+
p2: SharedValue<{ x: number; y: number }>;
|
|
353
|
+
palette: LiveChartPalette;
|
|
354
|
+
showLine: boolean;
|
|
355
|
+
crosshairLineColor?: string;
|
|
356
|
+
crosshairStrokeWidth: number;
|
|
357
|
+
crosshairLineCap?: "butt" | "round" | "square";
|
|
358
|
+
crosshairDash?: number[];
|
|
359
|
+
selectionDot?: ResolvedSelectionDotConfig | null;
|
|
360
|
+
scrubX: SharedValue<number>;
|
|
361
|
+
selectionY?: SharedValue<number>;
|
|
362
|
+
scrubActive: SharedValue<number> | SharedValue<boolean>;
|
|
363
|
+
selectionColor?: string;
|
|
364
|
+
showTooltip: boolean;
|
|
365
|
+
tipX: SharedValue<number>;
|
|
366
|
+
tipY: SharedValue<number>;
|
|
367
|
+
tipW: SharedValue<number>;
|
|
368
|
+
tipH: SharedValue<number>;
|
|
369
|
+
tooltipBorderRadius: number;
|
|
370
|
+
tooltipBackground?: string;
|
|
371
|
+
tooltipBorderColor?: string;
|
|
372
|
+
children?: ReactNode;
|
|
373
|
+
renderTooltip?: () => ReactNode;
|
|
374
|
+
tooltipShowValue: boolean;
|
|
375
|
+
effectiveTooltipShowTime: boolean;
|
|
376
|
+
valueTextX: SharedValue<number>;
|
|
377
|
+
timeTextX: SharedValue<number>;
|
|
378
|
+
line1Y: SharedValue<number>;
|
|
379
|
+
line2Y: SharedValue<number>;
|
|
380
|
+
valueStr: SharedValue<string>;
|
|
381
|
+
timeStr: SharedValue<string>;
|
|
382
|
+
font: SkFont;
|
|
383
|
+
tooltipColor?: string;
|
|
384
|
+
}) {
|
|
385
|
+
return (
|
|
386
|
+
<Group opacity={opacity}>
|
|
387
|
+
<CrosshairLine
|
|
388
|
+
show={showLine}
|
|
389
|
+
p1={p1}
|
|
390
|
+
p2={p2}
|
|
391
|
+
color={crosshairLineColor ?? palette.crosshairLine}
|
|
392
|
+
strokeWidth={crosshairStrokeWidth}
|
|
393
|
+
strokeCap={crosshairLineCap}
|
|
394
|
+
dash={crosshairDash}
|
|
395
|
+
/>
|
|
396
|
+
<SelectionDotSlot
|
|
397
|
+
config={selectionDot}
|
|
398
|
+
x={scrubX}
|
|
399
|
+
y={selectionY}
|
|
400
|
+
active={scrubActive}
|
|
401
|
+
opacity={opacity}
|
|
402
|
+
color={selectionColor ?? palette.line}
|
|
403
|
+
/>
|
|
404
|
+
<CrosshairTooltip
|
|
405
|
+
show={showTooltip}
|
|
406
|
+
tipX={tipX}
|
|
407
|
+
tipY={tipY}
|
|
408
|
+
tipW={tipW}
|
|
409
|
+
tipH={tipH}
|
|
410
|
+
borderRadius={tooltipBorderRadius}
|
|
411
|
+
background={tooltipBackground ?? palette.tooltipBg}
|
|
412
|
+
borderColor={tooltipBorderColor ?? palette.tooltipBorder}
|
|
413
|
+
renderTooltip={renderTooltip}
|
|
414
|
+
showValue={tooltipShowValue}
|
|
415
|
+
showTime={effectiveTooltipShowTime}
|
|
416
|
+
valueTextX={valueTextX}
|
|
417
|
+
timeTextX={timeTextX}
|
|
418
|
+
line1Y={line1Y}
|
|
419
|
+
line2Y={line2Y}
|
|
420
|
+
valueStr={valueStr}
|
|
421
|
+
timeStr={timeStr}
|
|
422
|
+
font={font}
|
|
423
|
+
valueColor={tooltipColor ?? palette.tooltipText}
|
|
424
|
+
timeColor={palette.gridLabel}
|
|
425
|
+
>
|
|
426
|
+
{children}
|
|
427
|
+
</CrosshairTooltip>
|
|
428
|
+
</Group>
|
|
429
|
+
);
|
|
430
|
+
}
|
|
246
431
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
432
|
+
function CrosshairLine({
|
|
433
|
+
show,
|
|
434
|
+
p1,
|
|
435
|
+
p2,
|
|
436
|
+
color,
|
|
437
|
+
strokeWidth,
|
|
438
|
+
strokeCap,
|
|
439
|
+
dash,
|
|
440
|
+
}: {
|
|
441
|
+
show: boolean;
|
|
442
|
+
p1: SharedValue<{ x: number; y: number }>;
|
|
443
|
+
p2: SharedValue<{ x: number; y: number }>;
|
|
444
|
+
color: string;
|
|
445
|
+
strokeWidth: number;
|
|
446
|
+
strokeCap?: "butt" | "round" | "square";
|
|
447
|
+
dash?: number[];
|
|
448
|
+
}) {
|
|
449
|
+
if (!show) return null;
|
|
450
|
+
return (
|
|
451
|
+
<Line
|
|
452
|
+
p1={p1}
|
|
453
|
+
p2={p2}
|
|
454
|
+
color={color}
|
|
455
|
+
strokeWidth={strokeWidth}
|
|
456
|
+
strokeCap={strokeCap}
|
|
457
|
+
>
|
|
458
|
+
{dash ? <DashPathEffect intervals={dash} /> : null}
|
|
459
|
+
</Line>
|
|
460
|
+
);
|
|
461
|
+
}
|
|
257
462
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
463
|
+
function CrosshairTooltip({
|
|
464
|
+
show,
|
|
465
|
+
tipX,
|
|
466
|
+
tipY,
|
|
467
|
+
tipW,
|
|
468
|
+
tipH,
|
|
469
|
+
borderRadius,
|
|
470
|
+
background,
|
|
471
|
+
borderColor,
|
|
472
|
+
children,
|
|
473
|
+
renderTooltip,
|
|
474
|
+
showValue,
|
|
475
|
+
showTime,
|
|
476
|
+
valueTextX,
|
|
477
|
+
timeTextX,
|
|
478
|
+
line1Y,
|
|
479
|
+
line2Y,
|
|
480
|
+
valueStr,
|
|
481
|
+
timeStr,
|
|
482
|
+
font,
|
|
483
|
+
valueColor,
|
|
484
|
+
timeColor,
|
|
485
|
+
}: {
|
|
486
|
+
show: boolean;
|
|
487
|
+
tipX: SharedValue<number>;
|
|
488
|
+
tipY: SharedValue<number>;
|
|
489
|
+
tipW: SharedValue<number>;
|
|
490
|
+
tipH: SharedValue<number>;
|
|
491
|
+
borderRadius: number;
|
|
492
|
+
background: string;
|
|
493
|
+
borderColor: string;
|
|
494
|
+
children?: ReactNode;
|
|
495
|
+
renderTooltip?: () => ReactNode;
|
|
496
|
+
showValue: boolean;
|
|
497
|
+
showTime: boolean;
|
|
498
|
+
valueTextX: SharedValue<number>;
|
|
499
|
+
timeTextX: SharedValue<number>;
|
|
500
|
+
line1Y: SharedValue<number>;
|
|
501
|
+
line2Y: SharedValue<number>;
|
|
502
|
+
valueStr: SharedValue<string>;
|
|
503
|
+
timeStr: SharedValue<string>;
|
|
504
|
+
font: SkFont;
|
|
505
|
+
valueColor: string;
|
|
506
|
+
timeColor: string;
|
|
507
|
+
}) {
|
|
508
|
+
if (!show) return null;
|
|
509
|
+
const body = children ?? renderTooltip?.();
|
|
510
|
+
return (
|
|
511
|
+
<>
|
|
512
|
+
<RoundedRect
|
|
513
|
+
x={tipX}
|
|
514
|
+
y={tipY}
|
|
515
|
+
width={tipW}
|
|
516
|
+
height={tipH}
|
|
517
|
+
r={borderRadius}
|
|
518
|
+
color={background}
|
|
519
|
+
/>
|
|
520
|
+
<RoundedRect
|
|
521
|
+
x={tipX}
|
|
522
|
+
y={tipY}
|
|
523
|
+
width={tipW}
|
|
524
|
+
height={tipH}
|
|
525
|
+
r={borderRadius}
|
|
526
|
+
color={borderColor}
|
|
527
|
+
style="stroke"
|
|
528
|
+
strokeWidth={1}
|
|
529
|
+
/>
|
|
530
|
+
{body ?? (
|
|
531
|
+
<Group>
|
|
532
|
+
{showValue ? (
|
|
533
|
+
<SkiaText
|
|
534
|
+
x={valueTextX}
|
|
535
|
+
y={line1Y}
|
|
536
|
+
text={valueStr}
|
|
537
|
+
font={font}
|
|
538
|
+
color={valueColor}
|
|
267
539
|
/>
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
color={
|
|
276
|
-
style="stroke"
|
|
277
|
-
strokeWidth={1}
|
|
540
|
+
) : null}
|
|
541
|
+
{showTime ? (
|
|
542
|
+
<SkiaText
|
|
543
|
+
x={timeTextX}
|
|
544
|
+
y={showValue ? line2Y : line1Y}
|
|
545
|
+
text={timeStr}
|
|
546
|
+
font={font}
|
|
547
|
+
color={timeColor}
|
|
278
548
|
/>
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
{tooltipShowValue && (
|
|
283
|
-
<SkiaText
|
|
284
|
-
x={valueTextX}
|
|
285
|
-
y={line1Y}
|
|
286
|
-
text={valueStr}
|
|
287
|
-
font={font}
|
|
288
|
-
color={tooltipColor ?? palette.tooltipText}
|
|
289
|
-
/>
|
|
290
|
-
)}
|
|
291
|
-
{effectiveTooltipShowTime && (
|
|
292
|
-
<SkiaText
|
|
293
|
-
x={timeTextX}
|
|
294
|
-
y={tooltipShowValue ? line2Y : line1Y}
|
|
295
|
-
text={timeStr}
|
|
296
|
-
font={font}
|
|
297
|
-
color={palette.gridLabel}
|
|
298
|
-
/>
|
|
299
|
-
)}
|
|
300
|
-
</Group>
|
|
301
|
-
)}
|
|
302
|
-
</>
|
|
303
|
-
)}
|
|
304
|
-
</Group>
|
|
549
|
+
) : null}
|
|
550
|
+
</Group>
|
|
551
|
+
)}
|
|
305
552
|
</>
|
|
306
553
|
);
|
|
307
554
|
}
|
|
@@ -30,6 +30,14 @@ import type {
|
|
|
30
30
|
/** Live group flags surfaced to `renderMarker` (mirrored to JS on change). */
|
|
31
31
|
type GroupState = Record<string, { isGrouped: boolean; groupCount: number }>;
|
|
32
32
|
|
|
33
|
+
function markerIdSet(key: string): Record<string, true> {
|
|
34
|
+
const ids: Record<string, true> = {};
|
|
35
|
+
for (const id of key.split("\x1f")) {
|
|
36
|
+
if (id) ids[id] = true;
|
|
37
|
+
}
|
|
38
|
+
return ids;
|
|
39
|
+
}
|
|
40
|
+
|
|
33
41
|
/**
|
|
34
42
|
* One custom-rendered marker: a React Native element floated over the canvas and
|
|
35
43
|
* pinned to the marker's live clustered `(x, y)` position. It reads its slot out
|
|
@@ -168,12 +176,7 @@ export function CustomMarkerOverlay({
|
|
|
168
176
|
|
|
169
177
|
// Stable id-set of customized markers (used by the UI-thread map below).
|
|
170
178
|
const customKey = custom.map((c) => c.marker.id).join("\x1f");
|
|
171
|
-
const customIds = useMemo
|
|
172
|
-
const o: Record<string, true> = {};
|
|
173
|
-
for (const c of custom) o[c.marker.id] = true;
|
|
174
|
-
return o;
|
|
175
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps -- recomputed when the id set (customKey) changes
|
|
176
|
-
}, [customKey]);
|
|
179
|
+
const customIds = useMemo(() => markerIdSet(customKey), [customKey]);
|
|
177
180
|
|
|
178
181
|
// One project+cluster over ALL markers each frame, exposed as an id→position map
|
|
179
182
|
// for just the custom markers (so views read their own slot by id). Reuses a
|
|
@@ -17,6 +17,36 @@ import type { ChartEngineLayout } from "../core/useLiveChartEngine";
|
|
|
17
17
|
|
|
18
18
|
type DegenPack = SharedValue<Float64Array<ArrayBuffer>>;
|
|
19
19
|
|
|
20
|
+
type ParticlePool = {
|
|
21
|
+
a: { transforms: SkRSXform[]; sprites: SkRect[]; colors: SkColor[] };
|
|
22
|
+
b: { transforms: SkRSXform[]; sprites: SkRect[]; colors: SkColor[] };
|
|
23
|
+
tick: boolean;
|
|
24
|
+
instances: ReturnType<typeof buildParticleInstances>;
|
|
25
|
+
instancePool: ReturnType<typeof buildParticleInstances>;
|
|
26
|
+
spriteRect: SkRect;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
function makeParticlePool(
|
|
30
|
+
particleSlotCount: number,
|
|
31
|
+
spriteSize: number,
|
|
32
|
+
): ParticlePool {
|
|
33
|
+
const instancePool = Array.from({ length: particleSlotCount }, () => ({
|
|
34
|
+
x: 0,
|
|
35
|
+
y: 0,
|
|
36
|
+
scale: 0,
|
|
37
|
+
alpha: 0,
|
|
38
|
+
colorIndex: 0,
|
|
39
|
+
}));
|
|
40
|
+
return {
|
|
41
|
+
a: { transforms: [], sprites: [], colors: [] },
|
|
42
|
+
b: { transforms: [], sprites: [], colors: [] },
|
|
43
|
+
tick: false,
|
|
44
|
+
instances: [],
|
|
45
|
+
instancePool,
|
|
46
|
+
spriteRect: Skia.XYWHRect(0, 0, spriteSize, spriteSize),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
20
50
|
/**
|
|
21
51
|
* Renders the degen particle burst with a single `drawAtlas` call.
|
|
22
52
|
*
|
|
@@ -64,30 +94,11 @@ export function DegenParticlesOverlay({
|
|
|
64
94
|
// memoizes this on `colorList`.
|
|
65
95
|
const colorRgb = colorList.map((c) => parseColorRgb(c));
|
|
66
96
|
|
|
67
|
-
const poolRef = useRef<
|
|
68
|
-
a: { transforms: SkRSXform[]; sprites: SkRect[]; colors: SkColor[] };
|
|
69
|
-
b: { transforms: SkRSXform[]; sprites: SkRect[]; colors: SkColor[] };
|
|
70
|
-
tick: boolean;
|
|
71
|
-
instances: ReturnType<typeof buildParticleInstances>;
|
|
72
|
-
instancePool: ReturnType<typeof buildParticleInstances>;
|
|
73
|
-
spriteRect: SkRect;
|
|
74
|
-
} | null>(null);
|
|
97
|
+
const poolRef = useRef<ParticlePool | null>(null);
|
|
75
98
|
if (poolRef.current === null) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
scale: 0,
|
|
80
|
-
alpha: 0,
|
|
81
|
-
colorIndex: 0,
|
|
82
|
-
}));
|
|
83
|
-
poolRef.current = {
|
|
84
|
-
a: { transforms: [], sprites: [], colors: [] },
|
|
85
|
-
b: { transforms: [], sprites: [], colors: [] },
|
|
86
|
-
tick: false,
|
|
87
|
-
instances: [],
|
|
88
|
-
instancePool,
|
|
89
|
-
spriteRect: Skia.XYWHRect(0, 0, sprite.size, sprite.size),
|
|
90
|
-
};
|
|
99
|
+
// React permits this predictable lazy-ref initialization: https://react.dev/reference/react/useRef#avoiding-recreating-the-ref-contents
|
|
100
|
+
// react-doctor-disable-next-line react-doctor/no-ref-current-in-render -- false positive for React's documented lazy-ref exception
|
|
101
|
+
poolRef.current = makeParticlePool(particleSlotCount, sprite.size);
|
|
91
102
|
}
|
|
92
103
|
|
|
93
104
|
// Single per-frame worklet. Reads `packRevision` so it re-runs each frame
|