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
@@ -54,6 +54,8 @@ std::vector<jsi::PropNameID> ChartHostObject::getPropertyNames(
54
54
  out.push_back(jsi::PropNameID::forAscii(rt, "setOverlays"));
55
55
  out.push_back(jsi::PropNameID::forAscii(rt, "setVWAP"));
56
56
  out.push_back(jsi::PropNameID::forAscii(rt, "setBollinger"));
57
+ out.push_back(jsi::PropNameID::forAscii(rt, "setVolume"));
58
+ out.push_back(jsi::PropNameID::forAscii(rt, "setVolumeCollapse"));
57
59
  out.push_back(jsi::PropNameID::forAscii(rt, "coordAt"));
58
60
  out.push_back(jsi::PropNameID::forAscii(rt, "setPriceLines"));
59
61
  out.push_back(jsi::PropNameID::forAscii(rt, "hitTestPriceLine"));
@@ -519,46 +521,106 @@ jsi::Value ChartHostObject::get(jsi::Runtime& rt,
519
521
  }
520
522
 
521
523
  if (name == "setRSI") {
522
- // setRSI(enabled, period, upperBand, lowerBand, maEnabled, maPeriod) —
523
- // configures the RSI pane. No render; the next render() picks it up.
524
+ // setRSI({enabled, period, upperBand, lowerBand, maPeriod, maKind,
525
+ // maVisible, lineColor, lineWidth, lineVisible, maColor, maWidth,
526
+ // bandColor, bandsVisible}) — configures the RSI pane. No render; the next
527
+ // render() picks it up.
524
528
  return jsi::Function::createFromHostFunction(
525
529
  rt,
526
530
  jsi::PropNameID::forAscii(rt, "setRSI"),
527
- 6,
528
- [this](jsi::Runtime& /*rt2*/,
531
+ 1,
532
+ [this](jsi::Runtime& rt2,
529
533
  const jsi::Value& /*thisVal*/,
530
534
  const jsi::Value* args,
531
535
  size_t count) -> jsi::Value {
532
- if (count < 6) return jsi::Value::undefined();
533
- const bool enabled = args[0].asBool();
534
- const int period = static_cast<int>(args[1].asNumber());
535
- const double upper = args[2].asNumber();
536
- const double lower = args[3].asNumber();
537
- const bool ma_enabled = args[4].asBool();
538
- const int ma_period = static_cast<int>(args[5].asNumber());
539
- vroom_chart_set_rsi(chart_, enabled, period, upper, lower, ma_enabled,
540
- ma_period);
536
+ if (count < 1 || !args[0].isObject()) return jsi::Value::undefined();
537
+ auto s = args[0].asObject(rt2);
538
+ VroomRSI cfg{};
539
+ cfg.enabled = s.getProperty(rt2, "enabled").asBool() ? 1 : 0;
540
+ cfg.period = static_cast<int32_t>(
541
+ s.getProperty(rt2, "period").asNumber());
542
+ cfg.upper_band = s.getProperty(rt2, "upperBand").asNumber();
543
+ cfg.lower_band = s.getProperty(rt2, "lowerBand").asNumber();
544
+ cfg.ma_period = static_cast<int32_t>(
545
+ s.getProperty(rt2, "maPeriod").asNumber());
546
+ cfg.ma_kind = static_cast<int32_t>(
547
+ s.getProperty(rt2, "maKind").asNumber());
548
+ cfg.ma_visible = s.getProperty(rt2, "maVisible").asBool() ? 1 : 0;
549
+ cfg.line_color = static_cast<uint32_t>(
550
+ s.getProperty(rt2, "lineColor").asNumber());
551
+ cfg.line_width = static_cast<float>(
552
+ s.getProperty(rt2, "lineWidth").asNumber());
553
+ cfg.line_visible = s.getProperty(rt2, "lineVisible").asBool() ? 1 : 0;
554
+ cfg.ma_color = static_cast<uint32_t>(
555
+ s.getProperty(rt2, "maColor").asNumber());
556
+ cfg.ma_width = static_cast<float>(
557
+ s.getProperty(rt2, "maWidth").asNumber());
558
+ cfg.band_color = static_cast<uint32_t>(
559
+ s.getProperty(rt2, "bandColor").asNumber());
560
+ cfg.bands_visible =
561
+ s.getProperty(rt2, "bandsVisible").asBool() ? 1 : 0;
562
+ vroom_chart_set_rsi(chart_, &cfg);
541
563
  return jsi::Value::undefined();
542
564
  });
543
565
  }
544
566
 
545
567
  if (name == "setMACD") {
546
- // setMACD(enabled, fast, slow, signal) configures the MACD pane. No
547
- // render; the next render() picks it up.
568
+ // setMACD({enabled, fast, slow, signal, source, maKind, signalMaKind,
569
+ // lineColor, lineWidth, lineVisible, signalColor, signalWidth,
570
+ // signalVisible, histVisible, histUpColor, histUpFadingColor,
571
+ // histDownColor, histDownFadingColor, zeroColor, zeroVisible}) — configures
572
+ // the MACD pane. No render; the next render() picks it up.
548
573
  return jsi::Function::createFromHostFunction(
549
574
  rt,
550
575
  jsi::PropNameID::forAscii(rt, "setMACD"),
551
- 4,
552
- [this](jsi::Runtime& /*rt2*/,
576
+ 1,
577
+ [this](jsi::Runtime& rt2,
553
578
  const jsi::Value& /*thisVal*/,
554
579
  const jsi::Value* args,
555
580
  size_t count) -> jsi::Value {
556
- if (count < 4) return jsi::Value::undefined();
557
- const bool enabled = args[0].asBool();
558
- const int fast = static_cast<int>(args[1].asNumber());
559
- const int slow = static_cast<int>(args[2].asNumber());
560
- const int signal = static_cast<int>(args[3].asNumber());
561
- vroom_chart_set_macd(chart_, enabled, fast, slow, signal);
581
+ if (count < 1 || !args[0].isObject()) return jsi::Value::undefined();
582
+ auto s = args[0].asObject(rt2);
583
+ VroomMACD cfg{};
584
+ cfg.enabled = s.getProperty(rt2, "enabled").asBool() ? 1 : 0;
585
+ cfg.fast = static_cast<int32_t>(
586
+ s.getProperty(rt2, "fast").asNumber());
587
+ cfg.slow = static_cast<int32_t>(
588
+ s.getProperty(rt2, "slow").asNumber());
589
+ cfg.signal = static_cast<int32_t>(
590
+ s.getProperty(rt2, "signal").asNumber());
591
+ cfg.source = static_cast<int32_t>(
592
+ s.getProperty(rt2, "source").asNumber());
593
+ cfg.ma_kind = static_cast<int32_t>(
594
+ s.getProperty(rt2, "maKind").asNumber());
595
+ cfg.signal_ma_kind = static_cast<int32_t>(
596
+ s.getProperty(rt2, "signalMaKind").asNumber());
597
+ cfg.line_color = static_cast<uint32_t>(
598
+ s.getProperty(rt2, "lineColor").asNumber());
599
+ cfg.line_width = static_cast<float>(
600
+ s.getProperty(rt2, "lineWidth").asNumber());
601
+ cfg.line_visible =
602
+ s.getProperty(rt2, "lineVisible").asBool() ? 1 : 0;
603
+ cfg.signal_color = static_cast<uint32_t>(
604
+ s.getProperty(rt2, "signalColor").asNumber());
605
+ cfg.signal_width = static_cast<float>(
606
+ s.getProperty(rt2, "signalWidth").asNumber());
607
+ cfg.signal_visible =
608
+ s.getProperty(rt2, "signalVisible").asBool() ? 1 : 0;
609
+ cfg.hist_visible =
610
+ s.getProperty(rt2, "histVisible").asBool() ? 1 : 0;
611
+ cfg.hist_up_color = static_cast<uint32_t>(
612
+ s.getProperty(rt2, "histUpColor").asNumber());
613
+ cfg.hist_up_fading_color = static_cast<uint32_t>(
614
+ s.getProperty(rt2, "histUpFadingColor").asNumber());
615
+ cfg.hist_down_color = static_cast<uint32_t>(
616
+ s.getProperty(rt2, "histDownColor").asNumber());
617
+ cfg.hist_down_fading_color = static_cast<uint32_t>(
618
+ s.getProperty(rt2, "histDownFadingColor").asNumber());
619
+ cfg.zero_color = static_cast<uint32_t>(
620
+ s.getProperty(rt2, "zeroColor").asNumber());
621
+ cfg.zero_visible =
622
+ s.getProperty(rt2, "zeroVisible").asBool() ? 1 : 0;
623
+ vroom_chart_set_macd(chart_, &cfg);
562
624
  return jsi::Value::undefined();
563
625
  });
564
626
  }
@@ -602,21 +664,26 @@ jsi::Value ChartHostObject::get(jsi::Runtime& rt,
602
664
  }
603
665
 
604
666
  if (name == "setVWAP") {
605
- // setVWAP(enabled, resetOffsetMin, color, width) — session VWAP overlay.
667
+ // setVWAP({enabled, resetOffsetMin, color, width}) — session VWAP overlay.
606
668
  return jsi::Function::createFromHostFunction(
607
669
  rt,
608
670
  jsi::PropNameID::forAscii(rt, "setVWAP"),
609
- 4,
610
- [this](jsi::Runtime& /*rt2*/,
671
+ 1,
672
+ [this](jsi::Runtime& rt2,
611
673
  const jsi::Value& /*thisVal*/,
612
674
  const jsi::Value* args,
613
675
  size_t count) -> jsi::Value {
614
- if (count < 4) return jsi::Value::undefined();
615
- const bool enabled = args[0].asBool();
616
- const int reset = static_cast<int>(args[1].asNumber());
617
- const uint32_t color = static_cast<uint32_t>(args[2].asNumber());
618
- const float width = static_cast<float>(args[3].asNumber());
619
- vroom_chart_set_vwap(chart_, enabled, reset, color, width);
676
+ if (count < 1 || !args[0].isObject()) return jsi::Value::undefined();
677
+ auto s = args[0].asObject(rt2);
678
+ VroomVWAP cfg{};
679
+ cfg.enabled = s.getProperty(rt2, "enabled").asBool() ? 1 : 0;
680
+ cfg.reset_offset_min = static_cast<int32_t>(
681
+ s.getProperty(rt2, "resetOffsetMin").asNumber());
682
+ cfg.color = static_cast<uint32_t>(
683
+ s.getProperty(rt2, "color").asNumber());
684
+ cfg.width = static_cast<float>(
685
+ s.getProperty(rt2, "width").asNumber());
686
+ vroom_chart_set_vwap(chart_, &cfg);
620
687
  return jsi::Value::undefined();
621
688
  });
622
689
  }
@@ -667,6 +734,57 @@ jsi::Value ChartHostObject::get(jsi::Runtime& rt,
667
734
  });
668
735
  }
669
736
 
737
+ if (name == "setVolume") {
738
+ // setVolume({enabled, heightFrac, opacity, radiusPx, upColor, downColor})
739
+ // — the volume bars. Negative floats / zero colors inherit the theme. No
740
+ // render; the next render() picks it up.
741
+ return jsi::Function::createFromHostFunction(
742
+ rt,
743
+ jsi::PropNameID::forAscii(rt, "setVolume"),
744
+ 1,
745
+ [this](jsi::Runtime& rt2,
746
+ const jsi::Value& /*thisVal*/,
747
+ const jsi::Value* args,
748
+ size_t count) -> jsi::Value {
749
+ if (count < 1 || !args[0].isObject()) return jsi::Value::undefined();
750
+ auto s = args[0].asObject(rt2);
751
+ VroomVolume cfg{};
752
+ cfg.enabled = s.getProperty(rt2, "enabled").asBool() ? 1 : 0;
753
+ cfg.height_frac = static_cast<float>(
754
+ s.getProperty(rt2, "heightFrac").asNumber());
755
+ cfg.opacity = static_cast<float>(
756
+ s.getProperty(rt2, "opacity").asNumber());
757
+ cfg.radius_px = static_cast<float>(
758
+ s.getProperty(rt2, "radiusPx").asNumber());
759
+ cfg.up_color = static_cast<uint32_t>(
760
+ s.getProperty(rt2, "upColor").asNumber());
761
+ cfg.down_color = static_cast<uint32_t>(
762
+ s.getProperty(rt2, "downColor").asNumber());
763
+ vroom_chart_set_volume(chart_, &cfg);
764
+ return jsi::Value::undefined();
765
+ });
766
+ }
767
+
768
+ if (name == "setVolumeCollapse") {
769
+ // setVolumeCollapse(t, easing) — staggered collapse of the volume bars. `t`
770
+ // is LINEAR progress; the core eases each bar over its own window.
771
+ return jsi::Function::createFromHostFunction(
772
+ rt,
773
+ jsi::PropNameID::forAscii(rt, "setVolumeCollapse"),
774
+ 2,
775
+ [this](jsi::Runtime& /*rt2*/,
776
+ const jsi::Value& /*thisVal*/,
777
+ const jsi::Value* args,
778
+ size_t count) -> jsi::Value {
779
+ if (count < 2) return jsi::Value::undefined();
780
+ vroom_chart_set_volume_collapse(
781
+ chart_,
782
+ static_cast<float>(args[0].asNumber()),
783
+ static_cast<int32_t>(args[1].asNumber()));
784
+ return jsi::Value::undefined();
785
+ });
786
+ }
787
+
670
788
  if (name == "coordAt") {
671
789
  // coordAt(x, y) -> { timeMs, price } | null. The continuous data coordinate
672
790
  // at a pixel — not snapped to a candle slot. Null when there are no candles
@@ -51,7 +51,7 @@ typedef struct VroomOverlay {
51
51
 
52
52
  // Bollinger Bands overlay drawn on the price pane: a basis MA of `source` over
53
53
  // `period`, banded at ± `mult` × population standard deviation of the same
54
- // window. Per TradingView semantics the stdev always uses the window's
54
+ // window. Per the standard semantics the stdev always uses the window's
55
55
  // arithmetic mean, even when `basis_kind` selects an EMA basis line.
56
56
  typedef struct VroomBollinger {
57
57
  int32_t enabled; // 0/1
@@ -69,6 +69,97 @@ typedef struct VroomBollinger {
69
69
  float fill_opacity; // 0..1, multiplied into upper_color's alpha
70
70
  } VroomBollinger;
71
71
 
72
+ // MACD, drawn in its own pane below the candles: the difference between a fast
73
+ // and a slow moving average of `source`, a signal line smoothing that
74
+ // difference, and a histogram of the gap between the two.
75
+ //
76
+ // The style fields carry an inherit sentinel so an untouched config renders the
77
+ // stock look: a fully transparent color falls back to the theme (histogram) or
78
+ // to the built-in default (lines), and a non-positive width falls back to 1.5.
79
+ // The two `fading` histogram colors — used when a bar moves back toward zero,
80
+ // i.e. momentum is easing — fall back to their base color at half alpha.
81
+ typedef struct VroomMACD {
82
+ int32_t enabled; // 0/1
83
+ int32_t fast; // fast MA length (clamped >= 1; default 12)
84
+ int32_t slow; // slow MA length (forced > fast; default 26)
85
+ int32_t signal; // signal MA length (clamped >= 1; default 9)
86
+ int32_t source; // 0=close,1=open,2=high,3=low,4=hl2,5=hlc3,6=ohlc4
87
+ int32_t ma_kind; // fast/slow legs: 0 = SMA, 1 = EMA
88
+ int32_t signal_ma_kind; // signal line: 0 = SMA, 1 = EMA
89
+
90
+ uint32_t line_color; // MACD line; 0xAARRGGBB, 0 inherits the default blue
91
+ float line_width; // stroke px; <= 0 inherits 1.5
92
+ int32_t line_visible; // 0/1
93
+ uint32_t signal_color; // 0 inherits the default orange
94
+ float signal_width;
95
+ int32_t signal_visible;
96
+
97
+ int32_t hist_visible;
98
+ uint32_t hist_up_color; // 0 inherits VROOM_COLOR_ACCENT_BULL
99
+ uint32_t hist_up_fading_color; // 0 inherits hist_up_color at half alpha
100
+ uint32_t hist_down_color; // 0 inherits VROOM_COLOR_ACCENT_BEAR
101
+ uint32_t hist_down_fading_color; // 0 inherits hist_down_color at half alpha
102
+
103
+ uint32_t zero_color; // 0 inherits the default gray
104
+ int32_t zero_visible; // 0/1
105
+ } VroomMACD;
106
+
107
+ // RSI, drawn in its own pane below the candles: the index line, an optional
108
+ // moving-average trendline over it, and two dashed rules at the overbought and
109
+ // oversold levels. Close-only by design (Wilder's definition), so unlike the
110
+ // other averaged indicators it takes no `source`.
111
+ //
112
+ // The style fields carry the same inherit sentinel as VroomMACD: a fully
113
+ // transparent color falls back to the built-in default and a non-positive width
114
+ // falls back to 1.5.
115
+ typedef struct VroomRSI {
116
+ int32_t enabled; // 0/1
117
+ int32_t period; // lookback in candles (clamped >= 2; default 14)
118
+ double upper_band; // overbought level (0..100; default 70)
119
+ double lower_band; // oversold level (0..100; default 30)
120
+ int32_t ma_period; // trendline length (clamped >= 1; default 14)
121
+ int32_t ma_kind; // trendline: 0 = SMA, 1 = EMA
122
+ int32_t ma_visible; // 0/1
123
+
124
+ uint32_t line_color; // 0 inherits the default violet
125
+ float line_width; // stroke px; <= 0 inherits 1.5
126
+ int32_t line_visible; // 0/1
127
+ uint32_t ma_color; // 0 inherits the default amber
128
+ float ma_width;
129
+ uint32_t band_color; // both dashed rules; 0 inherits the default gray
130
+ int32_t bands_visible; // 0/1
131
+ } VroomRSI;
132
+
133
+ // Session VWAP, drawn as a single line on the price pane. The session resets
134
+ // each UTC day shifted by `reset_offset_min` minutes, and the line lifts its pen
135
+ // at each reset. Color 0 inherits the default cyan; a non-positive width
136
+ // inherits 1.5.
137
+ typedef struct VroomVWAP {
138
+ int32_t enabled; // 0/1
139
+ int32_t reset_offset_min; // session boundary offset from UTC midnight
140
+ uint32_t color; // 0xAARRGGBB; 0 inherits the default
141
+ float width; // stroke px; <= 0 inherits 1.5
142
+ } VroomVWAP;
143
+
144
+ // Volume bars on the price pane, one per candle, drawn under the candles.
145
+ //
146
+ // `height_frac` is a ceiling, not a reservation: raising it lets the tallest bar
147
+ // reach further up over the candles rather than compressing them (matching the
148
+ // conventional volume overlay). Bar heights always auto-fit the loudest volume
149
+ // in view, so the tallest bar sits exactly at the ceiling.
150
+ //
151
+ // The style fields carry an inherit sentinel so the theme keeps supplying them
152
+ // when the consumer doesn't: a negative float or a fully transparent color falls
153
+ // back to the corresponding theme key.
154
+ typedef struct VroomVolume {
155
+ int32_t enabled; // 0/1 — default 1: bars draw unless turned off
156
+ float height_frac; // tallest bar as a fraction of the price pane; < 0 inherits 0.2
157
+ float opacity; // 0..1 (1 = opaque); < 0 inherits VROOM_FLOAT_VOLUME_OPACITY
158
+ float radius_px; // top-corner radius; < 0 inherits VROOM_FLOAT_VOLUME_RADIUS_PX
159
+ uint32_t up_color; // 0xAARRGGBB; 0 inherits VROOM_COLOR_ACCENT_BULL
160
+ uint32_t down_color; // 0xAARRGGBB; 0 inherits VROOM_COLOR_ACCENT_BEAR
161
+ } VroomVolume;
162
+
72
163
  // A drawing anchor in data space (so a drawing tracks the candles on pan/zoom).
73
164
  typedef struct VroomDrawPoint {
74
165
  int64_t time_ms; // epoch milliseconds (not snapped to a candle slot)
@@ -199,9 +290,25 @@ typedef enum {
199
290
  VROOM_FLOAT_WICK_ROUND_CAP, // 0/1: round the wick end caps
200
291
  VROOM_FLOAT_VOLUME_RADIUS_PX, // volume bar top-corner radius px (0 = square)
201
292
  VROOM_FLOAT_LINE_WIDTH_PX, // line-chart-mode polyline stroke width px
293
+ VROOM_FLOAT_LINE_GRADIENT_OPACITY, // fill under the line at its strongest (0 disables)
202
294
  VROOM_FLOAT_COUNT_
203
295
  } VroomFloatKey;
204
296
 
297
+ // ---- Animation ------------------------------------------------------------
298
+
299
+ // Easing curves for the animations the core paces itself. Mirrors the
300
+ // TransitionEasing union in @vroomchart/types, index for index.
301
+ //
302
+ // Most animations are eased by the host before it hands the core a progress
303
+ // value; this enum exists for the ones the core has to ease internally because
304
+ // each element runs on its own slice of the timeline.
305
+ typedef enum {
306
+ VROOM_EASING_LINEAR = 0,
307
+ VROOM_EASING_IN,
308
+ VROOM_EASING_OUT,
309
+ VROOM_EASING_IN_OUT, // smoothstep; the default for unknown values
310
+ } VroomEasing;
311
+
205
312
  // ---- Callbacks ------------------------------------------------------------
206
313
 
207
314
  typedef struct {
@@ -263,6 +370,45 @@ void vroom_chart_reset_view(VroomChart* chart);
263
370
  // switch) so the price scale re-fits the newly visible candles.
264
371
  void vroom_chart_reset_price_scale(VroomChart* chart);
265
372
 
373
+ // Reads the visible price *envelope* — the min low / max high across the
374
+ // currently visible candles. This is the extent the candles actually occupy, not
375
+ // the axis range (which is wider: see vroom_chart_preserve_price_envelope).
376
+ // Returns false (out params untouched) when no candles are visible. Either out
377
+ // pointer may be null.
378
+ bool vroom_chart_get_visible_price_envelope(VroomChart* chart,
379
+ double* out_low, double* out_high);
380
+
381
+ // "Scale lock" for a same-asset data swap that re-buckets the same price action
382
+ // into a different high-low span (a timeframe switch). Rescales a *manual* price
383
+ // range so the visible envelope keeps the exact pixel height and position that
384
+ // the [prev_low, prev_high] envelope had before the swap — so candles don't
385
+ // suddenly shrink or grow when the interval changes.
386
+ //
387
+ // Call after set_candles + set_visible_range, passing the envelope read by
388
+ // vroom_chart_get_visible_price_envelope *before* the swap.
389
+ //
390
+ // No-op in auto mode: auto-fit already widens the envelope by a fixed factor, so
391
+ // its pixel height is invariant. Falls back to reset_price_scale semantics when
392
+ // either envelope is degenerate.
393
+ void vroom_chart_preserve_price_envelope(VroomChart* chart,
394
+ double prev_low, double prev_high);
395
+
396
+ // Captures the currently visible candle geometry so the next data swap can be
397
+ // animated as a reshape rather than a jump: each candle's wick and body slide
398
+ // and stretch into the shape of its counterpart in the new data.
399
+ //
400
+ // Candles are paired by *slot* — position counting back from the right edge of
401
+ // the visible window, which a timeframe switch preserves. Call before
402
+ // set_candles, then drive vroom_chart_set_interval_morph from 0 to 1.
403
+ // No-op when nothing is visible.
404
+ void vroom_chart_begin_interval_morph(VroomChart* chart);
405
+
406
+ // Advances the interval morph started by vroom_chart_begin_interval_morph. `t`
407
+ // (clamped to 0..1) is the eased progress: 0 renders the captured geometry
408
+ // pixel-identically to the pre-swap frame, 1 renders the new candles and
409
+ // releases the capture. Driven per-frame by the host animation loop.
410
+ void vroom_chart_set_interval_morph(VroomChart* chart, float t);
411
+
266
412
  void vroom_chart_pan(VroomChart* chart, float dx_px, float dy_px);
267
413
  // Directional zoom. scale_x scales the time window around focus_x_px (>1 =
268
414
  // narrower window, wider candles); scale_y scales the price range around
@@ -351,39 +497,51 @@ bool vroom_chart_get_crosshair_info(VroomChart* chart, VroomCrosshairInfo* out);
351
497
 
352
498
  // ---- Indicators -----------------------------------------------------------
353
499
 
354
- // Configures the RSI indicator (rendered in a pane below the candles). `period`
355
- // is the RSI lookback in candle counts (clamped >= 2). `upper_band`/`lower_band`
356
- // are the overbought/oversold reference levels (0..100; default 70/30).
357
- // `ma_enabled` toggles the RSI-based moving-average trendline and `ma_period`
358
- // is its length (clamped >= 1). When enabled, the candle pane shrinks by
359
- // VROOM_FLOAT_INDICATOR_HEIGHT_FRAC.
360
- void vroom_chart_set_rsi(VroomChart* chart, bool enabled, int period,
361
- double upper_band, double lower_band,
362
- bool ma_enabled, int ma_period);
363
-
364
- // Configures the MACD indicator (its own pane below the candles). `fast`/`slow`
365
- // are the EMA lengths (clamped >= 1, slow forced > fast; default 12/26) and
366
- // `signal` is the signal-line EMA length (clamped >= 1; default 9). Panes stack
367
- // in enable order, most recently enabled at the bottom.
368
- void vroom_chart_set_macd(VroomChart* chart, bool enabled, int fast, int slow,
369
- int signal);
500
+ // Configures the RSI indicator (rendered in a pane below the candles). When
501
+ // enabled, the candle pane shrinks by VROOM_FLOAT_INDICATOR_HEIGHT_FRAC. Length
502
+ // and MA-kind changes recompute the series; band levels, colors, widths, and
503
+ // visibility changes only re-render.
504
+ void vroom_chart_set_rsi(VroomChart* chart, const VroomRSI* cfg);
505
+
506
+ // Configures the MACD indicator (its own pane below the candles). Panes stack
507
+ // in enable order, most recently enabled at the bottom. Length, source, and MA
508
+ // kind changes recompute the series; color, width, and visibility changes only
509
+ // re-render.
510
+ void vroom_chart_set_macd(VroomChart* chart, const VroomMACD* cfg);
370
511
 
371
512
  // Replaces the full set of moving-average overlay lines (SMA/EMA) drawn on the
372
513
  // price pane. Pass count 0 to clear them. Overlays don't reserve a pane.
373
514
  void vroom_chart_set_overlays(VroomChart* chart, const VroomOverlay* overlays,
374
515
  size_t count);
375
516
 
376
- // Configures the session VWAP overlay (a single price-pane line). The session
377
- // resets each UTC day shifted by `reset_offset_min` minutes (the configurable
378
- // reset time). `color` is 0xAARRGGBB; `width` is the stroke px.
379
- void vroom_chart_set_vwap(VroomChart* chart, bool enabled, int reset_offset_min,
380
- uint32_t color, float width);
517
+ // Configures the session VWAP overlay (a single price-pane line). Enabled and
518
+ // reset-time changes recompute the series; color and width changes only
519
+ // re-render.
520
+ void vroom_chart_set_vwap(VroomChart* chart, const VroomVWAP* cfg);
381
521
 
382
522
  // Configures the Bollinger Bands overlay (three price-pane lines + an optional
383
523
  // translucent fill between the bands; no pane is reserved). Color/width/fill
384
524
  // changes only re-render; enabled/period/mult/source/basis changes recompute.
385
525
  void vroom_chart_set_bollinger(VroomChart* chart, const VroomBollinger* cfg);
386
526
 
527
+ // Configures the volume bars. Render-only — the bars come straight off each
528
+ // candle's volume, so nothing is recomputed. Bars are enabled by default; pass
529
+ // a config with `enabled` 0 to hide them.
530
+ void vroom_chart_set_volume(VroomChart* chart, const VroomVolume* cfg);
531
+
532
+ // Advances the staggered volume-bar collapse: 0 leaves every bar at full height,
533
+ // 1 has them all flat. Driven per-frame by the host animation loop.
534
+ //
535
+ // `t` is *linear* progress, unlike the other morph setters. Each bar falls over
536
+ // its own slice of the timeline — the tallest starting first and every bar
537
+ // landing at 1 — so `easing` (a VroomEasing) is applied per bar inside the core;
538
+ // pre-easing here would compound the two curves.
539
+ //
540
+ // Symmetric, so driving 1 -> 0 reveals the bars: the shortest rises first and
541
+ // the tallest arrives last. vroom_chart_set_volume snaps this to match its
542
+ // `enabled`, so the host only needs it while animating.
543
+ void vroom_chart_set_volume_collapse(VroomChart* chart, float t, int32_t easing);
544
+
387
545
  // ---- Drawings (line annotations) ------------------------------------------
388
546
 
389
547
  // Replaces the full set of committed line drawings (data-anchored, so they track
@@ -19,7 +19,7 @@ namespace vroom::bollinger {
19
19
  // period window
20
20
  //
21
21
  // The stdev always uses the window's arithmetic mean, even when basis_kind
22
- // selects an EMA basis line (TradingView semantics).
22
+ // selects an EMA basis line (the standard semantics).
23
23
  //
24
24
  // Each output is resized to n; values are NaN for i < period-1 and when
25
25
  // n < period.