react-native-vroom-chart 0.15.0 → 0.17.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 (53) hide show
  1. package/cpp/VroomChartHostObject.cpp +252 -1
  2. package/cpp/_core_include/vroom/vroom_chart.h +178 -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/candles.cpp +15 -14
  8. package/cpp/_core_src/chart.cpp +758 -200
  9. package/cpp/_core_src/chart.h +221 -3
  10. package/cpp/_core_src/chart_facade.cpp +236 -23
  11. package/cpp/_core_src/color_lerp.h +28 -0
  12. package/cpp/_core_src/fair_value_gaps.cpp +76 -0
  13. package/cpp/_core_src/fair_value_gaps.h +54 -0
  14. package/cpp/_core_src/fvg_overlay.cpp +262 -0
  15. package/cpp/_core_src/fvg_overlay.h +43 -0
  16. package/cpp/_core_src/ichimoku.cpp +65 -0
  17. package/cpp/_core_src/ichimoku.h +45 -0
  18. package/cpp/_core_src/labels.cpp +3 -0
  19. package/cpp/_core_src/line_morph.h +159 -0
  20. package/cpp/_core_src/loading_line.cpp +183 -0
  21. package/cpp/_core_src/loading_line.h +38 -0
  22. package/cpp/_core_src/loading_wave.h +140 -0
  23. package/cpp/_core_src/ma_overlay.cpp +273 -45
  24. package/cpp/_core_src/ma_overlay.h +64 -2
  25. package/cpp/_core_src/macd.cpp +24 -1
  26. package/cpp/_core_src/macd.h +16 -0
  27. package/cpp/_core_src/macd_pane.cpp +71 -62
  28. package/cpp/_core_src/macd_pane.h +13 -1
  29. package/cpp/_core_src/pane_series.h +169 -0
  30. package/cpp/_core_src/price_indicator.cpp +21 -7
  31. package/cpp/_core_src/price_indicator.h +11 -1
  32. package/cpp/_core_src/price_indicator_anim.h +81 -0
  33. package/cpp/_core_src/rsi.cpp +4 -0
  34. package/cpp/_core_src/rsi.h +7 -0
  35. package/cpp/_core_src/rsi_pane.cpp +85 -29
  36. package/cpp/_core_src/rsi_pane.h +11 -1
  37. package/cpp/_core_src/theme.cpp +5 -0
  38. package/cpp/_core_src/tip_geometry.h +55 -0
  39. package/cpp/_core_src/viewport.h +68 -0
  40. package/lib/index.d.mts +281 -3
  41. package/lib/index.d.ts +281 -3
  42. package/lib/index.js +292 -14
  43. package/lib/index.js.map +1 -1
  44. package/lib/index.mjs +292 -14
  45. package/lib/index.mjs.map +1 -1
  46. package/package.json +1 -1
  47. package/src/VroomChart.tsx +33 -3
  48. package/src/dataTransitions.ts +47 -0
  49. package/src/index.ts +5 -0
  50. package/src/jsi.d.ts +139 -1
  51. package/src/theme.ts +1 -0
  52. package/src/types.ts +5 -0
  53. package/src/useChartCore.ts +402 -8
