react-native-vroom-chart 0.15.0 → 0.16.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.
Files changed (42) hide show
  1. package/cpp/VroomChartHostObject.cpp +171 -1
  2. package/cpp/_core_include/vroom/vroom_chart.h +137 -4
  3. package/cpp/_core_src/atr.cpp +67 -0
  4. package/cpp/_core_src/atr.h +44 -0
  5. package/cpp/_core_src/atr_pane.cpp +162 -0
  6. package/cpp/_core_src/atr_pane.h +48 -0
  7. package/cpp/_core_src/chart.cpp +461 -172
  8. package/cpp/_core_src/chart.h +150 -1
  9. package/cpp/_core_src/chart_facade.cpp +209 -23
  10. package/cpp/_core_src/fair_value_gaps.cpp +76 -0
  11. package/cpp/_core_src/fair_value_gaps.h +54 -0
  12. package/cpp/_core_src/fvg_overlay.cpp +262 -0
  13. package/cpp/_core_src/fvg_overlay.h +43 -0
  14. package/cpp/_core_src/ichimoku.cpp +65 -0
  15. package/cpp/_core_src/ichimoku.h +45 -0
  16. package/cpp/_core_src/labels.cpp +3 -0
  17. package/cpp/_core_src/line_morph.h +159 -0
  18. package/cpp/_core_src/ma_overlay.cpp +259 -36
  19. package/cpp/_core_src/ma_overlay.h +57 -2
  20. package/cpp/_core_src/macd.cpp +24 -1
  21. package/cpp/_core_src/macd.h +16 -0
  22. package/cpp/_core_src/macd_pane.cpp +71 -62
  23. package/cpp/_core_src/macd_pane.h +13 -1
  24. package/cpp/_core_src/pane_series.h +169 -0
  25. package/cpp/_core_src/rsi.cpp +4 -0
  26. package/cpp/_core_src/rsi.h +7 -0
  27. package/cpp/_core_src/rsi_pane.cpp +85 -29
  28. package/cpp/_core_src/rsi_pane.h +11 -1
  29. package/cpp/_core_src/viewport.h +35 -0
  30. package/lib/index.d.mts +251 -3
  31. package/lib/index.d.ts +251 -3
  32. package/lib/index.js +224 -6
  33. package/lib/index.js.map +1 -1
  34. package/lib/index.mjs +224 -6
  35. package/lib/index.mjs.map +1 -1
  36. package/package.json +1 -1
  37. package/src/VroomChart.tsx +21 -3
  38. package/src/dataTransitions.ts +47 -0
  39. package/src/index.ts +5 -0
  40. package/src/jsi.d.ts +97 -1
  41. package/src/types.ts +5 -0
  42. package/src/useChartCore.ts +284 -5
package/lib/index.js CHANGED
@@ -99,6 +99,13 @@ function classifyTransition(prev, next, seriesKeyChanged) {
99
99
  const endsTogether = Math.abs(nextEnd - prevEnd) <= MAX_END_DRIFT_STEPS * Math.max(prevStep, nextStep);
100
100
  return closeRatio <= MAX_SAME_ASSET_CLOSE_RATIO && endsTogether ? "timeframe" : "reset";
101
101
  }
