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.
- package/cpp/VroomChartHostObject.cpp +150 -32
- package/cpp/_core_include/vroom/vroom_chart.h +180 -22
- package/cpp/_core_src/bollinger.h +1 -1
- package/cpp/_core_src/candles.cpp +138 -41
- package/cpp/_core_src/candles.h +11 -1
- package/cpp/_core_src/chart.cpp +91 -40
- package/cpp/_core_src/chart.h +55 -20
- package/cpp/_core_src/chart_facade.cpp +206 -66
- package/cpp/_core_src/gradient.cpp +46 -0
- package/cpp/_core_src/gradient.h +31 -0
- package/cpp/_core_src/labels.cpp +49 -16
- package/cpp/_core_src/labels.h +33 -4
- package/cpp/_core_src/liquidity.cpp +3 -37
- package/cpp/_core_src/ma_overlay.cpp +174 -12
- package/cpp/_core_src/ma_overlay.h +55 -3
- package/cpp/_core_src/macd.cpp +13 -42
- package/cpp/_core_src/macd.h +11 -8
- package/cpp/_core_src/macd_pane.cpp +57 -27
- package/cpp/_core_src/price_line_layout.h +1 -1
- package/cpp/_core_src/rsi.cpp +4 -18
- package/cpp/_core_src/rsi.h +6 -6
- package/cpp/_core_src/rsi_pane.cpp +34 -19
- package/cpp/_core_src/series_ma.cpp +64 -0
- package/cpp/_core_src/series_ma.h +30 -0
- package/cpp/_core_src/style_inherit.h +35 -0
- package/cpp/_core_src/theme.cpp +2 -1
- package/cpp/_core_src/viewport.cpp +36 -12
- package/cpp/_core_src/viewport.h +50 -0
- package/cpp/_core_src/volume.cpp +33 -8
- package/cpp/_core_src/volume.h +12 -1
- package/cpp/_core_src/volume_anim.cpp +32 -0
- package/cpp/_core_src/volume_anim.h +41 -0
- package/lib/index.d.mts +161 -28
- package/lib/index.d.ts +161 -28
- package/lib/index.js +156 -31
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +156 -31
- package/lib/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/VroomChart.tsx +69 -3
- package/src/easing.ts +40 -0
- package/src/index.ts +3 -0
- package/src/jsi.d.ts +76 -20
- package/src/theme.ts +1 -0
- package/src/types.ts +3 -0
- package/src/useChartCore.ts +103 -27
package/cpp/_core_src/chart.h
CHANGED
|
@@ -64,6 +64,21 @@ struct VroomChart {
|
|
|
64
64
|
float morph_collapse = 0.f;
|
|
65
65
|
float morph_fade = 0.f;
|
|
66
66
|
|
|
67
|
+
// Interval morph: the outgoing candle geometry captured when a timeframe
|
|
68
|
+
// switch begins, indexed from the right of the visible slice (slot 0 =
|
|
69
|
+
// newest) — the pairing the preserved slot grid guarantees. Stored as
|
|
70
|
+
// normalized fractions so it survives the new bounds and a resize.
|
|
71
|
+
// Empty when not morphing.
|
|
72
|
+
std::vector<vroom::CandleSnapshot> morph_from;
|
|
73
|
+
float interval_morph_t = 1.f; // 1 = not morphing
|
|
74
|
+
// The pre-switch price scale and time window. The candle capture above is
|
|
75
|
+
// normalized against these, and the axes keep rendering their old ticks from
|
|
76
|
+
// them while fading out (see labels::interval_phase), so a label is never
|
|
77
|
+
// drawn at a position it didn't already occupy.
|
|
78
|
+
vroom::PriceBounds morph_from_bounds{0.0, 1.0};
|
|
79
|
+
int64_t morph_from_start_ms = 0;
|
|
80
|
+
int64_t morph_from_end_ms = 0;
|
|
81
|
+
|
|
67
82
|
// Cached y-axis width in pixels, sized to fit the widest formatted price
|
|
68
83
|
// label. 0 = uncomputed; layout() falls back to a width ratio.
|
|
69
84
|
float axis_width_px = 0.f;
|
|
@@ -86,24 +101,27 @@ struct VroomChart {
|
|
|
86
101
|
// RSI, drawn in a pane below the candles. rsi_cache is aligned to `candles`
|
|
87
102
|
// (one value per candle, NaN where undefined) and recomputed lazily by
|
|
88
103
|
// ensure_rsi() when rsi_dirty (set on any data or config change).
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
104
|
+
// Defaults: 14-period RSI with 70/30 bands and a 14-period SMA trendline,
|
|
105
|
+
// every style field on its inherit sentinel.
|
|
106
|
+
VroomRSI rsi{0, 14, 70.0, 30.0, 14, 0, 1,
|
|
107
|
+
0u, -1.f, 1, // RSI line
|
|
108
|
+
0u, -1.f, // trendline
|
|
109
|
+
0u, 1}; // band rules
|
|
95
110
|
std::vector<double> rsi_cache; // RSI per candle (NaN where undefined)
|
|
96
|
-
std::vector<double> rsi_ma_cache; //
|
|
111
|
+
std::vector<double> rsi_ma_cache; // MA of rsi_cache (empty if MA off)
|
|
97
112
|
bool rsi_dirty = true;
|
|
98
113
|
// User y-axis zoom for the RSI pane (drag on its y-axis strip). 1.0 = the
|
|
99
114
|
// default 0..100 fit; >1 zooms in (taller), <1 zooms out. Anchored at 50.
|
|
100
115
|
double rsi_y_scale = 1.0;
|
|
101
116
|
|
|
102
117
|
// MACD, drawn in its own pane. Caches aligned to `candles` (NaN warmup).
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
118
|
+
// Defaults: 12/26/9 EMAs of close, every style field on its inherit
|
|
119
|
+
// sentinel so an untouched chart keeps the stock look.
|
|
120
|
+
VroomMACD macd{0, 12, 26, 9, 0, 1, 1,
|
|
121
|
+
0u, -1.f, 1, // MACD line
|
|
122
|
+
0u, -1.f, 1, // signal line
|
|
123
|
+
1, 0u, 0u, 0u, 0u, // histogram
|
|
124
|
+
0u, 1}; // zero line
|
|
107
125
|
std::vector<double> macd_cache;
|
|
108
126
|
std::vector<double> macd_signal_cache;
|
|
109
127
|
std::vector<double> macd_hist_cache;
|
|
@@ -128,10 +146,8 @@ struct VroomChart {
|
|
|
128
146
|
|
|
129
147
|
// VWAP overlay (session anchor, configurable reset time). Single line on the
|
|
130
148
|
// price pane; vwap_breaks marks session resets so the line lifts the pen.
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
uint32_t vwap_color = 0xff00bcd4; // cyan
|
|
134
|
-
float vwap_width = 1.5f;
|
|
149
|
+
// Style fields sit on their inherit sentinel (cyan, 1.5px).
|
|
150
|
+
VroomVWAP vwap{0, 0, 0u, -1.f};
|
|
135
151
|
std::vector<double> vwap_cache;
|
|
136
152
|
std::vector<unsigned char> vwap_breaks;
|
|
137
153
|
bool vwap_dirty = true;
|
|
@@ -150,6 +166,18 @@ struct VroomChart {
|
|
|
150
166
|
std::vector<double> bb_lower_cache;
|
|
151
167
|
bool bollinger_dirty = true;
|
|
152
168
|
|
|
169
|
+
// Volume bars (price pane, under the candles). On by default with every
|
|
170
|
+
// style field left on its inherit sentinel, so an untouched chart looks
|
|
171
|
+
// exactly as it did before the config existed. No cache — bar heights come
|
|
172
|
+
// straight off the visible candles' volume.
|
|
173
|
+
VroomVolume volume{1, -1.f, -1.f, -1.f, 0u, 0u};
|
|
174
|
+
// Staggered collapse of those bars, driven per-frame by the host animation
|
|
175
|
+
// loop: 0 = full height, 1 = all flat. Doubles as the visibility gate, since
|
|
176
|
+
// a fully collapsed chart draws nothing — set_volume snaps it to match
|
|
177
|
+
// `volume.enabled`, and the host overrides it while animating.
|
|
178
|
+
float volume_collapse_t = 0.f;
|
|
179
|
+
int32_t volume_collapse_easing = VROOM_EASING_IN_OUT;
|
|
180
|
+
|
|
153
181
|
// --- drawings (annotations) --------------------------------------------
|
|
154
182
|
// Committed drawings, anchored in data space so they track the candles on
|
|
155
183
|
// pan/zoom. Drawn on the price pane above candles/overlays.
|
|
@@ -235,9 +263,9 @@ struct VroomChart {
|
|
|
235
263
|
std::chrono::steady_clock::time_point last_anim_tick{};
|
|
236
264
|
bool anim_started = false;
|
|
237
265
|
|
|
238
|
-
// dt of the current
|
|
239
|
-
//
|
|
240
|
-
//
|
|
266
|
+
// dt of the current frame — populated by begin_frame and consumed by the
|
|
267
|
+
// label-fade updaters. Public so the labels namespace can read it without a
|
|
268
|
+
// getter.
|
|
241
269
|
float last_dt_seconds = 0.f;
|
|
242
270
|
|
|
243
271
|
// --- methods ------------------------------------------------------------
|
|
@@ -267,11 +295,18 @@ struct VroomChart {
|
|
|
267
295
|
// indicator is enabled.
|
|
268
296
|
void ensure_bollinger();
|
|
269
297
|
|
|
270
|
-
// The main drawing pass. Calls into the labels and candles modules
|
|
298
|
+
// The main drawing pass. Calls into the labels and candles modules, and owns
|
|
299
|
+
// the per-frame animation clock (see begin_frame) so every host gets it —
|
|
300
|
+
// the web build draws straight through vroom_chart_draw rather than the
|
|
301
|
+
// SkPicture cache below.
|
|
271
302
|
void draw_chart(SkCanvas* canvas);
|
|
272
303
|
|
|
304
|
+
// Per-frame animation bookkeeping: computes `last_dt_seconds`, which paces
|
|
305
|
+
// the label fades. Called at the top of draw_chart.
|
|
306
|
+
void begin_frame();
|
|
307
|
+
|
|
273
308
|
// Re-records `chart_picture` by invoking draw_chart on a fresh
|
|
274
|
-
// SkPictureRecorder.
|
|
309
|
+
// SkPictureRecorder.
|
|
275
310
|
void rebuild_chart_picture();
|
|
276
311
|
|
|
277
312
|
// True if any axis label is mid-fade. Used by the JS-side animation loop
|
|
@@ -63,6 +63,13 @@ int64_t damp_future_delta(int64_t delta_ms, int64_t cur_future,
|
|
|
63
63
|
void apply_default_framing(VroomChart* chart) {
|
|
64
64
|
if (chart->candles.empty()) return;
|
|
65
65
|
|
|
66
|
+
// A candle occupies the slot [time_ms, time_ms + duration) and draws centered
|
|
67
|
+
// in it, so the right edge of the view is the last slot's *end*. Pinning it to
|
|
68
|
+
// the last candle's time_ms instead puts that candle's center half a slot past
|
|
69
|
+
// the plot area, hiding the newest candle behind the y-axis strip.
|
|
70
|
+
const int64_t right_edge_ms =
|
|
71
|
+
chart->candles.back().time_ms + chart->candle_duration_ms;
|
|
72
|
+
|
|
66
73
|
// Px-driven framing. Reuses the inversion from vroom_chart_scale_time_axis:
|
|
67
74
|
// body_w = usable × (dur / window) × ratio → window = usable × dur × ratio / body_w.
|
|
68
75
|
// Needs a valid layout (width_px); until size is known it falls through to
|
|
@@ -81,7 +88,7 @@ void apply_default_framing(VroomChart* chart) {
|
|
|
81
88
|
chart->candles.back().time_ms - chart->candles.front().time_ms;
|
|
82
89
|
window_ms = std::clamp<int64_t>(window_ms, chart->candle_duration_ms,
|
|
83
90
|
span > 0 ? span : window_ms);
|
|
84
|
-
chart->visible_end_ms =
|
|
91
|
+
chart->visible_end_ms = right_edge_ms;
|
|
85
92
|
chart->visible_start_ms = chart->visible_end_ms - window_ms;
|
|
86
93
|
return;
|
|
87
94
|
}
|
|
@@ -92,7 +99,7 @@ void apply_default_framing(VroomChart* chart) {
|
|
|
92
99
|
? chart->candles.size() - kDefaultVisible
|
|
93
100
|
: 0;
|
|
94
101
|
chart->visible_start_ms = chart->candles[start_idx].time_ms;
|
|
95
|
-
chart->visible_end_ms =
|
|
102
|
+
chart->visible_end_ms = right_edge_ms;
|
|
96
103
|
}
|
|
97
104
|
|
|
98
105
|
// On the first manual-y gesture, adopt the on-screen auto-fit bounds so the
|
|
@@ -242,6 +249,10 @@ extern "C" void vroom_chart_reset_view(VroomChart* chart) {
|
|
|
242
249
|
chart->visible_start_ms = 0;
|
|
243
250
|
chart->visible_end_ms = 0;
|
|
244
251
|
chart->price_bounds_manual = false;
|
|
252
|
+
// An asset switch reframes wholesale, so a slot-paired morph is meaningless
|
|
253
|
+
// here — drop any capture rather than leaving it to reshape the wrong data.
|
|
254
|
+
chart->morph_from.clear();
|
|
255
|
+
chart->interval_morph_t = 1.f;
|
|
245
256
|
apply_default_framing(chart);
|
|
246
257
|
vroom::labels::recompute_axis_width(*chart);
|
|
247
258
|
if (chart->cb.on_viewport_changed) {
|
|
@@ -258,6 +269,104 @@ extern "C" void vroom_chart_reset_price_scale(VroomChart* chart) {
|
|
|
258
269
|
chart->mark_dirty();
|
|
259
270
|
}
|
|
260
271
|
|
|
272
|
+
extern "C" bool vroom_chart_get_visible_price_envelope(VroomChart* chart,
|
|
273
|
+
double* out_low,
|
|
274
|
+
double* out_high) {
|
|
275
|
+
if (!chart) return false;
|
|
276
|
+
const auto idx = vroom::visible_indices(
|
|
277
|
+
chart->candles.data(), chart->candles.size(),
|
|
278
|
+
chart->visible_start_ms, chart->visible_end_ms);
|
|
279
|
+
if (idx.end <= idx.start) return false;
|
|
280
|
+
const auto env = vroom::price_bounds(chart->candles.data() + idx.start,
|
|
281
|
+
idx.end - idx.start);
|
|
282
|
+
if (out_low) *out_low = env.min;
|
|
283
|
+
if (out_high) *out_high = env.max;
|
|
284
|
+
return true;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
extern "C" void vroom_chart_preserve_price_envelope(VroomChart* chart,
|
|
288
|
+
double prev_low,
|
|
289
|
+
double prev_high) {
|
|
290
|
+
// Auto mode already holds the envelope's pixel height constant (auto_price_
|
|
291
|
+
// bounds widens it by a fixed factor), so there is nothing to preserve.
|
|
292
|
+
if (!chart || !chart->price_bounds_manual) return;
|
|
293
|
+
|
|
294
|
+
double new_low = 0.0, new_high = 0.0;
|
|
295
|
+
const bool has_new =
|
|
296
|
+
vroom_chart_get_visible_price_envelope(chart, &new_low, &new_high);
|
|
297
|
+
if (!has_new || !(prev_high > prev_low) || !(new_high > new_low)) {
|
|
298
|
+
// Nothing to scale against — fall back to re-fitting the price scale.
|
|
299
|
+
vroom_chart_reset_price_scale(chart);
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
chart->price_bounds = vroom::preserve_envelope_bounds(
|
|
304
|
+
chart->price_bounds, {prev_low, prev_high}, {new_low, new_high});
|
|
305
|
+
vroom::labels::recompute_axis_width(*chart);
|
|
306
|
+
chart->mark_dirty();
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
extern "C" void vroom_chart_begin_interval_morph(VroomChart* chart) {
|
|
310
|
+
if (!chart) return;
|
|
311
|
+
chart->morph_from.clear();
|
|
312
|
+
chart->interval_morph_t = 1.f;
|
|
313
|
+
|
|
314
|
+
const auto lay = chart->layout();
|
|
315
|
+
const float area_w = vroom::candle_area_width(lay);
|
|
316
|
+
const int64_t window_ms = chart->visible_end_ms - chart->visible_start_ms;
|
|
317
|
+
if (area_w <= 0.f || window_ms <= 0) return;
|
|
318
|
+
|
|
319
|
+
const auto idx = vroom::visible_indices(
|
|
320
|
+
chart->candles.data(), chart->candles.size(),
|
|
321
|
+
chart->visible_start_ms, chart->visible_end_ms);
|
|
322
|
+
const std::size_t n = idx.end - idx.start;
|
|
323
|
+
if (n == 0) return;
|
|
324
|
+
const ::VroomCandle* visible = chart->candles.data() + idx.start;
|
|
325
|
+
|
|
326
|
+
const auto bounds = chart->price_bounds_manual
|
|
327
|
+
? chart->price_bounds
|
|
328
|
+
: vroom::auto_price_bounds(visible, n);
|
|
329
|
+
|
|
330
|
+
// Normalized so the capture is independent of both the price bounds (which
|
|
331
|
+
// the swap is about to replace) and the surface size (which may change
|
|
332
|
+
// mid-morph). Newest candle first, matching the slot indexing in
|
|
333
|
+
// candles::draw.
|
|
334
|
+
chart->morph_from.resize(n);
|
|
335
|
+
for (std::size_t k = 0; k < n; ++k) {
|
|
336
|
+
const auto& c = visible[n - 1 - k];
|
|
337
|
+
chart->morph_from[k] = vroom::CandleSnapshot{
|
|
338
|
+
vroom::candle_center_x(lay, c.time_ms, chart->candle_duration_ms,
|
|
339
|
+
chart->visible_start_ms, window_ms) / area_w,
|
|
340
|
+
static_cast<float>(vroom::price_fraction(bounds, c.open)),
|
|
341
|
+
static_cast<float>(vroom::price_fraction(bounds, c.high)),
|
|
342
|
+
static_cast<float>(vroom::price_fraction(bounds, c.low)),
|
|
343
|
+
static_cast<float>(vroom::price_fraction(bounds, c.close)),
|
|
344
|
+
c.close >= c.open,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// The scale the capture was taken against — the axes render their outgoing
|
|
349
|
+
// ticks from it, and it's about to be replaced on the chart itself.
|
|
350
|
+
chart->morph_from_bounds = bounds;
|
|
351
|
+
chart->morph_from_start_ms = chart->visible_start_ms;
|
|
352
|
+
chart->morph_from_end_ms = chart->visible_end_ms;
|
|
353
|
+
|
|
354
|
+
// Open the morph at 0 rather than leaving it at 1: the caller still has to
|
|
355
|
+
// push the new candles, and any frame painted in between should show the
|
|
356
|
+
// captured geometry — which is what the pre-switch frame looked like.
|
|
357
|
+
chart->interval_morph_t = 0.f;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
extern "C" void vroom_chart_set_interval_morph(VroomChart* chart, float t) {
|
|
361
|
+
if (!chart) return;
|
|
362
|
+
chart->interval_morph_t = std::clamp(t, 0.f, 1.f);
|
|
363
|
+
if (chart->interval_morph_t >= 1.f) {
|
|
364
|
+
chart->morph_from.clear();
|
|
365
|
+
chart->morph_from.shrink_to_fit();
|
|
366
|
+
}
|
|
367
|
+
chart->mark_dirty();
|
|
368
|
+
}
|
|
369
|
+
|
|
261
370
|
extern "C" void vroom_chart_pan(VroomChart* chart, float dx_px, float /*dy_px*/) {
|
|
262
371
|
if (!chart || dx_px == 0.f || chart->candles.empty()) return;
|
|
263
372
|
|
|
@@ -481,7 +590,7 @@ extern "C" void vroom_chart_scale_time_axis(VroomChart* chart, float dx_px) {
|
|
|
481
590
|
extern "C" void vroom_chart_resize_indicator_pane(VroomChart* chart, float dy_px) {
|
|
482
591
|
if (!chart || dy_px == 0.f) return;
|
|
483
592
|
|
|
484
|
-
const int pane_count = (chart->
|
|
593
|
+
const int pane_count = (chart->rsi.enabled ? 1 : 0) + (chart->macd.enabled ? 1 : 0);
|
|
485
594
|
if (pane_count == 0) return; // nothing below the chart to resize
|
|
486
595
|
|
|
487
596
|
const auto lay = chart->layout();
|
|
@@ -535,8 +644,8 @@ extern "C" void vroom_chart_scale_indicator_axis(VroomChart* chart, float y_px,
|
|
|
535
644
|
struct ActivePane { int order; int type; }; // type: 0 = RSI, 1 = MACD
|
|
536
645
|
ActivePane panes[2];
|
|
537
646
|
int count = 0;
|
|
538
|
-
if (chart->
|
|
539
|
-
if (chart->
|
|
647
|
+
if (chart->rsi.enabled) panes[count++] = {chart->rsi_order, 0};
|
|
648
|
+
if (chart->macd.enabled) panes[count++] = {chart->macd_order, 1};
|
|
540
649
|
if (count == 0) return;
|
|
541
650
|
if (count == 2 && panes[0].order > panes[1].order) {
|
|
542
651
|
const ActivePane tmp = panes[0];
|
|
@@ -1059,61 +1168,66 @@ extern "C" void vroom_chart_translate_drawing(VroomChart* chart, int32_t index,
|
|
|
1059
1168
|
|
|
1060
1169
|
// ---- Indicators -----------------------------------------------------------
|
|
1061
1170
|
|
|
1062
|
-
extern "C" void vroom_chart_set_rsi(VroomChart* chart,
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
const
|
|
1078
|
-
|
|
1079
|
-
|
|
1171
|
+
extern "C" void vroom_chart_set_rsi(VroomChart* chart, const VroomRSI* cfg) {
|
|
1172
|
+
if (!chart || !cfg) return;
|
|
1173
|
+
VroomRSI next = *cfg;
|
|
1174
|
+
next.enabled = next.enabled ? 1 : 0;
|
|
1175
|
+
next.ma_visible = next.ma_visible ? 1 : 0;
|
|
1176
|
+
next.line_visible = next.line_visible ? 1 : 0;
|
|
1177
|
+
next.bands_visible = next.bands_visible ? 1 : 0;
|
|
1178
|
+
if (next.period < 2) next.period = 2;
|
|
1179
|
+
if (next.ma_period < 1) next.ma_period = 1;
|
|
1180
|
+
next.upper_band = std::clamp(next.upper_band, 0.0, 100.0);
|
|
1181
|
+
next.lower_band = std::clamp(next.lower_band, 0.0, 100.0);
|
|
1182
|
+
|
|
1183
|
+
// Only the series-affecting fields force a recompute; band levels, color,
|
|
1184
|
+
// width, and visibility changes are render-only. The trendline is one of
|
|
1185
|
+
// them: it isn't computed while hidden, so revealing it needs a recompute.
|
|
1186
|
+
const VroomRSI& cur = chart->rsi;
|
|
1187
|
+
const bool recompute = cur.enabled != next.enabled ||
|
|
1188
|
+
cur.period != next.period ||
|
|
1189
|
+
cur.ma_visible != next.ma_visible ||
|
|
1190
|
+
cur.ma_period != next.ma_period ||
|
|
1191
|
+
cur.ma_kind != next.ma_kind;
|
|
1080
1192
|
|
|
1081
1193
|
// Pane order: claim the next slot on an off->on transition, release on off.
|
|
1082
|
-
if (enabled && !
|
|
1083
|
-
else if (!enabled) chart->rsi_order = -1;
|
|
1084
|
-
|
|
1085
|
-
chart->
|
|
1086
|
-
chart->rsi_period = period;
|
|
1087
|
-
chart->rsi_upper = upper_band;
|
|
1088
|
-
chart->rsi_lower = lower_band;
|
|
1089
|
-
chart->rsi_ma_enabled = ma_enabled;
|
|
1090
|
-
chart->rsi_ma_period = ma_period;
|
|
1194
|
+
if (next.enabled && !cur.enabled) chart->rsi_order = chart->pane_seq++;
|
|
1195
|
+
else if (!next.enabled) chart->rsi_order = -1;
|
|
1196
|
+
|
|
1197
|
+
chart->rsi = next;
|
|
1091
1198
|
if (recompute) chart->rsi_dirty = true;
|
|
1092
1199
|
chart->mark_dirty();
|
|
1093
1200
|
}
|
|
1094
1201
|
|
|
1095
|
-
extern "C" void vroom_chart_set_macd(VroomChart* chart,
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1202
|
+
extern "C" void vroom_chart_set_macd(VroomChart* chart, const VroomMACD* cfg) {
|
|
1203
|
+
if (!chart || !cfg) return;
|
|
1204
|
+
VroomMACD next = *cfg;
|
|
1205
|
+
next.enabled = next.enabled ? 1 : 0;
|
|
1206
|
+
next.line_visible = next.line_visible ? 1 : 0;
|
|
1207
|
+
next.signal_visible = next.signal_visible ? 1 : 0;
|
|
1208
|
+
next.hist_visible = next.hist_visible ? 1 : 0;
|
|
1209
|
+
next.zero_visible = next.zero_visible ? 1 : 0;
|
|
1210
|
+
if (next.fast < 1) next.fast = 1;
|
|
1211
|
+
if (next.slow < 1) next.slow = 1;
|
|
1212
|
+
if (next.slow <= next.fast) next.slow = next.fast + 1;
|
|
1213
|
+
if (next.signal < 1) next.signal = 1;
|
|
1214
|
+
|
|
1215
|
+
// Only the series-affecting fields force a recompute; color, width, and
|
|
1216
|
+
// visibility changes are render-only.
|
|
1217
|
+
const VroomMACD& cur = chart->macd;
|
|
1218
|
+
const bool recompute = cur.enabled != next.enabled ||
|
|
1219
|
+
cur.fast != next.fast ||
|
|
1220
|
+
cur.slow != next.slow ||
|
|
1221
|
+
cur.signal != next.signal ||
|
|
1222
|
+
cur.source != next.source ||
|
|
1223
|
+
cur.ma_kind != next.ma_kind ||
|
|
1224
|
+
cur.signal_ma_kind != next.signal_ma_kind;
|
|
1225
|
+
|
|
1226
|
+
if (next.enabled && !cur.enabled) chart->macd_order = chart->pane_seq++;
|
|
1227
|
+
else if (!next.enabled) chart->macd_order = -1;
|
|
1228
|
+
|
|
1229
|
+
chart->macd = next;
|
|
1230
|
+
if (recompute) chart->macd_dirty = true;
|
|
1117
1231
|
chart->mark_dirty();
|
|
1118
1232
|
}
|
|
1119
1233
|
|
|
@@ -1126,19 +1240,21 @@ extern "C" void vroom_chart_set_overlays(VroomChart* chart,
|
|
|
1126
1240
|
chart->mark_dirty();
|
|
1127
1241
|
}
|
|
1128
1242
|
|
|
1129
|
-
extern "C" void vroom_chart_set_vwap(VroomChart* chart,
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1243
|
+
extern "C" void vroom_chart_set_vwap(VroomChart* chart, const VroomVWAP* cfg) {
|
|
1244
|
+
if (!chart || !cfg) return;
|
|
1245
|
+
VroomVWAP next = *cfg;
|
|
1246
|
+
next.enabled = next.enabled ? 1 : 0;
|
|
1133
1247
|
// Keep the offset within a day [0, 1440).
|
|
1134
|
-
|
|
1135
|
-
if (
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1248
|
+
next.reset_offset_min %= 1440;
|
|
1249
|
+
if (next.reset_offset_min < 0) next.reset_offset_min += 1440;
|
|
1250
|
+
|
|
1251
|
+
// Only the series-affecting fields force a recompute; color and width
|
|
1252
|
+
// changes are render-only.
|
|
1253
|
+
const VroomVWAP& cur = chart->vwap;
|
|
1254
|
+
const bool recompute = cur.enabled != next.enabled ||
|
|
1255
|
+
cur.reset_offset_min != next.reset_offset_min;
|
|
1256
|
+
|
|
1257
|
+
chart->vwap = next;
|
|
1142
1258
|
if (recompute) chart->vwap_dirty = true;
|
|
1143
1259
|
chart->mark_dirty();
|
|
1144
1260
|
}
|
|
@@ -1166,6 +1282,30 @@ extern "C" void vroom_chart_set_bollinger(VroomChart* chart,
|
|
|
1166
1282
|
chart->mark_dirty();
|
|
1167
1283
|
}
|
|
1168
1284
|
|
|
1285
|
+
extern "C" void vroom_chart_set_volume(VroomChart* chart,
|
|
1286
|
+
const VroomVolume* cfg) {
|
|
1287
|
+
if (!chart || !cfg) return;
|
|
1288
|
+
VroomVolume next = *cfg;
|
|
1289
|
+
next.enabled = next.enabled ? 1 : 0;
|
|
1290
|
+
// Negative means "inherit", so only clamp the ranges once a value is set.
|
|
1291
|
+
if (next.height_frac >= 0.f) next.height_frac = std::min(next.height_frac, 1.f);
|
|
1292
|
+
if (next.opacity >= 0.f) next.opacity = std::min(next.opacity, 1.f);
|
|
1293
|
+
chart->volume = next;
|
|
1294
|
+
// Snap the collapse to the new target, the way set_chart_type snaps the
|
|
1295
|
+
// candle↔line morph. A host that wants the toggle animated overrides this
|
|
1296
|
+
// from its frame loop before the next paint.
|
|
1297
|
+
chart->volume_collapse_t = next.enabled ? 0.f : 1.f;
|
|
1298
|
+
chart->mark_dirty();
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
extern "C" void vroom_chart_set_volume_collapse(VroomChart* chart, float t,
|
|
1302
|
+
int32_t easing) {
|
|
1303
|
+
if (!chart) return;
|
|
1304
|
+
chart->volume_collapse_t = std::clamp(t, 0.f, 1.f);
|
|
1305
|
+
chart->volume_collapse_easing = easing;
|
|
1306
|
+
chart->mark_dirty();
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1169
1309
|
// ---- Direct draw (used by hosts that don't need the SkPicture cache) ------
|
|
1170
1310
|
|
|
1171
1311
|
extern "C" void vroom_chart_draw(VroomChart* chart, SkCanvas* canvas) {
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
#include "gradient.h"
|
|
2
|
+
|
|
3
|
+
#pragma clang diagnostic push
|
|
4
|
+
#pragma clang diagnostic ignored "-Wdocumentation"
|
|
5
|
+
#include "include/core/SkPoint.h"
|
|
6
|
+
#include "include/core/SkShader.h"
|
|
7
|
+
#include "include/core/SkTileMode.h"
|
|
8
|
+
// See gradient.h: the two Skia versions we build against ship different
|
|
9
|
+
// linear-gradient headers, and neither ships both.
|
|
10
|
+
#if __has_include("include/effects/SkGradient.h")
|
|
11
|
+
# include "include/core/SkSpan.h"
|
|
12
|
+
# include "include/effects/SkGradient.h"
|
|
13
|
+
# define VROOM_SK_MODERN_GRADIENT 1
|
|
14
|
+
#else
|
|
15
|
+
# include "include/effects/SkGradientShader.h"
|
|
16
|
+
# define VROOM_SK_MODERN_GRADIENT 0
|
|
17
|
+
#endif
|
|
18
|
+
#pragma clang diagnostic pop
|
|
19
|
+
|
|
20
|
+
namespace vroom {
|
|
21
|
+
|
|
22
|
+
sk_sp<SkShader> linear_alpha_ramp(const SkPoint pts[2],
|
|
23
|
+
SkColor base,
|
|
24
|
+
float a0,
|
|
25
|
+
float a1) {
|
|
26
|
+
#if VROOM_SK_MODERN_GRADIENT
|
|
27
|
+
const float r = SkColorGetR(base) / 255.f;
|
|
28
|
+
const float g = SkColorGetG(base) / 255.f;
|
|
29
|
+
const float b = SkColorGetB(base) / 255.f;
|
|
30
|
+
const SkColor4f colors[2] = {SkColor4f{r, g, b, a0}, SkColor4f{r, g, b, a1}};
|
|
31
|
+
const SkGradient::Colors grad_colors(
|
|
32
|
+
SkSpan<const SkColor4f>(colors, 2), SkTileMode::kClamp);
|
|
33
|
+
const SkGradient grad(grad_colors, SkGradient::Interpolation{});
|
|
34
|
+
return SkShaders::LinearGradient(pts, grad);
|
|
35
|
+
#else
|
|
36
|
+
const auto to_u8 = [](float a) {
|
|
37
|
+
return static_cast<U8CPU>(a * 255.f + 0.5f);
|
|
38
|
+
};
|
|
39
|
+
const SkColor colors[2] = {SkColorSetA(base, to_u8(a0)),
|
|
40
|
+
SkColorSetA(base, to_u8(a1))};
|
|
41
|
+
return SkGradientShader::MakeLinear(pts, colors, nullptr, 2,
|
|
42
|
+
SkTileMode::kClamp);
|
|
43
|
+
#endif
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
} // namespace vroom
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Linear-gradient helper shared by the layers that fade a fill out.
|
|
2
|
+
//
|
|
3
|
+
// Skia's linear-gradient API differs across the Skia versions we build against:
|
|
4
|
+
// the WASM build uses a newer Skia (SkGradient.h + SkShaders::LinearGradient),
|
|
5
|
+
// while react-native-skia bundles an older one (SkGradientShader::MakeLinear).
|
|
6
|
+
// Neither ships both headers, so the implementation selects whichever is present
|
|
7
|
+
// and callers get one signature regardless of platform.
|
|
8
|
+
|
|
9
|
+
#pragma once
|
|
10
|
+
|
|
11
|
+
#pragma clang diagnostic push
|
|
12
|
+
#pragma clang diagnostic ignored "-Wdocumentation"
|
|
13
|
+
#include "include/core/SkColor.h"
|
|
14
|
+
#include "include/core/SkRefCnt.h"
|
|
15
|
+
#pragma clang diagnostic pop
|
|
16
|
+
|
|
17
|
+
struct SkPoint;
|
|
18
|
+
class SkShader;
|
|
19
|
+
|
|
20
|
+
namespace vroom {
|
|
21
|
+
|
|
22
|
+
// A gradient along pts[0] -> pts[1] holding `base`'s RGB while ramping alpha
|
|
23
|
+
// from `a0` to `a1`. RGB is held constant on purpose: interpolating toward a
|
|
24
|
+
// transparent black instead would darken the faded end into a grey smear.
|
|
25
|
+
// Clamps at both ends, so the shader can safely cover more than the fill does.
|
|
26
|
+
sk_sp<SkShader> linear_alpha_ramp(const SkPoint pts[2],
|
|
27
|
+
SkColor base,
|
|
28
|
+
float a0,
|
|
29
|
+
float a1);
|
|
30
|
+
|
|
31
|
+
} // namespace vroom
|