@@ -50,14 +50,16 @@ function isSkImage(frame: ChartFrame): frame is SkImage {
50
50
  * Skia-rendered candlestick chart. Pass OHLCV `candles` and size it via `style`
51
51
  * (it fills its parent by default). Pan to scroll, pinch to zoom, drag the
52
52
  * price/time axes to rescale, and long-press for the crosshair. Optional
53
- * indicators (`rsi`, `macd`, `movingAverages`, `vwap`), colors (`theme`), and
54
- * events (`onCrosshair`, `onViewportChange`) are configured through props.
53
+ * indicators (`rsi`, `macd`, `movingAverages`, `vwap`, `bollingerBands`,
54
+ * `ichimoku`, and more), colors (`theme`), and events (`onCrosshair`,
55
+ * `onViewportChange`) are configured through props.
55
56
  *
56
57
  * @see {@link VroomChartProps} for the full prop reference.
57
58
  */
58
59
  export function VroomChart(props: VroomChartProps) {
59
60
  const {
60
61
  candles,
62
+ loading,
61
63
  seriesKey,
62
64
  width: widthProp,
63
65
  height: heightProp,
@@ -68,12 +70,17 @@ export function VroomChart(props: VroomChartProps) {
68
70
  transitionMs,
69
71
  transitionEasing,
70
72
  intervalTransition,
73
+ streamTransition,
74
+ streamTransitionMs,
71
75
  theme,
72
76
  rsi,
73
77
  macd,
78
+ atr,
74
79
  movingAverages,
75
80
  vwap,
76
81
  bollingerBands,
82
+ ichimoku,
83
+ fairValueGaps,
77
84
  volume,
78
85
  crosshairOffset = 40,
79
86
  onCrosshair,
@@ -179,15 +186,34 @@ export function VroomChart(props: VroomChartProps) {
179
186
  theme,
180
187
  rsi,
181
188
  macd,
189
+ atr,
182
190
  movingAverages,
183
191
  vwap,
184
192
  bollingerBands,
193
+ ichimoku,
194
+ fairValueGaps,
185
195
  volume,
186
196
  priceLinesProp,
187
197
  footprintsProp,
188
- { seriesKey, transitionMs, transitionEasing, intervalTransition, reduceMotion, onFrame },
198
+ {
199
+ seriesKey,
200
+ transitionMs,
201
+ transitionEasing,
202
+ intervalTransition,
203
+ streamTransition,
204
+ streamTransitionMs,
205
+ reduceMotion,
206
+ onFrame,
207
+ },
208
+ loading,
189
209
  );
190
210
 
211
+ // Same condition useChartCore draws the line on: a refresh that still has
212
+ // data keeps the chart interactive. Every gesture below is gated on this —
213
+ // there's nothing to pan, zoom or inspect while the line is up, and a
214
+ // crosshair reading prices off a placeholder walk would be actively wrong.
215
+ const showLoadingLine = loading === true && candles.length === 0;
216
+
191
217
  // When the crosshair is showing, pan moves it (instead of scrolling) and
192
218
  // pinch is disabled. A ref (not state) so gesture callbacks read it
193
219
  // synchronously without re-subscribing. Tap dismisses it.
@@ -582,6 +608,7 @@ export function VroomChart(props: VroomChartProps) {
582
608
  };
583
609
 
584
610
  const pan = Gesture.Pan()
611
+ .enabled(!showLoadingLine)
585
612
  .runOnJS(true)
586
613
  .maxPointers(1) // don't fight Pinch's two-finger gesture
587
614
  .onStart((e) => {
@@ -727,6 +754,7 @@ export function VroomChart(props: VroomChartProps) {
727
754
  enableY: false,
728
755
  });
729
756
  const pinch = Gesture.Pinch()
757
+ .enabled(!showLoadingLine)
730
758
  .runOnJS(true)
731
759
  .onTouchesDown((e) => {
732
760
  if (e.numberOfTouches < 2) return;
@@ -777,6 +805,7 @@ export function VroomChart(props: VroomChartProps) {
777
805
  // activates `pan` (it needs movement first), so the chart won't scroll under
778
806
  // the hold. The dot/horizontal line are lifted above the fingertip.
779
807
  const longPress = Gesture.LongPress()
808
+ .enabled(!showLoadingLine)
780
809
  .runOnJS(true)
781
810
  .onStart((e) => {
782
811
  if (!handle) return;
@@ -808,6 +837,7 @@ export function VroomChart(props: VroomChartProps) {
808
837
  // badge, and otherwise dismisses the crosshair while it's up. Any other tap is a
809
838
  // no-op, so it never interferes with normal pan/pinch.
810
839
  const tap = Gesture.Tap()
840
+ .enabled(!showLoadingLine)
811
841
  .runOnJS(true)
812
842
  .onStart((e) => {
813
843
  if (!handle) return;
@@ -126,6 +126,53 @@ export function classifyTransition(
126
126
  return closeRatio <= MAX_SAME_ASSET_CLOSE_RATIO && endsTogether ? 'timeframe' : 'reset';
127
127
  }
128
128
 
129
+ /**
130
+ * What a `'stream'` update did to the series: `'tick'` revised the bar already
131
+ * on screen, `'append'` brought at least one new one.
132
+ *
133
+ * The two animate by different means. A tick keeps the bar count, so the morph
134
+ * capture's slots still pair one-to-one and the last bar can reshape in place.
135
+ * An append can't use that capture at all — slots pair from the right edge, so
136
+ * a new bar shifts every candle onto its neighbour's geometry — and instead
137
+ * advances the visible window, which translates the series left and lets the
138
+ * new bar in at the right edge.
139
+ */
140
+ export type StreamKind = 'tick' | 'append';
141
+
142
+ /**
143
+ * Which of the two a `'stream'` transition is. Read from the newest timestamp
144
+ * rather than a length comparison, so a rolling buffer that drops a bar from
145
+ * the front as it adds one to the back still reads as an append.
146
+ *
147
+ * An update that both appends and revises the bar that just closed counts as an
148
+ * append: the translation is the dominant motion, and the revision is a final
149
+ * print that has nowhere to slot-pair to.
150
+ */
151
+ export function classifyStream(prev: Candle[], next: Candle[]): StreamKind {
152
+ if (prev.length === 0 || next.length === 0) return 'tick';
153
+ return next[next.length - 1].timeMs > prev[prev.length - 1].timeMs
154
+ ? 'append'
155
+ : 'tick';
156
+ }
157
+
158
+ /**
159
+ * Whether the view is still following the newest bar, which is what decides if
160
+ * an appended bar should pull the window along with it.
161
+ *
162
+ * True when the right edge sits at or past the newest bar's slot *end* — where
163
+ * the default framing leaves it, plus whatever gap it reserved. Someone who has
164
+ * panned back into history falls below that and is left where they are: nothing
165
+ * is more disorienting than the chart walking out from under you while you read
166
+ * it.
167
+ */
168
+ export function isPinnedToLatest(
169
+ window: VisibleRange,
170
+ lastMs: number,
171
+ stepMs: number,
172
+ ): boolean {
173
+ return window.endMs >= lastMs + stepMs;
174
+ }
175
+
129
176
  /**
130
177
  * The visible window to apply after a timeframe switch so each candle keeps
131
178
  * the exact pixel width it had before: the visible slot count is preserved and
package/src/index.ts CHANGED
@@ -14,15 +14,20 @@ export type {
14
14
  VisibleRange,
15
15
  RSIConfig,
16
16
  MACDConfig,
17
+ ATRConfig,
18
+ ATRSmoothing,
17
19
  MASource,
18
20
  MAKind,
19
21
  MovingAverageOverlay,
20
22
  VWAPConfig,
21
23
  BollingerBandsConfig,
24
+ IchimokuConfig,
25
+ FairValueGapsConfig,
22
26
  VolumeConfig,
23
27
  ChartType,
24
28
  TransitionEasing,
25
29
  IntervalTransition,
30
+ StreamTransition,
26
31
  PriceLine,
27
32
  PriceLinesStyle,
28
33
  Footprint,
package/src/jsi.d.ts CHANGED
@@ -73,12 +73,67 @@ export interface ChartHandle {
73
73
  */
74
74
  beginIntervalMorph(mode?: IntervalTransition): void;
75
75
  /**
76
- * Advance the interval morph started by beginIntervalMorph. `t` (clamped to
76
+ * Capture the visible geometry so the next setCandles can ease a live tick
77
+ * into place. Always a transform, and unlike beginIntervalMorph it leaves the
78
+ * axes alone — the interval hasn't changed, so its ticks must not fade.
79
+ * Restarting one still in flight continues from the shape on screen, so ticks
80
+ * arriving faster than the animation lands stay smooth. Call before
81
+ * setCandles, then drive setIntervalMorph from 0 to 1.
82
+ *
83
+ * Not for an update that appends a bar: slots pair from the right edge, so a
84
+ * new bar would shift every candle onto its neighbour's geometry. Advance the
85
+ * visible range instead and let the series translate.
86
+ */
87
+ beginStreamMorph(): void;
88
+ /**
89
+ * Advance the morph started by either begin method above. `t` (clamped to
77
90
  * 0..1) is the eased progress: 0 renders the captured geometry pixel-
78
91
  * identically to the pre-swap frame, 1 renders the new candles and releases
79
92
  * the capture. Driven per-frame by the host animation loop.
80
93
  */
81
94
  setIntervalMorph(t: number): void;
95
+ /**
96
+ * Shows or hides the loading line: a single stroke drawn across the plot in a
97
+ * slow travelling sine, for a chart that is laid out but has no data yet.
98
+ * Suppresses the axis text, price badge, crosshair and indicator panes while
99
+ * it's up.
100
+ *
101
+ * `on` must mean "loading *and* holding no data" — the core takes it at face
102
+ * value rather than checking its candle buffer, since a host mid-asset-switch
103
+ * can still be holding the previous asset's bars. Pass `animate: false` for
104
+ * reduced motion: the line draws still and the chart may go idle.
105
+ *
106
+ * Passing `false` abandons any hand-off in flight, which is what a failed or
107
+ * superseded fetch wants. To hand off to data instead, use the two stages
108
+ * below.
109
+ */
110
+ setLoading(on: boolean, animate?: boolean): void;
111
+ /**
112
+ * Hand-off stage one: reshapes the loading line into the series. Freezes the
113
+ * sine where it is and aims each vertex at the vertical centre of the candle
114
+ * that will occupy its column, so the line resolves into the silhouette of
115
+ * the data.
116
+ *
117
+ * Call *after* setCandles — it reads them to know where to aim — then drive
118
+ * {@link setLoadingMorph} from 0 to 1. The axes and the candles stay
119
+ * suppressed throughout; stage two brings them in.
120
+ */
121
+ beginLoadingMorph(): void;
122
+ /**
123
+ * Advances stage one. `t` (clamped to 0..1) is the eased progress: 0 renders
124
+ * the frozen sine, 1 the polyline through the candle centres.
125
+ */
126
+ setLoadingMorph(t: number): void;
127
+ /**
128
+ * Hand-off stage two: the candles take over. Captures each one collapsed onto
129
+ * its own vertical centre at zero alpha and leaves the loading state, so the
130
+ * bars grow outward from the line and their color fades up as it fades out.
131
+ *
132
+ * Call once stage one has reached 1, then drive {@link setIntervalMorph} from
133
+ * 0 to 1 — the line's fade-out rides that same clock, so the two finish
134
+ * together.
135
+ */
136
+ beginLoadingReveal(): void;
82
137
  /** Shifts the visible range by `dx`/`dy` pixels and returns a fresh picture. */
83
138
  pan(dx: number, dy: number): ChartFrame | null;
84
139
  /**
@@ -173,6 +228,7 @@ export interface ChartHandle {
173
228
  maWidth: number;
174
229
  bandColor: number;
175
230
  bandsVisible: boolean;
231
+ extremeFill: boolean;
176
232
  }): void;
177
233
  /**
178
234
  * Configures the MACD pane. source/maKind mirror setOverlays' encodings;
@@ -201,6 +257,18 @@ export interface ChartHandle {
201
257
  zeroColor: number;
202
258
  zeroVisible: boolean;
203
259
  }): void;
260
+ /**
261
+ * Configures the ATR pane. smoothing: 0=RMA (Wilder), 1=SMA, 2=EMA;
262
+ * lineColor is packed 0xAARRGGBB where 0 means inherit, and a non-positive
263
+ * width inherits the default stroke.
264
+ */
265
+ setATR(spec: {
266
+ enabled: boolean;
267
+ period: number;
268
+ smoothing: number;
269
+ lineColor: number;
270
+ lineWidth: number;
271
+ }): void;
204
272
  /**
205
273
  * Replaces the full set of MA/EMA overlay lines drawn on the price pane.
206
274
  * kind: 0=SMA, 1=EMA; source: 0=close,1=open,2=high,3=low,4=hl2,5=hlc3,6=ohlc4;
@@ -245,6 +313,76 @@ export interface ChartHandle {
245
313
  fillEnabled: boolean;
246
314
  fillOpacity: number;
247
315
  }): void;
316
+ /**
317
+ * Configures the Ichimoku overlay (five price-pane lines + the cloud between
318
+ * the leading spans). Colors are packed 0xAARRGGBB; cloudOpacity is 0..1.
319
+ *
320
+ * `displacement` is in candle slots and applies at draw time: the leading
321
+ * spans plot that many slots ahead of the bar they came from, past the newest
322
+ * candle, and chikou that many behind. Enabling the overlay also pulls the
323
+ * view forward far enough to show them.
324
+ */
325
+ setIchimoku(spec: {
326
+ enabled: boolean;
327
+ tenkanPeriod: number;
328
+ kijunPeriod: number;
329
+ senkouBPeriod: number;
330
+ displacement: number;
331
+ tenkanColor: number;
332
+ tenkanWidth: number;
333
+ tenkanEnabled: boolean;
334
+ kijunColor: number;
335
+ kijunWidth: number;
336
+ kijunEnabled: boolean;
337
+ senkouAColor: number;
338
+ senkouAWidth: number;
339
+ senkouAEnabled: boolean;
340
+ senkouBColor: number;
341
+ senkouBWidth: number;
342
+ senkouBEnabled: boolean;
343
+ chikouColor: number;
344
+ chikouWidth: number;
345
+ chikouEnabled: boolean;
346
+ cloudEnabled: boolean;
347
+ bullishCloudColor: number;
348
+ bearishCloudColor: number;
349
+ cloudOpacity: number;
350
+ }): void;
351
+ /**
352
+ * Configures the Fair Value Gap overlay (shaded imbalance boxes on the price
353
+ * pane). Colors are packed 0xAARRGGBB; opacity is 0..1.
354
+ *
355
+ * `maxBarsBack`, `boxLength` and `labelDistance` are all counted in candle
356
+ * slots. `fillType` is 0 for a close past the far edge or 1 for a wick
357
+ * reaching it, and `borderStyle` is 0 solid / 1 dotted / 2 dashed. Only
358
+ * enabled, maxBarsBack, waitForClose and fillType rescan for gaps.
359
+ */
360
+ setFairValueGaps(spec: {
361
+ enabled: boolean;
362
+ maxBarsBack: number;
363
+ waitForClose: boolean;
364
+ fillType: number;
365
+ deleteAfterFill: boolean;
366
+ extendBoxes: boolean;
367
+ boxLength: number;
368
+ bullishColor: number;
369
+ bearishColor: number;
370
+ opacity: number;
371
+ borderEnabled: boolean;
372
+ borderStyle: number;
373
+ borderWidth: number;
374
+ bullishBorderColor: number;
375
+ bearishBorderColor: number;
376
+ labelsEnabled: boolean;
377
+ label: string;
378
+ labelDistance: number;
379
+ labelColor: number;
380
+ labelFontSize: number;
381
+ showInverse: boolean;
382
+ inverseBullishColor: number;
383
+ inverseBearishColor: number;
384
+ inverseLabel: string;
385
+ }): void;
248
386
  /**
249
387
  * Configures the volume bars under the candles. `heightFrac` is the tallest
250
388
  * bar as a fraction of the price pane. The style fields carry an inherit
package/src/theme.ts CHANGED
@@ -20,6 +20,7 @@ export const COLOR_KEYS: Partial<Record<keyof VroomTheme, number>> = {
20
20
  accentBull: 14, // VROOM_COLOR_ACCENT_BULL
21
21
  accentBear: 15, // VROOM_COLOR_ACCENT_BEAR
22
22
  lineColor: 16, // VROOM_COLOR_LINE
23
+ skeleton: 17, // VROOM_COLOR_SKELETON
23
24
  };
24
25
 
25
26
  // Maps each numeric VroomTheme field to its VroomFloatKey index.
package/src/types.ts CHANGED
@@ -16,11 +16,16 @@ export type {
16
16
  MovingAverageOverlay,
17
17
  VWAPConfig,
18
18
  BollingerBandsConfig,
19
+ IchimokuConfig,
20
+ FairValueGapsConfig,
19
21
  VolumeConfig,
20
22
  MACDConfig,
23
+ ATRConfig,
24
+ ATRSmoothing,
21
25
  ChartType,
22
26
  TransitionEasing,
23
27
  IntervalTransition,
28
+ StreamTransition,
24
29
  PriceLine,
25
30
  PriceLinesStyle,
26
31
  Footprint,