102
+ function classifyStream(prev, next) {
103
+ if (prev.length === 0 || next.length === 0) return "tick";
104
+ return next[next.length - 1].timeMs > prev[prev.length - 1].timeMs ? "append" : "tick";
105
+ }
106
+ function isPinnedToLatest(window, lastMs, stepMs) {
107
+ return window.endMs >= lastMs + stepMs;
108
+ }
102
109
  function timeframeWindow(oldWindow, oldStepMs, oldLastMs, newStepMs, newLastMs) {
103
110
  const slots = (oldWindow.endMs - oldWindow.startMs) / oldStepMs;
104
111
  const offsetRaw = (oldWindow.endMs - oldLastMs) / oldStepMs;
@@ -244,6 +251,7 @@ var MA_SOURCES = [
244
251
  "hlc3",
245
252
  "ohlc4"
246
253
  ];
254
+ var ATR_SMOOTHINGS = ["rma", "sma", "ema"];
247
255
  var inheritColor = (v) => (v != null ? parseColor(v) : null) ?? 0;
248
256
  function overlayToNumeric(o) {
249
257
  const srcIdx = o.source ? MA_SOURCES.indexOf(o.source) : 0;
@@ -270,7 +278,8 @@ function rsiToSpec(cfg) {
270
278
  maColor: inheritColor(cfg?.maColor),
271
279
  maWidth: cfg?.maWidth ?? -1,
272
280
  bandColor: inheritColor(cfg?.bandColor),
273
- bandsVisible: cfg?.bandsVisible ?? true
281
+ bandsVisible: cfg?.bandsVisible ?? true,
282
+ extremeFill: cfg?.extremeFill ?? true
274
283
  };
275
284
  }
276
285
  function vwapToSpec(cfg) {
@@ -301,6 +310,79 @@ function bollingerToSpec(cfg) {
301
310
  fillOpacity: cfg?.fillOpacity ?? 0.1
302
311
  };
303
312
  }
313
+ var DEFAULT_ICH_GREEN = 4280723098;
314
+ var DEFAULT_ICH_RED = 4293874512;
315
+ var DEFAULT_ICH_BLUE = 4280902399;
316
+ var DEFAULT_ICH_ORANGE = 4294929664;
317
+ var DEFAULT_ICH_TEAL = 4278238420;
318
+ function ichimokuToSpec(cfg) {
319
+ const color = (v, fallback) => (v != null ? parseColor(v) : null) ?? fallback;
320
+ return {
321
+ enabled: cfg?.enabled ?? false,
322
+ tenkanPeriod: cfg?.tenkanPeriod ?? 9,
323
+ kijunPeriod: cfg?.kijunPeriod ?? 26,
324
+ senkouBPeriod: cfg?.senkouBPeriod ?? 52,
325
+ displacement: cfg?.displacement ?? 26,
326
+ tenkanColor: color(cfg?.tenkanColor, DEFAULT_ICH_BLUE),
327
+ tenkanWidth: cfg?.tenkanWidth ?? 1,
328
+ tenkanEnabled: cfg?.tenkanVisible ?? true,
329
+ kijunColor: color(cfg?.kijunColor, DEFAULT_ICH_RED),
330
+ kijunWidth: cfg?.kijunWidth ?? 1,
331
+ kijunEnabled: cfg?.kijunVisible ?? true,
332
+ senkouAColor: color(cfg?.senkouAColor, DEFAULT_ICH_GREEN),
333
+ senkouAWidth: cfg?.senkouAWidth ?? 1,
334
+ senkouAEnabled: cfg?.senkouAVisible ?? true,
335
+ senkouBColor: color(cfg?.senkouBColor, DEFAULT_ICH_ORANGE),
336
+ senkouBWidth: cfg?.senkouBWidth ?? 1,
337
+ senkouBEnabled: cfg?.senkouBVisible ?? true,
338
+ chikouColor: color(cfg?.chikouColor, DEFAULT_ICH_TEAL),
339
+ chikouWidth: cfg?.chikouWidth ?? 1,
340
+ chikouEnabled: cfg?.chikouVisible ?? true,
341
+ cloudEnabled: cfg?.cloudVisible ?? true,
342
+ bullishCloudColor: color(cfg?.bullishCloudColor, DEFAULT_ICH_GREEN),
343
+ bearishCloudColor: color(cfg?.bearishCloudColor, DEFAULT_ICH_RED),
344
+ cloudOpacity: cfg?.cloudOpacity ?? 0.15
345
+ };
346
+ }
347
+ var DEFAULT_FVG_GREEN = 4280723098;
348
+ var DEFAULT_FVG_RED = 4293874512;
349
+ var FVG_FILL_TYPES = ["close", "wick"];
350
+ var FVG_BORDER_STYLES = ["solid", "dotted", "dashed"];
351
+ function fvgToSpec(cfg) {
352
+ const color = (v, fallback) => (v != null ? parseColor(v) : null) ?? fallback;
353
+ const bullish = color(cfg?.bullishColor, DEFAULT_FVG_GREEN);
354
+ const bearish = color(cfg?.bearishColor, DEFAULT_FVG_RED);
355
+ return {
356
+ enabled: cfg?.enabled ?? false,
357
+ maxBarsBack: cfg?.maxBarsBack ?? 300,
358
+ waitForClose: cfg?.waitForClose ?? false,
359
+ fillType: Math.max(0, FVG_FILL_TYPES.indexOf(cfg?.fillType ?? "close")),
360
+ deleteAfterFill: cfg?.deleteAfterFill ?? true,
361
+ extendBoxes: cfg?.extendBoxes ?? false,
362
+ boxLength: cfg?.boxLength ?? 20,
363
+ bullishColor: bullish,
364
+ bearishColor: bearish,
365
+ opacity: cfg?.opacity ?? 0.15,
366
+ borderEnabled: cfg?.borderVisible ?? true,
367
+ borderStyle: Math.max(
368
+ 0,
369
+ FVG_BORDER_STYLES.indexOf(cfg?.borderStyle ?? "solid")
370
+ ),
371
+ borderWidth: cfg?.borderWidth ?? 1,
372
+ bullishBorderColor: color(cfg?.bullishBorderColor, bullish),
373
+ bearishBorderColor: color(cfg?.bearishBorderColor, bearish),
374
+ labelsEnabled: cfg?.showLabels ?? true,
375
+ label: cfg?.label ?? "FVG",
376
+ labelDistance: cfg?.labelDistance ?? 10,
377
+ // Alpha 0 is the core's "inherit the border color" sentinel.
378
+ labelColor: color(cfg?.labelColor, 0),
379
+ labelFontSize: cfg?.labelFontSize ?? 0,
380
+ showInverse: cfg?.showInverse ?? false,
381
+ inverseBullishColor: color(cfg?.inverseBullishColor, bullish),
382
+ inverseBearishColor: color(cfg?.inverseBearishColor, bearish),
383
+ inverseLabel: cfg?.inverseLabel ?? "iFVG"
384
+ };
385
+ }
304
386
  function macdToSpec(cfg) {
305
387
  const srcIdx = cfg?.source ? MA_SOURCES.indexOf(cfg.source) : 0;
306
388
  return {
@@ -326,6 +408,15 @@ function macdToSpec(cfg) {
326
408
  zeroVisible: cfg?.zeroLineVisible ?? true
327
409
  };
328
410
  }
411
+ function atrToSpec(cfg) {
412
+ return {
413
+ enabled: cfg?.enabled ?? false,
414
+ period: cfg?.period ?? 14,
415
+ smoothing: Math.max(0, ATR_SMOOTHINGS.indexOf(cfg?.smoothing ?? "rma")),
416
+ lineColor: inheritColor(cfg?.lineColor),
417
+ lineWidth: cfg?.lineWidth ?? -1
418
+ };
419
+ }
329
420
  function volumeToSpec(cfg) {
330
421
  return {
331
422
  enabled: cfg?.enabled ?? true,
@@ -396,23 +487,37 @@ function ensureInstalled() {
396
487
  }
397
488
  installed = true;
398
489
  }
399
- function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType, theme, rsi, macd, movingAverages, vwap, bollingerBands, volume, priceLines, footprints, transition) {
490
+ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType, theme, rsi, macd, atr, movingAverages, vwap, bollingerBands, ichimoku, fairValueGaps, volume, priceLines, footprints, transition) {
400
491
  const handleRef = (0, import_react.useRef)(null);
401
492
  const defaultWidthAppliedRef = (0, import_react.useRef)(false);
402
493
  const volumeCollapseRef = (0, import_react.useRef)(null);
403
494
  const prevDataRef = (0, import_react.useRef)(null);
404
495
  const intervalMorphRaf = (0, import_react.useRef)(null);
496
+ const streamRaf = (0, import_react.useRef)(null);
497
+ const streamWindowRef = (0, import_react.useRef)(null);
498
+ const streamMorphRef = (0, import_react.useRef)(false);
405
499
  const [picture, setPicture] = (0, import_react.useState)(null);
406
500
  if (!handleRef.current && size.width > 0 && size.height > 0) {
407
501
  ensureInstalled();
408
502
  handleRef.current = globalThis.VroomChartJSI.create();
409
503
  }
410
- const animRef = (0, import_react.useRef)({ ms: 300, easing: void 0, reduceMotion: false, interval: "transform" });
504
+ const animRef = (0, import_react.useRef)({
505
+ ms: 300,
506
+ easing: void 0,
507
+ reduceMotion: false,
508
+ interval: "transform",
509
+ stream: "none",
510
+ streamMs: 150
511
+ });
411
512
  animRef.current = {
412
513
  ms: Math.max(0, transition?.transitionMs ?? 300),
413
514
  easing: transition?.transitionEasing,
414
515
  reduceMotion: transition?.reduceMotion ?? false,
415
- interval: transition?.intervalTransition === "fade" ? "fade" : "transform"
516
+ interval: transition?.intervalTransition === "fade" ? "fade" : "transform",
517
+ stream: transition?.streamTransition === "transform" ? "transform" : "none",
518
+ // Shorter than transitionMs by default: ticks can land faster than a 300ms
519
+ // curve, and every one that does interrupts the last.
520
+ streamMs: Math.max(0, transition?.streamTransitionMs ?? 150)
416
521
  };
417
522
  const onFrameRef = (0, import_react.useRef)(transition?.onFrame);
418
523
  onFrameRef.current = transition?.onFrame;
@@ -436,12 +541,70 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
436
541
  };
437
542
  intervalMorphRaf.current = requestAnimationFrame(step);
438
543
  }, []);
