react-native-vroom-chart 0.6.0 → 0.7.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 (46) hide show
  1. package/cpp/VroomChartHostObject.cpp +150 -32
  2. package/cpp/_core_include/vroom/vroom_chart.h +180 -22
  3. package/cpp/_core_src/bollinger.h +1 -1
  4. package/cpp/_core_src/candles.cpp +138 -41
  5. package/cpp/_core_src/candles.h +11 -1
  6. package/cpp/_core_src/chart.cpp +91 -40
  7. package/cpp/_core_src/chart.h +55 -20
  8. package/cpp/_core_src/chart_facade.cpp +206 -66
  9. package/cpp/_core_src/gradient.cpp +46 -0
  10. package/cpp/_core_src/gradient.h +31 -0
  11. package/cpp/_core_src/labels.cpp +49 -16
  12. package/cpp/_core_src/labels.h +33 -4
  13. package/cpp/_core_src/liquidity.cpp +3 -37
  14. package/cpp/_core_src/ma_overlay.cpp +174 -12
  15. package/cpp/_core_src/ma_overlay.h +55 -3
  16. package/cpp/_core_src/macd.cpp +13 -42
  17. package/cpp/_core_src/macd.h +11 -8
  18. package/cpp/_core_src/macd_pane.cpp +57 -27
  19. package/cpp/_core_src/price_line_layout.h +1 -1
  20. package/cpp/_core_src/rsi.cpp +4 -18
  21. package/cpp/_core_src/rsi.h +6 -6
  22. package/cpp/_core_src/rsi_pane.cpp +34 -19
  23. package/cpp/_core_src/series_ma.cpp +64 -0
  24. package/cpp/_core_src/series_ma.h +30 -0
  25. package/cpp/_core_src/style_inherit.h +35 -0
  26. package/cpp/_core_src/theme.cpp +2 -1
  27. package/cpp/_core_src/viewport.cpp +36 -12
  28. package/cpp/_core_src/viewport.h +50 -0
  29. package/cpp/_core_src/volume.cpp +33 -8
  30. package/cpp/_core_src/volume.h +12 -1
  31. package/cpp/_core_src/volume_anim.cpp +32 -0
  32. package/cpp/_core_src/volume_anim.h +41 -0
  33. package/lib/index.d.mts +161 -28
  34. package/lib/index.d.ts +161 -28
  35. package/lib/index.js +156 -31
  36. package/lib/index.js.map +1 -1
  37. package/lib/index.mjs +156 -31
  38. package/lib/index.mjs.map +1 -1
  39. package/package.json +3 -3
  40. package/src/VroomChart.tsx +69 -3
  41. package/src/easing.ts +40 -0
  42. package/src/index.ts +3 -0
  43. package/src/jsi.d.ts +76 -20
  44. package/src/theme.ts +1 -0
  45. package/src/types.ts +3 -0
  46. package/src/useChartCore.ts +103 -27
@@ -1,4 +1,5 @@
1
1
  import { useEffect, useRef, useState } from 'react';
2
+ import type { MutableRefObject } from 'react';
2
3
  import type { SkPicture } from '@shopify/react-native-skia';
3
4
 
4
5
  import NativeVroomChart from './NativeVroomChart';
@@ -15,6 +16,7 @@ import type {
15
16
  PriceLinesStyle,
16
17
  RSIConfig,
17
18
  VisibleRange,
19
+ VolumeConfig,
18
20
  VroomTheme,
19
21
  VWAPConfig,
20
22
  } from './types';
@@ -30,17 +32,49 @@ const MA_SOURCES = [
30
32
  'ohlc4',
31
33
  ] as const;
32
34
 
