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.
- package/LICENSE +21 -0
- package/README.md +27 -0
- package/android/README.md +7 -0
- package/cpp/README.md +11 -0
- package/cpp/VroomChartHostObject.cpp +458 -0
- package/cpp/VroomChartHostObject.h +36 -0
- package/cpp/VroomJsiInstaller.cpp +69 -0
- package/cpp/VroomJsiInstaller.h +10 -0
- package/cpp/VroomSkiaContext.cpp +25 -0
- package/cpp/VroomSkiaContext.h +30 -0
- package/cpp/_core_include/vroom/vroom_chart.h +195 -0
- package/cpp/_core_src/candles.cpp +74 -0
- package/cpp/_core_src/candles.h +34 -0
- package/cpp/_core_src/chart.cpp +317 -0
- package/cpp/_core_src/chart.h +167 -0
- package/cpp/_core_src/chart_facade.cpp +570 -0
- package/cpp/_core_src/chart_internal.h +30 -0
- package/cpp/_core_src/crosshair.cpp +65 -0
- package/cpp/_core_src/crosshair.h +32 -0
- package/cpp/_core_src/fonts.cpp +22 -0
- package/cpp/_core_src/fonts.h +25 -0
- package/cpp/_core_src/labels.cpp +340 -0
- package/cpp/_core_src/labels.h +85 -0
- package/cpp/_core_src/ma.cpp +53 -0
- package/cpp/_core_src/ma.h +39 -0
- package/cpp/_core_src/ma_overlay.cpp +73 -0
- package/cpp/_core_src/ma_overlay.h +42 -0
- package/cpp/_core_src/macd.cpp +68 -0
- package/cpp/_core_src/macd.h +29 -0
- package/cpp/_core_src/macd_pane.cpp +199 -0
- package/cpp/_core_src/macd_pane.h +41 -0
- package/cpp/_core_src/price_indicator.cpp +106 -0
- package/cpp/_core_src/price_indicator.h +29 -0
- package/cpp/_core_src/rsi.cpp +70 -0
- package/cpp/_core_src/rsi.h +32 -0
- package/cpp/_core_src/rsi_pane.cpp +167 -0
- package/cpp/_core_src/rsi_pane.h +39 -0
- package/cpp/_core_src/theme.cpp +41 -0
- package/cpp/_core_src/theme.h +23 -0
- package/cpp/_core_src/ticks.cpp +49 -0
- package/cpp/_core_src/ticks.h +30 -0
- package/cpp/_core_src/viewport.cpp +141 -0
- package/cpp/_core_src/viewport.h +100 -0
- package/cpp/_core_src/volume.cpp +70 -0
- package/cpp/_core_src/volume.h +34 -0
- package/cpp/_core_src/vwap.cpp +51 -0
- package/cpp/_core_src/vwap.h +27 -0
- package/ios/README.md +7 -0
- package/ios/VroomChartModule.h +11 -0
- package/ios/VroomChartModule.mm +44 -0
- package/lib/index.d.mts +185 -0
- package/lib/index.d.ts +185 -0
- package/lib/index.js +429 -0
- package/lib/index.js.map +1 -0
- package/lib/index.mjs +396 -0
- package/lib/index.mjs.map +1 -0
- package/package.json +75 -0
- package/react-native-vroom-chart.podspec +79 -0
- package/src/NativeVroomChart.ts +12 -0
- package/src/VroomChart.tsx +382 -0
- package/src/index.ts +14 -0
- package/src/jsi.d.ts +128 -0
- package/src/packCandles.ts +24 -0
- package/src/theme.ts +43 -0
- package/src/types.ts +27 -0
- 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,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
|