react-native-livechart 4.20.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/CustomThresholdBadgeOverlay.d.ts +18 -0
- package/dist/components/CustomThresholdBadgeOverlay.d.ts.map +1 -0
- package/dist/components/CustomTooltipOverlay.d.ts +4 -2
- package/dist/components/CustomTooltipOverlay.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 +10 -5
- package/dist/components/ReferenceLineOverlay.d.ts.map +1 -1
- package/dist/components/ReferenceLineSeriesOverlay.d.ts +19 -0
- package/dist/components/ReferenceLineSeriesOverlay.d.ts.map +1 -0
- package/dist/core/chartDataPresence.d.ts +8 -0
- package/dist/core/chartDataPresence.d.ts.map +1 -0
- package/dist/core/liveChartEngineTick.d.ts +6 -1
- package/dist/core/liveChartEngineTick.d.ts.map +1 -1
- package/dist/core/resolveConfig.d.ts +32 -1
- package/dist/core/resolveConfig.d.ts.map +1 -1
- package/dist/core/useLiveChartEngine.d.ts +9 -1
- package/dist/core/useLiveChartEngine.d.ts.map +1 -1
- package/dist/core/useLiveChartSeriesEngine.d.ts.map +1 -1
- package/dist/draw/candle.d.ts +3 -1
- package/dist/draw/candle.d.ts.map +1 -1
- package/dist/draw/candleGap.d.ts +14 -0
- package/dist/draw/candleGap.d.ts.map +1 -0
- package/dist/draw/lineGap.d.ts +25 -0
- package/dist/draw/lineGap.d.ts.map +1 -0
- package/dist/draw/markerAtlas.d.ts +3 -2
- package/dist/draw/markerAtlas.d.ts.map +1 -1
- package/dist/hooks/crosshairShared.d.ts +7 -3
- package/dist/hooks/crosshairShared.d.ts.map +1 -1
- package/dist/hooks/useCandleGapPaths.d.ts +12 -0
- package/dist/hooks/useCandleGapPaths.d.ts.map +1 -0
- package/dist/hooks/useChartPaths.d.ts +4 -1
- package/dist/hooks/useChartPaths.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 +9 -5
- package/dist/hooks/useCrosshair.d.ts.map +1 -1
- package/dist/hooks/useDegen.d.ts.map +1 -1
- package/dist/hooks/useLineGapPaths.d.ts +10 -0
- package/dist/hooks/useLineGapPaths.d.ts.map +1 -0
- package/dist/hooks/useLiveChartHasData.d.ts +2 -2
- package/dist/hooks/useLiveChartHasData.d.ts.map +1 -1
- package/dist/hooks/useReferenceLine.d.ts +4 -3
- package/dist/hooks/useReferenceLine.d.ts.map +1 -1
- package/dist/hooks/useReferenceLineSeries.d.ts +23 -0
- package/dist/hooks/useReferenceLineSeries.d.ts.map +1 -0
- package/dist/hooks/useReverseMorphEngineInputs.d.ts.map +1 -1
- package/dist/hooks/useThreshold.d.ts +13 -13
- package/dist/hooks/useThreshold.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/math/candleGaps.d.ts +18 -0
- package/dist/math/candleGaps.d.ts.map +1 -0
- package/dist/math/referenceLineSeries.d.ts +12 -0
- package/dist/math/referenceLineSeries.d.ts.map +1 -0
- package/dist/math/referenceLines.d.ts +5 -5
- package/dist/math/referenceLines.d.ts.map +1 -1
- package/dist/math/spline.d.ts +5 -1
- package/dist/math/spline.d.ts.map +1 -1
- package/dist/types.d.ts +191 -17
- package/dist/types.d.ts.map +1 -1
- 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/CustomThresholdBadgeOverlay.tsx +74 -0
- package/src/components/CustomTooltipOverlay.tsx +7 -3
- package/src/components/DegenParticlesOverlay.tsx +34 -23
- package/src/components/ExtremaConnectorOverlay.tsx +43 -3
- package/src/components/LiveChart.tsx +1918 -974
- package/src/components/LiveChartSeries.tsx +445 -296
- package/src/components/MarkerOverlay.tsx +215 -156
- package/src/components/ReferenceLineOverlay.tsx +113 -55
- package/src/components/ReferenceLineSeriesOverlay.tsx +141 -0
- package/src/core/chartDataPresence.ts +24 -0
- package/src/core/liveChartEngineTick.ts +34 -1
- package/src/core/resolveConfig.ts +186 -0
- package/src/core/useLiveChartEngine.ts +36 -1
- package/src/core/useLiveChartSeriesEngine.ts +2 -0
- package/src/draw/candle.ts +1 -1
- package/src/draw/candleGap.ts +132 -0
- package/src/draw/lineGap.ts +180 -0
- package/src/draw/markerAtlas.ts +8 -3
- package/src/hooks/crosshairShared.ts +92 -11
- package/src/hooks/useCandleGapPaths.ts +88 -0
- package/src/hooks/useChartPaths.ts +81 -22
- package/src/hooks/useChartReveal.ts +1 -1
- package/src/hooks/useCrosshair.ts +178 -69
- package/src/hooks/useDegen.ts +82 -53
- package/src/hooks/useLineGapPaths.ts +76 -0
- package/src/hooks/useLiveChartHasData.ts +8 -4
- package/src/hooks/useReferenceLine.ts +6 -5
- package/src/hooks/useReferenceLineSeries.ts +101 -0
- package/src/hooks/useReverseMorphEngineInputs.ts +11 -20
- package/src/hooks/useThreshold.ts +31 -22
- package/src/index.ts +15 -0
- package/src/math/candleGaps.ts +120 -0
- package/src/math/referenceLineSeries.ts +83 -0
- package/src/math/referenceLines.ts +34 -6
- package/src/math/spline.ts +15 -5
- package/src/types.ts +202 -17
|
@@ -4,11 +4,10 @@
|
|
|
4
4
|
*
|
|
5
5
|
* @see https://github.com/benjitaylor/liveline
|
|
6
6
|
*/
|
|
7
|
-
import { Canvas, Group, Rect } from "@shopify/react-native-skia";
|
|
7
|
+
import { Canvas, Group, Rect, type SkFont } from "@shopify/react-native-skia";
|
|
8
8
|
import {
|
|
9
9
|
forwardRef,
|
|
10
10
|
useImperativeHandle,
|
|
11
|
-
useLayoutEffect,
|
|
12
11
|
useState,
|
|
13
12
|
} from "react";
|
|
14
13
|
import { StyleSheet, View } from "react-native";
|
|
@@ -28,6 +27,7 @@ import {
|
|
|
28
27
|
MAX_MULTI_SERIES,
|
|
29
28
|
SCRUB_OVERLAY_FADE_MS,
|
|
30
29
|
} from "../constants";
|
|
30
|
+
import { hasMultiSeriesChartData } from "../core/chartDataPresence";
|
|
31
31
|
import {
|
|
32
32
|
lineColorsSignatureFromArray,
|
|
33
33
|
lineStyleSignatureFromArray,
|
|
@@ -146,7 +146,7 @@ function seriesConfigSig(s: SharedValue<SeriesConfig[]>) {
|
|
|
146
146
|
* overlay hooks and the color/style reaction state, returning a single render
|
|
147
147
|
* model so `SeriesChartStack` and `LiveChartSeries` stay small and presentational.
|
|
148
148
|
*/
|
|
149
|
-
function
|
|
149
|
+
function resolveLiveChartSeriesInputs({
|
|
150
150
|
series,
|
|
151
151
|
theme = "dark",
|
|
152
152
|
accentColor = DEFAULT_ACCENT_COLOR,
|
|
@@ -206,10 +206,6 @@ function useLiveChartSeriesController({
|
|
|
206
206
|
renderOffAxisReferenceLine,
|
|
207
207
|
leftEdgeFade = true,
|
|
208
208
|
}: LiveChartSeriesProps) {
|
|
209
|
-
const fullSeriesOpacity = useSharedValue(1);
|
|
210
|
-
const resolvedSeriesOpacity = seriesOpacity ?? fullSeriesOpacity;
|
|
211
|
-
const emptyMarkers = useSharedValue<Marker[]>([]);
|
|
212
|
-
const markersSV = markers ?? emptyMarkers;
|
|
213
209
|
const markerClusterCfg = resolveMarkerCluster(markerCluster);
|
|
214
210
|
const markersActive = markers != null;
|
|
215
211
|
const yAxisCfg = resolveYAxis(yAxis);
|
|
@@ -218,25 +214,13 @@ function useLiveChartSeriesController({
|
|
|
218
214
|
const bottomLabelCfg = resolveAxisLabel(bottomLabel);
|
|
219
215
|
const scrubCfg = resolveScrub(scrub);
|
|
220
216
|
const scrubEnabled = scrubCfg !== null;
|
|
221
|
-
// Opt-in and subordinate to the existing master `tooltip` switch. Keeping
|
|
222
|
-
// this null by default preserves LiveChartSeries' historical guide-only scrub.
|
|
223
217
|
const seriesTooltipCfg =
|
|
224
218
|
scrubCfg?.tooltip === true ? scrubCfg.seriesTooltip : null;
|
|
225
|
-
|
|
226
|
-
// Time-scroll + pinch-zoom (mirrors LiveChart). LiveChartSeries has no static
|
|
227
|
-
// mode, so these gate on the props alone. `holdToScrub` requires the scrub
|
|
228
|
-
// gesture to wait for a press-and-hold so a quick drag scrolls instead — the
|
|
229
|
-
// hold delay is threaded into `useCrosshairSeries` below.
|
|
230
219
|
const timeScrollEnabled = Boolean(timeScroll);
|
|
231
|
-
// Return-to-live glide duration (0 = instant); sibling of `timeScroll` so it
|
|
232
|
-
// survives `timeScroll={false}` (the disable that triggers it). See #164.
|
|
233
220
|
const returnToLiveMs = resolveReturnToLiveMs(returnToLive);
|
|
234
|
-
// Overscroll fraction ([0, 1)) — how far pan/zoom may travel past the data
|
|
235
|
-
// bounds into blank space. 0 (the default) keeps the classic hard stops.
|
|
236
221
|
const timeScrollOverscroll = timeScrollEnabled
|
|
237
222
|
? resolveOverscroll(timeScroll)
|
|
238
223
|
: 0;
|
|
239
|
-
// Release inertia (fling) — `timeScroll.fling: false` stops the pan dead.
|
|
240
224
|
const timeScrollFling = resolveFling(timeScroll);
|
|
241
225
|
const zoomCfg = resolveZoom(zoom);
|
|
242
226
|
const zoomEnabled = zoomCfg !== null;
|
|
@@ -246,22 +230,13 @@ function useLiveChartSeriesController({
|
|
|
246
230
|
: "holdToScrub";
|
|
247
231
|
const timeScrollHoldMs =
|
|
248
232
|
typeof timeScroll === "object" ? timeScroll.scrubHoldMs : undefined;
|
|
249
|
-
// Precedence: explicit scrubHoldMs, then scrub.panGestureDelay, then default.
|
|
250
|
-
// `||` (not `??`) skips the resolved panGestureDelay's 0 default.
|
|
251
233
|
const scrubHoldMs =
|
|
252
234
|
timeScrollEnabled && scrollGestureMode === "holdToScrub"
|
|
253
235
|
? (timeScrollHoldMs ?? (scrubCfg?.panGestureDelay || HOLD_TO_SCRUB_MS))
|
|
254
236
|
: (scrubCfg?.panGestureDelay ?? 0);
|
|
255
|
-
|
|
256
|
-
// Multi-series defaults the scrub selection dot OFF: it can only track one
|
|
257
|
-
// line (the leading series), which reads as a bug next to the other series.
|
|
258
|
-
// The crosshair line (and the opt-in tooltip's per-series intersection dots)
|
|
259
|
-
// mark the scrub point. Passing `selectionDot` explicitly still opts it in.
|
|
260
237
|
const selectionDotCfg = resolveSelectionDot(selectionDot ?? false);
|
|
261
238
|
const gridStyleCfg = resolveGridStyle(gridStyle);
|
|
262
239
|
const dotCfg = resolveMultiSeriesDot(dotProp);
|
|
263
|
-
// Outer visible footprint of a dot, including its crisp ring and optional
|
|
264
|
-
// blurred glow. Used to keep gutter labels clear of every dot effect.
|
|
265
240
|
const dotOuterRadius = Math.max(
|
|
266
241
|
dotCfg.radius + (dotCfg.ring?.width ?? 0),
|
|
267
242
|
dotCfg.glow ? dotGlowRadialOutset(dotCfg.glow.radius, dotCfg.glow.blur) : 0,
|
|
@@ -269,38 +244,228 @@ function useLiveChartSeriesController({
|
|
|
269
244
|
const legendCfg = resolveLegend(legendProp);
|
|
270
245
|
const degenCfg = resolveDegen(degen);
|
|
271
246
|
const metricsCfg = resolveMetrics(metrics);
|
|
272
|
-
|
|
273
247
|
const allRefLines = referenceLines ?? [];
|
|
274
248
|
const refValues = collectReferenceValues(allRefLines);
|
|
275
|
-
// Form-A lines a custom renderer owns keep their line stroke but suppress the
|
|
276
|
-
// built-in Skia tag. The callback is probed per line on the JS thread, matching
|
|
277
|
-
// LiveChart and CustomMarkerOverlay's per-item fallback model.
|
|
278
249
|
const refLineCustom = customReferenceLineFlags(
|
|
279
250
|
allRefLines,
|
|
280
251
|
renderReferenceLine,
|
|
281
252
|
);
|
|
282
|
-
// A full custom tag owns both states, so it takes precedence over an
|
|
283
|
-
// off-axis-only renderer for the same line.
|
|
284
253
|
const refLineOffAxisCustom = customReferenceLineFlags(
|
|
285
254
|
allRefLines,
|
|
286
255
|
renderOffAxisReferenceLine,
|
|
287
256
|
"off-axis",
|
|
288
257
|
).map((custom, index) => custom && !refLineCustom[index]);
|
|
289
258
|
const refLineKeys = referenceLineReactKeys(allRefLines);
|
|
290
|
-
// RN custom tags report their measured widths here so the Skia connector can
|
|
291
|
-
// start after the native badge instead of the hidden built-in pill.
|
|
292
|
-
const refLineCustomTagWidths = useSharedValue<number[]>([]);
|
|
293
|
-
|
|
294
259
|
const palette = applyPaletteOverride(
|
|
295
260
|
resolveTheme(accentColor, theme),
|
|
296
261
|
paletteOverride,
|
|
297
262
|
);
|
|
298
|
-
|
|
299
263
|
const leftEdgeFadeCfg = resolveLeftEdgeFade(
|
|
300
264
|
leftEdgeFade,
|
|
301
265
|
leftEdgeFadeColorsFromBgRgb(palette.bgRgb),
|
|
302
266
|
);
|
|
303
267
|
|
|
268
|
+
return {
|
|
269
|
+
series,
|
|
270
|
+
lineProp,
|
|
271
|
+
fontProp,
|
|
272
|
+
insets,
|
|
273
|
+
style,
|
|
274
|
+
seriesOpacity,
|
|
275
|
+
canvasMode,
|
|
276
|
+
timeWindow,
|
|
277
|
+
paused,
|
|
278
|
+
loading,
|
|
279
|
+
transitions,
|
|
280
|
+
snapKey,
|
|
281
|
+
smoothing,
|
|
282
|
+
exaggerate,
|
|
283
|
+
nonNegative,
|
|
284
|
+
maxValue,
|
|
285
|
+
yRangeScale,
|
|
286
|
+
windowBuffer,
|
|
287
|
+
nowOverride,
|
|
288
|
+
accessibilityLabel,
|
|
289
|
+
accessibilityRole,
|
|
290
|
+
emptyText,
|
|
291
|
+
formatValue,
|
|
292
|
+
formatTime,
|
|
293
|
+
yAxisCfg,
|
|
294
|
+
xAxisCfg,
|
|
295
|
+
topLabelCfg,
|
|
296
|
+
bottomLabelCfg,
|
|
297
|
+
scrubCfg,
|
|
298
|
+
scrubEnabled,
|
|
299
|
+
seriesTooltipCfg,
|
|
300
|
+
timeScrollEnabled,
|
|
301
|
+
returnToLiveMs,
|
|
302
|
+
timeScrollOverscroll,
|
|
303
|
+
timeScrollFling,
|
|
304
|
+
zoomCfg,
|
|
305
|
+
zoomEnabled,
|
|
306
|
+
scrollGestureMode,
|
|
307
|
+
scrubHoldMs,
|
|
308
|
+
selectionDotCfg,
|
|
309
|
+
gridStyleCfg,
|
|
310
|
+
dotCfg,
|
|
311
|
+
dotOuterRadius,
|
|
312
|
+
legendCfg,
|
|
313
|
+
degenCfg,
|
|
314
|
+
metricsCfg,
|
|
315
|
+
allRefLines,
|
|
316
|
+
refValues,
|
|
317
|
+
refLineCustom,
|
|
318
|
+
refLineOffAxisCustom,
|
|
319
|
+
refLineKeys,
|
|
320
|
+
palette,
|
|
321
|
+
leftEdgeFadeCfg,
|
|
322
|
+
onScrub,
|
|
323
|
+
onGestureStart,
|
|
324
|
+
onGestureEnd,
|
|
325
|
+
onVisibleRangeChange,
|
|
326
|
+
onReachStart,
|
|
327
|
+
onSeriesToggle,
|
|
328
|
+
onDegenShake,
|
|
329
|
+
markers,
|
|
330
|
+
onMarkerPress,
|
|
331
|
+
markerHitRadius,
|
|
332
|
+
markerClusterCfg,
|
|
333
|
+
markersActive,
|
|
334
|
+
renderMarker,
|
|
335
|
+
renderOverlay,
|
|
336
|
+
renderReferenceLine,
|
|
337
|
+
renderOffAxisReferenceLine,
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function resolveSeriesSnapshotLayout(
|
|
342
|
+
snapshot: SeriesConfig[],
|
|
343
|
+
dot: ReturnType<typeof resolveMultiSeriesDot>,
|
|
344
|
+
dotOuterRadius: number,
|
|
345
|
+
font: SkFont,
|
|
346
|
+
) {
|
|
347
|
+
const maxLabelWidth = dot.valueLabel
|
|
348
|
+
? Math.max(
|
|
349
|
+
0,
|
|
350
|
+
...snapshot.map((item) =>
|
|
351
|
+
measureFontTextWidth(font, item.label ?? item.id),
|
|
352
|
+
),
|
|
353
|
+
)
|
|
354
|
+
: 0;
|
|
355
|
+
return {
|
|
356
|
+
maxLabelWidth,
|
|
357
|
+
labelInset: dot.valueLabel ? dotOuterRadius + 8 + maxLabelWidth + 8 : 0,
|
|
358
|
+
representativeValue:
|
|
359
|
+
snapshot.length > 0 ? Math.max(...snapshot.map((item) => item.value)) : 0,
|
|
360
|
+
colors: resolveMultiSeriesLineColorsSnapshot(snapshot),
|
|
361
|
+
styles: resolveMultiSeriesLineStylesSnapshot(snapshot),
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function composeSeriesRootGesture(
|
|
366
|
+
crosshairGesture: ReturnType<typeof useCrosshairSeries>["gesture"],
|
|
367
|
+
markerTapGesture: ReturnType<typeof useMarkers>["tapGesture"],
|
|
368
|
+
panScrollGesture: ReturnType<typeof usePanScroll>,
|
|
369
|
+
pinchZoomGesture: ReturnType<typeof usePinchZoom>,
|
|
370
|
+
markersActive: boolean,
|
|
371
|
+
timeScrollEnabled: boolean,
|
|
372
|
+
zoomEnabled: boolean,
|
|
373
|
+
scrollGestureMode: "holdToScrub" | "axisDrag",
|
|
374
|
+
) {
|
|
375
|
+
let gesture = markersActive
|
|
376
|
+
? Gesture.Race(crosshairGesture, markerTapGesture)
|
|
377
|
+
: crosshairGesture;
|
|
378
|
+
if (timeScrollEnabled) {
|
|
379
|
+
gesture =
|
|
380
|
+
scrollGestureMode === "axisDrag"
|
|
381
|
+
? Gesture.Exclusive(panScrollGesture, gesture)
|
|
382
|
+
: Gesture.Race(panScrollGesture, gesture);
|
|
383
|
+
}
|
|
384
|
+
return zoomEnabled
|
|
385
|
+
? Gesture.Simultaneous(gesture, pinchZoomGesture)
|
|
386
|
+
: gesture;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function useLiveChartSeriesController(props: LiveChartSeriesProps) {
|
|
390
|
+
const {
|
|
391
|
+
series,
|
|
392
|
+
lineProp,
|
|
393
|
+
fontProp,
|
|
394
|
+
insets,
|
|
395
|
+
style,
|
|
396
|
+
seriesOpacity,
|
|
397
|
+
canvasMode,
|
|
398
|
+
timeWindow,
|
|
399
|
+
paused,
|
|
400
|
+
loading,
|
|
401
|
+
transitions,
|
|
402
|
+
snapKey,
|
|
403
|
+
smoothing,
|
|
404
|
+
exaggerate,
|
|
405
|
+
nonNegative,
|
|
406
|
+
maxValue,
|
|
407
|
+
yRangeScale,
|
|
408
|
+
windowBuffer,
|
|
409
|
+
nowOverride,
|
|
410
|
+
accessibilityLabel,
|
|
411
|
+
accessibilityRole,
|
|
412
|
+
emptyText,
|
|
413
|
+
formatValue,
|
|
414
|
+
formatTime,
|
|
415
|
+
yAxisCfg,
|
|
416
|
+
xAxisCfg,
|
|
417
|
+
topLabelCfg,
|
|
418
|
+
bottomLabelCfg,
|
|
419
|
+
scrubCfg,
|
|
420
|
+
scrubEnabled,
|
|
421
|
+
seriesTooltipCfg,
|
|
422
|
+
timeScrollEnabled,
|
|
423
|
+
returnToLiveMs,
|
|
424
|
+
timeScrollOverscroll,
|
|
425
|
+
timeScrollFling,
|
|
426
|
+
zoomCfg,
|
|
427
|
+
zoomEnabled,
|
|
428
|
+
scrollGestureMode,
|
|
429
|
+
scrubHoldMs,
|
|
430
|
+
selectionDotCfg,
|
|
431
|
+
gridStyleCfg,
|
|
432
|
+
dotCfg,
|
|
433
|
+
dotOuterRadius,
|
|
434
|
+
legendCfg,
|
|
435
|
+
degenCfg,
|
|
436
|
+
metricsCfg,
|
|
437
|
+
allRefLines,
|
|
438
|
+
refValues,
|
|
439
|
+
refLineCustom,
|
|
440
|
+
refLineOffAxisCustom,
|
|
441
|
+
refLineKeys,
|
|
442
|
+
palette,
|
|
443
|
+
leftEdgeFadeCfg,
|
|
444
|
+
onScrub,
|
|
445
|
+
onGestureStart,
|
|
446
|
+
onGestureEnd,
|
|
447
|
+
onVisibleRangeChange,
|
|
448
|
+
onReachStart,
|
|
449
|
+
onSeriesToggle,
|
|
450
|
+
onDegenShake,
|
|
451
|
+
markers,
|
|
452
|
+
onMarkerPress,
|
|
453
|
+
markerHitRadius,
|
|
454
|
+
markerClusterCfg,
|
|
455
|
+
markersActive,
|
|
456
|
+
renderMarker,
|
|
457
|
+
renderOverlay,
|
|
458
|
+
renderReferenceLine,
|
|
459
|
+
renderOffAxisReferenceLine,
|
|
460
|
+
} = resolveLiveChartSeriesInputs(props);
|
|
461
|
+
const fullSeriesOpacity = useSharedValue(1);
|
|
462
|
+
const resolvedSeriesOpacity = seriesOpacity ?? fullSeriesOpacity;
|
|
463
|
+
const emptyMarkers = useSharedValue<Marker[]>([]);
|
|
464
|
+
const markersSV = markers ?? emptyMarkers;
|
|
465
|
+
// RN custom tags report their measured widths here so the Skia connector can
|
|
466
|
+
// start after the native badge instead of the hidden built-in pill.
|
|
467
|
+
const refLineCustomTagWidths = useSharedValue<number[]>([]);
|
|
468
|
+
|
|
304
469
|
const skiaFont = useChartSkiaFont(
|
|
305
470
|
fontProp,
|
|
306
471
|
MONO_FONT_FAMILY,
|
|
@@ -308,43 +473,27 @@ function useLiveChartSeriesController({
|
|
|
308
473
|
);
|
|
309
474
|
|
|
310
475
|
// Snapshot of the series config (colors, styles, labels) for layout + line
|
|
311
|
-
// rendering.
|
|
312
|
-
//
|
|
313
|
-
// Reanimated's strict-mode warning. React flushes layout-effect state before
|
|
314
|
-
// paint, so the seed causes no flash.
|
|
476
|
+
// rendering. The animated reaction below emits the initial snapshot and every
|
|
477
|
+
// subsequent configuration change without reading the SharedValue in render.
|
|
315
478
|
const [seriesSnapshot, setSeriesSnapshot] = useState<SeriesConfig[]>([]);
|
|
316
|
-
// react-doctor-disable-next-line react-doctor/no-derived-state-effect -- Reanimated: series must be read off the render path
|
|
317
|
-
useLayoutEffect(() => {
|
|
318
|
-
// `.get()` (not `.value`): React Compiler hoists the `.value` getter read into
|
|
319
|
-
// render scope for memoization, which trips Reanimated's strict-mode warning;
|
|
320
|
-
// it leaves the `.get()` method call inside the effect.
|
|
321
|
-
// react-doctor-disable-next-line react-hooks-js/set-state-in-effect -- Reanimated: seeding from a SharedValue off render is the warning-free path
|
|
322
|
-
// eslint-disable-next-line react-hooks/set-state-in-effect -- Reanimated: seed from the SharedValue outside render to avoid strict-mode access warnings
|
|
323
|
-
setSeriesSnapshot(series.get().slice());
|
|
324
|
-
}, [series]);
|
|
325
479
|
|
|
326
480
|
// Mount per-series drawing worklets only for real series. The previous fixed
|
|
327
481
|
// 12-slot render kept 144 derived-value mappers alive for the default stroke,
|
|
328
482
|
// dot, and value-label layers even when a chart contained only one line.
|
|
329
483
|
const activeSeriesCount = Math.min(seriesSnapshot.length, MAX_MULTI_SERIES);
|
|
330
484
|
|
|
331
|
-
const
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
const representativeValue =
|
|
345
|
-
seriesSnapshot.length > 0
|
|
346
|
-
? Math.max(...seriesSnapshot.map((s) => s.value))
|
|
347
|
-
: 0;
|
|
485
|
+
const {
|
|
486
|
+
maxLabelWidth: maxSeriesLabelWidth,
|
|
487
|
+
labelInset: seriesLabelInset,
|
|
488
|
+
representativeValue,
|
|
489
|
+
colors: lineColors,
|
|
490
|
+
styles: lineStyles,
|
|
491
|
+
} = resolveSeriesSnapshotLayout(
|
|
492
|
+
seriesSnapshot,
|
|
493
|
+
dotCfg,
|
|
494
|
+
dotOuterRadius,
|
|
495
|
+
skiaFont,
|
|
496
|
+
);
|
|
348
497
|
|
|
349
498
|
const { strokeWidth, padding: effectivePadding } = resolveChartLayout({
|
|
350
499
|
palette,
|
|
@@ -366,11 +515,7 @@ function useLiveChartSeriesController({
|
|
|
366
515
|
|
|
367
516
|
const hasData = useDerivedValue(() => {
|
|
368
517
|
"worklet";
|
|
369
|
-
|
|
370
|
-
for (let i = 0; i < arr.length; i++) {
|
|
371
|
-
if (arr[i].data.length >= 2) return true;
|
|
372
|
-
}
|
|
373
|
-
return false;
|
|
518
|
+
return hasMultiSeriesChartData(series.value);
|
|
374
519
|
});
|
|
375
520
|
|
|
376
521
|
// Resolve the loading shell: null = not loading, else the styled config.
|
|
@@ -422,13 +567,6 @@ function useLiveChartSeriesController({
|
|
|
422
567
|
lineProp?.simplify,
|
|
423
568
|
);
|
|
424
569
|
|
|
425
|
-
// Per-series colors and stroke styles, derived from the off-render snapshot
|
|
426
|
-
// (React state — so no Reanimated read-during-render). The reaction below
|
|
427
|
-
// refreshes the snapshot when the series config signature changes. Plain
|
|
428
|
-
// derivations — React Compiler memoizes them, so no manual useMemo.
|
|
429
|
-
const lineColors = resolveMultiSeriesLineColorsSnapshot(seriesSnapshot);
|
|
430
|
-
const lineStyles = resolveMultiSeriesLineStylesSnapshot(seriesSnapshot);
|
|
431
|
-
|
|
432
570
|
// Read the `series` prop from closure, not a SharedValue passed through
|
|
433
571
|
// `scheduleOnRN`: the handle serialized across the worklet→JS boundary keeps
|
|
434
572
|
// the native `.value` accessor but loses the `.get()` method (`.get()` on it
|
|
@@ -581,22 +719,16 @@ function useLiveChartSeriesController({
|
|
|
581
719
|
onReachStart,
|
|
582
720
|
});
|
|
583
721
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
: Gesture.Race(panScrollGesture, rootGesture);
|
|
595
|
-
}
|
|
596
|
-
// Pinch runs alongside (two-finger, disjoint from the one-finger gestures).
|
|
597
|
-
if (zoomEnabled) {
|
|
598
|
-
rootGesture = Gesture.Simultaneous(rootGesture, pinchZoomGesture);
|
|
599
|
-
}
|
|
722
|
+
const rootGesture = composeSeriesRootGesture(
|
|
723
|
+
crosshair.gesture,
|
|
724
|
+
markerTapGesture,
|
|
725
|
+
panScrollGesture,
|
|
726
|
+
pinchZoomGesture,
|
|
727
|
+
markersActive,
|
|
728
|
+
timeScrollEnabled,
|
|
729
|
+
zoomEnabled,
|
|
730
|
+
scrollGestureMode,
|
|
731
|
+
);
|
|
600
732
|
|
|
601
733
|
const backgroundColor = `rgb(${palette.bgRgb[0]}, ${palette.bgRgb[1]}, ${palette.bgRgb[2]})`;
|
|
602
734
|
|
|
@@ -656,6 +788,7 @@ function useLiveChartSeriesController({
|
|
|
656
788
|
// engine + reveal
|
|
657
789
|
engine,
|
|
658
790
|
reveal,
|
|
791
|
+
loadingActive,
|
|
659
792
|
// loading shell styling (null → not loading)
|
|
660
793
|
loadingLineColor: loadingCfg?.color,
|
|
661
794
|
loadingStrokeWidth: loadingCfg?.strokeWidth,
|
|
@@ -1037,46 +1170,131 @@ function SeriesTooltipLayer({
|
|
|
1037
1170
|
);
|
|
1038
1171
|
}
|
|
1039
1172
|
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1173
|
+
function SeriesLegend({
|
|
1174
|
+
model,
|
|
1175
|
+
position,
|
|
1176
|
+
}: {
|
|
1177
|
+
model: LiveChartSeriesModel;
|
|
1178
|
+
position: "top" | "bottom";
|
|
1179
|
+
}) {
|
|
1180
|
+
const { legendCfg, series, palette, onSeriesToggle } = model;
|
|
1181
|
+
if (legendCfg.position !== position) return null;
|
|
1182
|
+
return (
|
|
1183
|
+
<SeriesToggleChips
|
|
1184
|
+
series={series}
|
|
1185
|
+
legend={legendCfg}
|
|
1186
|
+
palette={palette}
|
|
1187
|
+
onSeriesToggle={onSeriesToggle}
|
|
1188
|
+
/>
|
|
1052
1189
|
);
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
function SeriesCanvas({ model }: { model: LiveChartSeriesModel }) {
|
|
1053
1193
|
const {
|
|
1054
|
-
|
|
1055
|
-
backgroundColor,
|
|
1056
|
-
style,
|
|
1057
|
-
onLayout,
|
|
1058
|
-
accessibilityLabel,
|
|
1059
|
-
accessibilityRole,
|
|
1194
|
+
canvasMode,
|
|
1060
1195
|
layoutHeight,
|
|
1061
|
-
legendCfg,
|
|
1062
|
-
series,
|
|
1063
|
-
onSeriesToggle,
|
|
1064
|
-
leftEdgeFadeCfg,
|
|
1065
|
-
effectivePadding,
|
|
1066
1196
|
engine,
|
|
1197
|
+
backgroundColor,
|
|
1198
|
+
effectivePadding,
|
|
1199
|
+
topConnector,
|
|
1200
|
+
bottomConnector,
|
|
1201
|
+
loadingActive,
|
|
1202
|
+
topLabelCfg,
|
|
1203
|
+
leftEdgeFadeCfg,
|
|
1204
|
+
palette,
|
|
1067
1205
|
scrubCfg,
|
|
1068
1206
|
seriesTooltipCfg,
|
|
1069
1207
|
crosshair,
|
|
1070
|
-
palette,
|
|
1071
|
-
dotCfg,
|
|
1072
|
-
dotOuterRadius,
|
|
1073
1208
|
selectionDot,
|
|
1074
1209
|
selectionColor,
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1210
|
+
dotOuterRadius,
|
|
1211
|
+
dotCfg,
|
|
1212
|
+
} = model;
|
|
1213
|
+
const liveDotExtent = Math.max(
|
|
1214
|
+
dotOuterRadius,
|
|
1215
|
+
dotCfg.pulse
|
|
1216
|
+
? pulseRadialOutset(dotCfg.pulse.maxRadius, dotCfg.pulse.strokeWidth)
|
|
1217
|
+
: 0,
|
|
1218
|
+
);
|
|
1219
|
+
return (
|
|
1220
|
+
<Canvas
|
|
1221
|
+
key={canvasMode}
|
|
1222
|
+
style={{ flex: 1, minHeight: layoutHeight || 1 }}
|
|
1223
|
+
opaque={canvasMode === "opaque"}
|
|
1224
|
+
>
|
|
1225
|
+
{canvasMode === "opaque" ? (
|
|
1226
|
+
<Rect
|
|
1227
|
+
x={0}
|
|
1228
|
+
y={0}
|
|
1229
|
+
width={engine.canvasWidth}
|
|
1230
|
+
height={engine.canvasHeight}
|
|
1231
|
+
color={backgroundColor}
|
|
1232
|
+
/>
|
|
1233
|
+
) : null}
|
|
1234
|
+
<SeriesChartStack model={model} />
|
|
1235
|
+
<ExtremaConnectorOverlay
|
|
1236
|
+
engine={engine}
|
|
1237
|
+
padding={effectivePadding}
|
|
1238
|
+
top={topConnector}
|
|
1239
|
+
bottom={bottomConnector}
|
|
1240
|
+
hideExtrema={loadingActive}
|
|
1241
|
+
suppressBottomWhenCoincident={
|
|
1242
|
+
topLabelCfg?.position === "extrema" ||
|
|
1243
|
+
topLabelCfg?.position === "extrema-edge"
|
|
1244
|
+
}
|
|
1245
|
+
/>
|
|
1246
|
+
{leftEdgeFadeCfg ? (
|
|
1247
|
+
<LeftEdgeFade
|
|
1248
|
+
paddingLeft={effectivePadding.left}
|
|
1249
|
+
fadeWidth={leftEdgeFadeCfg.width}
|
|
1250
|
+
startColor={leftEdgeFadeCfg.startColor}
|
|
1251
|
+
endColor={leftEdgeFadeCfg.endColor}
|
|
1252
|
+
engine={engine}
|
|
1253
|
+
opaqueBackgroundRgb={
|
|
1254
|
+
canvasMode === "opaque" ? palette.bgRgb : undefined
|
|
1255
|
+
}
|
|
1256
|
+
/>
|
|
1257
|
+
) : null}
|
|
1258
|
+
<SeriesRefBadgeLayer model={model} />
|
|
1259
|
+
{scrubCfg ? (
|
|
1260
|
+
<CrosshairLine
|
|
1261
|
+
scrubX={crosshair.scrubX}
|
|
1262
|
+
crosshairOpacity={crosshair.crosshairOpacity}
|
|
1263
|
+
engine={engine}
|
|
1264
|
+
padding={effectivePadding}
|
|
1265
|
+
palette={palette}
|
|
1266
|
+
selectionDot={selectionDot}
|
|
1267
|
+
selectionY={crosshair.scrubDotY}
|
|
1268
|
+
scrubActive={crosshair.scrubActive}
|
|
1269
|
+
selectionColor={selectionColor}
|
|
1270
|
+
dimOpacity={scrubCfg.dimOpacity}
|
|
1271
|
+
liveDotExtent={liveDotExtent}
|
|
1272
|
+
crosshairLineColor={
|
|
1273
|
+
seriesTooltipCfg?.guideColor ?? scrubCfg.crosshairLineColor
|
|
1274
|
+
}
|
|
1275
|
+
crosshairStrokeWidth={
|
|
1276
|
+
seriesTooltipCfg?.guideWidth ?? scrubCfg.crosshairStrokeWidth
|
|
1277
|
+
}
|
|
1278
|
+
crosshairOvershoot={scrubCfg.crosshairOvershoot}
|
|
1279
|
+
crosshairFade={scrubCfg.crosshairFade}
|
|
1280
|
+
crosshairFadeDistance={scrubCfg.crosshairFadeDistance}
|
|
1281
|
+
crosshairLineCap={scrubCfg.crosshairLineCap}
|
|
1282
|
+
crosshairDash={
|
|
1283
|
+
seriesTooltipCfg
|
|
1284
|
+
? seriesTooltipCfg.guideDashPattern
|
|
1285
|
+
: scrubCfg.crosshairDash
|
|
1286
|
+
}
|
|
1287
|
+
crosshairDimColor={scrubCfg.crosshairDimColor}
|
|
1288
|
+
opaqueCanvas={canvasMode === "opaque"}
|
|
1289
|
+
/>
|
|
1290
|
+
) : null}
|
|
1291
|
+
<SeriesValueLabelLayer model={model} />
|
|
1292
|
+
</Canvas>
|
|
1293
|
+
);
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
function SeriesNativeOverlays({ model }: { model: LiveChartSeriesModel }) {
|
|
1297
|
+
const {
|
|
1080
1298
|
markersActive,
|
|
1081
1299
|
markersSV,
|
|
1082
1300
|
markerClusterCfg,
|
|
@@ -1089,34 +1307,107 @@ export const LiveChartSeries = forwardRef<
|
|
|
1089
1307
|
refLineOffAxisCustom,
|
|
1090
1308
|
refLineCustomTagWidths,
|
|
1091
1309
|
overlayScrubFade,
|
|
1092
|
-
|
|
1310
|
+
engine,
|
|
1311
|
+
effectivePadding,
|
|
1312
|
+
series,
|
|
1313
|
+
formatValue,
|
|
1314
|
+
seriesTooltipCfg,
|
|
1093
1315
|
} = model;
|
|
1094
|
-
|
|
1095
|
-
// Mirror the Skia overlay fade onto the RN custom-marker sibling so
|
|
1096
|
-
// `scrub.hideOverlaysOnScrub` hides it with the Skia markers.
|
|
1097
1316
|
const overlayFadeStyle = useAnimatedStyle(() => ({
|
|
1098
1317
|
opacity: overlayScrubFade.get(),
|
|
1099
1318
|
}));
|
|
1319
|
+
const hasCustomAnnotations =
|
|
1320
|
+
(markersActive && renderMarker != null) ||
|
|
1321
|
+
((renderReferenceLine != null || renderOffAxisReferenceLine != null) &&
|
|
1322
|
+
allRefLines.length > 0);
|
|
1100
1323
|
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1324
|
+
return (
|
|
1325
|
+
<>
|
|
1326
|
+
{hasCustomAnnotations ? (
|
|
1327
|
+
<Animated.View
|
|
1328
|
+
pointerEvents="box-none"
|
|
1329
|
+
style={[StyleSheet.absoluteFill, overlayFadeStyle]}
|
|
1330
|
+
>
|
|
1331
|
+
{markersActive && renderMarker ? (
|
|
1332
|
+
<CustomMarkerOverlay
|
|
1333
|
+
markers={markersSV}
|
|
1334
|
+
renderMarker={renderMarker}
|
|
1335
|
+
engine={engine}
|
|
1336
|
+
padding={effectivePadding}
|
|
1337
|
+
series={series}
|
|
1338
|
+
cluster={markerClusterCfg}
|
|
1339
|
+
/>
|
|
1340
|
+
) : null}
|
|
1341
|
+
{renderReferenceLine && allRefLines.length > 0 ? (
|
|
1342
|
+
<CustomReferenceLineOverlay
|
|
1343
|
+
lines={allRefLines}
|
|
1344
|
+
renderReferenceLine={renderReferenceLine}
|
|
1345
|
+
custom={refLineCustom}
|
|
1346
|
+
engine={engine}
|
|
1347
|
+
padding={effectivePadding}
|
|
1348
|
+
formatValue={formatValue}
|
|
1349
|
+
tagWidths={refLineCustomTagWidths}
|
|
1350
|
+
/>
|
|
1351
|
+
) : null}
|
|
1352
|
+
{renderOffAxisReferenceLine && allRefLines.length > 0 ? (
|
|
1353
|
+
<CustomReferenceLineOverlay
|
|
1354
|
+
lines={allRefLines}
|
|
1355
|
+
renderReferenceLine={renderOffAxisReferenceLine}
|
|
1356
|
+
custom={refLineOffAxisCustom}
|
|
1357
|
+
engine={engine}
|
|
1358
|
+
padding={effectivePadding}
|
|
1359
|
+
formatValue={formatValue}
|
|
1360
|
+
tagWidths={refLineCustomTagWidths}
|
|
1361
|
+
offAxisOnly
|
|
1362
|
+
/>
|
|
1363
|
+
) : null}
|
|
1364
|
+
</Animated.View>
|
|
1365
|
+
) : null}
|
|
1366
|
+
{seriesTooltipCfg ? (
|
|
1367
|
+
<View
|
|
1368
|
+
testID="live-chart-series-tooltip-overlay"
|
|
1369
|
+
pointerEvents="none"
|
|
1370
|
+
style={StyleSheet.absoluteFill}
|
|
1371
|
+
>
|
|
1372
|
+
<Canvas style={StyleSheet.absoluteFill}>
|
|
1373
|
+
<SeriesTooltipLayer model={model} config={seriesTooltipCfg} />
|
|
1374
|
+
</Canvas>
|
|
1375
|
+
</View>
|
|
1376
|
+
) : null}
|
|
1377
|
+
{renderOverlay ? <SeriesCustomConsumerOverlay model={model} /> : null}
|
|
1378
|
+
</>
|
|
1109
1379
|
);
|
|
1380
|
+
}
|
|
1110
1381
|
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
)
|
|
1382
|
+
export const LiveChartSeries = forwardRef<
|
|
1383
|
+
LiveChartHandle,
|
|
1384
|
+
LiveChartSeriesProps
|
|
1385
|
+
>(function LiveChartSeries(props, ref) {
|
|
1386
|
+
const model = useLiveChartSeriesController(props);
|
|
1387
|
+
const { viewEnd, viewWindow } = model.engine;
|
|
1388
|
+
useImperativeHandle(
|
|
1389
|
+
ref,
|
|
1390
|
+
() => ({
|
|
1391
|
+
resetZoom: () => scheduleOnUI(resetPinchZoom, { viewEnd, viewWindow }),
|
|
1392
|
+
}),
|
|
1393
|
+
[viewEnd, viewWindow],
|
|
1394
|
+
);
|
|
1395
|
+
const {
|
|
1396
|
+
rootGesture,
|
|
1397
|
+
backgroundColor,
|
|
1398
|
+
style,
|
|
1399
|
+
onLayout,
|
|
1400
|
+
accessibilityLabel,
|
|
1401
|
+
accessibilityRole,
|
|
1402
|
+
layoutHeight,
|
|
1403
|
+
effectivePadding,
|
|
1404
|
+
engine,
|
|
1405
|
+
palette,
|
|
1406
|
+
formatValue,
|
|
1407
|
+
topLabelCfg,
|
|
1408
|
+
bottomLabelCfg,
|
|
1409
|
+
loadingActive,
|
|
1410
|
+
} = model;
|
|
1120
1411
|
|
|
1121
1412
|
return (
|
|
1122
1413
|
<View
|
|
@@ -1125,92 +1416,14 @@ export const LiveChartSeries = forwardRef<
|
|
|
1125
1416
|
accessibilityLabel={accessibilityLabel}
|
|
1126
1417
|
accessibilityRole={accessibilityRole}
|
|
1127
1418
|
>
|
|
1128
|
-
{
|
|
1419
|
+
<SeriesLegend model={model} position="top" />
|
|
1129
1420
|
{/* Gesture + layout wrap ONLY the canvas: the legend chips are Pressables
|
|
1130
1421
|
that must sit outside the scrub gesture to receive taps, and the engine
|
|
1131
1422
|
canvas height must measure the canvas alone (excluding the legend row),
|
|
1132
1423
|
else points map into a taller area and the x-axis draws past the edge. */}
|
|
1133
1424
|
<GestureDetector gesture={rootGesture}>
|
|
1134
1425
|
<View style={{ flex: 1 }} onLayout={onLayout}>
|
|
1135
|
-
{
|
|
1136
|
-
<Canvas
|
|
1137
|
-
key={canvasMode}
|
|
1138
|
-
style={{ flex: 1, minHeight: layoutHeight || 1 }}
|
|
1139
|
-
opaque={canvasMode === "opaque"}
|
|
1140
|
-
>
|
|
1141
|
-
{canvasMode === "opaque" && (
|
|
1142
|
-
<Rect
|
|
1143
|
-
x={0}
|
|
1144
|
-
y={0}
|
|
1145
|
-
width={engine.canvasWidth}
|
|
1146
|
-
height={engine.canvasHeight}
|
|
1147
|
-
color={backgroundColor}
|
|
1148
|
-
/>
|
|
1149
|
-
)}
|
|
1150
|
-
<SeriesChartStack model={model} />
|
|
1151
|
-
|
|
1152
|
-
{/* "extrema-edge" connector lines (dot → edge readout). Outside the
|
|
1153
|
-
stack's degen-shake group so they track the (unshaken) RN dot. */}
|
|
1154
|
-
<ExtremaConnectorOverlay
|
|
1155
|
-
engine={engine}
|
|
1156
|
-
padding={effectivePadding}
|
|
1157
|
-
top={topConnector}
|
|
1158
|
-
bottom={bottomConnector}
|
|
1159
|
-
/>
|
|
1160
|
-
|
|
1161
|
-
{leftEdgeFadeCfg && (
|
|
1162
|
-
<LeftEdgeFade
|
|
1163
|
-
paddingLeft={effectivePadding.left}
|
|
1164
|
-
fadeWidth={leftEdgeFadeCfg.width}
|
|
1165
|
-
startColor={leftEdgeFadeCfg.startColor}
|
|
1166
|
-
endColor={leftEdgeFadeCfg.endColor}
|
|
1167
|
-
engine={engine}
|
|
1168
|
-
opaqueBackgroundRgb={
|
|
1169
|
-
canvasMode === "opaque" ? palette.bgRgb : undefined
|
|
1170
|
-
}
|
|
1171
|
-
/>
|
|
1172
|
-
)}
|
|
1173
|
-
|
|
1174
|
-
{/* Reference-line badges + labels above the fade so they stay crisp. */}
|
|
1175
|
-
<SeriesRefBadgeLayer model={model} />
|
|
1176
|
-
|
|
1177
|
-
{scrubCfg && (
|
|
1178
|
-
<CrosshairLine
|
|
1179
|
-
scrubX={crosshair.scrubX}
|
|
1180
|
-
crosshairOpacity={crosshair.crosshairOpacity}
|
|
1181
|
-
engine={engine}
|
|
1182
|
-
padding={effectivePadding}
|
|
1183
|
-
palette={palette}
|
|
1184
|
-
selectionDot={selectionDot}
|
|
1185
|
-
selectionY={crosshair.scrubDotY}
|
|
1186
|
-
scrubActive={crosshair.scrubActive}
|
|
1187
|
-
selectionColor={selectionColor}
|
|
1188
|
-
dimOpacity={scrubCfg.dimOpacity}
|
|
1189
|
-
liveDotExtent={liveDotExtent}
|
|
1190
|
-
crosshairLineColor={
|
|
1191
|
-
seriesTooltipCfg?.guideColor ?? scrubCfg.crosshairLineColor
|
|
1192
|
-
}
|
|
1193
|
-
crosshairStrokeWidth={
|
|
1194
|
-
seriesTooltipCfg?.guideWidth ?? scrubCfg.crosshairStrokeWidth
|
|
1195
|
-
}
|
|
1196
|
-
crosshairOvershoot={scrubCfg.crosshairOvershoot}
|
|
1197
|
-
crosshairFade={scrubCfg.crosshairFade}
|
|
1198
|
-
crosshairFadeDistance={scrubCfg.crosshairFadeDistance}
|
|
1199
|
-
crosshairLineCap={scrubCfg.crosshairLineCap}
|
|
1200
|
-
crosshairDash={
|
|
1201
|
-
seriesTooltipCfg
|
|
1202
|
-
? seriesTooltipCfg.guideDashPattern
|
|
1203
|
-
: scrubCfg.crosshairDash
|
|
1204
|
-
}
|
|
1205
|
-
crosshairDimColor={scrubCfg.crosshairDimColor}
|
|
1206
|
-
opaqueCanvas={canvasMode === "opaque"}
|
|
1207
|
-
/>
|
|
1208
|
-
)}
|
|
1209
|
-
|
|
1210
|
-
{/* Per-series value labels on top of the scrub dim so the dim never
|
|
1211
|
-
clips them (they track each series' live value, not the scrub). */}
|
|
1212
|
-
<SeriesValueLabelLayer model={model} />
|
|
1213
|
-
</Canvas>
|
|
1426
|
+
<SeriesCanvas model={model} />
|
|
1214
1427
|
|
|
1215
1428
|
{/* RN labels floated over the canvas (sibling of <Canvas>, an RN
|
|
1216
1429
|
view). Inside the canvas wrapper so its top/bottom edges align
|
|
@@ -1222,77 +1435,13 @@ export const LiveChartSeries = forwardRef<
|
|
|
1222
1435
|
formatValue={formatValue}
|
|
1223
1436
|
defaultColor={palette.gridLabel}
|
|
1224
1437
|
padding={effectivePadding}
|
|
1438
|
+
hideExtrema={loadingActive}
|
|
1225
1439
|
/>
|
|
1226
1440
|
|
|
1227
|
-
{
|
|
1228
|
-
pinned to their live positions. As a post-Canvas sibling they render
|
|
1229
|
-
above the left-edge fade and scrub overlay. The box-none fade wrapper
|
|
1230
|
-
keeps `scrub.hideOverlaysOnScrub` behavior aligned with Skia. */}
|
|
1231
|
-
{((markersActive && renderMarker) ||
|
|
1232
|
-
((renderReferenceLine || renderOffAxisReferenceLine) &&
|
|
1233
|
-
allRefLines.length > 0)) && (
|
|
1234
|
-
<Animated.View
|
|
1235
|
-
pointerEvents="box-none"
|
|
1236
|
-
style={[StyleSheet.absoluteFill, overlayFadeStyle]}
|
|
1237
|
-
>
|
|
1238
|
-
{markersActive && renderMarker && (
|
|
1239
|
-
<CustomMarkerOverlay
|
|
1240
|
-
markers={markersSV}
|
|
1241
|
-
renderMarker={renderMarker}
|
|
1242
|
-
engine={engine}
|
|
1243
|
-
padding={effectivePadding}
|
|
1244
|
-
series={series}
|
|
1245
|
-
cluster={markerClusterCfg}
|
|
1246
|
-
/>
|
|
1247
|
-
)}
|
|
1248
|
-
{renderReferenceLine && allRefLines.length > 0 && (
|
|
1249
|
-
<CustomReferenceLineOverlay
|
|
1250
|
-
lines={allRefLines}
|
|
1251
|
-
renderReferenceLine={renderReferenceLine}
|
|
1252
|
-
custom={refLineCustom}
|
|
1253
|
-
engine={engine}
|
|
1254
|
-
padding={effectivePadding}
|
|
1255
|
-
formatValue={formatValue}
|
|
1256
|
-
tagWidths={refLineCustomTagWidths}
|
|
1257
|
-
/>
|
|
1258
|
-
)}
|
|
1259
|
-
{renderOffAxisReferenceLine && allRefLines.length > 0 && (
|
|
1260
|
-
<CustomReferenceLineOverlay
|
|
1261
|
-
lines={allRefLines}
|
|
1262
|
-
renderReferenceLine={renderOffAxisReferenceLine}
|
|
1263
|
-
custom={refLineOffAxisCustom}
|
|
1264
|
-
engine={engine}
|
|
1265
|
-
padding={effectivePadding}
|
|
1266
|
-
formatValue={formatValue}
|
|
1267
|
-
tagWidths={refLineCustomTagWidths}
|
|
1268
|
-
offAxisOnly
|
|
1269
|
-
/>
|
|
1270
|
-
)}
|
|
1271
|
-
</Animated.View>
|
|
1272
|
-
)}
|
|
1273
|
-
|
|
1274
|
-
{/* A dedicated transparent Canvas keeps the UI-thread tooltip above
|
|
1275
|
-
custom RN annotations. Rendering it in the primary Canvas would
|
|
1276
|
-
put every post-Canvas `renderReferenceLine` sibling above it and
|
|
1277
|
-
produce mixed stacking through translucent custom tags. */}
|
|
1278
|
-
{seriesTooltipCfg && (
|
|
1279
|
-
<View
|
|
1280
|
-
testID="live-chart-series-tooltip-overlay"
|
|
1281
|
-
pointerEvents="none"
|
|
1282
|
-
style={StyleSheet.absoluteFill}
|
|
1283
|
-
>
|
|
1284
|
-
<Canvas style={StyleSheet.absoluteFill}>
|
|
1285
|
-
<SeriesTooltipLayer model={model} config={seriesTooltipCfg} />
|
|
1286
|
-
</Canvas>
|
|
1287
|
-
</View>
|
|
1288
|
-
)}
|
|
1289
|
-
|
|
1290
|
-
{/* Custom consumer overlay — topmost RN sibling with the live plot
|
|
1291
|
-
rect, including this multi-series chart's resolved axis inset. */}
|
|
1292
|
-
{renderOverlay && <SeriesCustomConsumerOverlay model={model} />}
|
|
1441
|
+
<SeriesNativeOverlays model={model} />
|
|
1293
1442
|
</View>
|
|
1294
1443
|
</GestureDetector>
|
|
1295
|
-
{
|
|
1444
|
+
<SeriesLegend model={model} position="bottom" />
|
|
1296
1445
|
</View>
|
|
1297
1446
|
);
|
|
1298
1447
|
});
|