35
+ // An unset style color marshals as the core's transparent inherit sentinel.
36
+ const inheritColor = (v: string | number | undefined): number =>
37
+ (v != null ? parseColor(v) : null) ?? 0;
38
+
33
39
  function overlayToNumeric(o: MovingAverageOverlay) {
34
40
  const srcIdx = o.source ? MA_SOURCES.indexOf(o.source) : 0;
35
41
  return {
36
- kind: o.kind === 'ema' ? 1 : 0,
37
- period: o.length,
42
+ kind: o.maType === 'ema' ? 1 : 0,
43
+ period: o.period,
38
44
  source: srcIdx < 0 ? 0 : srcIdx,
39
45
  color: (o.color != null ? parseColor(o.color) : null) ?? 0xff2962ff,
40
46
  width: o.width ?? 1.5,
41
47
  };
42
48
  }
43
49
 
50
+ function rsiToSpec(cfg: RSIConfig | undefined) {
51
+ return {
52
+ enabled: cfg?.enabled ?? false,
53
+ period: cfg?.period ?? 14,
54
+ upperBand: cfg?.upperBand ?? 70,
55
+ lowerBand: cfg?.lowerBand ?? 30,
56
+ maPeriod: cfg?.maPeriod ?? 14,
57
+ maKind: cfg?.maType === 'ema' ? 1 : 0,
58
+ maVisible: cfg?.maVisible ?? true,
59
+ lineColor: inheritColor(cfg?.lineColor),
60
+ lineWidth: cfg?.lineWidth ?? -1,
61
+ lineVisible: cfg?.lineVisible ?? true,
62
+ maColor: inheritColor(cfg?.maColor),
63
+ maWidth: cfg?.maWidth ?? -1,
64
+ bandColor: inheritColor(cfg?.bandColor),
65
+ bandsVisible: cfg?.bandsVisible ?? true,
66
+ };
67
+ }
68
+
69
+ function vwapToSpec(cfg: VWAPConfig | undefined) {
70
+ return {
71
+ enabled: cfg?.enabled ?? false,
72
+ resetOffsetMin: cfg?.resetMinutes ?? 0,
73
+ color: inheritColor(cfg?.color),
74
+ width: cfg?.width ?? -1,
75
+ };
76
+ }
77
+
44
78
  // Bollinger defaults: blue bands / orange basis, matching the repo palette.
45
79
  const DEFAULT_BB_BAND_COLOR = 0xff2962ff;
46
80
  const DEFAULT_BB_BASIS_COLOR = 0xffff6d00;
@@ -52,7 +86,7 @@ function bollingerToSpec(cfg: BollingerBandsConfig | undefined) {
52
86
  period: cfg?.period ?? 20,
53
87
  mult: cfg?.stdDev ?? 2,
54
88
  source: srcIdx < 0 ? 0 : srcIdx,
55
- basisKind: cfg?.basis === 'ema' ? 1 : 0,
89
+ basisKind: cfg?.maType === 'ema' ? 1 : 0,
56
90
  upperColor:
57
91
  (cfg?.upperColor != null ? parseColor(cfg.upperColor) : null) ??
58
92
  DEFAULT_BB_BAND_COLOR,
@@ -65,11 +99,51 @@ function bollingerToSpec(cfg: BollingerBandsConfig | undefined) {
65
99
  (cfg?.lowerColor != null ? parseColor(cfg.lowerColor) : null) ??
66
100
  DEFAULT_BB_BAND_COLOR,
67
101
  lowerWidth: cfg?.lowerWidth ?? 1,
68
- fillEnabled: cfg?.fill ?? true,
102
+ fillEnabled: cfg?.fillVisible ?? true,
69
103
  fillOpacity: cfg?.fillOpacity ?? 0.1,
70
104
  };
71
105
  }
72
106
 
