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.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);
|
|
@@ -404,6 +427,7 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
|
|
|
404
427
|
const bollingerKey = bollingerBands ? JSON.stringify(bollingerBands) : "";
|
|
405
428
|
const volumeKey = volume ? JSON.stringify(volume) : "";
|
|
406
429
|
const priceLinesKey = priceLines ? JSON.stringify(priceLines) : "";
|
|
430
|
+
const footprintsKey = footprints ? JSON.stringify(footprints) : "";
|
|
407
431
|
useEffect(() => {
|
|
408
432
|
const h = handleRef.current;
|
|
409
433
|
if (!h) return;
|
|
@@ -481,12 +505,16 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
|
|
|
481
505
|
h.setPriceLines(
|
|
482
506
|
priceLines?.lines.length ? priceLinesToSpec(priceLines) : EMPTY_PRICE_LINES
|
|
483
507
|
);
|
|
508
|
+
h.setFootprints(
|
|
509
|
+
footprints?.prints.length ? footprintsToSpec(footprints) : EMPTY_FOOTPRINTS
|
|
510
|
+
);
|
|
484
511
|
if (!morphing) setPicture(h.render());
|
|
485
|
-
}, [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]);
|
|
486
513
|
return { handle: handleRef.current, picture, volumeCollapseRef };
|
|
487
514
|
}
|
|
488
515
|
|
|
489
516
|
// src/VroomChart.tsx
|
|
517
|
+
var FOOTPRINT_SELL2 = 1;
|
|
490
518
|
function isSkImage(frame) {
|
|
491
519
|
return typeof frame.getImageInfo === "function";
|
|
492
520
|
}
|
|
@@ -517,7 +545,10 @@ function VroomChart(props) {
|
|
|
517
545
|
priceLinesStyle,
|
|
518
546
|
onPriceLineDrag,
|
|
519
547
|
onPriceLineDragEnd,
|
|
520
|
-
onPriceLineClose
|
|
548
|
+
onPriceLineClose,
|
|
549
|
+
footprints,
|
|
550
|
+
footprintsStyle,
|
|
551
|
+
onFootprint
|
|
521
552
|
} = props;
|
|
522
553
|
const [measured, setMeasured] = useState2({ width: 0, height: 0 });
|
|
523
554
|
const width = widthProp ?? measured.width;
|
|
@@ -537,6 +568,10 @@ function VroomChart(props) {
|
|
|
537
568
|
} : void 0,
|
|
538
569
|
[priceLines, priceLinesStyle, onPriceLineClose]
|
|
539
570
|
);
|
|
571
|
+
const footprintsProp = useMemo(
|
|
572
|
+
() => footprints ? { prints: footprints, style: footprintsStyle } : void 0,
|
|
573
|
+
[footprints, footprintsStyle]
|
|
574
|
+
);
|
|
540
575
|
const emptyPicture = useMemo(() => {
|
|
541
576
|
const rec = Skia.PictureRecorder();
|
|
542
577
|
rec.beginRecording(Skia.XYWHRect(0, 0, 1, 1));
|
|
@@ -585,9 +620,11 @@ function VroomChart(props) {
|
|
|
585
620
|
bollingerBands,
|
|
586
621
|
volume,
|
|
587
622
|
priceLinesProp,
|
|
623
|
+
footprintsProp,
|
|
588
624
|
{ seriesKey, transitionMs, transitionEasing, intervalTransition, reduceMotion, onFrame }
|
|
589
625
|
);
|
|
590
626
|
const crosshairActive = useRef2(false);
|
|
627
|
+
const footprintActive = useRef2(false);
|
|
591
628
|
const lastCrosshairTime = useRef2(null);
|
|
592
629
|
const decayRaf = useRef2(null);
|
|
593
630
|
const cancelDecay = useCallback2(() => {
|
|
@@ -847,6 +884,24 @@ function VroomChart(props) {
|
|
|
847
884
|
null
|
|
848
885
|
);
|
|
849
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
|
+
};
|
|
850
905
|
const pan = Gesture.Pan().runOnJS(true).maxPointers(1).onStart((e) => {
|
|
851
906
|
cancelDecay();
|
|
852
907
|
panMode.current = hitAxis(e.x, e.y);
|
|
@@ -861,6 +916,7 @@ function VroomChart(props) {
|
|
|
861
916
|
if (p) applyFrame(p);
|
|
862
917
|
}
|
|
863
918
|
}
|
|
919
|
+
if (panMode.current !== "price-line") dismissFootprint();
|
|
864
920
|
}).onChange((e) => {
|
|
865
921
|
if (!handle) return;
|
|
866
922
|
let next = null;
|
|
@@ -943,6 +999,7 @@ function VroomChart(props) {
|
|
|
943
999
|
});
|
|
944
1000
|
const pinch = Gesture.Pinch().runOnJS(true).onTouchesDown((e) => {
|
|
945
1001
|
if (e.numberOfTouches < 2) return;
|
|
1002
|
+
dismissFootprint();
|
|
946
1003
|
const [a, b] = e.allTouches;
|
|
947
1004
|
const spanX = Math.abs(a.x - b.x);
|
|
948
1005
|
const spanY = Math.abs(a.y - b.y);
|
|
@@ -983,6 +1040,7 @@ function VroomChart(props) {
|
|
|
983
1040
|
if (hitAxis(e.x, e.y) !== "chart") return;
|
|
984
1041
|
if (hitPriceLine(e.x, e.y)) return;
|
|
985
1042
|
cancelDecay();
|
|
1043
|
+
dismissFootprint(false);
|
|
986
1044
|
crosshairActive.current = true;
|
|
987
1045
|
const ch = handle.setCrosshair(e.x, e.y - crosshairOffset);
|
|
988
1046
|
if (ch) applyFrame(ch);
|
|
@@ -999,6 +1057,28 @@ function VroomChart(props) {
|
|
|
999
1057
|
});
|
|
1000
1058
|
const tap = Gesture.Tap().runOnJS(true).onStart((e) => {
|
|
1001
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();
|
|
1002
1082
|
const pl = hitPriceLine(e.x, e.y);
|
|
1003
1083
|
if (pl && pl.part === 1) {
|
|
1004
1084
|
onPriceLineClose?.(pl.line.id);
|