544
+ const settleStream = (0, import_react.useCallback)((keepMorph = false) => {
545
+ if (streamRaf.current != null) {
546
+ cancelAnimationFrame(streamRaf.current);
547
+ streamRaf.current = null;
548
+ }
549
+ const h = handleRef.current;
550
+ const target = streamWindowRef.current;
551
+ streamWindowRef.current = null;
552
+ if (target) h?.setVisibleRange(target.startMs, target.endMs);
553
+ if (streamMorphRef.current && !keepMorph) {
554
+ streamMorphRef.current = false;
555
+ h?.setIntervalMorph(1);
556
+ }
557
+ }, []);
558
+ const startStreamAnim = (0, import_react.useCallback)(
559
+ (h, morphing, window) => {
560
+ const { streamMs, easing } = animRef.current;
561
+ let from = window ? h.getVisibleRange() : null;
562
+ let applied = null;
563
+ streamWindowRef.current = window;
564
+ streamMorphRef.current = morphing;
565
+ const start = performance.now();
566
+ const step = (now) => {
567
+ if (from && applied) {
568
+ const now_w = h.getVisibleRange();
569
+ if (now_w.startMs !== applied.startMs || now_w.endMs !== applied.endMs) {
570
+ from = null;
571
+ streamWindowRef.current = null;
572
+ }
573
+ }
574
+ const p = Math.min(1, (now - start) / streamMs);
575
+ const e = p < 1 ? ease(easing, p) : 1;
576
+ if (morphing) h.setIntervalMorph(e);
577
+ if (from && window) {
578
+ applied = {
579
+ startMs: Math.round(from.startMs + (window.startMs - from.startMs) * e),
580
+ endMs: Math.round(from.endMs + (window.endMs - from.endMs) * e)
581
+ };
582
+ h.setVisibleRange(applied.startMs, applied.endMs);
583
+ }
584
+ const pic = h.render();
585
+ if (pic) onFrameRef.current?.(pic);
586
+ if (p < 1) {
587
+ streamRaf.current = requestAnimationFrame(step);
588
+ } else {
589
+ streamRaf.current = null;
590
+ streamWindowRef.current = null;
591
+ streamMorphRef.current = false;
592
+ }
593
+ };
594
+ streamRaf.current = requestAnimationFrame(step);
595
+ },
596
+ []
597
+ );
439
598
  (0, import_react.useEffect)(() => {
440
599
  return () => {
441
600
  if (intervalMorphRaf.current != null) {
442
601
  cancelAnimationFrame(intervalMorphRaf.current);
443
602
  intervalMorphRaf.current = null;
444
603
  }
604
+ if (streamRaf.current != null) {
605
+ cancelAnimationFrame(streamRaf.current);
606
+ streamRaf.current = null;
607
+ }
445
608
  };
446
609
  }, []);