107
+ function macdToSpec(cfg: MACDConfig | undefined) {
108
+ const srcIdx = cfg?.source ? MA_SOURCES.indexOf(cfg.source) : 0;
109
+ return {
110
+ enabled: cfg?.enabled ?? false,
111
+ fast: cfg?.fast ?? 12,
112
+ slow: cfg?.slow ?? 26,
113
+ signal: cfg?.signal ?? 9,
114
+ source: srcIdx < 0 ? 0 : srcIdx,
115
+ maKind: cfg?.maType === 'sma' ? 0 : 1,
116
+ signalMaKind: cfg?.signalMaType === 'sma' ? 0 : 1,
117
+ lineColor: inheritColor(cfg?.lineColor),
118
+ lineWidth: cfg?.lineWidth ?? -1,
119
+ lineVisible: cfg?.lineVisible ?? true,
120
+ signalColor: inheritColor(cfg?.signalColor),
121
+ signalWidth: cfg?.signalWidth ?? -1,
122
+ signalVisible: cfg?.signalVisible ?? true,
123
+ histVisible: cfg?.histogramVisible ?? true,
124
+ histUpColor: inheritColor(cfg?.histogramUpColor),
125
+ histUpFadingColor: inheritColor(cfg?.histogramUpFadingColor),
126
+ histDownColor: inheritColor(cfg?.histogramDownColor),
127
+ histDownFadingColor: inheritColor(cfg?.histogramDownFadingColor),
128
+ zeroColor: inheritColor(cfg?.zeroLineColor),
129
+ zeroVisible: cfg?.zeroLineVisible ?? true,
130
+ };
131
+ }
132
+
133
+ // Unset style fields go down as the core's inherit sentinels (negative float,
134
+ // transparent color) rather than as literal defaults, so the theme keys stay in
135
+ // charge of anything the consumer didn't set.
136
+ function volumeToSpec(cfg: VolumeConfig | undefined) {
137
+ return {
138
+ enabled: cfg?.enabled ?? true,
139
+ heightFrac: cfg?.height ?? -1,
140
+ opacity: cfg?.opacity ?? -1,
141
+ radiusPx: cfg?.radius ?? -1,
142
+ upColor: (cfg?.upColor != null ? parseColor(cfg.upColor) : null) ?? 0,
143
+ downColor: (cfg?.downColor != null ? parseColor(cfg.downColor) : null) ?? 0,
144
+ };
145
+ }
146
+
73
147
  // Price-line defaults: a soft red dotted rule with a dark translucent label,
74
148
  // close in weight to the current-price indicator it sits beside.
75
149
  const DEFAULT_PRICE_LINE_COLOR = 0xffef5350;
@@ -136,10 +210,19 @@ function ensureInstalled(): void {
136
210
  installed = true;
137
211
  }
138
212
 
213
+ /** Progress + curve of the staggered volume-bar collapse. See setVolumeCollapse. */
214
+ export type VolumeCollapse = { t: number; easing: number };
215
+
139
216
  export type ChartCoreState = {
140
217
  handle: ChartHandle | null;
141
218
  /** Picture freshly rendered after the latest data/size/range push. */
142
219
  picture: SkPicture | null;
220
+ /**
221
+ * The last volume collapse handed to the core, or null before the first push.
222
+ * VroomChart's animation loop owns this — it lives here only so the data effect
223
+ * can restore it, since setVolume snaps the scalar (see below).
224
+ */
225
+ volumeCollapseRef: MutableRefObject<VolumeCollapse | null>;
143
226
  };
144
227
 
145
228
  // Owns a ChartHandle and produces an "initial" picture whenever data, size,
