react-native-vroom-chart 0.13.1 → 0.15.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/cpp/VroomChartHostObject.cpp +133 -8
- package/cpp/_core_include/vroom/vroom_chart.h +90 -7
- package/cpp/_core_src/candles.h +7 -5
- package/cpp/_core_src/chart.cpp +287 -152
- package/cpp/_core_src/chart.h +29 -3
- package/cpp/_core_src/chart_facade.cpp +113 -1
- package/cpp/_core_src/footprints.cpp +226 -0
- package/cpp/_core_src/footprints.h +45 -0
- package/cpp/_core_src/footprints_layout.cpp +140 -0
- package/cpp/_core_src/footprints_layout.h +94 -0
- package/cpp/_core_src/tip_pulse.h +4 -4
- package/cpp/_core_src/viewport.h +4 -0
- package/lib/index.d.mts +186 -6
- package/lib/index.d.ts +186 -6
- package/lib/index.js +89 -7
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +89 -7
- package/lib/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/VroomChart.tsx +89 -5
- package/src/index.ts +7 -0
- package/src/jsi.d.ts +45 -8
- package/src/types.ts +7 -0
- package/src/useChartCore.ts +56 -5
package/lib/index.mjs
CHANGED
|
@@ -335,6 +335,29 @@ function priceLinesToSpec(cfg) {
|
|
|
335
335
|
};
|
|
336
336
|
}
|
|
337
337
|
var EMPTY_PRICE_LINES = priceLinesToSpec({ lines: [], hasCloseHandler: false });
|
|
338
|
+
var DEFAULT_FOOTPRINT_HOVER_BOOST = 1.25;
|
|
339
|
+
var FOOTPRINT_BUY = 0;
|
|
340
|
+
var FOOTPRINT_SELL = 1;
|
|
341
|
+
var UNBUCKETABLE_MS = -864e13;
|
|
342
|
+
function footprintsToSpec(cfg) {
|
|
343
|
+
return {
|
|
344
|
+
// Index alignment is load-bearing: the core reports hits as indices into this
|
|
345
|
+
// array and the gesture layer maps them straight back to the consumer's
|
|
346
|
+
// `footprints`. So a non-finite time — which can't be bucketed and would
|
|
347
|
+
// reach the native side as a garbage int64 — is neutralized *in place* rather
|
|
348
|
+
// than filtered out, which would shift every index after it onto the wrong
|
|
349
|
+
// trade.
|
|
350
|
+
prints: cfg.prints.map((f) => ({
|
|
351
|
+
timeMs: Number.isFinite(f.timeMs) ? f.timeMs : UNBUCKETABLE_MS,
|
|
352
|
+
side: f.side === "sell" ? FOOTPRINT_SELL : FOOTPRINT_BUY
|
|
353
|
+
})),
|
|
354
|
+
radiusPx: cfg.style?.radius ?? 0,
|
|
355
|
+
gapPx: cfg.style?.gap ?? 0,
|
|
356
|
+
marginPx: cfg.style?.margin ?? 0,
|
|
357
|
+
hoverBoost: cfg.style?.hoverBoost ?? DEFAULT_FOOTPRINT_HOVER_BOOST
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
var EMPTY_FOOTPRINTS = footprintsToSpec({ prints: [] });
|
|
338
361
|
var installed = false;
|
|
339
362
|
function ensureInstalled() {
|
|
340
363
|
if (installed) return;
|
|
@@ -345,7 +368,7 @@ function ensureInstalled() {
|
|
|
345
368
|
}
|
|
346
369
|
installed = true;
|
|
347
370
|
}
|
|
348
|
-
function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType, theme, rsi, macd, movingAverages, vwap, bollingerBands, volume, priceLines, transition) {
|
|
371
|
+
function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType, theme, rsi, macd, movingAverages, vwap, bollingerBands, volume, priceLines, footprints, transition) {
|
|
349
372
|
const handleRef = useRef(null);
|
|
350
373
|
const defaultWidthAppliedRef = useRef(false);
|
|
351
374
|
const volumeCollapseRef = useRef(null);
|
|
@@ -356,11 +379,12 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
|
|
|
356
379
|
ensureInstalled();
|
|
357
380
|
handleRef.current = globalThis.VroomChartJSI.create();
|
|
358
381
|
}
|
|
359
|
-
const animRef = useRef({ ms: 300, easing: void 0, reduceMotion: false });
|
|
382
|
+
const animRef = useRef({ ms: 300, easing: void 0, reduceMotion: false, interval: "transform" });
|
|
360
383
|
animRef.current = {
|
|
361
384
|
ms: Math.max(0, transition?.transitionMs ?? 300),
|
|
362
385
|
easing: transition?.transitionEasing,
|
|
363
|
-
reduceMotion: transition?.reduceMotion ?? false
|
|
386
|
+
reduceMotion: transition?.reduceMotion ?? false,
|
|
387
|
+
interval: transition?.intervalTransition === "fade" ? "fade" : "transform"
|
|
364
388
|
};
|
|
365
389
|
const onFrameRef = useRef(transition?.onFrame);
|
|
366
390
|
onFrameRef.current = transition?.onFrame;
|
|
@@ -403,6 +427,7 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
|
|
|
403
427
|
const bollingerKey = bollingerBands ? JSON.stringify(bollingerBands) : "";
|
|
404
428
|
const volumeKey = volume ? JSON.stringify(volume) : "";
|
|
405
429
|
const priceLinesKey = priceLines ? JSON.stringify(priceLines) : "";
|
|
430
|
+
const footprintsKey = footprints ? JSON.stringify(footprints) : "";
|
|
406
431
|
useEffect(() => {
|
|
407
432
|
const h = handleRef.current;
|
|
408
433
|
if (!h) return;
|
|
@@ -433,7 +458,7 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
|
|
|
433
458
|
morphing = animRef.current.ms > 0 && !animRef.current.reduceMotion && onFrameRef.current != null;
|
|
434
459
|
if (morphing) {
|
|
435
460
|
endIntervalMorph();
|
|
436
|
-
h.beginIntervalMorph();
|
|
461
|
+
h.beginIntervalMorph(animRef.current.interval);
|
|
437
462
|
}
|
|
438
463
|
} else if (transitionKind === "initial" || transitionKind === "reset") {
|
|
439
464
|
endIntervalMorph();
|
|
@@ -480,12 +505,16 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
|
|
|
480
505
|
h.setPriceLines(
|
|
481
506
|
priceLines?.lines.length ? priceLinesToSpec(priceLines) : EMPTY_PRICE_LINES
|
|
482
507
|
);
|
|
508
|
+
h.setFootprints(
|
|
509
|
+
footprints?.prints.length ? footprintsToSpec(footprints) : EMPTY_FOOTPRINTS
|
|
510
|
+
);
|
|
483
511
|
if (!morphing) setPicture(h.render());
|
|
484
|
-
}, [candles, seriesKey, size.width, size.height, size.pxRatio, explicit, startMs, endMs, defaultCandleWidth, themeKey, rsiKey, macdKey, maKey, vwapKey, bollingerKey, volumeKey, priceLinesKey, startIntervalMorph, endIntervalMorph]);
|
|
512
|
+
}, [candles, seriesKey, size.width, size.height, size.pxRatio, explicit, startMs, endMs, defaultCandleWidth, themeKey, rsiKey, macdKey, maKey, vwapKey, bollingerKey, volumeKey, priceLinesKey, footprintsKey, startIntervalMorph, endIntervalMorph]);
|
|
485
513
|
return { handle: handleRef.current, picture, volumeCollapseRef };
|
|
486
514
|
}
|
|
487
515
|
|
|
488
516
|
// src/VroomChart.tsx
|
|
517
|
+
var FOOTPRINT_SELL2 = 1;
|
|
489
518
|
function isSkImage(frame) {
|
|
490
519
|
return typeof frame.getImageInfo === "function";
|
|
491
520
|
}
|
|
@@ -501,6 +530,7 @@ function VroomChart(props) {
|
|
|
501
530
|
chartType,
|
|
502
531
|
transitionMs,
|
|
503
532
|
transitionEasing,
|
|
533
|
+
intervalTransition,
|
|
504
534
|
theme,
|
|
505
535
|
rsi,
|
|
506
536
|
macd,
|
|
@@ -515,7 +545,10 @@ function VroomChart(props) {
|
|
|
515
545
|
priceLinesStyle,
|
|
516
546
|
onPriceLineDrag,
|
|
517
547
|
onPriceLineDragEnd,
|
|
518
|
-
onPriceLineClose
|
|
548
|
+
onPriceLineClose,
|
|
549
|
+
footprints,
|
|
550
|
+
footprintsStyle,
|
|
551
|
+
onFootprint
|
|
519
552
|
} = props;
|
|
520
553
|
const [measured, setMeasured] = useState2({ width: 0, height: 0 });
|
|
521
554
|
const width = widthProp ?? measured.width;
|
|
@@ -535,6 +568,10 @@ function VroomChart(props) {
|
|
|
535
568
|
} : void 0,
|
|
536
569
|
[priceLines, priceLinesStyle, onPriceLineClose]
|
|
537
570
|
);
|
|
571
|
+
const footprintsProp = useMemo(
|
|
572
|
+
() => footprints ? { prints: footprints, style: footprintsStyle } : void 0,
|
|
573
|
+
[footprints, footprintsStyle]
|
|
574
|
+
);
|
|
538
575
|
const emptyPicture = useMemo(() => {
|
|
539
576
|
const rec = Skia.PictureRecorder();
|
|
540
577
|
rec.beginRecording(Skia.XYWHRect(0, 0, 1, 1));
|
|
@@ -583,9 +620,11 @@ function VroomChart(props) {
|
|
|
583
620
|
bollingerBands,
|
|
584
621
|
volume,
|
|
585
622
|
priceLinesProp,
|
|
586
|
-
|
|
623
|
+
footprintsProp,
|
|
624
|
+
{ seriesKey, transitionMs, transitionEasing, intervalTransition, reduceMotion, onFrame }
|
|
587
625
|
);
|
|
588
626
|
const crosshairActive = useRef2(false);
|
|
627
|
+
const footprintActive = useRef2(false);
|
|
589
628
|
const lastCrosshairTime = useRef2(null);
|
|
590
629
|
const decayRaf = useRef2(null);
|
|
591
630
|
const cancelDecay = useCallback2(() => {
|
|
@@ -845,6 +884,24 @@ function VroomChart(props) {
|
|
|
845
884
|
null
|
|
846
885
|
);
|
|
847
886
|
const panMode = useRef2("chart");
|
|
887
|
+
const dismissFootprint = (redraw = true) => {
|
|
888
|
+
if (!handle || !footprintActive.current) return;
|
|
889
|
+
footprintActive.current = false;
|
|
890
|
+
handle.setFootprintHover(0, -1);
|
|
891
|
+
if (redraw) {
|
|
892
|
+
const frame = handle.render();
|
|
893
|
+
if (frame) applyFrame(frame);
|
|
894
|
+
}
|
|
895
|
+
onFootprint?.({
|
|
896
|
+
active: false,
|
|
897
|
+
reason: "hide",
|
|
898
|
+
side: null,
|
|
899
|
+
timeMs: null,
|
|
900
|
+
footprints: [],
|
|
901
|
+
badge: null,
|
|
902
|
+
pane: null
|
|
903
|
+
});
|
|
904
|
+
};
|
|
848
905
|
const pan = Gesture.Pan().runOnJS(true).maxPointers(1).onStart((e) => {
|
|
849
906
|
cancelDecay();
|
|
850
907
|
panMode.current = hitAxis(e.x, e.y);
|
|
@@ -859,6 +916,7 @@ function VroomChart(props) {
|
|
|
859
916
|
if (p) applyFrame(p);
|
|
860
917
|
}
|
|
861
918
|
}
|
|
919
|
+
if (panMode.current !== "price-line") dismissFootprint();
|
|
862
920
|
}).onChange((e) => {
|
|
863
921
|
if (!handle) return;
|
|
864
922
|
let next = null;
|
|
@@ -941,6 +999,7 @@ function VroomChart(props) {
|
|
|
941
999
|
});
|
|
942
1000
|
const pinch = Gesture.Pinch().runOnJS(true).onTouchesDown((e) => {
|
|
943
1001
|
if (e.numberOfTouches < 2) return;
|
|
1002
|
+
dismissFootprint();
|
|
944
1003
|
const [a, b] = e.allTouches;
|
|
945
1004
|
const spanX = Math.abs(a.x - b.x);
|
|
946
1005
|
const spanY = Math.abs(a.y - b.y);
|
|
@@ -981,6 +1040,7 @@ function VroomChart(props) {
|
|
|
981
1040
|
if (hitAxis(e.x, e.y) !== "chart") return;
|
|
982
1041
|
if (hitPriceLine(e.x, e.y)) return;
|
|
983
1042
|
cancelDecay();
|
|
1043
|
+
dismissFootprint(false);
|
|
984
1044
|
crosshairActive.current = true;
|
|
985
1045
|
const ch = handle.setCrosshair(e.x, e.y - crosshairOffset);
|
|
986
1046
|
if (ch) applyFrame(ch);
|
|
@@ -997,6 +1057,28 @@ function VroomChart(props) {
|
|
|
997
1057
|
});
|
|
998
1058
|
const tap = Gesture.Tap().runOnJS(true).onStart((e) => {
|
|
999
1059
|
if (!handle) return;
|
|
1060
|
+
const prints = footprints ?? [];
|
|
1061
|
+
const fp = prints.length ? handle.hitTestFootprint(e.x, e.y) : null;
|
|
1062
|
+
if (fp) {
|
|
1063
|
+
handle.setFootprintHover(fp.candleTimeMs, fp.side);
|
|
1064
|
+
const frame = handle.render();
|
|
1065
|
+
if (frame) applyFrame(frame);
|
|
1066
|
+
const wasActive = footprintActive.current;
|
|
1067
|
+
footprintActive.current = true;
|
|
1068
|
+
onFootprint?.({
|
|
1069
|
+
active: true,
|
|
1070
|
+
reason: wasActive ? "move" : "show",
|
|
1071
|
+
side: fp.side === FOOTPRINT_SELL2 ? "sell" : "buy",
|
|
1072
|
+
timeMs: fp.candleTimeMs,
|
|
1073
|
+
// The core reports indices into the array we last pushed, which is this
|
|
1074
|
+
// same prop — so this rejoins each badge to the consumer's own objects.
|
|
1075
|
+
footprints: fp.indices.map((i) => prints[i]).filter((f) => f != null),
|
|
1076
|
+
badge: { x: fp.x, y: fp.y, radius: fp.radius },
|
|
1077
|
+
pane: fp.pane
|
|
1078
|
+
});
|
|
1079
|
+
return;
|
|
1080
|
+
}
|
|
1081
|
+
dismissFootprint();
|
|
1000
1082
|
const pl = hitPriceLine(e.x, e.y);
|
|
1001
1083
|
if (pl && pl.part === 1) {
|
|
1002
1084
|
onPriceLineClose?.(pl.line.id);
|