447
610
  const explicit = visibleRange != null;
@@ -450,9 +613,12 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
450
613
  const themeKey = theme ? JSON.stringify(theme) : "";
451
614
  const rsiKey = rsi ? JSON.stringify(rsi) : "";
452
615
  const macdKey = macd ? JSON.stringify(macd) : "";
616
+ const atrKey = atr ? JSON.stringify(atr) : "";
453
617
  const maKey = movingAverages ? JSON.stringify(movingAverages) : "";
454
618
  const vwapKey = vwap ? JSON.stringify(vwap) : "";
455
619
  const bollingerKey = bollingerBands ? JSON.stringify(bollingerBands) : "";
620
+ const ichimokuKey = ichimoku ? JSON.stringify(ichimoku) : "";
621
+ const fvgKey = fairValueGaps ? JSON.stringify(fairValueGaps) : "";
456
622
  const volumeKey = volume ? JSON.stringify(volume) : "";
457
623
  const priceLinesKey = priceLines ? JSON.stringify(priceLines) : "";
458
624
  const footprintsKey = footprints ? JSON.stringify(footprints) : "";
@@ -460,6 +626,7 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
460
626
  const h = handleRef.current;
461
627
  if (!h) return;
462
628
  h.setSize(size.width, size.height, size.pxRatio ?? 1);