@@ -158,6 +241,7 @@ export function useChartCore(
158
241
  movingAverages?: MovingAverageOverlay[],
159
242
  vwap?: VWAPConfig,
160
243
  bollingerBands?: BollingerBandsConfig,
244
+ volume?: VolumeConfig,
161
245
  priceLines?: PriceLinesProp,
162
246
  ): ChartCoreState {
163
247
  const handleRef = useRef<ChartHandle | null>(null);
@@ -165,6 +249,7 @@ export function useChartCore(
165
249
  // every data change, and the core setter re-frames when candles are present,
166
250
  // so re-pushing would snap the view away from the user's pan/zoom.
167
251
  const defaultWidthAppliedRef = useRef(false);
252
+ const volumeCollapseRef = useRef<VolumeCollapse | null>(null);
168
253
  const [picture, setPicture] = useState<SkPicture | null>(null);
169
254
 
170
255
  if (!handleRef.current && size.width > 0 && size.height > 0) {
@@ -188,6 +273,7 @@ export function useChartCore(
188
273
  const maKey = movingAverages ? JSON.stringify(movingAverages) : '';
189
274
  const vwapKey = vwap ? JSON.stringify(vwap) : '';
190
275
  const bollingerKey = bollingerBands ? JSON.stringify(bollingerBands) : '';
276
+ const volumeKey = volume ? JSON.stringify(volume) : '';
191
277
  const priceLinesKey = priceLines ? JSON.stringify(priceLines) : '';
192
278
 
193
279
  useEffect(() => {
@@ -217,38 +303,28 @@ export function useChartCore(
217
303
  if (theme) {
218
304
  applyTheme(h, theme);
219
305
  }
220
- h.setRSI(
221
- rsi?.enabled ?? false,
222
- rsi?.period ?? 14,
223
- rsi?.upperBand ?? 70,
224
- rsi?.lowerBand ?? 30,
225
- rsi?.maEnabled ?? true,
226
- rsi?.maPeriod ?? 14,
227
- );
228
- h.setMACD(
229
- macd?.enabled ?? false,
230
- macd?.fast ?? 12,
231
- macd?.slow ?? 26,
232
- macd?.signal ?? 9,
233
- );
306
+ h.setRSI(rsiToSpec(rsi));
307
+ h.setMACD(macdToSpec(macd));
234
308
  h.setOverlays((movingAverages ?? []).map(overlayToNumeric));
235
- h.setVWAP(
236
- vwap?.enabled ?? false,
237
- vwap?.resetMinutes ?? 0,
238
- (vwap?.color != null ? parseColor(vwap.color) : null) ?? 0xff00bcd4,
239
- vwap?.width ?? 1.5,
240
- );
309
+ h.setVWAP(vwapToSpec(vwap));
241
310
  h.setBollinger(bollingerToSpec(bollingerBands));
311
+ h.setVolume(volumeToSpec(volume));
312
+ // setVolume snaps the collapse scalar to its `enabled`, which would cut a
313
+ // toggle animation short whenever this effect re-runs (a streaming candle, a
314
+ // resize). Hand the in-flight value back; VroomChart's loop drives it from
315
+ // there.
316
+ const collapse = volumeCollapseRef.current;
317
+ if (collapse) h.setVolumeCollapse(collapse.t, collapse.easing);
242
318
  h.setPriceLines(
243
319
  priceLines?.lines.length ? priceLinesToSpec(priceLines) : EMPTY_PRICE_LINES,
244
320
  );
245
321
  // TODO(rn-parity): mirror the web `liquidity` overlay here (setLiquidity +
246
322
  // the VroomBand structs in the JSI handle) — web-only for now.
247
323
  setPicture(h.render());
248
- // theme/rsi/macd/movingAverages/vwap/bollingerBands/priceLines are
324
+ // theme/rsi/macd/movingAverages/vwap/bollingerBands/volume/priceLines are
249
325
  // represented by their *Key deps.
250
326
  // eslint-disable-next-line react-hooks/exhaustive-deps
251
- }, [candles, size.width, size.height, size.pxRatio, explicit, startMs, endMs, defaultCandleWidth, themeKey, rsiKey, macdKey, maKey, vwapKey, bollingerKey, priceLinesKey]);
327
+ }, [candles, size.width, size.height, size.pxRatio, explicit, startMs, endMs, defaultCandleWidth, themeKey, rsiKey, macdKey, maKey, vwapKey, bollingerKey, volumeKey, priceLinesKey]);
252
328
 
253
- return { handle: handleRef.current, picture };
329
+ return { handle: handleRef.current, picture, volumeCollapseRef };
254
330
  }