react-native-vroom-chart 0.14.0 → 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 +118 -1
- package/cpp/_core_include/vroom/vroom_chart.h +84 -0
- package/cpp/_core_src/chart.cpp +16 -0
- package/cpp/_core_src/chart.h +23 -0
- package/cpp/_core_src/chart_facade.cpp +108 -0
- 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/lib/index.d.mts +135 -1
- package/lib/index.d.ts +135 -1
- package/lib/index.js +83 -3
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +83 -3
- package/lib/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/VroomChart.tsx +87 -4
- package/src/index.ts +5 -0
- package/src/jsi.d.ts +39 -0
- package/src/types.ts +5 -0
- package/src/useChartCore.ts +49 -3
package/lib/index.js
CHANGED
|
@@ -363,6 +363,29 @@ function priceLinesToSpec(cfg) {
|
|
|
363
363
|
};
|
|
364
364
|
}
|
|
365
365
|
var EMPTY_PRICE_LINES = priceLinesToSpec({ lines: [], hasCloseHandler: false });
|
|
366
|
+
var DEFAULT_FOOTPRINT_HOVER_BOOST = 1.25;
|
|
367
|
+
var FOOTPRINT_BUY = 0;
|
|
368
|
+
var FOOTPRINT_SELL = 1;
|
|
369
|
+
var UNBUCKETABLE_MS = -864e13;
|
|
370
|
+
function footprintsToSpec(cfg) {
|
|
371
|
+
return {
|
|
372
|
+
// Index alignment is load-bearing: the core reports hits as indices into this
|
|
373
|
+
// array and the gesture layer maps them straight back to the consumer's
|
|
374
|
+
// `footprints`. So a non-finite time — which can't be bucketed and would
|
|
375
|
+
// reach the native side as a garbage int64 — is neutralized *in place* rather
|
|
376
|
+
// than filtered out, which would shift every index after it onto the wrong
|
|
377
|
+
// trade.
|
|
378
|
+
prints: cfg.prints.map((f) => ({
|
|
379
|
+
timeMs: Number.isFinite(f.timeMs) ? f.timeMs : UNBUCKETABLE_MS,
|
|
380
|
+
side: f.side === "sell" ? FOOTPRINT_SELL : FOOTPRINT_BUY
|
|
381
|
+
})),
|
|
382
|
+
radiusPx: cfg.style?.radius ?? 0,
|
|
383
|
+
gapPx: cfg.style?.gap ?? 0,
|
|
384
|
+
marginPx: cfg.style?.margin ?? 0,
|
|
385
|
+
hoverBoost: cfg.style?.hoverBoost ?? DEFAULT_FOOTPRINT_HOVER_BOOST
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
var EMPTY_FOOTPRINTS = footprintsToSpec({ prints: [] });
|
|
366
389
|
var installed = false;
|
|
367
390
|
function ensureInstalled() {
|
|
368
391
|
if (installed) return;
|
|
@@ -373,7 +396,7 @@ function ensureInstalled() {
|
|
|
373
396
|
}
|
|
374
397
|
installed = true;
|
|
375
398
|
}
|
|
376
|
-
function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType, theme, rsi, macd, movingAverages, vwap, bollingerBands, volume, priceLines, transition) {
|
|
399
|
+
function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType, theme, rsi, macd, movingAverages, vwap, bollingerBands, volume, priceLines, footprints, transition) {
|
|
377
400
|
const handleRef = (0, import_react.useRef)(null);
|
|
378
401
|
const defaultWidthAppliedRef = (0, import_react.useRef)(false);
|
|
379
402
|
const volumeCollapseRef = (0, import_react.useRef)(null);
|
|
@@ -432,6 +455,7 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
|
|
|
432
455
|
const bollingerKey = bollingerBands ? JSON.stringify(bollingerBands) : "";
|
|
433
456
|
const volumeKey = volume ? JSON.stringify(volume) : "";
|
|
434
457
|
const priceLinesKey = priceLines ? JSON.stringify(priceLines) : "";
|
|
458
|
+
const footprintsKey = footprints ? JSON.stringify(footprints) : "";
|
|
435
459
|
(0, import_react.useEffect)(() => {
|
|
436
460
|
const h = handleRef.current;
|
|
437
461
|
if (!h) return;
|
|
@@ -509,12 +533,16 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
|
|
|
509
533
|
h.setPriceLines(
|
|
510
534
|
priceLines?.lines.length ? priceLinesToSpec(priceLines) : EMPTY_PRICE_LINES
|
|
511
535
|
);
|
|
536
|
+
h.setFootprints(
|
|
537
|
+
footprints?.prints.length ? footprintsToSpec(footprints) : EMPTY_FOOTPRINTS
|
|
538
|
+
);
|
|
512
539
|
if (!morphing) setPicture(h.render());
|
|
513
|
-
}, [candles, seriesKey, size.width, size.height, size.pxRatio, explicit, startMs, endMs, defaultCandleWidth, themeKey, rsiKey, macdKey, maKey, vwapKey, bollingerKey, volumeKey, priceLinesKey, startIntervalMorph, endIntervalMorph]);
|
|
540
|
+
}, [candles, seriesKey, size.width, size.height, size.pxRatio, explicit, startMs, endMs, defaultCandleWidth, themeKey, rsiKey, macdKey, maKey, vwapKey, bollingerKey, volumeKey, priceLinesKey, footprintsKey, startIntervalMorph, endIntervalMorph]);
|
|
514
541
|
return { handle: handleRef.current, picture, volumeCollapseRef };
|
|
515
542
|
}
|
|
516
543
|
|
|
517
544
|
// src/VroomChart.tsx
|
|
545
|
+
var FOOTPRINT_SELL2 = 1;
|
|
518
546
|
function isSkImage(frame) {
|
|
519
547
|
return typeof frame.getImageInfo === "function";
|
|
520
548
|
}
|
|
@@ -545,7 +573,10 @@ function VroomChart(props) {
|
|
|
545
573
|
priceLinesStyle,
|
|
546
574
|
onPriceLineDrag,
|
|
547
575
|
onPriceLineDragEnd,
|
|
548
|
-
onPriceLineClose
|
|
576
|
+
onPriceLineClose,
|
|
577
|
+
footprints,
|
|
578
|
+
footprintsStyle,
|
|
579
|
+
onFootprint
|
|
549
580
|
} = props;
|
|
550
581
|
const [measured, setMeasured] = (0, import_react2.useState)({ width: 0, height: 0 });
|
|
551
582
|
const width = widthProp ?? measured.width;
|
|
@@ -565,6 +596,10 @@ function VroomChart(props) {
|
|
|
565
596
|
} : void 0,
|
|
566
597
|
[priceLines, priceLinesStyle, onPriceLineClose]
|
|
567
598
|
);
|
|
599
|
+
const footprintsProp = (0, import_react2.useMemo)(
|
|
600
|
+
() => footprints ? { prints: footprints, style: footprintsStyle } : void 0,
|
|
601
|
+
[footprints, footprintsStyle]
|
|
602
|
+
);
|
|
568
603
|
const emptyPicture = (0, import_react2.useMemo)(() => {
|
|
569
604
|
const rec = import_react_native_skia.Skia.PictureRecorder();
|
|
570
605
|
rec.beginRecording(import_react_native_skia.Skia.XYWHRect(0, 0, 1, 1));
|
|
@@ -613,9 +648,11 @@ function VroomChart(props) {
|
|
|
613
648
|
bollingerBands,
|
|
614
649
|
volume,
|
|
615
650
|
priceLinesProp,
|
|
651
|
+
footprintsProp,
|
|
616
652
|
{ seriesKey, transitionMs, transitionEasing, intervalTransition, reduceMotion, onFrame }
|
|
617
653
|
);
|
|
618
654
|
const crosshairActive = (0, import_react2.useRef)(false);
|
|
655
|
+
const footprintActive = (0, import_react2.useRef)(false);
|
|
619
656
|
const lastCrosshairTime = (0, import_react2.useRef)(null);
|
|
620
657
|
const decayRaf = (0, import_react2.useRef)(null);
|
|
621
658
|
const cancelDecay = (0, import_react2.useCallback)(() => {
|
|
@@ -875,6 +912,24 @@ function VroomChart(props) {
|
|
|
875
912
|
null
|
|
876
913
|
);
|
|
877
914
|
const panMode = (0, import_react2.useRef)("chart");
|
|
915
|
+
const dismissFootprint = (redraw = true) => {
|
|
916
|
+
if (!handle || !footprintActive.current) return;
|
|
917
|
+
footprintActive.current = false;
|
|
918
|
+
handle.setFootprintHover(0, -1);
|
|
919
|
+
if (redraw) {
|
|
920
|
+
const frame = handle.render();
|
|
921
|
+
if (frame) applyFrame(frame);
|
|
922
|
+
}
|
|
923
|
+
onFootprint?.({
|
|
924
|
+
active: false,
|
|
925
|
+
reason: "hide",
|
|
926
|
+
side: null,
|
|
927
|
+
timeMs: null,
|
|
928
|
+
footprints: [],
|
|
929
|
+
badge: null,
|
|
930
|
+
pane: null
|
|
931
|
+
});
|
|
932
|
+
};
|
|
878
933
|
const pan = import_react_native_gesture_handler.Gesture.Pan().runOnJS(true).maxPointers(1).onStart((e) => {
|
|
879
934
|
cancelDecay();
|
|
880
935
|
panMode.current = hitAxis(e.x, e.y);
|
|
@@ -889,6 +944,7 @@ function VroomChart(props) {
|
|
|
889
944
|
if (p) applyFrame(p);
|
|
890
945
|
}
|
|
891
946
|
}
|
|
947
|
+
if (panMode.current !== "price-line") dismissFootprint();
|
|
892
948
|
}).onChange((e) => {
|
|
893
949
|
if (!handle) return;
|
|
894
950
|
let next = null;
|
|
@@ -971,6 +1027,7 @@ function VroomChart(props) {
|
|
|
971
1027
|
});
|
|
972
1028
|
const pinch = import_react_native_gesture_handler.Gesture.Pinch().runOnJS(true).onTouchesDown((e) => {
|
|
973
1029
|
if (e.numberOfTouches < 2) return;
|
|
1030
|
+
dismissFootprint();
|
|
974
1031
|
const [a, b] = e.allTouches;
|
|
975
1032
|
const spanX = Math.abs(a.x - b.x);
|
|
976
1033
|
const spanY = Math.abs(a.y - b.y);
|
|
@@ -1011,6 +1068,7 @@ function VroomChart(props) {
|
|
|
1011
1068
|
if (hitAxis(e.x, e.y) !== "chart") return;
|
|
1012
1069
|
if (hitPriceLine(e.x, e.y)) return;
|
|
1013
1070
|
cancelDecay();
|
|
1071
|
+
dismissFootprint(false);
|
|
1014
1072
|
crosshairActive.current = true;
|
|
1015
1073
|
const ch = handle.setCrosshair(e.x, e.y - crosshairOffset);
|
|
1016
1074
|
if (ch) applyFrame(ch);
|
|
@@ -1027,6 +1085,28 @@ function VroomChart(props) {
|
|
|
1027
1085
|
});
|
|
1028
1086
|
const tap = import_react_native_gesture_handler.Gesture.Tap().runOnJS(true).onStart((e) => {
|
|
1029
1087
|
if (!handle) return;
|
|
1088
|
+
const prints = footprints ?? [];
|
|
1089
|
+
const fp = prints.length ? handle.hitTestFootprint(e.x, e.y) : null;
|
|
1090
|
+
if (fp) {
|
|
1091
|
+
handle.setFootprintHover(fp.candleTimeMs, fp.side);
|
|
1092
|
+
const frame = handle.render();
|
|
1093
|
+
if (frame) applyFrame(frame);
|
|
1094
|
+
const wasActive = footprintActive.current;
|
|
1095
|
+
footprintActive.current = true;
|
|
1096
|
+
onFootprint?.({
|
|
1097
|
+
active: true,
|
|
1098
|
+
reason: wasActive ? "move" : "show",
|
|
1099
|
+
side: fp.side === FOOTPRINT_SELL2 ? "sell" : "buy",
|
|
1100
|
+
timeMs: fp.candleTimeMs,
|
|
1101
|
+
// The core reports indices into the array we last pushed, which is this
|
|
1102
|
+
// same prop — so this rejoins each badge to the consumer's own objects.
|
|
1103
|
+
footprints: fp.indices.map((i) => prints[i]).filter((f) => f != null),
|
|
1104
|
+
badge: { x: fp.x, y: fp.y, radius: fp.radius },
|
|
1105
|
+
pane: fp.pane
|
|
1106
|
+
});
|
|
1107
|
+
return;
|
|
1108
|
+
}
|
|
1109
|
+
dismissFootprint();
|
|
1030
1110
|
const pl = hitPriceLine(e.x, e.y);
|
|
1031
1111
|
if (pl && pl.part === 1) {
|
|
1032
1112
|
onPriceLineClose?.(pl.line.id);
|