629
+ h.setIchimoku(ichimokuToSpec(ichimoku));
463
630
  if (!defaultWidthAppliedRef.current && !explicit && defaultCandleWidth != null && defaultCandleWidth > 0) {
464
631
  h.setDefaultCandleWidth(defaultCandleWidth);
465
632
  defaultWidthAppliedRef.current = true;
@@ -472,6 +639,36 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
472
639
  const transitionKind = freshHandle ? "initial" : explicit ? "stream" : classifyTransition(prev.candles, candles, seriesKey !== prev.seriesKey);
473
640
  let tfArgs = null;
474
641
  let prevEnvelope = null;
642
+ let stream = null;
643
+ if (transitionKind === "stream" && prev != null && !explicit) {
644
+ const { stream: mode, streamMs, reduceMotion } = animRef.current;
645
+ const stepMs = inferStepMs(candles);
646
+ if (mode === "transform" && streamMs > 0 && stepMs != null && !reduceMotion && onFrameRef.current != null) {
647
+ const lastMs = candles[candles.length - 1].timeMs;
648
+ const prevLastMs = prev.candles[prev.candles.length - 1].timeMs;
649
+ if (classifyStream(prev.candles, candles) === "append") {
650
+ const w = h.getVisibleRange();
651
+ const prevStepMs = inferStepMs(prev.candles) ?? stepMs;
652
+ if (isPinnedToLatest(w, prevLastMs, prevStepMs)) {
653
+ const by = lastMs - prevLastMs;
654
+ stream = {
655
+ morph: false,
656
+ window: { startMs: w.startMs + by, endMs: w.endMs + by }
657
+ };
658
+ }
659
+ } else {
660
+ stream = { morph: true, window: null };
661
+ }
662
+ }
663
+ if (stream?.morph) {
664
+ settleStream(true);
665
+ h.beginStreamMorph();
666
+ } else {
667
+ settleStream();
668
+ }
669
+ } else if (transitionKind === "stream") {
670
+ settleStream();
671
+ }
475
672
  if (transitionKind === "timeframe" && prev != null) {
476
673
  const oldWindow = h.getVisibleRange();
477
674
  const oldStepMs = inferStepMs(prev.candles);
@@ -507,6 +704,8 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
507
704
  if (prevEnvelope) h.preservePriceEnvelope(prevEnvelope.low, prevEnvelope.high);
508
705
  else h.resetPriceScale();
509
706
  if (morphing) startIntervalMorph(h);
707
+ } else if (stream) {
708
+ startStreamAnim(h, stream.morph, stream.window);
510
709
  } else if (transitionKind === "reset") {
511
710
  h.resetView();
512
711
  }
@@ -524,9 +723,11 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
524
723
  }
