react-native-livechart 4.9.2 → 4.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 +24 -0
- package/dist/components/CrosshairLine.d.ts +3 -1
- package/dist/components/CrosshairLine.d.ts.map +1 -1
- package/dist/components/CrosshairOverlay.d.ts +3 -1
- package/dist/components/CrosshairOverlay.d.ts.map +1 -1
- package/dist/components/CustomReferenceLineOverlay.d.ts +4 -2
- package/dist/components/CustomReferenceLineOverlay.d.ts.map +1 -1
- package/dist/components/DegenParticlesOverlay.d.ts.map +1 -1
- package/dist/components/LeftEdgeFade.d.ts +3 -1
- package/dist/components/LeftEdgeFade.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/LoadingOverlay.d.ts +3 -1
- package/dist/components/LoadingOverlay.d.ts.map +1 -1
- package/dist/components/MarkerOverlay.d.ts.map +1 -1
- package/dist/components/MultiSeriesDots.d.ts +3 -1
- package/dist/components/MultiSeriesDots.d.ts.map +1 -1
- package/dist/components/MultiSeriesTooltipStack.d.ts.map +1 -1
- package/dist/components/MultiSeriesValueLabels.d.ts +3 -1
- package/dist/components/MultiSeriesValueLabels.d.ts.map +1 -1
- package/dist/components/MultiSeriesValueLines.d.ts +3 -1
- package/dist/components/MultiSeriesValueLines.d.ts.map +1 -1
- package/dist/components/ThresholdSplitShader.d.ts.map +1 -1
- package/dist/components/YAxisOverlay.d.ts +9 -1
- package/dist/components/YAxisOverlay.d.ts.map +1 -1
- package/dist/components/thresholdSplitShaderSource.d.ts +13 -0
- package/dist/components/thresholdSplitShaderSource.d.ts.map +1 -0
- package/dist/core/useLiveChartEngine.d.ts +9 -1
- package/dist/core/useLiveChartEngine.d.ts.map +1 -1
- package/dist/core/useLiveChartSeriesEngine.d.ts +10 -5
- package/dist/core/useLiveChartSeriesEngine.d.ts.map +1 -1
- package/dist/draw/markerAtlas.d.ts +2 -0
- package/dist/draw/markerAtlas.d.ts.map +1 -1
- package/dist/draw/particleAtlas.d.ts +3 -2
- package/dist/draw/particleAtlas.d.ts.map +1 -1
- package/dist/hooks/crosshairShared.d.ts +6 -6
- package/dist/hooks/crosshairShared.d.ts.map +1 -1
- package/dist/hooks/delayedPanGuard.d.ts +31 -0
- package/dist/hooks/delayedPanGuard.d.ts.map +1 -0
- package/dist/hooks/useChartPaths.d.ts.map +1 -1
- package/dist/hooks/useCrosshair.d.ts.map +1 -1
- package/dist/hooks/useCrosshairSeries.d.ts.map +1 -1
- package/dist/hooks/useMultiSeriesLinePaths.d.ts +4 -4
- package/dist/hooks/useMultiSeriesLinePaths.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/math/squiggly.d.ts +6 -3
- package/dist/math/squiggly.d.ts.map +1 -1
- package/dist/math/threshold.d.ts +2 -2
- package/dist/types.d.ts +35 -4
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/CrosshairLine.tsx +17 -0
- package/src/components/CrosshairOverlay.tsx +54 -36
- package/src/components/CustomReferenceLineOverlay.tsx +16 -7
- package/src/components/DegenParticlesOverlay.tsx +40 -5
- package/src/components/LeftEdgeFade.tsx +12 -6
- package/src/components/LiveChart.tsx +34 -16
- package/src/components/LiveChartSeries.tsx +94 -21
- package/src/components/LoadingOverlay.tsx +26 -12
- package/src/components/MarkerOverlay.tsx +31 -19
- package/src/components/MultiSeriesDots.tsx +4 -2
- package/src/components/MultiSeriesTooltipStack.tsx +4 -2
- package/src/components/MultiSeriesValueLabels.tsx +4 -2
- package/src/components/MultiSeriesValueLines.tsx +4 -2
- package/src/components/ThresholdSplitShader.tsx +5 -37
- package/src/components/YAxisOverlay.tsx +43 -1
- package/src/components/thresholdSplitShaderSource.ts +70 -0
- package/src/core/useLiveChartEngine.ts +110 -42
- package/src/core/useLiveChartSeriesEngine.ts +137 -70
- package/src/draw/markerAtlas.ts +22 -2
- package/src/draw/particleAtlas.ts +21 -10
- package/src/hooks/crosshairShared.ts +24 -18
- package/src/hooks/delayedPanGuard.ts +80 -0
- package/src/hooks/useChartPaths.ts +11 -0
- package/src/hooks/useCrosshair.ts +53 -10
- package/src/hooks/useCrosshairSeries.ts +49 -6
- package/src/hooks/useMultiSeriesLinePaths.ts +15 -9
- package/src/index.ts +1 -0
- package/src/math/squiggly.ts +28 -11
- package/src/math/threshold.ts +2 -2
- package/src/types.ts +40 -5
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
type SkRSXform,
|
|
6
6
|
type SkRect,
|
|
7
7
|
} from "@shopify/react-native-skia";
|
|
8
|
+
import { useRef } from "react";
|
|
8
9
|
import { useDerivedValue, type SharedValue } from "react-native-reanimated";
|
|
9
10
|
import {
|
|
10
11
|
buildParticleInstances,
|
|
@@ -63,14 +64,46 @@ export function DegenParticlesOverlay({
|
|
|
63
64
|
// memoizes this on `colorList`.
|
|
64
65
|
const colorRgb = colorList.map((c) => parseColorRgb(c));
|
|
65
66
|
|
|
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);
|
|
75
|
+
if (poolRef.current === null) {
|
|
76
|
+
const instancePool = Array.from({ length: particleSlotCount }, () => ({
|
|
77
|
+
x: 0,
|
|
78
|
+
y: 0,
|
|
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
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
66
93
|
// Single per-frame worklet. Reads `packRevision` so it re-runs each frame
|
|
67
94
|
// while a burst is alive (the pack buffer is mutated in place, so its
|
|
68
95
|
// reference is stable and wouldn't otherwise re-notify).
|
|
69
96
|
const atlasData = useDerivedValue(() => {
|
|
70
97
|
const rev = packRevision.get();
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
const
|
|
98
|
+
const pool = poolRef.current!;
|
|
99
|
+
pool.tick = !pool.tick;
|
|
100
|
+
const frame = pool.tick ? pool.a : pool.b;
|
|
101
|
+
const transforms = frame.transforms;
|
|
102
|
+
const sprites = frame.sprites;
|
|
103
|
+
const colorsOut = frame.colors;
|
|
104
|
+
transforms.length = 0;
|
|
105
|
+
sprites.length = 0;
|
|
106
|
+
colorsOut.length = 0;
|
|
74
107
|
if (rev >= 0) {
|
|
75
108
|
const instances = buildParticleInstances(
|
|
76
109
|
pack.get(),
|
|
@@ -79,6 +112,8 @@ export function DegenParticlesOverlay({
|
|
|
79
112
|
particleBurstDurationSec,
|
|
80
113
|
particleOpacity,
|
|
81
114
|
sprite.radius,
|
|
115
|
+
pool.instances,
|
|
116
|
+
pool.instancePool,
|
|
82
117
|
);
|
|
83
118
|
const half = sprite.size / 2;
|
|
84
119
|
for (let i = 0; i < instances.length; i++) {
|
|
@@ -92,12 +127,12 @@ export function DegenParticlesOverlay({
|
|
|
92
127
|
inst.y - inst.scale * half,
|
|
93
128
|
),
|
|
94
129
|
);
|
|
95
|
-
sprites.push(
|
|
130
|
+
sprites.push(pool.spriteRect);
|
|
96
131
|
const [r, g, b] = colorRgb[inst.colorIndex % colorRgb.length];
|
|
97
132
|
colorsOut.push(Skia.Color(`rgba(${r}, ${g}, ${b}, ${inst.alpha})`));
|
|
98
133
|
}
|
|
99
134
|
}
|
|
100
|
-
return
|
|
135
|
+
return frame;
|
|
101
136
|
}, [
|
|
102
137
|
pack,
|
|
103
138
|
packRevision,
|
|
@@ -2,6 +2,7 @@ import { Group, LinearGradient, Rect, vec } from "@shopify/react-native-skia";
|
|
|
2
2
|
|
|
3
3
|
import { useDerivedValue } from "react-native-reanimated";
|
|
4
4
|
import type { ChartEngineLayout } from "../core/useLiveChartEngine";
|
|
5
|
+
import { parseColorRgba } from "../theme";
|
|
5
6
|
|
|
6
7
|
export function LeftEdgeFade({
|
|
7
8
|
paddingLeft,
|
|
@@ -9,27 +10,32 @@ export function LeftEdgeFade({
|
|
|
9
10
|
startColor,
|
|
10
11
|
endColor,
|
|
11
12
|
engine,
|
|
13
|
+
opaqueBackgroundRgb,
|
|
12
14
|
}: {
|
|
13
15
|
paddingLeft: number;
|
|
14
16
|
fadeWidth: number;
|
|
15
17
|
startColor: string;
|
|
16
18
|
endColor: string;
|
|
17
19
|
engine: ChartEngineLayout;
|
|
20
|
+
/** When set, paint the owned background instead of erasing destination alpha. */
|
|
21
|
+
opaqueBackgroundRgb?: [number, number, number];
|
|
18
22
|
}) {
|
|
19
23
|
const rectWidth = paddingLeft + fadeWidth;
|
|
20
24
|
const height = useDerivedValue(() => engine.canvasHeight.value);
|
|
21
25
|
|
|
22
26
|
const gStart = vec(paddingLeft, 0);
|
|
23
27
|
const gEnd = vec(paddingLeft + fadeWidth, 0);
|
|
28
|
+
const colors = opaqueBackgroundRgb
|
|
29
|
+
? [
|
|
30
|
+
`rgba(${opaqueBackgroundRgb[0]},${opaqueBackgroundRgb[1]},${opaqueBackgroundRgb[2]},${parseColorRgba(startColor)[3]})`,
|
|
31
|
+
`rgba(${opaqueBackgroundRgb[0]},${opaqueBackgroundRgb[1]},${opaqueBackgroundRgb[2]},${parseColorRgba(endColor)[3]})`,
|
|
32
|
+
]
|
|
33
|
+
: [startColor, endColor];
|
|
24
34
|
|
|
25
35
|
return (
|
|
26
|
-
<Group blendMode="dstOut">
|
|
36
|
+
<Group blendMode={opaqueBackgroundRgb ? undefined : "dstOut"}>
|
|
27
37
|
<Rect x={0} y={0} width={rectWidth} height={height}>
|
|
28
|
-
<LinearGradient
|
|
29
|
-
start={gStart}
|
|
30
|
-
end={gEnd}
|
|
31
|
-
colors={[startColor, endColor]}
|
|
32
|
-
/>
|
|
38
|
+
<LinearGradient start={gStart} end={gEnd} colors={colors} />
|
|
33
39
|
</Rect>
|
|
34
40
|
</Group>
|
|
35
41
|
);
|
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
useEffect,
|
|
3
|
-
useLayoutEffect,
|
|
4
|
-
useMemo,
|
|
5
|
-
useRef,
|
|
6
|
-
useState,
|
|
7
|
-
} from "react";
|
|
1
|
+
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
8
2
|
import { StyleSheet, View } from "react-native";
|
|
9
3
|
import { Gesture, GestureDetector } from "react-native-gesture-handler";
|
|
10
4
|
import Animated, {
|
|
@@ -27,6 +21,7 @@ import {
|
|
|
27
21
|
Group,
|
|
28
22
|
LinearGradient,
|
|
29
23
|
Path,
|
|
24
|
+
Rect,
|
|
30
25
|
vec,
|
|
31
26
|
} from "@shopify/react-native-skia";
|
|
32
27
|
|
|
@@ -253,6 +248,7 @@ function useLiveChartController({
|
|
|
253
248
|
font: fontProp,
|
|
254
249
|
insets,
|
|
255
250
|
style,
|
|
251
|
+
canvasMode = "transparent",
|
|
256
252
|
|
|
257
253
|
// ── Candlestick ─────────────────────────────────────────────────────────
|
|
258
254
|
mode = "line",
|
|
@@ -1204,6 +1200,7 @@ function useLiveChartController({
|
|
|
1204
1200
|
return {
|
|
1205
1201
|
// passthrough props the render needs
|
|
1206
1202
|
style,
|
|
1203
|
+
canvasMode,
|
|
1207
1204
|
accessibilityLabel,
|
|
1208
1205
|
accessibilityRole,
|
|
1209
1206
|
emptyText,
|
|
@@ -1362,9 +1359,7 @@ function ChartWithDegen({
|
|
|
1362
1359
|
onDegenShake,
|
|
1363
1360
|
isStatic,
|
|
1364
1361
|
);
|
|
1365
|
-
return
|
|
1366
|
-
<ChartView model={model} yAxisEntries={yAxisEntries} degen={state} />
|
|
1367
|
-
);
|
|
1362
|
+
return <ChartView model={model} yAxisEntries={yAxisEntries} degen={state} />;
|
|
1368
1363
|
}
|
|
1369
1364
|
|
|
1370
1365
|
/**
|
|
@@ -1412,8 +1407,10 @@ function ChartYAxisLayer({
|
|
|
1412
1407
|
effectivePadding,
|
|
1413
1408
|
palette,
|
|
1414
1409
|
skiaFont,
|
|
1410
|
+
dotY,
|
|
1415
1411
|
badgeUsesRightGutter,
|
|
1416
1412
|
badgeCfg,
|
|
1413
|
+
badgeFont,
|
|
1417
1414
|
metricsCfg,
|
|
1418
1415
|
gridStyleCfg,
|
|
1419
1416
|
yAxisFloat,
|
|
@@ -1431,6 +1428,9 @@ function ChartYAxisLayer({
|
|
|
1431
1428
|
badge={badgeUsesRightGutter}
|
|
1432
1429
|
badgeTail={badgeCfg?.tail ?? true}
|
|
1433
1430
|
badgeMetrics={metricsCfg.badge}
|
|
1431
|
+
badgeCenterY={badgeUsesRightGutter ? dotY : undefined}
|
|
1432
|
+
badgeFontSize={badgeUsesRightGutter ? badgeFont.getSize() : undefined}
|
|
1433
|
+
badgeOffsetY={badgeCfg?.offsetY ?? 0}
|
|
1434
1434
|
gridStyle={gridStyleCfg}
|
|
1435
1435
|
/>
|
|
1436
1436
|
</Group>
|
|
@@ -1716,6 +1716,7 @@ function ChartStack({
|
|
|
1716
1716
|
loadingStrokeWidth,
|
|
1717
1717
|
loadingAmplitude,
|
|
1718
1718
|
loadingSpeed,
|
|
1719
|
+
canvasMode,
|
|
1719
1720
|
} = model;
|
|
1720
1721
|
return (
|
|
1721
1722
|
<Group transform={degen?.shakeTransform}>
|
|
@@ -1934,6 +1935,7 @@ function ChartStack({
|
|
|
1934
1935
|
lineStrokeWidth={loadingStrokeWidth}
|
|
1935
1936
|
waveAmplitude={loadingAmplitude}
|
|
1936
1937
|
waveSpeed={loadingSpeed}
|
|
1938
|
+
opaqueCanvas={canvasMode === "opaque"}
|
|
1937
1939
|
/>
|
|
1938
1940
|
</Group>
|
|
1939
1941
|
);
|
|
@@ -1998,6 +2000,7 @@ function ChartScrubLayer({
|
|
|
1998
2000
|
selectionDot,
|
|
1999
2001
|
selectionColor,
|
|
2000
2002
|
renderTooltip,
|
|
2003
|
+
canvasMode,
|
|
2001
2004
|
} = model;
|
|
2002
2005
|
// A custom tooltip is an RN overlay (sibling of <Canvas>), so the built-in
|
|
2003
2006
|
// Skia tooltip is suppressed here while it's active — the line pill in line
|
|
@@ -2042,6 +2045,7 @@ function ChartScrubLayer({
|
|
|
2042
2045
|
tooltipBorderRadius={scrubCfg.tooltipBorderRadius}
|
|
2043
2046
|
tooltipShowValue={scrubCfg.tooltipShowValue}
|
|
2044
2047
|
tooltipShowTime={scrubCfg.tooltipShowTime}
|
|
2048
|
+
opaqueCanvas={canvasMode === "opaque"}
|
|
2045
2049
|
>
|
|
2046
2050
|
{/* Candle charts render a multi-line OHLC tooltip; the line
|
|
2047
2051
|
chart falls back to CrosshairOverlay's default value/time
|
|
@@ -2359,6 +2363,7 @@ function ChartView({
|
|
|
2359
2363
|
extremaTimeOffset,
|
|
2360
2364
|
topConnector,
|
|
2361
2365
|
bottomConnector,
|
|
2366
|
+
canvasMode,
|
|
2362
2367
|
} = model;
|
|
2363
2368
|
|
|
2364
2369
|
return (
|
|
@@ -2370,7 +2375,21 @@ function ChartView({
|
|
|
2370
2375
|
accessibilityLabel={accessibilityLabel}
|
|
2371
2376
|
accessibilityRole={accessibilityRole}
|
|
2372
2377
|
>
|
|
2373
|
-
|
|
2378
|
+
{/* Skia chooses TextureView vs SurfaceView when the native Canvas mounts. */}
|
|
2379
|
+
<Canvas
|
|
2380
|
+
key={canvasMode}
|
|
2381
|
+
style={{ flex: 1 }}
|
|
2382
|
+
opaque={canvasMode === "opaque"}
|
|
2383
|
+
>
|
|
2384
|
+
{canvasMode === "opaque" && (
|
|
2385
|
+
<Rect
|
|
2386
|
+
x={0}
|
|
2387
|
+
y={0}
|
|
2388
|
+
width={engine.canvasWidth}
|
|
2389
|
+
height={engine.canvasHeight}
|
|
2390
|
+
color={backgroundColor}
|
|
2391
|
+
/>
|
|
2392
|
+
)}
|
|
2374
2393
|
{/* Background fills first, then the left-edge fade (a canvas-space sibling
|
|
2375
2394
|
so dstOut blends correctly), then the line stack on top — so the fade
|
|
2376
2395
|
softens only the fills and the line stays crisp at the left edge. */}
|
|
@@ -2387,15 +2406,14 @@ function ChartView({
|
|
|
2387
2406
|
startColor={leftEdgeFadeCfg.startColor}
|
|
2388
2407
|
endColor={leftEdgeFadeCfg.endColor}
|
|
2389
2408
|
engine={engine}
|
|
2409
|
+
opaqueBackgroundRgb={
|
|
2410
|
+
canvasMode === "opaque" ? palette.bgRgb : undefined
|
|
2411
|
+
}
|
|
2390
2412
|
/>
|
|
2391
2413
|
)}
|
|
2392
2414
|
|
|
2393
2415
|
{/* Line stack above the fade so the line stays crisp at the left edge. */}
|
|
2394
|
-
<ChartStack
|
|
2395
|
-
model={model}
|
|
2396
|
-
yAxisEntries={yAxisEntries}
|
|
2397
|
-
degen={degen}
|
|
2398
|
-
/>
|
|
2416
|
+
<ChartStack model={model} yAxisEntries={yAxisEntries} degen={degen} />
|
|
2399
2417
|
|
|
2400
2418
|
{/* "extrema-edge" connector lines (dot → edge readout), above the chart
|
|
2401
2419
|
content so the dashed guide reads over the line / candles. */}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* @see https://github.com/benjitaylor/liveline
|
|
6
6
|
*/
|
|
7
|
-
import { Canvas, Group } from "@shopify/react-native-skia";
|
|
7
|
+
import { Canvas, Group, Rect } from "@shopify/react-native-skia";
|
|
8
8
|
import { useLayoutEffect, useState } from "react";
|
|
9
9
|
import { StyleSheet, View } from "react-native";
|
|
10
10
|
import { Gesture, GestureDetector } from "react-native-gesture-handler";
|
|
@@ -83,6 +83,10 @@ import {
|
|
|
83
83
|
labelConnector,
|
|
84
84
|
} from "./ExtremaConnectorOverlay";
|
|
85
85
|
import { CustomMarkerOverlay } from "./CustomMarkerOverlay";
|
|
86
|
+
import {
|
|
87
|
+
CustomReferenceLineOverlay,
|
|
88
|
+
customReferenceLineFlags,
|
|
89
|
+
} from "./CustomReferenceLineOverlay";
|
|
86
90
|
import { CrosshairLine } from "./CrosshairLine";
|
|
87
91
|
import { DegenParticlesOverlay } from "./DegenParticlesOverlay";
|
|
88
92
|
import { LeftEdgeFade } from "./LeftEdgeFade";
|
|
@@ -128,6 +132,7 @@ function useLiveChartSeriesController({
|
|
|
128
132
|
font: fontProp,
|
|
129
133
|
insets,
|
|
130
134
|
style,
|
|
135
|
+
canvasMode = "transparent",
|
|
131
136
|
timeWindow = 30,
|
|
132
137
|
paused = false,
|
|
133
138
|
loading = false,
|
|
@@ -172,6 +177,7 @@ function useLiveChartSeriesController({
|
|
|
172
177
|
markerHitRadius = 16,
|
|
173
178
|
markerCluster,
|
|
174
179
|
renderMarker,
|
|
180
|
+
renderReferenceLine,
|
|
175
181
|
leftEdgeFade = true,
|
|
176
182
|
}: LiveChartSeriesProps) {
|
|
177
183
|
const emptyMarkers = useSharedValue<Marker[]>([]);
|
|
@@ -224,6 +230,13 @@ function useLiveChartSeriesController({
|
|
|
224
230
|
|
|
225
231
|
const allRefLines = referenceLines ?? [];
|
|
226
232
|
const refValues = collectReferenceValues(allRefLines);
|
|
233
|
+
// Form-A lines a custom renderer owns keep their line stroke but suppress the
|
|
234
|
+
// built-in Skia tag. The callback is probed per line on the JS thread, matching
|
|
235
|
+
// LiveChart and CustomMarkerOverlay's per-item fallback model.
|
|
236
|
+
const refLineCustom = customReferenceLineFlags(
|
|
237
|
+
allRefLines,
|
|
238
|
+
renderReferenceLine,
|
|
239
|
+
);
|
|
227
240
|
|
|
228
241
|
const palette = applyPaletteOverride(
|
|
229
242
|
resolveTheme(accentColor, theme),
|
|
@@ -256,6 +269,11 @@ function useLiveChartSeriesController({
|
|
|
256
269
|
setSeriesSnapshot(series.get().slice());
|
|
257
270
|
}, [series]);
|
|
258
271
|
|
|
272
|
+
// Mount per-series drawing worklets only for real series. The previous fixed
|
|
273
|
+
// 12-slot render kept 144 derived-value mappers alive for the default stroke,
|
|
274
|
+
// dot, and value-label layers even when a chart contained only one line.
|
|
275
|
+
const activeSeriesCount = Math.min(seriesSnapshot.length, MAX_MULTI_SERIES);
|
|
276
|
+
|
|
259
277
|
const maxSeriesLabelWidth = dotCfg.valueLabel
|
|
260
278
|
? Math.max(
|
|
261
279
|
0,
|
|
@@ -336,7 +354,11 @@ function useLiveChartSeriesController({
|
|
|
336
354
|
nowOverride,
|
|
337
355
|
});
|
|
338
356
|
const { layoutHeight, onLayout } = useCanvasLayout(engine);
|
|
339
|
-
const linePaths = useMultiSeriesLinePaths(
|
|
357
|
+
const linePaths = useMultiSeriesLinePaths(
|
|
358
|
+
engine,
|
|
359
|
+
effectivePadding,
|
|
360
|
+
activeSeriesCount,
|
|
361
|
+
);
|
|
340
362
|
|
|
341
363
|
// Per-series colors and stroke styles, derived from the off-render snapshot
|
|
342
364
|
// (React state — so no Reanimated read-during-render). The reaction below
|
|
@@ -503,6 +525,7 @@ function useLiveChartSeriesController({
|
|
|
503
525
|
// passthrough props the render needs
|
|
504
526
|
series,
|
|
505
527
|
style,
|
|
528
|
+
canvasMode,
|
|
506
529
|
accessibilityLabel,
|
|
507
530
|
accessibilityRole,
|
|
508
531
|
emptyText,
|
|
@@ -519,6 +542,7 @@ function useLiveChartSeriesController({
|
|
|
519
542
|
degenCfg,
|
|
520
543
|
metricsCfg,
|
|
521
544
|
allRefLines,
|
|
545
|
+
refLineCustom,
|
|
522
546
|
leftEdgeFadeCfg,
|
|
523
547
|
// theme / layout / fonts
|
|
524
548
|
palette,
|
|
@@ -540,6 +564,7 @@ function useLiveChartSeriesController({
|
|
|
540
564
|
layoutHeight,
|
|
541
565
|
onLayout,
|
|
542
566
|
linePaths,
|
|
567
|
+
activeSeriesCount,
|
|
543
568
|
lineColors,
|
|
544
569
|
lineStyles,
|
|
545
570
|
degenPack,
|
|
@@ -555,6 +580,7 @@ function useLiveChartSeriesController({
|
|
|
555
580
|
markerGroupOpacity,
|
|
556
581
|
overlayScrubFade,
|
|
557
582
|
renderMarker,
|
|
583
|
+
renderReferenceLine,
|
|
558
584
|
// selection dot: resolved config + fallback color (the leading series' color)
|
|
559
585
|
selectionDot: selectionDotCfg,
|
|
560
586
|
selectionColor: lineColors[0],
|
|
@@ -610,6 +636,8 @@ function SeriesChartStack({ model }: { model: LiveChartSeriesModel }) {
|
|
|
610
636
|
loadingStrokeWidth,
|
|
611
637
|
loadingAmplitude,
|
|
612
638
|
loadingSpeed,
|
|
639
|
+
canvasMode,
|
|
640
|
+
activeSeriesCount,
|
|
613
641
|
} = model;
|
|
614
642
|
|
|
615
643
|
return (
|
|
@@ -629,12 +657,13 @@ function SeriesChartStack({ model }: { model: LiveChartSeriesModel }) {
|
|
|
629
657
|
</Group>
|
|
630
658
|
)}
|
|
631
659
|
|
|
632
|
-
{/*
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
`scrub.hideOverlaysOnScrub` ease the lines out
|
|
660
|
+
{/* Reference lines are index-addressed throughout the drag/press API, and
|
|
661
|
+
duplicate working orders may share all visible content. Positional keys
|
|
662
|
+
keep both orders mounted without collapsing them to one React child.
|
|
663
|
+
Fade group lets `scrub.hideOverlaysOnScrub` ease the lines out. */}
|
|
636
664
|
<Group opacity={overlayScrubFade}>
|
|
637
665
|
{allRefLines.map((rl, i) => (
|
|
666
|
+
/* react-doctor-disable-next-line react-doctor/no-array-index-as-key */
|
|
638
667
|
<ReferenceLineOverlay
|
|
639
668
|
key={i}
|
|
640
669
|
engine={engine}
|
|
@@ -654,12 +683,13 @@ function SeriesChartStack({ model }: { model: LiveChartSeriesModel }) {
|
|
|
654
683
|
padding={effectivePadding}
|
|
655
684
|
colors={lineColors}
|
|
656
685
|
config={dotCfg.valueLine}
|
|
686
|
+
seriesCount={activeSeriesCount}
|
|
657
687
|
/>
|
|
658
688
|
</Group>
|
|
659
689
|
)}
|
|
660
690
|
|
|
661
691
|
<Group opacity={reveal.lineOpacity}>
|
|
662
|
-
{Array.from({ length:
|
|
692
|
+
{Array.from({ length: activeSeriesCount }, (_, i) => (
|
|
663
693
|
<MultiSeriesStroke
|
|
664
694
|
key={i}
|
|
665
695
|
index={i}
|
|
@@ -694,6 +724,7 @@ function SeriesChartStack({ model }: { model: LiveChartSeriesModel }) {
|
|
|
694
724
|
color={dotCfg.color}
|
|
695
725
|
pulse={dotCfg.pulse}
|
|
696
726
|
viewEnd={engine.viewEnd}
|
|
727
|
+
seriesCount={activeSeriesCount}
|
|
697
728
|
/>
|
|
698
729
|
</Group>
|
|
699
730
|
)}
|
|
@@ -749,6 +780,7 @@ function SeriesChartStack({ model }: { model: LiveChartSeriesModel }) {
|
|
|
749
780
|
lineStrokeWidth={loadingStrokeWidth}
|
|
750
781
|
waveAmplitude={loadingAmplitude}
|
|
751
782
|
waveSpeed={loadingSpeed}
|
|
783
|
+
opaqueCanvas={canvasMode === "opaque"}
|
|
752
784
|
/>
|
|
753
785
|
</Group>
|
|
754
786
|
);
|
|
@@ -767,6 +799,7 @@ function SeriesValueLabelLayer({ model }: { model: LiveChartSeriesModel }) {
|
|
|
767
799
|
skiaFont,
|
|
768
800
|
reveal,
|
|
769
801
|
degenShakeTransform,
|
|
802
|
+
activeSeriesCount,
|
|
770
803
|
} = model;
|
|
771
804
|
if (!dotCfg.valueLabel) return null;
|
|
772
805
|
return (
|
|
@@ -778,6 +811,7 @@ function SeriesValueLabelLayer({ model }: { model: LiveChartSeriesModel }) {
|
|
|
778
811
|
colors={lineColors}
|
|
779
812
|
font={skiaFont}
|
|
780
813
|
dotRadius={dotOuterRadius}
|
|
814
|
+
seriesCount={activeSeriesCount}
|
|
781
815
|
/>
|
|
782
816
|
</Group>
|
|
783
817
|
</Group>
|
|
@@ -789,6 +823,7 @@ function SeriesValueLabelLayer({ model }: { model: LiveChartSeriesModel }) {
|
|
|
789
823
|
function SeriesRefBadgeLayer({ model }: { model: LiveChartSeriesModel }) {
|
|
790
824
|
const {
|
|
791
825
|
allRefLines,
|
|
826
|
+
refLineCustom,
|
|
792
827
|
engine,
|
|
793
828
|
effectivePadding,
|
|
794
829
|
palette,
|
|
@@ -796,11 +831,13 @@ function SeriesRefBadgeLayer({ model }: { model: LiveChartSeriesModel }) {
|
|
|
796
831
|
skiaFont,
|
|
797
832
|
degenShakeTransform,
|
|
798
833
|
overlayScrubFade,
|
|
834
|
+
canvasMode,
|
|
799
835
|
} = model;
|
|
800
836
|
if (allRefLines.length === 0) return null;
|
|
801
837
|
return (
|
|
802
838
|
<Group transform={degenShakeTransform} opacity={overlayScrubFade}>
|
|
803
839
|
{allRefLines.map((rl, i) => (
|
|
840
|
+
/* react-doctor-disable-next-line react-doctor/no-array-index-as-key */
|
|
804
841
|
<ReferenceLineOverlay
|
|
805
842
|
key={i}
|
|
806
843
|
engine={engine}
|
|
@@ -810,6 +847,7 @@ function SeriesRefBadgeLayer({ model }: { model: LiveChartSeriesModel }) {
|
|
|
810
847
|
formatValue={formatValue}
|
|
811
848
|
font={skiaFont}
|
|
812
849
|
badgeLayer
|
|
850
|
+
suppressTag={refLineCustom[i]}
|
|
813
851
|
/>
|
|
814
852
|
))}
|
|
815
853
|
</Group>
|
|
@@ -848,7 +886,11 @@ export function LiveChartSeries(props: LiveChartSeriesProps) {
|
|
|
848
886
|
markersSV,
|
|
849
887
|
markerClusterCfg,
|
|
850
888
|
renderMarker,
|
|
889
|
+
renderReferenceLine,
|
|
890
|
+
allRefLines,
|
|
891
|
+
refLineCustom,
|
|
851
892
|
overlayScrubFade,
|
|
893
|
+
canvasMode,
|
|
852
894
|
} = model;
|
|
853
895
|
|
|
854
896
|
// Mirror the Skia overlay fade onto the RN custom-marker sibling so
|
|
@@ -891,7 +933,21 @@ export function LiveChartSeries(props: LiveChartSeriesProps) {
|
|
|
891
933
|
else points map into a taller area and the x-axis draws past the edge. */}
|
|
892
934
|
<GestureDetector gesture={rootGesture}>
|
|
893
935
|
<View style={{ flex: 1 }} onLayout={onLayout}>
|
|
894
|
-
|
|
936
|
+
{/* Skia chooses TextureView vs SurfaceView when the native Canvas mounts. */}
|
|
937
|
+
<Canvas
|
|
938
|
+
key={canvasMode}
|
|
939
|
+
style={{ flex: 1, minHeight: layoutHeight || 1 }}
|
|
940
|
+
opaque={canvasMode === "opaque"}
|
|
941
|
+
>
|
|
942
|
+
{canvasMode === "opaque" && (
|
|
943
|
+
<Rect
|
|
944
|
+
x={0}
|
|
945
|
+
y={0}
|
|
946
|
+
width={engine.canvasWidth}
|
|
947
|
+
height={engine.canvasHeight}
|
|
948
|
+
color={backgroundColor}
|
|
949
|
+
/>
|
|
950
|
+
)}
|
|
895
951
|
<SeriesChartStack model={model} />
|
|
896
952
|
|
|
897
953
|
{/* "extrema-edge" connector lines (dot → edge readout). Outside the
|
|
@@ -910,6 +966,9 @@ export function LiveChartSeries(props: LiveChartSeriesProps) {
|
|
|
910
966
|
startColor={leftEdgeFadeCfg.startColor}
|
|
911
967
|
endColor={leftEdgeFadeCfg.endColor}
|
|
912
968
|
engine={engine}
|
|
969
|
+
opaqueBackgroundRgb={
|
|
970
|
+
canvasMode === "opaque" ? palette.bgRgb : undefined
|
|
971
|
+
}
|
|
913
972
|
/>
|
|
914
973
|
)}
|
|
915
974
|
|
|
@@ -932,6 +991,7 @@ export function LiveChartSeries(props: LiveChartSeriesProps) {
|
|
|
932
991
|
crosshairLineColor={scrubCfg.crosshairLineColor}
|
|
933
992
|
crosshairDash={scrubCfg.crosshairDash}
|
|
934
993
|
crosshairDimColor={scrubCfg.crosshairDimColor}
|
|
994
|
+
opaqueCanvas={canvasMode === "opaque"}
|
|
935
995
|
/>
|
|
936
996
|
)}
|
|
937
997
|
|
|
@@ -952,23 +1012,36 @@ export function LiveChartSeries(props: LiveChartSeriesProps) {
|
|
|
952
1012
|
padding={effectivePadding}
|
|
953
1013
|
/>
|
|
954
1014
|
|
|
955
|
-
{/* Custom
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
{markersActive && renderMarker
|
|
1015
|
+
{/* Custom annotations — RN views floated over the canvas (non-Skia),
|
|
1016
|
+
pinned to their live positions. As a post-Canvas sibling they render
|
|
1017
|
+
above the left-edge fade and scrub overlay. The box-none fade wrapper
|
|
1018
|
+
keeps `scrub.hideOverlaysOnScrub` behavior aligned with Skia. */}
|
|
1019
|
+
{((markersActive && renderMarker) ||
|
|
1020
|
+
(renderReferenceLine && allRefLines.length > 0)) && (
|
|
960
1021
|
<Animated.View
|
|
961
1022
|
pointerEvents="box-none"
|
|
962
1023
|
style={[StyleSheet.absoluteFill, overlayFadeStyle]}
|
|
963
1024
|
>
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
1025
|
+
{markersActive && renderMarker && (
|
|
1026
|
+
<CustomMarkerOverlay
|
|
1027
|
+
markers={markersSV}
|
|
1028
|
+
renderMarker={renderMarker}
|
|
1029
|
+
engine={engine}
|
|
1030
|
+
padding={effectivePadding}
|
|
1031
|
+
series={series}
|
|
1032
|
+
cluster={markerClusterCfg}
|
|
1033
|
+
/>
|
|
1034
|
+
)}
|
|
1035
|
+
{renderReferenceLine && allRefLines.length > 0 && (
|
|
1036
|
+
<CustomReferenceLineOverlay
|
|
1037
|
+
lines={allRefLines}
|
|
1038
|
+
renderReferenceLine={renderReferenceLine}
|
|
1039
|
+
custom={refLineCustom}
|
|
1040
|
+
engine={engine}
|
|
1041
|
+
padding={effectivePadding}
|
|
1042
|
+
formatValue={formatValue}
|
|
1043
|
+
/>
|
|
1044
|
+
)}
|
|
972
1045
|
</Animated.View>
|
|
973
1046
|
)}
|
|
974
1047
|
</View>
|