react-native-livechart 4.9.0 → 4.9.2
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/LiveChart.d.ts.map +1 -1
- package/dist/components/LiveChartSeries.d.ts.map +1 -1
- package/dist/core/liveChartEngineTick.d.ts.map +1 -1
- package/dist/core/liveChartSeriesEngineTick.d.ts.map +1 -1
- package/dist/hooks/useChartSkiaFont.d.ts +2 -1
- package/dist/hooks/useChartSkiaFont.d.ts.map +1 -1
- package/dist/hooks/useCrosshair.d.ts.map +1 -1
- package/dist/hooks/useXAxis.d.ts.map +1 -1
- package/dist/types.d.ts +8 -3
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/LiveChart.tsx +500 -318
- package/src/components/LiveChartSeries.tsx +5 -1
- package/src/core/liveChartEngineTick.ts +17 -4
- package/src/core/liveChartSeriesEngineTick.ts +6 -0
- package/src/hooks/useChartSkiaFont.ts +31 -5
- package/src/hooks/useCrosshair.ts +195 -182
- package/src/hooks/useXAxis.ts +21 -5
- package/src/types.ts +8 -3
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
useEffect,
|
|
3
|
+
useLayoutEffect,
|
|
4
|
+
useMemo,
|
|
5
|
+
useRef,
|
|
6
|
+
useState,
|
|
7
|
+
} from "react";
|
|
2
8
|
import { StyleSheet, View } from "react-native";
|
|
3
9
|
import { Gesture, GestureDetector } from "react-native-gesture-handler";
|
|
4
10
|
import Animated, {
|
|
@@ -118,7 +124,6 @@ import type {
|
|
|
118
124
|
LiveChartPoint,
|
|
119
125
|
LiveChartProps,
|
|
120
126
|
Marker,
|
|
121
|
-
TradeEvent,
|
|
122
127
|
} from "../types";
|
|
123
128
|
import {
|
|
124
129
|
ThresholdBadgeOverlay,
|
|
@@ -159,7 +164,6 @@ import { ValueLineOverlay } from "./ValueLineOverlay";
|
|
|
159
164
|
import { XAxisOverlay } from "./XAxisOverlay";
|
|
160
165
|
import { YAxisOverlay } from "./YAxisOverlay";
|
|
161
166
|
|
|
162
|
-
|
|
163
167
|
/** Stable empty grouping result (identity-stable so downstream worklets don't
|
|
164
168
|
* re-run) used when reference-line grouping is off. */
|
|
165
169
|
const EMPTY_GROUPING: ReferenceGrouping = { hidden: [], groups: [] };
|
|
@@ -324,8 +328,6 @@ function useLiveChartController({
|
|
|
324
328
|
onReachStart,
|
|
325
329
|
onDegenShake,
|
|
326
330
|
}: LiveChartProps) {
|
|
327
|
-
const emptyTradeStream = useSharedValue<TradeEvent[]>([]);
|
|
328
|
-
const tradeStreamSV = tradeStream ?? emptyTradeStream;
|
|
329
331
|
const emptyMarkers = useSharedValue<Marker[]>([]);
|
|
330
332
|
const markersSV = markers ?? emptyMarkers;
|
|
331
333
|
// Stand-in threshold value so `useThreshold` can be called unconditionally
|
|
@@ -408,7 +410,10 @@ function useLiveChartController({
|
|
|
408
410
|
|
|
409
411
|
// Form-A lines a custom `renderReferenceLine` owns → suppress their built-in tag
|
|
410
412
|
// (no double-draw). Probed on the JS thread, index-aligned with `allRefLines`.
|
|
411
|
-
const refLineCustom = customReferenceLineFlags(
|
|
413
|
+
const refLineCustom = customReferenceLineFlags(
|
|
414
|
+
allRefLines,
|
|
415
|
+
renderReferenceLine,
|
|
416
|
+
);
|
|
412
417
|
|
|
413
418
|
// Live Y values of the *draggable* Form-A lines, folded into the engine's
|
|
414
419
|
// axis-range fit so dragging a line toward / past the visible edge expands the
|
|
@@ -726,7 +731,14 @@ function useLiveChartController({
|
|
|
726
731
|
continue;
|
|
727
732
|
}
|
|
728
733
|
const v = dragValues.get()[i] ?? l.value;
|
|
729
|
-
const y = computeScrubDotY(
|
|
734
|
+
const y = computeScrubDotY(
|
|
735
|
+
v,
|
|
736
|
+
dMin,
|
|
737
|
+
dMax,
|
|
738
|
+
ch,
|
|
739
|
+
top,
|
|
740
|
+
effectivePadding.bottom,
|
|
741
|
+
);
|
|
730
742
|
ys.push(y < 0 ? -1 : Math.min(bottom, Math.max(top, y)));
|
|
731
743
|
}
|
|
732
744
|
return groupReferenceLines(ys, refGroupingRadius);
|
|
@@ -890,27 +902,6 @@ function useLiveChartController({
|
|
|
890
902
|
adA * (areaDotsCfg?.opacity ?? 1),
|
|
891
903
|
];
|
|
892
904
|
|
|
893
|
-
const {
|
|
894
|
-
upBodiesPath,
|
|
895
|
-
downBodiesPath,
|
|
896
|
-
upWicksPath,
|
|
897
|
-
downWicksPath,
|
|
898
|
-
upBarsPath,
|
|
899
|
-
downBarsPath,
|
|
900
|
-
} = useCandlePaths(
|
|
901
|
-
engine,
|
|
902
|
-
effectivePadding,
|
|
903
|
-
// Match engine: stashed candles while reverse-morphing in candle mode.
|
|
904
|
-
isCandle ? candlesEngine : candles,
|
|
905
|
-
isCandle ? liveEngine : liveCandle,
|
|
906
|
-
candleWidth,
|
|
907
|
-
isCandle,
|
|
908
|
-
metricsCfg.candle,
|
|
909
|
-
volumeBandHeight,
|
|
910
|
-
volumeCfg?.radius ?? 0,
|
|
911
|
-
!isStatic, // static: no candle-width lerp loop
|
|
912
|
-
transitionsCfg.candleLerpSpeed, // `transitions.candleLerpSpeed` (1 = instant)
|
|
913
|
-
);
|
|
914
905
|
const { dotX, dotY } = useLiveDot(
|
|
915
906
|
engine,
|
|
916
907
|
effectivePadding,
|
|
@@ -918,64 +909,9 @@ function useLiveChartController({
|
|
|
918
909
|
badgeCfg?.followViewEdge ?? false,
|
|
919
910
|
);
|
|
920
911
|
|
|
921
|
-
// Price↔pixel / time↔pixel bridge for a custom `renderOverlay`. Built
|
|
922
|
-
// unconditionally (hooks rule); only mounted when `renderOverlay` is provided.
|
|
923
|
-
const overlayContext = useChartOverlayContext(engine, effectivePadding);
|
|
924
|
-
|
|
925
912
|
const momentumSV = useMomentum(engine, momentum);
|
|
926
913
|
|
|
927
|
-
const tradeMarkers = useTradeStream(
|
|
928
|
-
engine,
|
|
929
|
-
tradeStreamSV,
|
|
930
|
-
effectivePadding,
|
|
931
|
-
!isStatic && tradeStreamResolved !== null,
|
|
932
|
-
!isStatic, // static: no trade-tape loop
|
|
933
|
-
);
|
|
934
|
-
|
|
935
|
-
const {
|
|
936
|
-
pack: degenPack,
|
|
937
|
-
packRevision: degenPackRevision,
|
|
938
|
-
shakeTransform: degenShakeTransform,
|
|
939
|
-
} = useDegen(engine, dotX, dotY, momentumSV, degenCfg, onDegenShake, isStatic);
|
|
940
|
-
|
|
941
914
|
// ── Overlay hooks ─────────────────────────────────────────────────────
|
|
942
|
-
const { yAxisEntries } = useYAxis(
|
|
943
|
-
engine,
|
|
944
|
-
effectivePadding,
|
|
945
|
-
formatValue,
|
|
946
|
-
skiaFont,
|
|
947
|
-
yAxisCfg?.minGap ?? 36,
|
|
948
|
-
metricsCfg.grid,
|
|
949
|
-
yAxisCfg?.count ?? 0,
|
|
950
|
-
);
|
|
951
|
-
|
|
952
|
-
const { xAxisEntries } = useXAxis(
|
|
953
|
-
engine,
|
|
954
|
-
effectivePadding,
|
|
955
|
-
formatTime,
|
|
956
|
-
skiaFont,
|
|
957
|
-
);
|
|
958
|
-
|
|
959
|
-
const badgeData = useBadge(
|
|
960
|
-
engine,
|
|
961
|
-
effectivePadding,
|
|
962
|
-
palette,
|
|
963
|
-
formatValue,
|
|
964
|
-
badgeFont,
|
|
965
|
-
badgeCfg?.variant ?? "default",
|
|
966
|
-
badgeCfg?.tail ?? true,
|
|
967
|
-
momentumSV,
|
|
968
|
-
badgeCfg?.position ?? "right",
|
|
969
|
-
badgeCfg?.background,
|
|
970
|
-
metricsCfg.badge,
|
|
971
|
-
metricsCfg.motion.badgeColorSpeed,
|
|
972
|
-
effectiveYAxisFloat,
|
|
973
|
-
engine.edgeValue,
|
|
974
|
-
badgeCfg?.followViewEdge ?? false,
|
|
975
|
-
badgeCfg?.radius,
|
|
976
|
-
badgeCfg?.textColor,
|
|
977
|
-
);
|
|
978
|
-
|
|
979
915
|
// Scrub/crosshair must see the same stash-backed candles as the engine.
|
|
980
916
|
const candleOpts = isCandle
|
|
981
917
|
? {
|
|
@@ -1239,8 +1175,7 @@ function useLiveChartController({
|
|
|
1239
1175
|
// Hide the live dot while scrubbing when a selection dot is marking the scrub
|
|
1240
1176
|
// point instead — otherwise both dots show at once. Applies on static charts
|
|
1241
1177
|
// too, now that they're scrubbable.
|
|
1242
|
-
const selectionDotDuringScrub =
|
|
1243
|
-
scrubCfg !== null && selectionDotCfg !== null;
|
|
1178
|
+
const selectionDotDuringScrub = scrubCfg !== null && selectionDotCfg !== null;
|
|
1244
1179
|
const liveDotOpacity = useDerivedValue(
|
|
1245
1180
|
() =>
|
|
1246
1181
|
reveal.dotOpacity.value *
|
|
@@ -1276,7 +1211,9 @@ function useLiveChartController({
|
|
|
1276
1211
|
valueMomentumColor,
|
|
1277
1212
|
lineProp,
|
|
1278
1213
|
formatValue,
|
|
1214
|
+
formatTime,
|
|
1279
1215
|
isCandle,
|
|
1216
|
+
isStatic,
|
|
1280
1217
|
// Half a candle width (seconds) so an "extrema" axis label's dot lands on the
|
|
1281
1218
|
// candle's drawn center, not its bucket-start (left) edge. 0 in line mode.
|
|
1282
1219
|
extremaTimeOffset: isCandle ? candleWidth / 2 : 0,
|
|
@@ -1297,6 +1234,7 @@ function useLiveChartController({
|
|
|
1297
1234
|
gridStyleCfg,
|
|
1298
1235
|
degenCfg,
|
|
1299
1236
|
tradeStreamResolved,
|
|
1237
|
+
tradeStream,
|
|
1300
1238
|
leftEdgeFadeCfg,
|
|
1301
1239
|
metricsCfg,
|
|
1302
1240
|
allRefLines,
|
|
@@ -1355,18 +1293,17 @@ function useLiveChartController({
|
|
|
1355
1293
|
gradientPositions,
|
|
1356
1294
|
lineGroupOpacity,
|
|
1357
1295
|
candleGroupOpacity,
|
|
1296
|
+
candlesEngine,
|
|
1297
|
+
liveEngine,
|
|
1298
|
+
candleWidth,
|
|
1299
|
+
transitionsCfg,
|
|
1358
1300
|
layoutWidth,
|
|
1359
1301
|
onLayout,
|
|
1360
1302
|
linePath,
|
|
1361
1303
|
fillPath,
|
|
1362
1304
|
thresholdFillPath,
|
|
1363
1305
|
lineIsLinear,
|
|
1364
|
-
|
|
1365
|
-
downBodiesPath,
|
|
1366
|
-
upWicksPath,
|
|
1367
|
-
downWicksPath,
|
|
1368
|
-
upBarsPath,
|
|
1369
|
-
downBarsPath,
|
|
1306
|
+
volumeCfg,
|
|
1370
1307
|
// Volume bars: active flag, fade-in opacity, and resolved colors (default to
|
|
1371
1308
|
// the candle palette). The reserved band height is read by the x-axis.
|
|
1372
1309
|
volumeActive: volumeCfg !== null,
|
|
@@ -1380,13 +1317,7 @@ function useLiveChartController({
|
|
|
1380
1317
|
overlayScrubFade,
|
|
1381
1318
|
markerGroupOpacity,
|
|
1382
1319
|
momentumSV,
|
|
1383
|
-
|
|
1384
|
-
degenPack,
|
|
1385
|
-
degenPackRevision,
|
|
1386
|
-
degenShakeTransform,
|
|
1387
|
-
yAxisEntries,
|
|
1388
|
-
xAxisEntries,
|
|
1389
|
-
badgeData,
|
|
1320
|
+
onDegenShake,
|
|
1390
1321
|
crosshair,
|
|
1391
1322
|
rootGesture,
|
|
1392
1323
|
markersActive,
|
|
@@ -1395,7 +1326,6 @@ function useLiveChartController({
|
|
|
1395
1326
|
renderMarker,
|
|
1396
1327
|
renderTooltip,
|
|
1397
1328
|
renderOverlay,
|
|
1398
|
-
overlayContext,
|
|
1399
1329
|
// selection dot: resolved config + fallback color (the chart line/accent color)
|
|
1400
1330
|
selectionDot: selectionDotCfg,
|
|
1401
1331
|
selectionColor: lineProp?.color ?? palette.line,
|
|
@@ -1410,27 +1340,151 @@ function useLiveChartController({
|
|
|
1410
1340
|
|
|
1411
1341
|
type LiveChartModel = ReturnType<typeof useLiveChartController>;
|
|
1412
1342
|
|
|
1343
|
+
type YAxisEntries = ReturnType<typeof useYAxis>["yAxisEntries"];
|
|
1344
|
+
type DegenState = ReturnType<typeof useDegen>;
|
|
1345
|
+
|
|
1346
|
+
/** Owns the particle/shake state only while the degen effect is enabled. */
|
|
1347
|
+
function ChartWithDegen({
|
|
1348
|
+
model,
|
|
1349
|
+
yAxisEntries,
|
|
1350
|
+
}: {
|
|
1351
|
+
model: LiveChartModel;
|
|
1352
|
+
yAxisEntries: YAxisEntries | null;
|
|
1353
|
+
}) {
|
|
1354
|
+
const { engine, dotX, dotY, momentumSV, degenCfg, onDegenShake, isStatic } =
|
|
1355
|
+
model;
|
|
1356
|
+
const state = useDegen(
|
|
1357
|
+
engine,
|
|
1358
|
+
dotX,
|
|
1359
|
+
dotY,
|
|
1360
|
+
momentumSV,
|
|
1361
|
+
degenCfg,
|
|
1362
|
+
onDegenShake,
|
|
1363
|
+
isStatic,
|
|
1364
|
+
);
|
|
1365
|
+
return (
|
|
1366
|
+
<ChartView model={model} yAxisEntries={yAxisEntries} degen={state} />
|
|
1367
|
+
);
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1413
1370
|
/**
|
|
1414
|
-
*
|
|
1415
|
-
*
|
|
1416
|
-
*
|
|
1417
|
-
*
|
|
1371
|
+
* Owns the Y-axis worklets. The provider itself is mounted only while the axis
|
|
1372
|
+
* is enabled, so `yAxis={false}` never registers its shared values or mapper.
|
|
1373
|
+
* The entries are passed through ordinary props so both paint-order positions
|
|
1374
|
+
* receive the same mapper across the React Native → Skia renderer boundary.
|
|
1418
1375
|
*/
|
|
1419
|
-
function
|
|
1376
|
+
function ChartWithYAxis({ model }: { model: LiveChartModel }) {
|
|
1420
1377
|
const {
|
|
1421
|
-
|
|
1378
|
+
engine,
|
|
1379
|
+
effectivePadding,
|
|
1380
|
+
formatValue,
|
|
1381
|
+
skiaFont,
|
|
1422
1382
|
yAxisCfg,
|
|
1423
|
-
|
|
1383
|
+
metricsCfg,
|
|
1384
|
+
} = model;
|
|
1385
|
+
const { yAxisEntries } = useYAxis(
|
|
1386
|
+
engine,
|
|
1387
|
+
effectivePadding,
|
|
1388
|
+
formatValue,
|
|
1389
|
+
skiaFont,
|
|
1390
|
+
yAxisCfg?.minGap ?? 36,
|
|
1391
|
+
metricsCfg.grid,
|
|
1392
|
+
yAxisCfg?.count ?? 0,
|
|
1393
|
+
);
|
|
1394
|
+
if (model.degenCfg) {
|
|
1395
|
+
return <ChartWithDegen model={model} yAxisEntries={yAxisEntries} />;
|
|
1396
|
+
}
|
|
1397
|
+
return <ChartView model={model} yAxisEntries={yAxisEntries} degen={null} />;
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
function ChartYAxisLayer({
|
|
1401
|
+
model,
|
|
1402
|
+
variant,
|
|
1403
|
+
entries,
|
|
1404
|
+
}: {
|
|
1405
|
+
model: LiveChartModel;
|
|
1406
|
+
variant: "all" | "grid" | "labels";
|
|
1407
|
+
entries: YAxisEntries;
|
|
1408
|
+
}) {
|
|
1409
|
+
const {
|
|
1424
1410
|
reveal,
|
|
1425
|
-
yAxisEntries,
|
|
1426
1411
|
engine,
|
|
1427
1412
|
effectivePadding,
|
|
1428
1413
|
palette,
|
|
1429
1414
|
skiaFont,
|
|
1430
1415
|
badgeUsesRightGutter,
|
|
1431
1416
|
badgeCfg,
|
|
1432
|
-
gridStyleCfg,
|
|
1433
1417
|
metricsCfg,
|
|
1418
|
+
gridStyleCfg,
|
|
1419
|
+
yAxisFloat,
|
|
1420
|
+
} = model;
|
|
1421
|
+
return (
|
|
1422
|
+
<Group opacity={reveal.yAxisOpacity}>
|
|
1423
|
+
<YAxisOverlay
|
|
1424
|
+
variant={variant}
|
|
1425
|
+
float={variant === "labels" && yAxisFloat}
|
|
1426
|
+
entries={entries}
|
|
1427
|
+
engine={engine}
|
|
1428
|
+
padding={effectivePadding}
|
|
1429
|
+
palette={palette}
|
|
1430
|
+
font={skiaFont}
|
|
1431
|
+
badge={badgeUsesRightGutter}
|
|
1432
|
+
badgeTail={badgeCfg?.tail ?? true}
|
|
1433
|
+
badgeMetrics={metricsCfg.badge}
|
|
1434
|
+
gridStyle={gridStyleCfg}
|
|
1435
|
+
/>
|
|
1436
|
+
</Group>
|
|
1437
|
+
);
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
/** Owns the X-axis worklet and only mounts when `xAxis` is enabled. */
|
|
1441
|
+
function ChartXAxisLayer({ model }: { model: LiveChartModel }) {
|
|
1442
|
+
const {
|
|
1443
|
+
engine,
|
|
1444
|
+
effectivePadding,
|
|
1445
|
+
formatTime,
|
|
1446
|
+
skiaFont,
|
|
1447
|
+
palette,
|
|
1448
|
+
volumeBandHeight,
|
|
1449
|
+
} = model;
|
|
1450
|
+
const { xAxisEntries } = useXAxis(
|
|
1451
|
+
engine,
|
|
1452
|
+
effectivePadding,
|
|
1453
|
+
formatTime,
|
|
1454
|
+
skiaFont,
|
|
1455
|
+
);
|
|
1456
|
+
return (
|
|
1457
|
+
<XAxisOverlay
|
|
1458
|
+
entries={xAxisEntries}
|
|
1459
|
+
engine={engine}
|
|
1460
|
+
padding={effectivePadding}
|
|
1461
|
+
palette={palette}
|
|
1462
|
+
font={skiaFont}
|
|
1463
|
+
volumeBandHeight={volumeBandHeight}
|
|
1464
|
+
/>
|
|
1465
|
+
);
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
/**
|
|
1469
|
+
* Background fills drawn BENEATH the left-edge fade: the y-axis grid, the area
|
|
1470
|
+
* gradient, and the threshold profit/loss band. Split out from `ChartStack` so
|
|
1471
|
+
* the fade's `dstOut` only softens the fills — the line and everything above it
|
|
1472
|
+
* (drawn in `ChartStack`, after the fade) stay crisp at the left edge.
|
|
1473
|
+
*/
|
|
1474
|
+
function ChartFillLayer({
|
|
1475
|
+
model,
|
|
1476
|
+
yAxisEntries,
|
|
1477
|
+
degen,
|
|
1478
|
+
}: {
|
|
1479
|
+
model: LiveChartModel;
|
|
1480
|
+
yAxisEntries: YAxisEntries | null;
|
|
1481
|
+
degen: DegenState | null;
|
|
1482
|
+
}) {
|
|
1483
|
+
const {
|
|
1484
|
+
yAxisCfg,
|
|
1485
|
+
yAxisFloat,
|
|
1486
|
+
reveal,
|
|
1487
|
+
effectivePadding,
|
|
1434
1488
|
gradientCfg,
|
|
1435
1489
|
areaDotsCfg,
|
|
1436
1490
|
areaDotColorVec,
|
|
@@ -1446,27 +1500,17 @@ function ChartFillLayer({ model }: { model: LiveChartModel }) {
|
|
|
1446
1500
|
thresholdSeriesHasPoints,
|
|
1447
1501
|
thresholdFillUniforms,
|
|
1448
1502
|
} = model;
|
|
1449
|
-
|
|
1450
1503
|
return (
|
|
1451
|
-
<Group transform={
|
|
1504
|
+
<Group transform={degen?.shakeTransform}>
|
|
1452
1505
|
{/* Y-axis. Default: grid + labels here (in a reserved gutter). Floating
|
|
1453
1506
|
mode: grid only — the labels + a soft edge fade draw above the candles
|
|
1454
1507
|
in ChartStack so the plot runs full-width and candles dim under them. */}
|
|
1455
1508
|
{yAxisCfg && (
|
|
1456
|
-
<
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
padding={effectivePadding}
|
|
1462
|
-
palette={palette}
|
|
1463
|
-
font={skiaFont}
|
|
1464
|
-
badge={badgeUsesRightGutter}
|
|
1465
|
-
badgeTail={badgeCfg?.tail ?? true}
|
|
1466
|
-
badgeMetrics={metricsCfg.badge}
|
|
1467
|
-
gridStyle={gridStyleCfg}
|
|
1468
|
-
/>
|
|
1469
|
-
</Group>
|
|
1509
|
+
<ChartYAxisLayer
|
|
1510
|
+
model={model}
|
|
1511
|
+
variant={yAxisFloat ? "grid" : "all"}
|
|
1512
|
+
entries={yAxisEntries!}
|
|
1513
|
+
/>
|
|
1470
1514
|
)}
|
|
1471
1515
|
|
|
1472
1516
|
{/* Dot-lattice area fill (the under-line `fillPath` painted with a dot
|
|
@@ -1529,13 +1573,95 @@ function ChartFillLayer({ model }: { model: LiveChartModel }) {
|
|
|
1529
1573
|
);
|
|
1530
1574
|
}
|
|
1531
1575
|
|
|
1576
|
+
/**
|
|
1577
|
+
* Candle/volume paths are a mode-specific subsystem. Keeping their hooks in a
|
|
1578
|
+
* child means a line chart never registers the candle-width frame callback or
|
|
1579
|
+
* the six derived path worklets.
|
|
1580
|
+
*/
|
|
1581
|
+
function ChartCandleLayer({ model }: { model: LiveChartModel }) {
|
|
1582
|
+
const {
|
|
1583
|
+
engine,
|
|
1584
|
+
effectivePadding,
|
|
1585
|
+
candlesEngine,
|
|
1586
|
+
liveEngine,
|
|
1587
|
+
candleWidth,
|
|
1588
|
+
metricsCfg,
|
|
1589
|
+
volumeBandHeight,
|
|
1590
|
+
volumeCfg,
|
|
1591
|
+
isStatic,
|
|
1592
|
+
transitionsCfg,
|
|
1593
|
+
candleGroupOpacity,
|
|
1594
|
+
palette,
|
|
1595
|
+
volumeOpacity,
|
|
1596
|
+
volumeUpColor,
|
|
1597
|
+
volumeDownColor,
|
|
1598
|
+
} = model;
|
|
1599
|
+
const {
|
|
1600
|
+
upBodiesPath,
|
|
1601
|
+
downBodiesPath,
|
|
1602
|
+
upWicksPath,
|
|
1603
|
+
downWicksPath,
|
|
1604
|
+
upBarsPath,
|
|
1605
|
+
downBarsPath,
|
|
1606
|
+
} = useCandlePaths(
|
|
1607
|
+
engine,
|
|
1608
|
+
effectivePadding,
|
|
1609
|
+
candlesEngine,
|
|
1610
|
+
liveEngine,
|
|
1611
|
+
candleWidth,
|
|
1612
|
+
true,
|
|
1613
|
+
metricsCfg.candle,
|
|
1614
|
+
volumeBandHeight,
|
|
1615
|
+
volumeCfg?.radius ?? 0,
|
|
1616
|
+
!isStatic,
|
|
1617
|
+
transitionsCfg.candleLerpSpeed,
|
|
1618
|
+
);
|
|
1619
|
+
|
|
1620
|
+
return (
|
|
1621
|
+
<>
|
|
1622
|
+
<Group opacity={candleGroupOpacity}>
|
|
1623
|
+
<Path
|
|
1624
|
+
path={upWicksPath}
|
|
1625
|
+
style="stroke"
|
|
1626
|
+
strokeWidth={metricsCfg.candle.wickWidth}
|
|
1627
|
+
color={palette.wickUp}
|
|
1628
|
+
/>
|
|
1629
|
+
<Path
|
|
1630
|
+
path={downWicksPath}
|
|
1631
|
+
style="stroke"
|
|
1632
|
+
strokeWidth={metricsCfg.candle.wickWidth}
|
|
1633
|
+
color={palette.wickDown}
|
|
1634
|
+
/>
|
|
1635
|
+
<Path path={upBodiesPath} style="fill" color={palette.candleUp} />
|
|
1636
|
+
<Path path={downBodiesPath} style="fill" color={palette.candleDown} />
|
|
1637
|
+
</Group>
|
|
1638
|
+
|
|
1639
|
+
{volumeCfg && (
|
|
1640
|
+
<Group opacity={candleGroupOpacity}>
|
|
1641
|
+
<Group opacity={volumeOpacity}>
|
|
1642
|
+
<Path path={upBarsPath} style="fill" color={volumeUpColor} />
|
|
1643
|
+
<Path path={downBarsPath} style="fill" color={volumeDownColor} />
|
|
1644
|
+
</Group>
|
|
1645
|
+
</Group>
|
|
1646
|
+
)}
|
|
1647
|
+
</>
|
|
1648
|
+
);
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1532
1651
|
/** Main shaken chart stack drawn ABOVE the left-edge fade so the line stays crisp:
|
|
1533
1652
|
* segment dividers, value/reference lines, the line/candles, axes, dot, degen,
|
|
1534
1653
|
* markers, and the loading/empty art. Background fills are in `ChartFillLayer`
|
|
1535
1654
|
* (below the fade); the live value text is `ChartValueOverlay` (above the fade). */
|
|
1536
|
-
function ChartStack({
|
|
1655
|
+
function ChartStack({
|
|
1656
|
+
model,
|
|
1657
|
+
yAxisEntries,
|
|
1658
|
+
degen,
|
|
1659
|
+
}: {
|
|
1660
|
+
model: LiveChartModel;
|
|
1661
|
+
yAxisEntries: YAxisEntries | null;
|
|
1662
|
+
degen: DegenState | null;
|
|
1663
|
+
}) {
|
|
1537
1664
|
const {
|
|
1538
|
-
degenShakeTransform,
|
|
1539
1665
|
reveal,
|
|
1540
1666
|
engine,
|
|
1541
1667
|
effectivePadding,
|
|
@@ -1567,27 +1693,13 @@ function ChartStack({ model }: { model: LiveChartModel }) {
|
|
|
1567
1693
|
lineIsLinear,
|
|
1568
1694
|
strokeWidth,
|
|
1569
1695
|
lineProp,
|
|
1570
|
-
|
|
1571
|
-
upWicksPath,
|
|
1572
|
-
downWicksPath,
|
|
1573
|
-
upBodiesPath,
|
|
1574
|
-
downBodiesPath,
|
|
1575
|
-
upBarsPath,
|
|
1576
|
-
downBarsPath,
|
|
1577
|
-
volumeActive,
|
|
1578
|
-
volumeBandHeight,
|
|
1579
|
-
volumeOpacity,
|
|
1580
|
-
volumeUpColor,
|
|
1581
|
-
volumeDownColor,
|
|
1696
|
+
isCandle,
|
|
1582
1697
|
xAxisCfg,
|
|
1583
|
-
xAxisEntries,
|
|
1584
1698
|
dotX,
|
|
1585
1699
|
liveDotOpacity,
|
|
1586
1700
|
pulseCfg,
|
|
1587
1701
|
dotCfg,
|
|
1588
1702
|
degenCfg,
|
|
1589
|
-
degenPack,
|
|
1590
|
-
degenPackRevision,
|
|
1591
1703
|
markersActive,
|
|
1592
1704
|
markersSV,
|
|
1593
1705
|
markerClusterCfg,
|
|
@@ -1600,17 +1712,13 @@ function ChartStack({ model }: { model: LiveChartModel }) {
|
|
|
1600
1712
|
layoutWidth,
|
|
1601
1713
|
yAxisCfg,
|
|
1602
1714
|
yAxisFloat,
|
|
1603
|
-
yAxisEntries,
|
|
1604
|
-
badgeUsesRightGutter,
|
|
1605
|
-
gridStyleCfg,
|
|
1606
1715
|
loadingLineColor,
|
|
1607
1716
|
loadingStrokeWidth,
|
|
1608
1717
|
loadingAmplitude,
|
|
1609
1718
|
loadingSpeed,
|
|
1610
1719
|
} = model;
|
|
1611
|
-
|
|
1612
1720
|
return (
|
|
1613
|
-
<Group transform={
|
|
1721
|
+
<Group transform={degen?.shakeTransform}>
|
|
1614
1722
|
{/* Segment dividers + labels (behind the line). The scrub-focus emphasis is
|
|
1615
1723
|
painted on the line stroke itself, below — this overlay draws no fill. */}
|
|
1616
1724
|
{resolvedSegments.map((seg, i) => (
|
|
@@ -1722,73 +1830,22 @@ function ChartStack({ model }: { model: LiveChartModel }) {
|
|
|
1722
1830
|
</Path>
|
|
1723
1831
|
</Group>
|
|
1724
1832
|
|
|
1725
|
-
{
|
|
1726
|
-
<Group opacity={candleGroupOpacity}>
|
|
1727
|
-
<Path
|
|
1728
|
-
path={upWicksPath}
|
|
1729
|
-
style="stroke"
|
|
1730
|
-
strokeWidth={metricsCfg.candle.wickWidth}
|
|
1731
|
-
color={palette.wickUp}
|
|
1732
|
-
/>
|
|
1733
|
-
<Path
|
|
1734
|
-
path={downWicksPath}
|
|
1735
|
-
style="stroke"
|
|
1736
|
-
strokeWidth={metricsCfg.candle.wickWidth}
|
|
1737
|
-
color={palette.wickDown}
|
|
1738
|
-
/>
|
|
1739
|
-
<Path path={upBodiesPath} style="fill" color={palette.candleUp} />
|
|
1740
|
-
<Path
|
|
1741
|
-
path={downBodiesPath}
|
|
1742
|
-
style="fill"
|
|
1743
|
-
color={palette.candleDown}
|
|
1744
|
-
/>
|
|
1745
|
-
</Group>
|
|
1746
|
-
|
|
1747
|
-
{/* Volume bars in the reserved band below the candles. Fades in with the
|
|
1748
|
-
candle group (outer opacity); the config opacity dims the whole band
|
|
1749
|
-
(inner). Up/down bars carry their own colors (default candle palette). */}
|
|
1750
|
-
{volumeActive && (
|
|
1751
|
-
<Group opacity={candleGroupOpacity}>
|
|
1752
|
-
<Group opacity={volumeOpacity}>
|
|
1753
|
-
<Path path={upBarsPath} style="fill" color={volumeUpColor} />
|
|
1754
|
-
<Path path={downBarsPath} style="fill" color={volumeDownColor} />
|
|
1755
|
-
</Group>
|
|
1756
|
-
</Group>
|
|
1757
|
-
)}
|
|
1833
|
+
{isCandle && <ChartCandleLayer model={model} />}
|
|
1758
1834
|
|
|
1759
1835
|
{/* Floating axis: the labels float ABOVE the candles (right-aligned at the
|
|
1760
1836
|
edge) so the plot runs full-width and candles stay fully visible behind
|
|
1761
1837
|
them. (Default non-floating axis draws grid + labels in ChartFillLayer.) */}
|
|
1762
1838
|
{yAxisCfg && yAxisFloat && (
|
|
1763
|
-
<
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
engine={engine}
|
|
1769
|
-
padding={effectivePadding}
|
|
1770
|
-
palette={palette}
|
|
1771
|
-
font={skiaFont}
|
|
1772
|
-
badge={badgeUsesRightGutter}
|
|
1773
|
-
badgeTail={badgeCfg?.tail ?? true}
|
|
1774
|
-
badgeMetrics={metricsCfg.badge}
|
|
1775
|
-
gridStyle={gridStyleCfg}
|
|
1776
|
-
/>
|
|
1777
|
-
</Group>
|
|
1839
|
+
<ChartYAxisLayer
|
|
1840
|
+
model={model}
|
|
1841
|
+
variant="labels"
|
|
1842
|
+
entries={yAxisEntries!}
|
|
1843
|
+
/>
|
|
1778
1844
|
)}
|
|
1779
1845
|
|
|
1780
1846
|
{/* X-axis time labels. With a volume band the bottom padding is inflated by
|
|
1781
1847
|
the band height; pass it so the axis shifts back to the very bottom. */}
|
|
1782
|
-
{xAxisCfg &&
|
|
1783
|
-
<XAxisOverlay
|
|
1784
|
-
entries={xAxisEntries}
|
|
1785
|
-
engine={engine}
|
|
1786
|
-
padding={effectivePadding}
|
|
1787
|
-
palette={palette}
|
|
1788
|
-
font={skiaFont}
|
|
1789
|
-
volumeBandHeight={volumeBandHeight}
|
|
1790
|
-
/>
|
|
1791
|
-
)}
|
|
1848
|
+
{xAxisCfg && <ChartXAxisLayer model={model} />}
|
|
1792
1849
|
|
|
1793
1850
|
{/* Live dot — the badge is drawn later (after the scrub layer) so the
|
|
1794
1851
|
scrub dim never clips the live-price badge's left edge. Hidden while
|
|
@@ -1812,8 +1869,8 @@ function ChartStack({ model }: { model: LiveChartModel }) {
|
|
|
1812
1869
|
{degenCfg && (
|
|
1813
1870
|
<Group opacity={reveal.dotOpacity}>
|
|
1814
1871
|
<DegenParticlesOverlay
|
|
1815
|
-
pack={
|
|
1816
|
-
packRevision={
|
|
1872
|
+
pack={degen!.pack}
|
|
1873
|
+
packRevision={degen!.packRevision}
|
|
1817
1874
|
engine={engine}
|
|
1818
1875
|
palette={palette}
|
|
1819
1876
|
particleSlotCount={degenCfg.particleSlotCount}
|
|
@@ -1882,18 +1939,58 @@ function ChartStack({ model }: { model: LiveChartModel }) {
|
|
|
1882
1939
|
);
|
|
1883
1940
|
}
|
|
1884
1941
|
|
|
1885
|
-
/**
|
|
1886
|
-
|
|
1887
|
-
|
|
1942
|
+
/** Owns the trade-tape frame callback and only mounts with a trade stream. */
|
|
1943
|
+
function ChartTradeStreamLayer({
|
|
1944
|
+
model,
|
|
1945
|
+
degen,
|
|
1946
|
+
}: {
|
|
1947
|
+
model: LiveChartModel;
|
|
1948
|
+
degen: DegenState | null;
|
|
1949
|
+
}) {
|
|
1888
1950
|
const {
|
|
1951
|
+
engine,
|
|
1952
|
+
tradeStream,
|
|
1889
1953
|
tradeStreamResolved,
|
|
1954
|
+
effectivePadding,
|
|
1955
|
+
palette,
|
|
1956
|
+
skiaFont,
|
|
1957
|
+
reveal,
|
|
1958
|
+
isStatic,
|
|
1959
|
+
} = model;
|
|
1960
|
+
const tradeMarkers = useTradeStream(
|
|
1961
|
+
engine,
|
|
1962
|
+
tradeStream!,
|
|
1963
|
+
effectivePadding,
|
|
1964
|
+
!isStatic,
|
|
1965
|
+
!isStatic,
|
|
1966
|
+
);
|
|
1967
|
+
return (
|
|
1968
|
+
<Group transform={degen?.shakeTransform}>
|
|
1969
|
+
<TradeStreamOverlay
|
|
1970
|
+
markers={tradeMarkers}
|
|
1971
|
+
palette={palette}
|
|
1972
|
+
padding={effectivePadding}
|
|
1973
|
+
font={skiaFont}
|
|
1974
|
+
opacity={reveal.dotOpacity}
|
|
1975
|
+
labelOffsetX={tradeStreamResolved!.labelOffsetX}
|
|
1976
|
+
/>
|
|
1977
|
+
</Group>
|
|
1978
|
+
);
|
|
1979
|
+
}
|
|
1980
|
+
|
|
1981
|
+
/** Scrub crosshair/tooltip drawn in canvas space on top of the shaken stack. */
|
|
1982
|
+
function ChartScrubLayer({
|
|
1983
|
+
model,
|
|
1984
|
+
degen,
|
|
1985
|
+
}: {
|
|
1986
|
+
model: LiveChartModel;
|
|
1987
|
+
degen: DegenState | null;
|
|
1988
|
+
}) {
|
|
1989
|
+
const {
|
|
1890
1990
|
scrubCfg,
|
|
1891
|
-
degenShakeTransform,
|
|
1892
|
-
tradeMarkers,
|
|
1893
1991
|
palette,
|
|
1894
1992
|
effectivePadding,
|
|
1895
1993
|
skiaFont,
|
|
1896
|
-
reveal,
|
|
1897
1994
|
crosshair,
|
|
1898
1995
|
isCandle,
|
|
1899
1996
|
pulseCfg,
|
|
@@ -1902,13 +1999,12 @@ function ChartScrubLayer({ model }: { model: LiveChartModel }) {
|
|
|
1902
1999
|
selectionColor,
|
|
1903
2000
|
renderTooltip,
|
|
1904
2001
|
} = model;
|
|
1905
|
-
|
|
1906
2002
|
// A custom tooltip is an RN overlay (sibling of <Canvas>), so the built-in
|
|
1907
2003
|
// Skia tooltip is suppressed here while it's active — the line pill in line
|
|
1908
2004
|
// mode, and the OHLC stack in candle mode (see the stack gate below).
|
|
1909
2005
|
const customTooltipActive = renderTooltip != null;
|
|
1910
2006
|
|
|
1911
|
-
if (!
|
|
2007
|
+
if (!scrubCfg) return null;
|
|
1912
2008
|
|
|
1913
2009
|
// Extend the scrub dim past the plot's right edge to fully cover the live dot
|
|
1914
2010
|
// (with its halo) and pulse ring, all centered on that edge. The gutter
|
|
@@ -1919,18 +2015,7 @@ function ChartScrubLayer({ model }: { model: LiveChartModel }) {
|
|
|
1919
2015
|
);
|
|
1920
2016
|
|
|
1921
2017
|
return (
|
|
1922
|
-
<Group transform={
|
|
1923
|
-
{tradeStreamResolved && (
|
|
1924
|
-
<TradeStreamOverlay
|
|
1925
|
-
markers={tradeMarkers}
|
|
1926
|
-
palette={palette}
|
|
1927
|
-
padding={effectivePadding}
|
|
1928
|
-
font={skiaFont}
|
|
1929
|
-
opacity={reveal.dotOpacity}
|
|
1930
|
-
labelOffsetX={tradeStreamResolved.labelOffsetX}
|
|
1931
|
-
/>
|
|
1932
|
-
)}
|
|
1933
|
-
|
|
2018
|
+
<Group transform={degen?.shakeTransform}>
|
|
1934
2019
|
{scrubCfg && (
|
|
1935
2020
|
<CrosshairOverlay
|
|
1936
2021
|
scrubX={crosshair.scrubX}
|
|
@@ -1978,10 +2063,15 @@ function ChartScrubLayer({ model }: { model: LiveChartModel }) {
|
|
|
1978
2063
|
/** Live-value text drawn as its own canvas layer, above both the area gradient
|
|
1979
2064
|
* and the left-edge fade, so the large number stays crisp at the left edge
|
|
1980
2065
|
* instead of being washed out by the fade's `dstOut` blend. */
|
|
1981
|
-
function ChartValueOverlay({
|
|
2066
|
+
function ChartValueOverlay({
|
|
2067
|
+
model,
|
|
2068
|
+
degen,
|
|
2069
|
+
}: {
|
|
2070
|
+
model: LiveChartModel;
|
|
2071
|
+
degen: DegenState | null;
|
|
2072
|
+
}) {
|
|
1982
2073
|
const {
|
|
1983
2074
|
showValue,
|
|
1984
|
-
degenShakeTransform,
|
|
1985
2075
|
engine,
|
|
1986
2076
|
effectivePadding,
|
|
1987
2077
|
palette,
|
|
@@ -1991,11 +2081,10 @@ function ChartValueOverlay({ model }: { model: LiveChartModel }) {
|
|
|
1991
2081
|
valueMomentumColor,
|
|
1992
2082
|
reveal,
|
|
1993
2083
|
} = model;
|
|
1994
|
-
|
|
1995
2084
|
if (!showValue) return null;
|
|
1996
2085
|
|
|
1997
2086
|
return (
|
|
1998
|
-
<Group transform={
|
|
2087
|
+
<Group transform={degen?.shakeTransform}>
|
|
1999
2088
|
<Group opacity={reveal.lineOpacity}>
|
|
2000
2089
|
<ValueTextOverlay
|
|
2001
2090
|
engine={engine}
|
|
@@ -2013,11 +2102,46 @@ function ChartValueOverlay({ model }: { model: LiveChartModel }) {
|
|
|
2013
2102
|
|
|
2014
2103
|
/** Live-price badge, drawn above the scrub dim so the dim never clips its left
|
|
2015
2104
|
* edge. Shares the degen shake transform so it tracks the shaken stack. */
|
|
2016
|
-
function ChartBadgeLayer({
|
|
2017
|
-
|
|
2018
|
-
|
|
2105
|
+
function ChartBadgeLayer({
|
|
2106
|
+
model,
|
|
2107
|
+
degen,
|
|
2108
|
+
}: {
|
|
2109
|
+
model: LiveChartModel;
|
|
2110
|
+
degen: DegenState | null;
|
|
2111
|
+
}) {
|
|
2112
|
+
const {
|
|
2113
|
+
badgeFont,
|
|
2114
|
+
reveal,
|
|
2115
|
+
engine,
|
|
2116
|
+
effectivePadding,
|
|
2117
|
+
palette,
|
|
2118
|
+
formatValue,
|
|
2119
|
+
momentumSV,
|
|
2120
|
+
metricsCfg,
|
|
2121
|
+
yAxisFloat,
|
|
2122
|
+
} = model;
|
|
2123
|
+
const badgeCfg = model.badgeCfg!;
|
|
2124
|
+
const badgeData = useBadge(
|
|
2125
|
+
engine,
|
|
2126
|
+
effectivePadding,
|
|
2127
|
+
palette,
|
|
2128
|
+
formatValue,
|
|
2129
|
+
badgeFont,
|
|
2130
|
+
badgeCfg.variant,
|
|
2131
|
+
badgeCfg.tail,
|
|
2132
|
+
momentumSV,
|
|
2133
|
+
badgeCfg.position,
|
|
2134
|
+
badgeCfg.background,
|
|
2135
|
+
metricsCfg.badge,
|
|
2136
|
+
metricsCfg.motion.badgeColorSpeed,
|
|
2137
|
+
yAxisFloat,
|
|
2138
|
+
engine.edgeValue,
|
|
2139
|
+
badgeCfg.followViewEdge,
|
|
2140
|
+
badgeCfg.radius,
|
|
2141
|
+
badgeCfg.textColor,
|
|
2142
|
+
);
|
|
2019
2143
|
return (
|
|
2020
|
-
<Group transform={
|
|
2144
|
+
<Group transform={degen?.shakeTransform}>
|
|
2021
2145
|
<Group opacity={reveal.badgeOpacity}>
|
|
2022
2146
|
<BadgeOverlay
|
|
2023
2147
|
badge={badgeData}
|
|
@@ -2036,7 +2160,13 @@ function ChartBadgeLayer({ model }: { model: LiveChartModel }) {
|
|
|
2036
2160
|
* left-pinned badge (off-axis / `labelBadge`) and any label stay crisp instead
|
|
2037
2161
|
* of being erased by the fade's dstOut. The lines/bands themselves render in the
|
|
2038
2162
|
* base pass inside ChartStack (behind the chart content). */
|
|
2039
|
-
function ChartRefBadgeLayer({
|
|
2163
|
+
function ChartRefBadgeLayer({
|
|
2164
|
+
model,
|
|
2165
|
+
degen,
|
|
2166
|
+
}: {
|
|
2167
|
+
model: LiveChartModel;
|
|
2168
|
+
degen: DegenState | null;
|
|
2169
|
+
}) {
|
|
2040
2170
|
const {
|
|
2041
2171
|
allRefLines,
|
|
2042
2172
|
refLineCustom,
|
|
@@ -2053,12 +2183,11 @@ function ChartRefBadgeLayer({ model }: { model: LiveChartModel }) {
|
|
|
2053
2183
|
formatValue,
|
|
2054
2184
|
skiaFont,
|
|
2055
2185
|
fontProp,
|
|
2056
|
-
degenShakeTransform,
|
|
2057
2186
|
overlayScrubFade,
|
|
2058
2187
|
} = model;
|
|
2059
2188
|
if (allRefLines.length === 0) return null;
|
|
2060
2189
|
return (
|
|
2061
|
-
<Group transform={
|
|
2190
|
+
<Group transform={degen?.shakeTransform} opacity={overlayScrubFade}>
|
|
2062
2191
|
{allRefLines.map((rl, i) => (
|
|
2063
2192
|
<ReferenceLineOverlay
|
|
2064
2193
|
key={i}
|
|
@@ -2096,8 +2225,14 @@ function ChartRefBadgeLayer({ model }: { model: LiveChartModel }) {
|
|
|
2096
2225
|
* shake group so the rendered badge stays aligned with the untransformed tap
|
|
2097
2226
|
* hit-test; it tracks the locked reticle, not the shaken stack. */
|
|
2098
2227
|
function ChartScrubActionLayer({ model }: { model: LiveChartModel }) {
|
|
2099
|
-
const {
|
|
2100
|
-
|
|
2228
|
+
const {
|
|
2229
|
+
scrubActionCfg,
|
|
2230
|
+
crosshair,
|
|
2231
|
+
engine,
|
|
2232
|
+
effectivePadding,
|
|
2233
|
+
palette,
|
|
2234
|
+
skiaFont,
|
|
2235
|
+
} = model;
|
|
2101
2236
|
if (
|
|
2102
2237
|
!scrubActionCfg ||
|
|
2103
2238
|
!crosshair.lockActive ||
|
|
@@ -2126,8 +2261,79 @@ function ChartScrubActionLayer({ model }: { model: LiveChartModel }) {
|
|
|
2126
2261
|
);
|
|
2127
2262
|
}
|
|
2128
2263
|
|
|
2129
|
-
|
|
2130
|
-
|
|
2264
|
+
/**
|
|
2265
|
+
* RN-backed marker and reference-line slots. Their animated fade mapper is
|
|
2266
|
+
* registered only when at least one custom annotation renderer is present.
|
|
2267
|
+
*/
|
|
2268
|
+
function ChartCustomAnnotations({ model }: { model: LiveChartModel }) {
|
|
2269
|
+
const {
|
|
2270
|
+
markersActive,
|
|
2271
|
+
markersSV,
|
|
2272
|
+
markerClusterCfg,
|
|
2273
|
+
renderMarker,
|
|
2274
|
+
renderReferenceLine,
|
|
2275
|
+
allRefLines,
|
|
2276
|
+
refLineCustom,
|
|
2277
|
+
dragValues,
|
|
2278
|
+
dragActive,
|
|
2279
|
+
engine,
|
|
2280
|
+
effectivePadding,
|
|
2281
|
+
formatValue,
|
|
2282
|
+
lineIsLinear,
|
|
2283
|
+
overlayScrubFade,
|
|
2284
|
+
} = model;
|
|
2285
|
+
const overlayFadeStyle = useAnimatedStyle(() => ({
|
|
2286
|
+
opacity: overlayScrubFade.get(),
|
|
2287
|
+
}));
|
|
2288
|
+
|
|
2289
|
+
return (
|
|
2290
|
+
<Animated.View
|
|
2291
|
+
pointerEvents="box-none"
|
|
2292
|
+
style={[StyleSheet.absoluteFill, overlayFadeStyle]}
|
|
2293
|
+
>
|
|
2294
|
+
{markersActive && renderMarker && (
|
|
2295
|
+
<CustomMarkerOverlay
|
|
2296
|
+
markers={markersSV}
|
|
2297
|
+
renderMarker={renderMarker}
|
|
2298
|
+
engine={engine}
|
|
2299
|
+
padding={effectivePadding}
|
|
2300
|
+
lineData={engine.data}
|
|
2301
|
+
lineLinear={lineIsLinear}
|
|
2302
|
+
cluster={markerClusterCfg}
|
|
2303
|
+
/>
|
|
2304
|
+
)}
|
|
2305
|
+
{renderReferenceLine && allRefLines.length > 0 && (
|
|
2306
|
+
<CustomReferenceLineOverlay
|
|
2307
|
+
lines={allRefLines}
|
|
2308
|
+
renderReferenceLine={renderReferenceLine}
|
|
2309
|
+
custom={refLineCustom}
|
|
2310
|
+
engine={engine}
|
|
2311
|
+
padding={effectivePadding}
|
|
2312
|
+
formatValue={formatValue}
|
|
2313
|
+
dragValues={dragValues}
|
|
2314
|
+
dragActive={dragActive}
|
|
2315
|
+
/>
|
|
2316
|
+
)}
|
|
2317
|
+
</Animated.View>
|
|
2318
|
+
);
|
|
2319
|
+
}
|
|
2320
|
+
|
|
2321
|
+
/** Owns the price/time projection worklets for the custom overlay slot. */
|
|
2322
|
+
function ChartCustomConsumerOverlay({ model }: { model: LiveChartModel }) {
|
|
2323
|
+
const { engine, effectivePadding, renderOverlay } = model;
|
|
2324
|
+
const overlayContext = useChartOverlayContext(engine, effectivePadding);
|
|
2325
|
+
return <ChartOverlayLayer render={renderOverlay!} context={overlayContext} />;
|
|
2326
|
+
}
|
|
2327
|
+
|
|
2328
|
+
function ChartView({
|
|
2329
|
+
model,
|
|
2330
|
+
yAxisEntries,
|
|
2331
|
+
degen,
|
|
2332
|
+
}: {
|
|
2333
|
+
model: LiveChartModel;
|
|
2334
|
+
yAxisEntries: YAxisEntries | null;
|
|
2335
|
+
degen: DegenState | null;
|
|
2336
|
+
}) {
|
|
2131
2337
|
const {
|
|
2132
2338
|
rootGesture,
|
|
2133
2339
|
backgroundColor,
|
|
@@ -2143,33 +2349,18 @@ export function LiveChart(props: LiveChartProps) {
|
|
|
2143
2349
|
topLabelCfg,
|
|
2144
2350
|
bottomLabelCfg,
|
|
2145
2351
|
markersActive,
|
|
2146
|
-
markersSV,
|
|
2147
|
-
markerClusterCfg,
|
|
2148
2352
|
renderMarker,
|
|
2149
2353
|
renderTooltip,
|
|
2150
2354
|
renderOverlay,
|
|
2151
2355
|
renderReferenceLine,
|
|
2152
2356
|
allRefLines,
|
|
2153
|
-
refLineCustom,
|
|
2154
|
-
dragValues,
|
|
2155
|
-
dragActive,
|
|
2156
|
-
overlayContext,
|
|
2157
2357
|
scrubCfg,
|
|
2158
2358
|
crosshair,
|
|
2159
|
-
isCandle,
|
|
2160
2359
|
extremaTimeOffset,
|
|
2161
2360
|
topConnector,
|
|
2162
2361
|
bottomConnector,
|
|
2163
|
-
lineIsLinear,
|
|
2164
|
-
overlayScrubFade,
|
|
2165
2362
|
} = model;
|
|
2166
2363
|
|
|
2167
|
-
// Mirror the Skia overlay fade onto the RN custom-overlay siblings (custom
|
|
2168
|
-
// markers / reference-line tags) so `scrub.hideOverlaysOnScrub` hides them too.
|
|
2169
|
-
const overlayFadeStyle = useAnimatedStyle(() => ({
|
|
2170
|
-
opacity: overlayScrubFade.get(),
|
|
2171
|
-
}));
|
|
2172
|
-
|
|
2173
2364
|
return (
|
|
2174
2365
|
<GestureDetector gesture={rootGesture}>
|
|
2175
2366
|
<View
|
|
@@ -2183,7 +2374,11 @@ export function LiveChart(props: LiveChartProps) {
|
|
|
2183
2374
|
{/* Background fills first, then the left-edge fade (a canvas-space sibling
|
|
2184
2375
|
so dstOut blends correctly), then the line stack on top — so the fade
|
|
2185
2376
|
softens only the fills and the line stays crisp at the left edge. */}
|
|
2186
|
-
<ChartFillLayer
|
|
2377
|
+
<ChartFillLayer
|
|
2378
|
+
model={model}
|
|
2379
|
+
yAxisEntries={yAxisEntries}
|
|
2380
|
+
degen={degen}
|
|
2381
|
+
/>
|
|
2187
2382
|
|
|
2188
2383
|
{leftEdgeFadeCfg && (
|
|
2189
2384
|
<LeftEdgeFade
|
|
@@ -2196,28 +2391,38 @@ export function LiveChart(props: LiveChartProps) {
|
|
|
2196
2391
|
)}
|
|
2197
2392
|
|
|
2198
2393
|
{/* Line stack above the fade so the line stays crisp at the left edge. */}
|
|
2199
|
-
<ChartStack
|
|
2394
|
+
<ChartStack
|
|
2395
|
+
model={model}
|
|
2396
|
+
yAxisEntries={yAxisEntries}
|
|
2397
|
+
degen={degen}
|
|
2398
|
+
/>
|
|
2200
2399
|
|
|
2201
2400
|
{/* "extrema-edge" connector lines (dot → edge readout), above the chart
|
|
2202
2401
|
content so the dashed guide reads over the line / candles. */}
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2402
|
+
{(topConnector || bottomConnector) && (
|
|
2403
|
+
<ExtremaConnectorOverlay
|
|
2404
|
+
engine={engine}
|
|
2405
|
+
padding={effectivePadding}
|
|
2406
|
+
extremaTimeOffset={extremaTimeOffset}
|
|
2407
|
+
top={topConnector}
|
|
2408
|
+
bottom={bottomConnector}
|
|
2409
|
+
/>
|
|
2410
|
+
)}
|
|
2210
2411
|
|
|
2211
2412
|
{/* Reference-line badges + labels above the fade so they stay crisp. */}
|
|
2212
|
-
<ChartRefBadgeLayer model={model} />
|
|
2413
|
+
<ChartRefBadgeLayer model={model} degen={degen} />
|
|
2213
2414
|
|
|
2214
|
-
<ChartValueOverlay model={model} />
|
|
2415
|
+
<ChartValueOverlay model={model} degen={degen} />
|
|
2215
2416
|
|
|
2216
|
-
|
|
2417
|
+
{model.tradeStreamResolved && model.tradeStream && (
|
|
2418
|
+
<ChartTradeStreamLayer model={model} degen={degen} />
|
|
2419
|
+
)}
|
|
2420
|
+
|
|
2421
|
+
<ChartScrubLayer model={model} degen={degen} />
|
|
2217
2422
|
|
|
2218
2423
|
{/* Live-price badge on top of the scrub dim so the dim never clips
|
|
2219
2424
|
its left edge (the badge tracks the live value, not the scrub). */}
|
|
2220
|
-
<ChartBadgeLayer model={model} />
|
|
2425
|
+
{model.badgeCfg && <ChartBadgeLayer model={model} degen={degen} />}
|
|
2221
2426
|
|
|
2222
2427
|
{/* Scrub-action reticle + action badge — top-most, no shake transform. */}
|
|
2223
2428
|
<ChartScrubActionLayer model={model} />
|
|
@@ -2225,58 +2430,26 @@ export function LiveChart(props: LiveChartProps) {
|
|
|
2225
2430
|
|
|
2226
2431
|
{/* RN labels floated over the canvas (sibling of <Canvas>, an RN view).
|
|
2227
2432
|
Pinned to the plot's top/bottom edges via the resolved padding. */}
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2433
|
+
{(topLabelCfg || bottomLabelCfg) && (
|
|
2434
|
+
<AxisLabelOverlay
|
|
2435
|
+
topLabel={topLabelCfg}
|
|
2436
|
+
bottomLabel={bottomLabelCfg}
|
|
2437
|
+
engine={engine}
|
|
2438
|
+
formatValue={formatValue}
|
|
2439
|
+
defaultColor={palette.gridLabel}
|
|
2440
|
+
padding={effectivePadding}
|
|
2441
|
+
extremaTimeOffset={extremaTimeOffset}
|
|
2442
|
+
/>
|
|
2443
|
+
)}
|
|
2237
2444
|
|
|
2238
2445
|
{/* Custom-rendered markers — RN views floated over the canvas (non-Skia),
|
|
2239
2446
|
pinned to each marker's live position. Sibling of <Canvas>. Wrapped in
|
|
2240
2447
|
a box-none fade layer so `scrub.hideOverlaysOnScrub` hides them with the
|
|
2241
2448
|
Skia markers (the wrapper is full-bleed; children keep their own
|
|
2242
2449
|
absolute positions). */}
|
|
2243
|
-
{markersActive && renderMarker
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
style={[StyleSheet.absoluteFill, overlayFadeStyle]}
|
|
2247
|
-
>
|
|
2248
|
-
<CustomMarkerOverlay
|
|
2249
|
-
markers={markersSV}
|
|
2250
|
-
renderMarker={renderMarker}
|
|
2251
|
-
engine={engine}
|
|
2252
|
-
padding={effectivePadding}
|
|
2253
|
-
lineData={engine.data}
|
|
2254
|
-
lineLinear={lineIsLinear}
|
|
2255
|
-
cluster={markerClusterCfg}
|
|
2256
|
-
/>
|
|
2257
|
-
</Animated.View>
|
|
2258
|
-
)}
|
|
2259
|
-
|
|
2260
|
-
{/* Custom-rendered reference-line tags — RN views floated over the canvas
|
|
2261
|
-
(non-Skia), pinned to each Form-A line's value. Sibling of <Canvas>.
|
|
2262
|
-
Built-in Skia tags for these lines are suppressed (no double-draw).
|
|
2263
|
-
Same box-none fade wrapper as the custom markers above. */}
|
|
2264
|
-
{renderReferenceLine && allRefLines.length > 0 && (
|
|
2265
|
-
<Animated.View
|
|
2266
|
-
pointerEvents="box-none"
|
|
2267
|
-
style={[StyleSheet.absoluteFill, overlayFadeStyle]}
|
|
2268
|
-
>
|
|
2269
|
-
<CustomReferenceLineOverlay
|
|
2270
|
-
lines={allRefLines}
|
|
2271
|
-
renderReferenceLine={renderReferenceLine}
|
|
2272
|
-
custom={refLineCustom}
|
|
2273
|
-
engine={engine}
|
|
2274
|
-
padding={effectivePadding}
|
|
2275
|
-
formatValue={formatValue}
|
|
2276
|
-
dragValues={dragValues}
|
|
2277
|
-
dragActive={dragActive}
|
|
2278
|
-
/>
|
|
2279
|
-
</Animated.View>
|
|
2450
|
+
{((markersActive && renderMarker) ||
|
|
2451
|
+
(renderReferenceLine && allRefLines.length > 0)) && (
|
|
2452
|
+
<ChartCustomAnnotations model={model} />
|
|
2280
2453
|
)}
|
|
2281
2454
|
|
|
2282
2455
|
{/* Custom scrub tooltip — an RN view floated over the canvas (non-Skia),
|
|
@@ -2304,10 +2477,19 @@ export function LiveChart(props: LiveChartProps) {
|
|
|
2304
2477
|
{/* Custom consumer overlay — an RN view tree floated over the canvas with
|
|
2305
2478
|
the price↔pixel / time↔pixel bridge, for order / avg-entry / liquidation
|
|
2306
2479
|
tags etc. Topmost RN sibling; `box-none` so empty areas still scrub. */}
|
|
2307
|
-
{renderOverlay &&
|
|
2308
|
-
<ChartOverlayLayer render={renderOverlay} context={overlayContext} />
|
|
2309
|
-
)}
|
|
2480
|
+
{renderOverlay && <ChartCustomConsumerOverlay model={model} />}
|
|
2310
2481
|
</View>
|
|
2311
2482
|
</GestureDetector>
|
|
2312
2483
|
);
|
|
2313
2484
|
}
|
|
2485
|
+
|
|
2486
|
+
export function LiveChart(props: LiveChartProps) {
|
|
2487
|
+
const model = useLiveChartController(props);
|
|
2488
|
+
if (model.yAxisCfg) {
|
|
2489
|
+
return <ChartWithYAxis model={model} />;
|
|
2490
|
+
}
|
|
2491
|
+
if (model.degenCfg) {
|
|
2492
|
+
return <ChartWithDegen model={model} yAxisEntries={null} />;
|
|
2493
|
+
}
|
|
2494
|
+
return <ChartView model={model} yAxisEntries={null} degen={null} />;
|
|
2495
|
+
}
|