525
724
  h.setRSI(rsiToSpec(rsi));
526
725
  h.setMACD(macdToSpec(macd));
726
+ h.setATR(atrToSpec(atr));
527
727
  h.setOverlays((movingAverages ?? []).map(overlayToNumeric));
528
728
  h.setVWAP(vwapToSpec(vwap));
529
729
  h.setBollinger(bollingerToSpec(bollingerBands));
730
+ h.setFairValueGaps(fvgToSpec(fairValueGaps));
530
731
  h.setVolume(volumeToSpec(volume));
531
732
  const collapse = volumeCollapseRef.current;
532
733
  if (collapse) h.setVolumeCollapse(collapse.t, collapse.easing);
@@ -537,7 +738,7 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
537
738
  footprints?.prints.length ? footprintsToSpec(footprints) : EMPTY_FOOTPRINTS
538
739
  );
539
740
  if (!morphing) setPicture(h.render());
540
- }, [candles, seriesKey, size.width, size.height, size.pxRatio, explicit, startMs, endMs, defaultCandleWidth, themeKey, rsiKey, macdKey, maKey, vwapKey, bollingerKey, volumeKey, priceLinesKey, footprintsKey, startIntervalMorph, endIntervalMorph]);
741
+ }, [candles, seriesKey, size.width, size.height, size.pxRatio, explicit, startMs, endMs, defaultCandleWidth, themeKey, rsiKey, macdKey, atrKey, maKey, vwapKey, bollingerKey, ichimokuKey, fvgKey, volumeKey, priceLinesKey, footprintsKey, startIntervalMorph, endIntervalMorph, startStreamAnim, settleStream]);
541
742
  return { handle: handleRef.current, picture, volumeCollapseRef };
542
743
  }
543
744
 
@@ -559,12 +760,17 @@ function VroomChart(props) {
559
760
  transitionMs,
560
761
  transitionEasing,
561
762
  intervalTransition,
763
+ streamTransition,
764
+ streamTransitionMs,
562
765
  theme,
563
766
  rsi,
564
767
  macd,
768
+ atr,
565
769
  movingAverages,
566
770
  vwap,
567
771
  bollingerBands,
772
+ ichimoku,
773
+ fairValueGaps,
568
774
  volume,
569
775
  crosshairOffset = 40,
570
776
  onCrosshair,
@@ -643,13 +849,25 @@ function VroomChart(props) {
643
849
  theme,
644
850
  rsi,
645
851
  macd,
852
+ atr,
646
853
  movingAverages,
647
854
  vwap,
648
855
  bollingerBands,
856
+ ichimoku,
857
+ fairValueGaps,
649
858
  volume,
650
859
  priceLinesProp,
651
860
  footprintsProp,
652
- { seriesKey, transitionMs, transitionEasing, intervalTransition, reduceMotion, onFrame }
861
+ {
862
+ seriesKey,
863
+ transitionMs,
864
+ transitionEasing,
865
+ intervalTransition,
866
+ streamTransition,
867
+ streamTransitionMs,
868
+ reduceMotion,
869
+ onFrame
870
+ }
653
871
  );
654
872
  const crosshairActive = (0, import_react2.useRef)(false);
655
873
  const footprintActive = (0, import_react2.useRef)(false);