react-native-vroom-chart 0.5.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/android/CMakeLists.txt +143 -0
- package/android/README.md +60 -5
- package/android/build.gradle +115 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/cpp/VroomChartJni.cpp +24 -0
- package/android/src/main/java/com/vroom/chart/VroomChartModule.kt +31 -0
- package/android/src/main/java/com/vroom/chart/VroomChartPackage.kt +31 -0
- package/cpp/VroomChartHostObject.cpp +368 -33
- package/cpp/VroomJsiInstaller.cpp +11 -1
- package/cpp/VroomSkiaContext.cpp +15 -7
- package/cpp/_core_include/vroom/vroom_chart.h +250 -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 +98 -40
- package/cpp/_core_src/chart.h +82 -20
- package/cpp/_core_src/chart_facade.cpp +297 -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.cpp +87 -0
- package/cpp/_core_src/price_line_layout.h +80 -0
- package/cpp/_core_src/price_lines.cpp +428 -0
- package/cpp/_core_src/price_lines.h +56 -0
- 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 +261 -25
- package/lib/index.d.ts +261 -25
- package/lib/index.js +252 -36
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +252 -36
- package/lib/index.mjs.map +1 -1
- package/package.json +5 -4
- package/src/VroomChart.tsx +162 -11
- package/src/easing.ts +40 -0
- package/src/index.ts +5 -0
- package/src/jsi.d.ts +124 -20
- package/src/theme.ts +1 -0
- package/src/types.ts +5 -0
- package/src/useChartCore.ts +166 -28
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
#include "chart.h"
|
|
15
15
|
#include "drawings.h"
|
|
16
16
|
#include "labels.h"
|
|
17
|
+
#include "price_lines.h"
|
|
17
18
|
#include "viewport.h"
|
|
18
19
|
|
|
19
20
|
class SkCanvas;
|
|
@@ -62,6 +63,13 @@ int64_t damp_future_delta(int64_t delta_ms, int64_t cur_future,
|
|
|
62
63
|
void apply_default_framing(VroomChart* chart) {
|
|
63
64
|
if (chart->candles.empty()) return;
|
|
64
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
|
+
|
|
65
73
|
// Px-driven framing. Reuses the inversion from vroom_chart_scale_time_axis:
|
|
66
74
|
// body_w = usable × (dur / window) × ratio → window = usable × dur × ratio / body_w.
|
|
67
75
|
// Needs a valid layout (width_px); until size is known it falls through to
|
|
@@ -80,7 +88,7 @@ void apply_default_framing(VroomChart* chart) {
|
|
|
80
88
|
chart->candles.back().time_ms - chart->candles.front().time_ms;
|
|
81
89
|
window_ms = std::clamp<int64_t>(window_ms, chart->candle_duration_ms,
|
|
82
90
|
span > 0 ? span : window_ms);
|
|
83
|
-
chart->visible_end_ms =
|
|
91
|
+
chart->visible_end_ms = right_edge_ms;
|
|
84
92
|
chart->visible_start_ms = chart->visible_end_ms - window_ms;
|
|
85
93
|
return;
|
|
86
94
|
}
|
|
@@ -91,7 +99,7 @@ void apply_default_framing(VroomChart* chart) {
|
|
|
91
99
|
? chart->candles.size() - kDefaultVisible
|
|
92
100
|
: 0;
|
|
93
101
|
chart->visible_start_ms = chart->candles[start_idx].time_ms;
|
|
94
|
-
chart->visible_end_ms =
|
|
102
|
+
chart->visible_end_ms = right_edge_ms;
|
|
95
103
|
}
|
|
96
104
|
|
|
97
105
|
// On the first manual-y gesture, adopt the on-screen auto-fit bounds so the
|
|
@@ -241,6 +249,10 @@ extern "C" void vroom_chart_reset_view(VroomChart* chart) {
|
|
|
241
249
|
chart->visible_start_ms = 0;
|
|
242
250
|
chart->visible_end_ms = 0;
|
|
243
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;
|
|
244
256
|
apply_default_framing(chart);
|
|
245
257
|
vroom::labels::recompute_axis_width(*chart);
|
|
246
258
|
if (chart->cb.on_viewport_changed) {
|
|
@@ -257,6 +269,104 @@ extern "C" void vroom_chart_reset_price_scale(VroomChart* chart) {
|
|
|
257
269
|
chart->mark_dirty();
|
|
258
270
|
}
|
|
259
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
|
+
|
|
260
370
|
extern "C" void vroom_chart_pan(VroomChart* chart, float dx_px, float /*dy_px*/) {
|
|
261
371
|
if (!chart || dx_px == 0.f || chart->candles.empty()) return;
|
|
262
372
|
|
|
@@ -480,7 +590,7 @@ extern "C" void vroom_chart_scale_time_axis(VroomChart* chart, float dx_px) {
|
|
|
480
590
|
extern "C" void vroom_chart_resize_indicator_pane(VroomChart* chart, float dy_px) {
|
|
481
591
|
if (!chart || dy_px == 0.f) return;
|
|
482
592
|
|
|
483
|
-
const int pane_count = (chart->
|
|
593
|
+
const int pane_count = (chart->rsi.enabled ? 1 : 0) + (chart->macd.enabled ? 1 : 0);
|
|
484
594
|
if (pane_count == 0) return; // nothing below the chart to resize
|
|
485
595
|
|
|
486
596
|
const auto lay = chart->layout();
|
|
@@ -534,8 +644,8 @@ extern "C" void vroom_chart_scale_indicator_axis(VroomChart* chart, float y_px,
|
|
|
534
644
|
struct ActivePane { int order; int type; }; // type: 0 = RSI, 1 = MACD
|
|
535
645
|
ActivePane panes[2];
|
|
536
646
|
int count = 0;
|
|
537
|
-
if (chart->
|
|
538
|
-
if (chart->
|
|
647
|
+
if (chart->rsi.enabled) panes[count++] = {chart->rsi_order, 0};
|
|
648
|
+
if (chart->macd.enabled) panes[count++] = {chart->macd_order, 1};
|
|
539
649
|
if (count == 0) return;
|
|
540
650
|
if (count == 2 && panes[0].order > panes[1].order) {
|
|
541
651
|
const ActivePane tmp = panes[0];
|
|
@@ -812,6 +922,70 @@ extern "C" void vroom_chart_set_liquidity(VroomChart* chart,
|
|
|
812
922
|
chart->mark_dirty();
|
|
813
923
|
}
|
|
814
924
|
|
|
925
|
+
// ---- Price status lines ---------------------------------------------------
|
|
926
|
+
|
|
927
|
+
extern "C" void vroom_chart_set_price_lines(VroomChart* chart,
|
|
928
|
+
const VroomPriceLine* lines,
|
|
929
|
+
size_t count,
|
|
930
|
+
const VroomPriceLineStyle* style) {
|
|
931
|
+
if (!chart) return;
|
|
932
|
+
// Deep-copy into the owning form: the label strings live in the chart, so the
|
|
933
|
+
// caller's buffers may be freed as soon as this returns.
|
|
934
|
+
chart->price_lines.clear();
|
|
935
|
+
chart->price_lines.reserve(count);
|
|
936
|
+
for (size_t i = 0; i < count; ++i) {
|
|
937
|
+
const VroomPriceLine& src = lines[i];
|
|
938
|
+
VroomChart::StoredPriceLine pl;
|
|
939
|
+
pl.price = src.price;
|
|
940
|
+
pl.color = src.color;
|
|
941
|
+
pl.width = src.width;
|
|
942
|
+
pl.line_style = src.line_style;
|
|
943
|
+
if (src.text) pl.text = src.text;
|
|
944
|
+
if (src.quantity) pl.quantity = src.quantity;
|
|
945
|
+
pl.flags = src.flags;
|
|
946
|
+
chart->price_lines.push_back(std::move(pl));
|
|
947
|
+
}
|
|
948
|
+
if (style) chart->price_line_style = *style;
|
|
949
|
+
// Drop interaction state that no longer refers to a live line — otherwise a
|
|
950
|
+
// removed line would leave a stuck hover highlight or drag preview.
|
|
951
|
+
const auto n = static_cast<int32_t>(count);
|
|
952
|
+
if (chart->hovered_price_line >= n) {
|
|
953
|
+
chart->hovered_price_line = -1;
|
|
954
|
+
chart->hovered_price_line_part = -1;
|
|
955
|
+
}
|
|
956
|
+
if (chart->dragged_price_line >= n) chart->dragged_price_line = -1;
|
|
957
|
+
chart->mark_dirty();
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
extern "C" void vroom_chart_set_price_line_hover(VroomChart* chart, int32_t index,
|
|
961
|
+
int32_t part) {
|
|
962
|
+
if (!chart) return;
|
|
963
|
+
if (index < 0 || index >= static_cast<int32_t>(chart->price_lines.size())) {
|
|
964
|
+
index = -1;
|
|
965
|
+
part = -1;
|
|
966
|
+
}
|
|
967
|
+
if (chart->hovered_price_line == index && chart->hovered_price_line_part == part) {
|
|
968
|
+
return; // hover fires on every pointer move; don't redraw for nothing
|
|
969
|
+
}
|
|
970
|
+
chart->hovered_price_line = index;
|
|
971
|
+
chart->hovered_price_line_part = part;
|
|
972
|
+
chart->mark_dirty();
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
extern "C" void vroom_chart_set_price_line_drag(VroomChart* chart, int32_t index,
|
|
976
|
+
double price) {
|
|
977
|
+
if (!chart) return;
|
|
978
|
+
if (index < 0 || index >= static_cast<int32_t>(chart->price_lines.size())) {
|
|
979
|
+
if (chart->dragged_price_line == -1) return;
|
|
980
|
+
chart->dragged_price_line = -1;
|
|
981
|
+
chart->mark_dirty();
|
|
982
|
+
return;
|
|
983
|
+
}
|
|
984
|
+
chart->dragged_price_line = index;
|
|
985
|
+
chart->dragged_price_line_price = price;
|
|
986
|
+
chart->mark_dirty();
|
|
987
|
+
}
|
|
988
|
+
|
|
815
989
|
extern "C" void vroom_chart_set_draft(VroomChart* chart, int64_t a_time,
|
|
816
990
|
double a_price, bool has_b, int64_t b_time,
|
|
817
991
|
double b_price, bool guide, uint32_t color,
|
|
@@ -926,6 +1100,32 @@ extern "C" bool vroom_chart_hit_test_drawing(VroomChart* chart, float x_px,
|
|
|
926
1100
|
return true;
|
|
927
1101
|
}
|
|
928
1102
|
|
|
1103
|
+
extern "C" bool vroom_chart_hit_test_price_line(VroomChart* chart, float x_px,
|
|
1104
|
+
float y_px, int32_t* out_index,
|
|
1105
|
+
int32_t* out_part) {
|
|
1106
|
+
if (!chart || chart->price_lines.empty() || chart->candles.empty()) return false;
|
|
1107
|
+
const auto lay = chart->layout();
|
|
1108
|
+
// Same bounds and pane geometry as draw_chart, so the grab regions line up
|
|
1109
|
+
// with what's on screen.
|
|
1110
|
+
const auto range = vroom::visible_indices(
|
|
1111
|
+
chart->candles.data(), chart->candles.size(),
|
|
1112
|
+
chart->visible_start_ms, chart->visible_end_ms);
|
|
1113
|
+
const size_t n = range.end - range.start;
|
|
1114
|
+
const auto bounds =
|
|
1115
|
+
chart->price_bounds_manual
|
|
1116
|
+
? chart->price_bounds
|
|
1117
|
+
: vroom::auto_price_bounds(chart->candles.data() + range.start, n);
|
|
1118
|
+
const float candle_area_h = vroom::price_pane_bottom(lay);
|
|
1119
|
+
const float candle_right =
|
|
1120
|
+
chart->width_px - lay.y_axis_width_px - lay.right_padding_px;
|
|
1121
|
+
const auto hit = vroom::price_lines::hit_test(*chart, lay, bounds, candle_right,
|
|
1122
|
+
candle_area_h, x_px, y_px);
|
|
1123
|
+
if (hit.index < 0) return false;
|
|
1124
|
+
if (out_index) *out_index = hit.index;
|
|
1125
|
+
if (out_part) *out_part = hit.part;
|
|
1126
|
+
return true;
|
|
1127
|
+
}
|
|
1128
|
+
|
|
929
1129
|
extern "C" void vroom_chart_set_selected_drawing(VroomChart* chart,
|
|
930
1130
|
int32_t index,
|
|
931
1131
|
int32_t grabbed_endpoint) {
|
|
@@ -968,61 +1168,66 @@ extern "C" void vroom_chart_translate_drawing(VroomChart* chart, int32_t index,
|
|
|
968
1168
|
|
|
969
1169
|
// ---- Indicators -----------------------------------------------------------
|
|
970
1170
|
|
|
971
|
-
extern "C" void vroom_chart_set_rsi(VroomChart* chart,
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
const
|
|
987
|
-
|
|
988
|
-
|
|
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;
|
|
989
1192
|
|
|
990
1193
|
// Pane order: claim the next slot on an off->on transition, release on off.
|
|
991
|
-
if (enabled && !
|
|
992
|
-
else if (!enabled) chart->rsi_order = -1;
|
|
993
|
-
|
|
994
|
-
chart->
|
|
995
|
-
chart->rsi_period = period;
|
|
996
|
-
chart->rsi_upper = upper_band;
|
|
997
|
-
chart->rsi_lower = lower_band;
|
|
998
|
-
chart->rsi_ma_enabled = ma_enabled;
|
|
999
|
-
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;
|
|
1000
1198
|
if (recompute) chart->rsi_dirty = true;
|
|
1001
1199
|
chart->mark_dirty();
|
|
1002
1200
|
}
|
|
1003
1201
|
|
|
1004
|
-
extern "C" void vroom_chart_set_macd(VroomChart* chart,
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
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;
|
|
1026
1231
|
chart->mark_dirty();
|
|
1027
1232
|
}
|
|
1028
1233
|
|
|
@@ -1035,19 +1240,21 @@ extern "C" void vroom_chart_set_overlays(VroomChart* chart,
|
|
|
1035
1240
|
chart->mark_dirty();
|
|
1036
1241
|
}
|
|
1037
1242
|
|
|
1038
|
-
extern "C" void vroom_chart_set_vwap(VroomChart* chart,
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
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;
|
|
1042
1247
|
// Keep the offset within a day [0, 1440).
|
|
1043
|
-
|
|
1044
|
-
if (
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
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;
|
|
1051
1258
|
if (recompute) chart->vwap_dirty = true;
|
|
1052
1259
|
chart->mark_dirty();
|
|
1053
1260
|
}
|
|
@@ -1075,6 +1282,30 @@ extern "C" void vroom_chart_set_bollinger(VroomChart* chart,
|
|
|
1075
1282
|
chart->mark_dirty();
|
|
1076
1283
|
}
|
|
1077
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
|
+
|
|
1078
1309
|
// ---- Direct draw (used by hosts that don't need the SkPicture cache) ------
|
|
1079
1310
|
|
|
1080
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
|