react-native-vroom-chart 0.1.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 (66) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +27 -0
  3. package/android/README.md +7 -0
  4. package/cpp/README.md +11 -0
  5. package/cpp/VroomChartHostObject.cpp +458 -0
  6. package/cpp/VroomChartHostObject.h +36 -0
  7. package/cpp/VroomJsiInstaller.cpp +69 -0
  8. package/cpp/VroomJsiInstaller.h +10 -0
  9. package/cpp/VroomSkiaContext.cpp +25 -0
  10. package/cpp/VroomSkiaContext.h +30 -0
  11. package/cpp/_core_include/vroom/vroom_chart.h +195 -0
  12. package/cpp/_core_src/candles.cpp +74 -0
  13. package/cpp/_core_src/candles.h +34 -0
  14. package/cpp/_core_src/chart.cpp +317 -0
  15. package/cpp/_core_src/chart.h +167 -0
  16. package/cpp/_core_src/chart_facade.cpp +570 -0
  17. package/cpp/_core_src/chart_internal.h +30 -0
  18. package/cpp/_core_src/crosshair.cpp +65 -0
  19. package/cpp/_core_src/crosshair.h +32 -0
  20. package/cpp/_core_src/fonts.cpp +22 -0
  21. package/cpp/_core_src/fonts.h +25 -0
  22. package/cpp/_core_src/labels.cpp +340 -0
  23. package/cpp/_core_src/labels.h +85 -0
  24. package/cpp/_core_src/ma.cpp +53 -0
  25. package/cpp/_core_src/ma.h +39 -0
  26. package/cpp/_core_src/ma_overlay.cpp +73 -0
  27. package/cpp/_core_src/ma_overlay.h +42 -0
  28. package/cpp/_core_src/macd.cpp +68 -0
  29. package/cpp/_core_src/macd.h +29 -0
  30. package/cpp/_core_src/macd_pane.cpp +199 -0
  31. package/cpp/_core_src/macd_pane.h +41 -0
  32. package/cpp/_core_src/price_indicator.cpp +106 -0
  33. package/cpp/_core_src/price_indicator.h +29 -0
  34. package/cpp/_core_src/rsi.cpp +70 -0
  35. package/cpp/_core_src/rsi.h +32 -0
  36. package/cpp/_core_src/rsi_pane.cpp +167 -0
  37. package/cpp/_core_src/rsi_pane.h +39 -0
  38. package/cpp/_core_src/theme.cpp +41 -0
  39. package/cpp/_core_src/theme.h +23 -0
  40. package/cpp/_core_src/ticks.cpp +49 -0
  41. package/cpp/_core_src/ticks.h +30 -0
  42. package/cpp/_core_src/viewport.cpp +141 -0
  43. package/cpp/_core_src/viewport.h +100 -0
  44. package/cpp/_core_src/volume.cpp +70 -0
  45. package/cpp/_core_src/volume.h +34 -0
  46. package/cpp/_core_src/vwap.cpp +51 -0
  47. package/cpp/_core_src/vwap.h +27 -0
  48. package/ios/README.md +7 -0
  49. package/ios/VroomChartModule.h +11 -0
  50. package/ios/VroomChartModule.mm +44 -0
  51. package/lib/index.d.mts +185 -0
  52. package/lib/index.d.ts +185 -0
  53. package/lib/index.js +429 -0
  54. package/lib/index.js.map +1 -0
  55. package/lib/index.mjs +396 -0
  56. package/lib/index.mjs.map +1 -0
  57. package/package.json +75 -0
  58. package/react-native-vroom-chart.podspec +79 -0
  59. package/src/NativeVroomChart.ts +12 -0
  60. package/src/VroomChart.tsx +382 -0
  61. package/src/index.ts +14 -0
  62. package/src/jsi.d.ts +128 -0
  63. package/src/packCandles.ts +24 -0
  64. package/src/theme.ts +43 -0
  65. package/src/types.ts +27 -0
  66. package/src/useChartCore.ts +135 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Darion Welch
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,27 @@
1
+ # react-native-vroom-chart
2
+
3
+ React Native component for the vroom Skia candlestick chart.
4
+
5
+ ```tsx
6
+ import { VroomChart } from 'react-native-vroom-chart';
7
+
8
+ <VroomChart
9
+ candles={candles}
10
+ width={width}
11
+ height={240}
12
+ onCrosshair={(e) => console.log(e)}
13
+ />
14
+ ```
15
+
16
+ ## Peer dependencies
17
+
18
+ - `react-native` ≥ 0.76
19
+ - `@shopify/react-native-skia` ≥ 1.5 — provides the Skia runtime we draw into
20
+ - `react-native-gesture-handler` ≥ 2.16 — long-press / pan / pinch recognition
21
+
22
+ ## Status
23
+
24
+ Pre-alpha. The TS layer renders a placeholder chart area with working
25
+ gestures (long-press crosshair); the actual candle rendering will move to
26
+ the C++ core (`@vroomchart/core`) via JSI once that bridge lands. See
27
+ `cpp/README.md`.
@@ -0,0 +1,7 @@
1
+ # Android shim (placeholder)
2
+
3
+ When native is wired up, this directory will hold the JNI bindings and a
4
+ Java/Kotlin view that hosts the chart and forwards gestures into the C++ core.
5
+
6
+ `build.gradle` (also to come) will compile `cpp/` + this directory + the
7
+ linked `@vroomchart/core` static library via CMake.
package/cpp/README.md ADDED
@@ -0,0 +1,11 @@
1
+ # JSI bridge (placeholder)
2
+
3
+ When the JS API is stable enough to wire to native, this directory will hold:
4
+
5
+ - `VroomChartJSI.h` / `VroomChartJSI.cpp` — JSI HostObject(s) exposing the C
6
+ facade from `@vroomchart/core` to JS.
7
+ - Glue for borrowing an `SkCanvas` from `@shopify/react-native-skia` and
8
+ passing it to `vroom_chart_draw`.
9
+
10
+ Until then the JS layer fakes the rendering with RN-Skia primitives so we
11
+ can iterate on the component API without native rebuilds.
@@ -0,0 +1,458 @@
1
+ #include "VroomChartHostObject.h"
2
+
3
+ #include <cstring>
4
+
5
+ #include "chart_internal.h"
6
+ #include "vroom/vroom_chart.h"
7
+
8
+ // RN-Skia headers.
9
+ #include "JsiSkHostObjects.h"
10
+ #include "JsiSkPicture.h"
11
+
12
+ namespace vroom {
13
+
14
+ namespace jsi = facebook::jsi;
15
+
16
+ ChartHostObject::ChartHostObject() {
17
+ chart_ = vroom_chart_create(nullptr, nullptr);
18
+ }
19
+
20
+ ChartHostObject::~ChartHostObject() {
21
+ if (chart_) {
22
+ vroom_chart_destroy(chart_);
23
+ chart_ = nullptr;
24
+ }
25
+ }
26
+
27
+ std::vector<jsi::PropNameID> ChartHostObject::getPropertyNames(
28
+ jsi::Runtime& rt) {
29
+ std::vector<jsi::PropNameID> out;
30
+ out.reserve(19);
31
+ out.push_back(jsi::PropNameID::forAscii(rt, "setCandles"));
32
+ out.push_back(jsi::PropNameID::forAscii(rt, "setSize"));
33
+ out.push_back(jsi::PropNameID::forAscii(rt, "setColor"));
34
+ out.push_back(jsi::PropNameID::forAscii(rt, "setVisibleRange"));
35
+ out.push_back(jsi::PropNameID::forAscii(rt, "pan"));
36
+ out.push_back(jsi::PropNameID::forAscii(rt, "translate"));
37
+ out.push_back(jsi::PropNameID::forAscii(rt, "zoom"));
38
+ out.push_back(jsi::PropNameID::forAscii(rt, "scalePriceAxis"));
39
+ out.push_back(jsi::PropNameID::forAscii(rt, "scaleTimeAxis"));
40
+ out.push_back(jsi::PropNameID::forAscii(rt, "getAxisMetrics"));
41
+ out.push_back(jsi::PropNameID::forAscii(rt, "isAnimating"));
42
+ out.push_back(jsi::PropNameID::forAscii(rt, "setCrosshair"));
43
+ out.push_back(jsi::PropNameID::forAscii(rt, "clearCrosshair"));
44
+ out.push_back(jsi::PropNameID::forAscii(rt, "getCrosshairCandle"));
45
+ out.push_back(jsi::PropNameID::forAscii(rt, "setRSI"));
46
+ out.push_back(jsi::PropNameID::forAscii(rt, "setMACD"));
47
+ out.push_back(jsi::PropNameID::forAscii(rt, "setOverlays"));
48
+ out.push_back(jsi::PropNameID::forAscii(rt, "setVWAP"));
49
+ out.push_back(jsi::PropNameID::forAscii(rt, "render"));
50
+ return out;
51
+ }
52
+
53
+ // Shared helper: wraps a fresh picture for return to JS, with memory pressure
54
+ // reported to Hermes so GC keeps up under gesture-rate churn.
55
+ static facebook::jsi::Value wrapPicture(facebook::jsi::Runtime& rt,
56
+ const sk_sp<SkPicture>& pic) {
57
+ if (!pic) return facebook::jsi::Value::null();
58
+ auto host =
59
+ std::make_shared<RNSkia::JsiSkPicture>(/*context=*/nullptr, pic);
60
+ return JSI_CREATE_HOST_OBJECT_WITH_MEMORY_PRESSURE(rt, host,
61
+ /*context=*/nullptr);
62
+ }
63
+
64
+ jsi::Value ChartHostObject::get(jsi::Runtime& rt,
65
+ const jsi::PropNameID& propName) {
66
+ const std::string name = propName.utf8(rt);
67
+
68
+ if (name == "setCandles") {
69
+ return jsi::Function::createFromHostFunction(
70
+ rt,
71
+ jsi::PropNameID::forAscii(rt, "setCandles"),
72
+ 1,
73
+ [this](jsi::Runtime& rt2,
74
+ const jsi::Value& /*thisVal*/,
75
+ const jsi::Value* args,
76
+ size_t count) -> jsi::Value {
77
+ if (count < 1 || !args[0].isObject()) return jsi::Value::undefined();
78
+ auto obj = args[0].asObject(rt2);
79
+ if (!obj.isArrayBuffer(rt2)) return jsi::Value::undefined();
80
+ auto buf = obj.getArrayBuffer(rt2);
81
+
82
+ const size_t bytes = buf.size(rt2);
83
+ const size_t n = bytes / sizeof(VroomCandle);
84
+ if (n == 0 || n * sizeof(VroomCandle) != bytes) {
85
+ return jsi::Value::undefined();
86
+ }
87
+ const auto* data =
88
+ reinterpret_cast<const VroomCandle*>(buf.data(rt2));
89
+ vroom_chart_set_candles(chart_, data, n);
90
+ return jsi::Value::undefined();
91
+ });
92
+ }
93
+
94
+ if (name == "setSize") {
95
+ return jsi::Function::createFromHostFunction(
96
+ rt,
97
+ jsi::PropNameID::forAscii(rt, "setSize"),
98
+ 3,
99
+ [this](jsi::Runtime& /*rt2*/,
100
+ const jsi::Value& /*thisVal*/,
101
+ const jsi::Value* args,
102
+ size_t count) -> jsi::Value {
103
+ if (count < 3) return jsi::Value::undefined();
104
+ const float w = static_cast<float>(args[0].asNumber());
105
+ const float h = static_cast<float>(args[1].asNumber());
106
+ const float r = static_cast<float>(args[2].asNumber());
107
+ vroom_chart_set_size(chart_, w, h, r);
108
+ return jsi::Value::undefined();
109
+ });
110
+ }
111
+
112
+ if (name == "setColor") {
113
+ // setColor(keyIndex, argb) — keyIndex is a VroomColorKey; argb is a packed
114
+ // 0xAARRGGBB integer. The core bounds-checks the key and marks dirty.
115
+ return jsi::Function::createFromHostFunction(
116
+ rt,
117
+ jsi::PropNameID::forAscii(rt, "setColor"),
118
+ 2,
119
+ [this](jsi::Runtime& /*rt2*/,
120
+ const jsi::Value& /*thisVal*/,
121
+ const jsi::Value* args,
122
+ size_t count) -> jsi::Value {
123
+ if (count < 2) return jsi::Value::undefined();
124
+ const int key = static_cast<int>(args[0].asNumber());
125
+ const uint32_t argb =
126
+ static_cast<uint32_t>(args[1].asNumber());
127
+ vroom_chart_set_color(chart_, static_cast<VroomColorKey>(key), argb);
128
+ return jsi::Value::undefined();
129
+ });
130
+ }
131
+
132
+ if (name == "setVisibleRange") {
133
+ // Pass 0,0 to clear (show all).
134
+ // Timestamps are Number on the JS side — fine for ms-since-epoch since
135
+ // those fit in 53 bits well past year 285,000.
136
+ return jsi::Function::createFromHostFunction(
137
+ rt,
138
+ jsi::PropNameID::forAscii(rt, "setVisibleRange"),
139
+ 2,
140
+ [this](jsi::Runtime& /*rt2*/,
141
+ const jsi::Value& /*thisVal*/,
142
+ const jsi::Value* args,
143
+ size_t count) -> jsi::Value {
144
+ if (count < 2) return jsi::Value::undefined();
145
+ const int64_t s = static_cast<int64_t>(args[0].asNumber());
146
+ const int64_t e = static_cast<int64_t>(args[1].asNumber());
147
+ vroom_chart_set_visible_range(chart_, s, e);
148
+ return jsi::Value::undefined();
149
+ });
150
+ }
151
+
152
+ if (name == "pan") {
153
+ // pan(dx, dy) -> JsiSkPicture
154
+ // Mutates the visible range and renders in one JSI call so gesture
155
+ // handlers don't pay round-trip overhead twice per frame.
156
+ return jsi::Function::createFromHostFunction(
157
+ rt,
158
+ jsi::PropNameID::forAscii(rt, "pan"),
159
+ 2,
160
+ [this](jsi::Runtime& rt2,
161
+ const jsi::Value& /*thisVal*/,
162
+ const jsi::Value* args,
163
+ size_t count) -> jsi::Value {
164
+ if (count < 2) return jsi::Value::null();
165
+ const float dx = static_cast<float>(args[0].asNumber());
166
+ const float dy = static_cast<float>(args[1].asNumber());
167
+ vroom_chart_pan(chart_, dx, dy);
168
+ return wrapPicture(rt2, render_chart_picture(chart_));
169
+ });
170
+ }
171
+
172
+ if (name == "translate") {
173
+ // translate(dx, dy) -> JsiSkPicture — shifts time + price bounds
174
+ // without rescaling. Wired to the two-finger pan gesture in JS.
175
+ return jsi::Function::createFromHostFunction(
176
+ rt,
177
+ jsi::PropNameID::forAscii(rt, "translate"),
178
+ 2,
179
+ [this](jsi::Runtime& rt2,
180
+ const jsi::Value& /*thisVal*/,
181
+ const jsi::Value* args,
182
+ size_t count) -> jsi::Value {
183
+ if (count < 2) return jsi::Value::null();
184
+ const float dx = static_cast<float>(args[0].asNumber());
185
+ const float dy = static_cast<float>(args[1].asNumber());
186
+ vroom_chart_translate(chart_, dx, dy);
187
+ return wrapPicture(rt2, render_chart_picture(chart_));
188
+ });
189
+ }
190
+
191
+ if (name == "zoom") {
192
+ // zoom(scaleX, scaleY, fx, fy) -> JsiSkPicture
193
+ // Per-axis multiplicative factors since the last call (> 1 = zoom in).
194
+ // scaleX scales the time window around fx; scaleY scales price around fy.
195
+ return jsi::Function::createFromHostFunction(
196
+ rt,
197
+ jsi::PropNameID::forAscii(rt, "zoom"),
198
+ 4,
199
+ [this](jsi::Runtime& rt2,
200
+ const jsi::Value& /*thisVal*/,
201
+ const jsi::Value* args,
202
+ size_t count) -> jsi::Value {
203
+ if (count < 4) return jsi::Value::null();
204
+ const float sx = static_cast<float>(args[0].asNumber());
205
+ const float sy = static_cast<float>(args[1].asNumber());
206
+ const float fx = static_cast<float>(args[2].asNumber());
207
+ const float fy = static_cast<float>(args[3].asNumber());
208
+ vroom_chart_zoom(chart_, sx, sy, fx, fy);
209
+ return wrapPicture(rt2, render_chart_picture(chart_));
210
+ });
211
+ }
212
+
213
+ if (name == "scalePriceAxis") {
214
+ return jsi::Function::createFromHostFunction(
215
+ rt,
216
+ jsi::PropNameID::forAscii(rt, "scalePriceAxis"),
217
+ 1,
218
+ [this](jsi::Runtime& rt2,
219
+ const jsi::Value& /*thisVal*/,
220
+ const jsi::Value* args,
221
+ size_t count) -> jsi::Value {
222
+ if (count < 1) return jsi::Value::null();
223
+ const float dy = static_cast<float>(args[0].asNumber());
224
+ vroom_chart_scale_price_axis(chart_, dy);
225
+ return wrapPicture(rt2, render_chart_picture(chart_));
226
+ });
227
+ }
228
+
229
+ if (name == "scaleTimeAxis") {
230
+ return jsi::Function::createFromHostFunction(
231
+ rt,
232
+ jsi::PropNameID::forAscii(rt, "scaleTimeAxis"),
233
+ 1,
234
+ [this](jsi::Runtime& rt2,
235
+ const jsi::Value& /*thisVal*/,
236
+ const jsi::Value* args,
237
+ size_t count) -> jsi::Value {
238
+ if (count < 1) return jsi::Value::null();
239
+ const float dx = static_cast<float>(args[0].asNumber());
240
+ vroom_chart_scale_time_axis(chart_, dx);
241
+ return wrapPicture(rt2, render_chart_picture(chart_));
242
+ });
243
+ }
244
+
245
+ if (name == "getAxisMetrics") {
246
+ return jsi::Function::createFromHostFunction(
247
+ rt,
248
+ jsi::PropNameID::forAscii(rt, "getAxisMetrics"),
249
+ 0,
250
+ [this](jsi::Runtime& rt2,
251
+ const jsi::Value& /*thisVal*/,
252
+ const jsi::Value* /*args*/,
253
+ size_t /*count*/) -> jsi::Value {
254
+ float yw = 0.f, xh = 0.f, ih = 0.f;
255
+ vroom_chart_get_axis_metrics(chart_, &yw, &xh, &ih);
256
+ jsi::Object obj(rt2);
257
+ obj.setProperty(rt2, "yAxisWidth", static_cast<double>(yw));
258
+ obj.setProperty(rt2, "xAxisHeight", static_cast<double>(xh));
259
+ obj.setProperty(rt2, "indicatorHeight", static_cast<double>(ih));
260
+ return obj;
261
+ });
262
+ }
263
+
264
+ if (name == "isAnimating") {
265
+ return jsi::Function::createFromHostFunction(
266
+ rt,
267
+ jsi::PropNameID::forAscii(rt, "isAnimating"),
268
+ 0,
269
+ [this](jsi::Runtime& /*rt2*/,
270
+ const jsi::Value& /*thisVal*/,
271
+ const jsi::Value* /*args*/,
272
+ size_t /*count*/) -> jsi::Value {
273
+ return jsi::Value(is_animating(chart_));
274
+ });
275
+ }
276
+
277
+ if (name == "setCrosshair") {
278
+ // setCrosshair(x, y) -> JsiSkPicture. Activates the crosshair at (x, y) in
279
+ // pixels (y already lifted above the touch on the JS side) and renders.
280
+ return jsi::Function::createFromHostFunction(
281
+ rt,
282
+ jsi::PropNameID::forAscii(rt, "setCrosshair"),
283
+ 2,
284
+ [this](jsi::Runtime& rt2,
285
+ const jsi::Value& /*thisVal*/,
286
+ const jsi::Value* args,
287
+ size_t count) -> jsi::Value {
288
+ if (count < 2) return jsi::Value::null();
289
+ const float x = static_cast<float>(args[0].asNumber());
290
+ const float y = static_cast<float>(args[1].asNumber());
291
+ vroom_chart_set_crosshair(chart_, x, y);
292
+ return wrapPicture(rt2, render_chart_picture(chart_));
293
+ });
294
+ }
295
+
296
+ if (name == "clearCrosshair") {
297
+ // clearCrosshair() -> JsiSkPicture. Hides the crosshair and renders.
298
+ return jsi::Function::createFromHostFunction(
299
+ rt,
300
+ jsi::PropNameID::forAscii(rt, "clearCrosshair"),
301
+ 0,
302
+ [this](jsi::Runtime& rt2,
303
+ const jsi::Value& /*thisVal*/,
304
+ const jsi::Value* /*args*/,
305
+ size_t /*count*/) -> jsi::Value {
306
+ vroom_chart_clear_crosshair(chart_);
307
+ return wrapPicture(rt2, render_chart_picture(chart_));
308
+ });
309
+ }
310
+
311
+ if (name == "getCrosshairCandle") {
312
+ // getCrosshairCandle() -> { timeMs, open, high, low, close, volume } | null.
313
+ // The OHLCV of the candle the crosshair currently snaps to; null when the
314
+ // crosshair is inactive. Cheap to call at gesture rate — no rendering.
315
+ return jsi::Function::createFromHostFunction(
316
+ rt,
317
+ jsi::PropNameID::forAscii(rt, "getCrosshairCandle"),
318
+ 0,
319
+ [this](jsi::Runtime& rt2,
320
+ const jsi::Value& /*thisVal*/,
321
+ const jsi::Value* /*args*/,
322
+ size_t /*count*/) -> jsi::Value {
323
+ VroomCandle c{};
324
+ if (!vroom_chart_get_crosshair_candle(chart_, &c)) {
325
+ return jsi::Value::null();
326
+ }
327
+ jsi::Object obj(rt2);
328
+ // time_ms fits in 53 bits well past year 285,000, so Number is exact.
329
+ obj.setProperty(rt2, "timeMs", static_cast<double>(c.time_ms));
330
+ obj.setProperty(rt2, "open", c.open);
331
+ obj.setProperty(rt2, "high", c.high);
332
+ obj.setProperty(rt2, "low", c.low);
333
+ obj.setProperty(rt2, "close", c.close);
334
+ obj.setProperty(rt2, "volume", c.volume);
335
+ return obj;
336
+ });
337
+ }
338
+
339
+ if (name == "setRSI") {
340
+ // setRSI(enabled, period, upperBand, lowerBand, maEnabled, maPeriod) —
341
+ // configures the RSI pane. No render; the next render() picks it up.
342
+ return jsi::Function::createFromHostFunction(
343
+ rt,
344
+ jsi::PropNameID::forAscii(rt, "setRSI"),
345
+ 6,
346
+ [this](jsi::Runtime& /*rt2*/,
347
+ const jsi::Value& /*thisVal*/,
348
+ const jsi::Value* args,
349
+ size_t count) -> jsi::Value {
350
+ if (count < 6) return jsi::Value::undefined();
351
+ const bool enabled = args[0].asBool();
352
+ const int period = static_cast<int>(args[1].asNumber());
353
+ const double upper = args[2].asNumber();
354
+ const double lower = args[3].asNumber();
355
+ const bool ma_enabled = args[4].asBool();
356
+ const int ma_period = static_cast<int>(args[5].asNumber());
357
+ vroom_chart_set_rsi(chart_, enabled, period, upper, lower, ma_enabled,
358
+ ma_period);
359
+ return jsi::Value::undefined();
360
+ });
361
+ }
362
+
363
+ if (name == "setMACD") {
364
+ // setMACD(enabled, fast, slow, signal) — configures the MACD pane. No
365
+ // render; the next render() picks it up.
366
+ return jsi::Function::createFromHostFunction(
367
+ rt,
368
+ jsi::PropNameID::forAscii(rt, "setMACD"),
369
+ 4,
370
+ [this](jsi::Runtime& /*rt2*/,
371
+ const jsi::Value& /*thisVal*/,
372
+ const jsi::Value* args,
373
+ size_t count) -> jsi::Value {
374
+ if (count < 4) return jsi::Value::undefined();
375
+ const bool enabled = args[0].asBool();
376
+ const int fast = static_cast<int>(args[1].asNumber());
377
+ const int slow = static_cast<int>(args[2].asNumber());
378
+ const int signal = static_cast<int>(args[3].asNumber());
379
+ vroom_chart_set_macd(chart_, enabled, fast, slow, signal);
380
+ return jsi::Value::undefined();
381
+ });
382
+ }
383
+
384
+ if (name == "setOverlays") {
385
+ // setOverlays([{ kind, period, source, color, width }, ...]) — replaces the
386
+ // full set of MA/EMA overlay lines. No render; the next render() picks it up.
387
+ return jsi::Function::createFromHostFunction(
388
+ rt,
389
+ jsi::PropNameID::forAscii(rt, "setOverlays"),
390
+ 1,
391
+ [this](jsi::Runtime& rt2,
392
+ const jsi::Value& /*thisVal*/,
393
+ const jsi::Value* args,
394
+ size_t count) -> jsi::Value {
395
+ if (count < 1 || !args[0].isObject()) return jsi::Value::undefined();
396
+ auto obj = args[0].asObject(rt2);
397
+ if (!obj.isArray(rt2)) return jsi::Value::undefined();
398
+ auto arr = obj.asArray(rt2);
399
+ const size_t len = arr.size(rt2);
400
+ std::vector<VroomOverlay> overlays;
401
+ overlays.reserve(len);
402
+ for (size_t i = 0; i < len; ++i) {
403
+ auto o = arr.getValueAtIndex(rt2, i).asObject(rt2);
404
+ VroomOverlay ov;
405
+ ov.kind = static_cast<int32_t>(
406
+ o.getProperty(rt2, "kind").asNumber());
407
+ ov.period = static_cast<int32_t>(
408
+ o.getProperty(rt2, "period").asNumber());
409
+ ov.source = static_cast<int32_t>(
410
+ o.getProperty(rt2, "source").asNumber());
411
+ ov.color = static_cast<uint32_t>(
412
+ o.getProperty(rt2, "color").asNumber());
413
+ ov.width = static_cast<float>(
414
+ o.getProperty(rt2, "width").asNumber());
415
+ overlays.push_back(ov);
416
+ }
417
+ vroom_chart_set_overlays(chart_, overlays.data(), overlays.size());
418
+ return jsi::Value::undefined();
419
+ });
420
+ }
421
+
422
+ if (name == "setVWAP") {
423
+ // setVWAP(enabled, resetOffsetMin, color, width) — session VWAP overlay.
424
+ return jsi::Function::createFromHostFunction(
425
+ rt,
426
+ jsi::PropNameID::forAscii(rt, "setVWAP"),
427
+ 4,
428
+ [this](jsi::Runtime& /*rt2*/,
429
+ const jsi::Value& /*thisVal*/,
430
+ const jsi::Value* args,
431
+ size_t count) -> jsi::Value {
432
+ if (count < 4) return jsi::Value::undefined();
433
+ const bool enabled = args[0].asBool();
434
+ const int reset = static_cast<int>(args[1].asNumber());
435
+ const uint32_t color = static_cast<uint32_t>(args[2].asNumber());
436
+ const float width = static_cast<float>(args[3].asNumber());
437
+ vroom_chart_set_vwap(chart_, enabled, reset, color, width);
438
+ return jsi::Value::undefined();
439
+ });
440
+ }
441
+
442
+ if (name == "render") {
443
+ return jsi::Function::createFromHostFunction(
444
+ rt,
445
+ jsi::PropNameID::forAscii(rt, "render"),
446
+ 0,
447
+ [this](jsi::Runtime& rt2,
448
+ const jsi::Value& /*thisVal*/,
449
+ const jsi::Value* /*args*/,
450
+ size_t /*count*/) -> jsi::Value {
451
+ return wrapPicture(rt2, render_chart_picture(chart_));
452
+ });
453
+ }
454
+
455
+ return jsi::Value::undefined();
456
+ }
457
+
458
+ } // namespace vroom
@@ -0,0 +1,36 @@
1
+ // Per-chart JSI HostObject. One instance is created by
2
+ // VroomChartJSI.create() and held by the React component. When the JS object
3
+ // is garbage collected, this destructor runs and deletes the underlying
4
+ // VroomChart, freeing the candle buffer and any cached pictures.
5
+ //
6
+ // Methods exposed to JS:
7
+ // setCandles(arrayBuffer) — copies packed VroomCandle[] into the core
8
+ // setSize(width, height, pxRatio)
9
+ // render() -> JsiSkPicture — returns the current chart picture
10
+
11
+ #pragma once
12
+
13
+ #include <memory>
14
+
15
+ #include <jsi/jsi.h>
16
+
17
+ struct VroomChart;
18
+
19
+ namespace vroom {
20
+
21
+ class ChartHostObject : public facebook::jsi::HostObject {
22
+ public:
23
+ ChartHostObject();
24
+ ~ChartHostObject() override;
25
+
26
+ facebook::jsi::Value get(facebook::jsi::Runtime& runtime,
27
+ const facebook::jsi::PropNameID& name) override;
28
+
29
+ std::vector<facebook::jsi::PropNameID> getPropertyNames(
30
+ facebook::jsi::Runtime& runtime) override;
31
+
32
+ private:
33
+ VroomChart* chart_ = nullptr;
34
+ };
35
+
36
+ } // namespace vroom
@@ -0,0 +1,69 @@
1
+ // Installs global.VroomChartJSI on the JSI runtime.
2
+ //
3
+ // API:
4
+ // VroomChartJSI.create() -> ChartHostObject
5
+ // methods on the returned object:
6
+ // setCandles(arrayBuffer)
7
+ // setSize(w, h, pxRatio)
8
+ // render() -> JsiSkPicture
9
+ //
10
+ // The host object's lifetime is managed by JS GC — when the React component
11
+ // drops its reference, the underlying VroomChart is destroyed.
12
+
13
+ #include "VroomJsiInstaller.h"
14
+
15
+ #include <memory>
16
+
17
+ #include "VroomChartHostObject.h"
18
+ #include "VroomSkiaContext.h"
19
+
20
+ #include "chart_internal.h"
21
+
22
+ #pragma clang diagnostic push
23
+ #pragma clang diagnostic ignored "-Wdocumentation"
24
+ #include "include/core/SkFontMgr.h"
25
+ #include "include/core/SkFontStyle.h"
26
+ #include "include/core/SkTypeface.h"
27
+ #pragma clang diagnostic pop
28
+
29
+ namespace vroom {
30
+
31
+ namespace jsi = facebook::jsi;
32
+
33
+ // Loads the system typeface once via RN-Skia's platform context and hands
34
+ // it to the chart core. Retries on each create() until it succeeds (in case
35
+ // SkiaApi isn't installed yet when our first chart instance is created).
36
+ static void ensureAxisTypeface(jsi::Runtime& runtime) {
37
+ static bool done = false;
38
+ if (done) return;
39
+ auto ctx = vroom::getRNSkContext(runtime);
40
+ if (!ctx) return;
41
+ auto mgr = ctx->createFontMgr();
42
+ if (!mgr) return;
43
+ auto tf = mgr->matchFamilyStyle(nullptr, SkFontStyle()); // system default
44
+ if (!tf) return;
45
+ vroom::set_axis_typeface(tf);
46
+ done = true;
47
+ }
48
+
49
+ void installJsi(jsi::Runtime& runtime) {
50
+ auto create = jsi::Function::createFromHostFunction(
51
+ runtime,
52
+ jsi::PropNameID::forAscii(runtime, "create"),
53
+ 0,
54
+ [](jsi::Runtime& rt,
55
+ const jsi::Value& /*thisVal*/,
56
+ const jsi::Value* /*args*/,
57
+ size_t /*count*/) -> jsi::Value {
58
+ ensureAxisTypeface(rt);
59
+ auto host = std::make_shared<ChartHostObject>();
60
+ return jsi::Object::createFromHostObject(rt, host);
61
+ });
62
+
63
+ jsi::Object api(runtime);
64
+ api.setProperty(runtime, "create", std::move(create));
65
+
66
+ runtime.global().setProperty(runtime, "VroomChartJSI", std::move(api));
67
+ }
68
+
69
+ } // namespace vroom
@@ -0,0 +1,10 @@
1
+ #pragma once
2
+
3
+ #include <jsi/jsi.h>
4
+
5
+ namespace vroom {
6
+
7
+ // Installs `global.VroomChartJSI` on the given runtime. Idempotent.
8
+ void installJsi(facebook::jsi::Runtime& runtime);
9
+
10
+ } // namespace vroom
@@ -0,0 +1,25 @@
1
+ #include "VroomSkiaContext.h"
2
+
3
+ namespace vroom {
4
+
5
+ namespace jsi = facebook::jsi;
6
+
7
+ std::shared_ptr<RNSkia::RNSkPlatformContext> getRNSkContext(
8
+ jsi::Runtime& runtime) {
9
+ auto val = runtime.global().getProperty(runtime, "SkiaApi");
10
+ if (!val.isObject()) return nullptr;
11
+ auto obj = val.asObject(runtime);
12
+ if (!obj.isHostObject(runtime)) return nullptr;
13
+ auto host = obj.asHostObject(runtime);
14
+
15
+ auto base = std::dynamic_pointer_cast<RNSkia::JsiSkHostObject>(host);
16
+ if (!base) return nullptr;
17
+
18
+ // The accessor class adds no fields; static_cast is safe even though the
19
+ // runtime type is JsiSkApi (a different JsiSkHostObject subclass). We only
20
+ // call the inherited non-virtual getContext() through this view.
21
+ auto* accessor = static_cast<HostObjectAccessor*>(base.get());
22
+ return accessor->getContext();
23
+ }
24
+
25
+ } // namespace vroom