react-native-vroom-chart 0.15.0 → 0.17.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 +252 -1
- package/cpp/_core_include/vroom/vroom_chart.h +178 -4
- package/cpp/_core_src/atr.cpp +67 -0
- package/cpp/_core_src/atr.h +44 -0
- package/cpp/_core_src/atr_pane.cpp +162 -0
- package/cpp/_core_src/atr_pane.h +48 -0
- package/cpp/_core_src/candles.cpp +15 -14
- package/cpp/_core_src/chart.cpp +758 -200
- package/cpp/_core_src/chart.h +221 -3
- package/cpp/_core_src/chart_facade.cpp +236 -23
- package/cpp/_core_src/color_lerp.h +28 -0
- package/cpp/_core_src/fair_value_gaps.cpp +76 -0
- package/cpp/_core_src/fair_value_gaps.h +54 -0
- package/cpp/_core_src/fvg_overlay.cpp +262 -0
- package/cpp/_core_src/fvg_overlay.h +43 -0
- package/cpp/_core_src/ichimoku.cpp +65 -0
- package/cpp/_core_src/ichimoku.h +45 -0
- package/cpp/_core_src/labels.cpp +3 -0
- package/cpp/_core_src/line_morph.h +159 -0
- package/cpp/_core_src/loading_line.cpp +183 -0
- package/cpp/_core_src/loading_line.h +38 -0
- package/cpp/_core_src/loading_wave.h +140 -0
- package/cpp/_core_src/ma_overlay.cpp +273 -45
- package/cpp/_core_src/ma_overlay.h +64 -2
- package/cpp/_core_src/macd.cpp +24 -1
- package/cpp/_core_src/macd.h +16 -0
- package/cpp/_core_src/macd_pane.cpp +71 -62
- package/cpp/_core_src/macd_pane.h +13 -1
- package/cpp/_core_src/pane_series.h +169 -0
- package/cpp/_core_src/price_indicator.cpp +21 -7
- package/cpp/_core_src/price_indicator.h +11 -1
- package/cpp/_core_src/price_indicator_anim.h +81 -0
- package/cpp/_core_src/rsi.cpp +4 -0
- package/cpp/_core_src/rsi.h +7 -0
- package/cpp/_core_src/rsi_pane.cpp +85 -29
- package/cpp/_core_src/rsi_pane.h +11 -1
- package/cpp/_core_src/theme.cpp +5 -0
- package/cpp/_core_src/tip_geometry.h +55 -0
- package/cpp/_core_src/viewport.h +68 -0
- package/lib/index.d.mts +281 -3
- package/lib/index.d.ts +281 -3
- package/lib/index.js +292 -14
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +292 -14
- package/lib/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/VroomChart.tsx +33 -3
- package/src/dataTransitions.ts +47 -0
- package/src/index.ts +5 -0
- package/src/jsi.d.ts +139 -1
- package/src/theme.ts +1 -0
- package/src/types.ts +5 -0
- package/src/useChartCore.ts +402 -8
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
|
|
19
19
|
#include "curve.h"
|
|
20
20
|
#include "gradient.h"
|
|
21
|
+
#include "line_morph.h"
|
|
22
|
+
#include "tip_geometry.h"
|
|
21
23
|
#include "tip_pulse.h"
|
|
22
24
|
#include "viewport.h"
|
|
23
25
|
|
|
@@ -51,9 +53,82 @@ void stroke_path(SkCanvas* canvas,
|
|
|
51
53
|
|
|
52
54
|
inline float lerp(float a, float b, float t) { return a + (b - a) * t; }
|
|
53
55
|
|
|
54
|
-
//
|
|
55
|
-
//
|
|
56
|
-
|
|
56
|
+
// Slot `k` of an indicator series in screen space, blended from the outgoing
|
|
57
|
+
// capture toward the new value. Slot 0 is the rightmost vertex on both sides —
|
|
58
|
+
// the pairing a timeframe switch preserves, same as close_vertex below.
|
|
59
|
+
//
|
|
60
|
+
// The capture is in fractions of the candle area and the price band, so it
|
|
61
|
+
// lands on the pixels it occupied pre-switch even though both the surface and
|
|
62
|
+
// the bounds may have changed under it.
|
|
63
|
+
MorphVertex series_vertex(const Layout& lay,
|
|
64
|
+
const PriceBounds& bounds,
|
|
65
|
+
const ::VroomCandle* visible,
|
|
66
|
+
std::size_t n,
|
|
67
|
+
const double* values,
|
|
68
|
+
int64_t window_ms,
|
|
69
|
+
int64_t visible_start_ms,
|
|
70
|
+
int64_t candle_duration_ms,
|
|
71
|
+
int64_t time_shift_ms,
|
|
72
|
+
const LineMorph* from,
|
|
73
|
+
std::size_t from_count,
|
|
74
|
+
float morph_t,
|
|
75
|
+
std::size_t k) {
|
|
76
|
+
MorphVertex to;
|
|
77
|
+
if (values && k < n) {
|
|
78
|
+
const std::size_t i = n - 1 - k;
|
|
79
|
+
const double v = values[i];
|
|
80
|
+
if (std::isfinite(v)) {
|
|
81
|
+
to = MorphVertex{
|
|
82
|
+
vroom::candle_center_x(lay, visible[i].time_ms + time_shift_ms,
|
|
83
|
+
candle_duration_ms, visible_start_ms,
|
|
84
|
+
window_ms),
|
|
85
|
+
vroom::price_to_y(lay, bounds, v), true};
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
MorphVertex frm;
|
|
89
|
+
if (k < from_count) {
|
|
90
|
+
const LineSnapshot& s = from->pts[k];
|
|
91
|
+
if (s.valid) {
|
|
92
|
+
frm = MorphVertex{s.x * vroom::candle_area_width(lay),
|
|
93
|
+
vroom::y_at_fraction(lay, s.y), true};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return vroom::morph_vertex(to, frm, morph_t);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// The same series walked left to right into `out`, for the two fills — they
|
|
100
|
+
// need to look ahead for crossovers and back along the opposite edge, so they
|
|
101
|
+
// can't stream the way the stroke does. out[j] is slot slots-1-j.
|
|
102
|
+
void resolve_series(const Layout& lay,
|
|
103
|
+
const PriceBounds& bounds,
|
|
104
|
+
const ::VroomCandle* visible,
|
|
105
|
+
std::size_t n,
|
|
106
|
+
const double* values,
|
|
107
|
+
int64_t window_ms,
|
|
108
|
+
int64_t visible_start_ms,
|
|
109
|
+
int64_t candle_duration_ms,
|
|
110
|
+
int64_t time_shift_ms,
|
|
111
|
+
const LineMorph* from,
|
|
112
|
+
float morph_t,
|
|
113
|
+
std::size_t slots,
|
|
114
|
+
std::vector<MorphVertex>& out) {
|
|
115
|
+
const std::size_t from_count = vroom::morph_line_count(from, morph_t);
|
|
116
|
+
out.clear();
|
|
117
|
+
out.reserve(slots);
|
|
118
|
+
for (std::size_t j = 0; j < slots; ++j) {
|
|
119
|
+
out.push_back(series_vertex(lay, bounds, visible, n, values, window_ms,
|
|
120
|
+
visible_start_ms, candle_duration_ms,
|
|
121
|
+
time_shift_ms, from, from_count, morph_t,
|
|
122
|
+
slots - 1 - j));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// How many vertices a morphing series spans: the new slice and the capture can
|
|
127
|
+
// disagree on length, and every slot either defines has to be walked.
|
|
128
|
+
inline std::size_t series_slots(std::size_t n, const LineMorph* from,
|
|
129
|
+
float morph_t) {
|
|
130
|
+
return std::max(n, vroom::morph_line_count(from, morph_t));
|
|
131
|
+
}
|
|
57
132
|
|
|
58
133
|
// One vertex of the close polyline, in screen space. Slot `k` counts back from
|
|
59
134
|
// the right edge (0 = the newest close), the pairing a timeframe switch
|
|
@@ -198,31 +273,43 @@ void draw(SkCanvas* canvas,
|
|
|
198
273
|
uint32_t color,
|
|
199
274
|
float width,
|
|
200
275
|
const unsigned char* break_before,
|
|
201
|
-
float opacity
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
276
|
+
float opacity,
|
|
277
|
+
int64_t time_shift_ms,
|
|
278
|
+
const LineMorph* from,
|
|
279
|
+
float morph_t) {
|
|
280
|
+
if (!canvas || candle_right <= 0.f || candle_area_h <= 0.f) return;
|
|
206
281
|
opacity = std::clamp(opacity, 0.f, 1.f);
|
|
207
282
|
if (opacity <= 0.f) return;
|
|
283
|
+
morph_t = std::clamp(morph_t, 0.f, 1.f);
|
|
284
|
+
|
|
285
|
+
const std::size_t from_count = vroom::morph_line_count(from, morph_t);
|
|
286
|
+
const std::size_t slots = std::max(n, from_count);
|
|
287
|
+
if (slots == 0 || (!values_visible && from_count == 0)) return;
|
|
208
288
|
|
|
289
|
+
// Descending slots, which walks the line left to right. Settled (no
|
|
290
|
+
// capture) this visits exactly the source indices 0..n-1 in order, so the
|
|
291
|
+
// path is the one it has always emitted.
|
|
209
292
|
// SkPathBuilder (not SkPath's edit methods, removed in newer Skia tips).
|
|
210
293
|
SkPathBuilder path;
|
|
211
294
|
bool pen_down = false;
|
|
212
|
-
for (std::size_t
|
|
213
|
-
const
|
|
214
|
-
|
|
295
|
+
for (std::size_t j = 0; j < slots; ++j) {
|
|
296
|
+
const std::size_t k = slots - 1 - j;
|
|
297
|
+
const MorphVertex p = series_vertex(
|
|
298
|
+
lay, bounds, visible, n, values_visible, window_ms,
|
|
299
|
+
visible_start_ms, candle_duration_ms, time_shift_ms, from,
|
|
300
|
+
from_count, morph_t, k);
|
|
301
|
+
if (!p.valid) {
|
|
215
302
|
pen_down = false;
|
|
216
303
|
continue;
|
|
217
304
|
}
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
if (pen_down && !
|
|
223
|
-
path.lineTo(x, y);
|
|
305
|
+
// Session breaks are a property of the incoming data, so a slot the new
|
|
306
|
+
// series doesn't reach can't carry one.
|
|
307
|
+
const bool breaks =
|
|
308
|
+
break_before && k < n && break_before[n - 1 - k] != 0;
|
|
309
|
+
if (pen_down && !breaks) {
|
|
310
|
+
path.lineTo(p.x, p.y);
|
|
224
311
|
} else {
|
|
225
|
-
path.moveTo(x, y);
|
|
312
|
+
path.moveTo(p.x, p.y);
|
|
226
313
|
pen_down = true;
|
|
227
314
|
}
|
|
228
315
|
}
|
|
@@ -329,6 +416,7 @@ void draw_close_tip(SkCanvas* canvas,
|
|
|
329
416
|
int64_t visible_start_ms,
|
|
330
417
|
int64_t candle_duration_ms,
|
|
331
418
|
float candle_right,
|
|
419
|
+
float clip_right,
|
|
332
420
|
float candle_area_h,
|
|
333
421
|
uint32_t line_color,
|
|
334
422
|
uint32_t bg_color,
|
|
@@ -357,14 +445,21 @@ void draw_close_tip(SkCanvas* canvas,
|
|
|
357
445
|
if (tip.fY < 0.f || tip.fY > candle_area_h) return;
|
|
358
446
|
if (tip.fX < 0.f || tip.fX > candle_right) return;
|
|
359
447
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
const float
|
|
363
|
-
const float dot_r = std::max(2.f, w * 1.5f);
|
|
364
|
-
const float border_r = dot_r + kTipBorderPx;
|
|
448
|
+
const auto geo = vroom::tip_geometry::of(line_width);
|
|
449
|
+
const float dot_r = geo.dot_r;
|
|
450
|
+
const float border_r = geo.border_r;
|
|
365
451
|
|
|
366
452
|
canvas->save();
|
|
367
|
-
|
|
453
|
+
// Wider than the on-pane test above: the dot is anchored at the newest
|
|
454
|
+
// candle's center, which on a view pinned to the latest bar is closer to
|
|
455
|
+
// candle_right than the dot's own radius, so it has to reach into the gutter
|
|
456
|
+
// to draw in full. `clip_right` is the gutter's far side — the y-axis
|
|
457
|
+
// strip's edge — which the layout keeps wide enough for the dot (see
|
|
458
|
+
// tip_geometry::gutter_px). The pulse ring is much wider still and clips
|
|
459
|
+
// there; that is intended.
|
|
460
|
+
canvas->clipRect(
|
|
461
|
+
SkRect::MakeLTRB(0.f, 0.f, std::max(candle_right, clip_right),
|
|
462
|
+
candle_area_h));
|
|
368
463
|
|
|
369
464
|
// Ring first: the border paints over its inner edge, so it reads as
|
|
370
465
|
// expanding out from underneath the dot rather than around it.
|
|
@@ -419,42 +514,51 @@ void fill_between(SkCanvas* canvas,
|
|
|
419
514
|
float candle_right,
|
|
420
515
|
float candle_area_h,
|
|
421
516
|
uint32_t color,
|
|
422
|
-
float opacity
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
517
|
+
float opacity,
|
|
518
|
+
const LineMorph* upper_from,
|
|
519
|
+
const LineMorph* lower_from,
|
|
520
|
+
float morph_t) {
|
|
521
|
+
if (!canvas || candle_right <= 0.f || candle_area_h <= 0.f) return;
|
|
427
522
|
opacity = std::clamp(opacity, 0.f, 1.f);
|
|
428
523
|
if (opacity <= 0.f) return;
|
|
524
|
+
morph_t = std::clamp(morph_t, 0.f, 1.f);
|
|
525
|
+
|
|
526
|
+
const std::size_t slots =
|
|
527
|
+
std::max(series_slots(n, upper_from, morph_t),
|
|
528
|
+
series_slots(n, lower_from, morph_t));
|
|
529
|
+
if (slots == 0) return;
|
|
429
530
|
|
|
430
|
-
//
|
|
531
|
+
// Both edges resolved left to right first: mid-morph a vertex is a blend of
|
|
532
|
+
// the captured shape and the new one, and the fill has to be stitched from
|
|
533
|
+
// the same positions the two lines are stroked at.
|
|
534
|
+
static thread_local std::vector<MorphVertex> upper;
|
|
535
|
+
static thread_local std::vector<MorphVertex> lower;
|
|
536
|
+
resolve_series(lay, bounds, visible, n, upper_visible, window_ms,
|
|
537
|
+
visible_start_ms, candle_duration_ms, 0, upper_from, morph_t,
|
|
538
|
+
slots, upper);
|
|
539
|
+
resolve_series(lay, bounds, visible, n, lower_visible, window_ms,
|
|
540
|
+
visible_start_ms, candle_duration_ms, 0, lower_from, morph_t,
|
|
541
|
+
slots, lower);
|
|
542
|
+
|
|
543
|
+
// One closed contour per maximal run where both edges are defined; a
|
|
431
544
|
// single-point run has no area. Multiple runs (e.g. around a data gap)
|
|
432
545
|
// become multiple contours in one path.
|
|
433
546
|
SkPathBuilder path;
|
|
434
547
|
std::size_t i = 0;
|
|
435
|
-
while (i <
|
|
436
|
-
if (!
|
|
437
|
-
!std::isfinite(lower_visible[i])) {
|
|
548
|
+
while (i < slots) {
|
|
549
|
+
if (!upper[i].valid || !lower[i].valid) {
|
|
438
550
|
++i;
|
|
439
551
|
continue;
|
|
440
552
|
}
|
|
441
553
|
std::size_t e = i;
|
|
442
|
-
while (e + 1 <
|
|
443
|
-
std::isfinite(lower_visible[e + 1])) {
|
|
444
|
-
++e;
|
|
445
|
-
}
|
|
554
|
+
while (e + 1 < slots && upper[e + 1].valid && lower[e + 1].valid) ++e;
|
|
446
555
|
if (e > i) {
|
|
447
|
-
|
|
448
|
-
return vroom::candle_center_x(lay, visible[k].time_ms,
|
|
449
|
-
candle_duration_ms,
|
|
450
|
-
visible_start_ms, window_ms);
|
|
451
|
-
};
|
|
452
|
-
path.moveTo(x_at(i), vroom::price_to_y(lay, bounds, upper_visible[i]));
|
|
556
|
+
path.moveTo(upper[i].x, upper[i].y);
|
|
453
557
|
for (std::size_t k = i + 1; k <= e; ++k) {
|
|
454
|
-
path.lineTo(
|
|
558
|
+
path.lineTo(upper[k].x, upper[k].y);
|
|
455
559
|
}
|
|
456
560
|
for (std::size_t k = e + 1; k-- > i;) {
|
|
457
|
-
path.lineTo(
|
|
561
|
+
path.lineTo(lower[k].x, lower[k].y);
|
|
458
562
|
}
|
|
459
563
|
path.close();
|
|
460
564
|
}
|
|
@@ -473,4 +577,128 @@ void fill_between(SkCanvas* canvas,
|
|
|
473
577
|
canvas->restore();
|
|
474
578
|
}
|
|
475
579
|
|
|
580
|
+
void fill_cloud(SkCanvas* canvas,
|
|
581
|
+
const Layout& lay,
|
|
582
|
+
const PriceBounds& bounds,
|
|
583
|
+
const ::VroomCandle* visible,
|
|
584
|
+
std::size_t n,
|
|
585
|
+
const double* a_visible,
|
|
586
|
+
const double* b_visible,
|
|
587
|
+
int64_t window_ms,
|
|
588
|
+
int64_t visible_start_ms,
|
|
589
|
+
int64_t candle_duration_ms,
|
|
590
|
+
float candle_right,
|
|
591
|
+
float candle_area_h,
|
|
592
|
+
uint32_t above_color,
|
|
593
|
+
uint32_t below_color,
|
|
594
|
+
float opacity,
|
|
595
|
+
int64_t time_shift_ms,
|
|
596
|
+
const LineMorph* a_from,
|
|
597
|
+
const LineMorph* b_from,
|
|
598
|
+
float morph_t) {
|
|
599
|
+
if (!canvas || candle_right <= 0.f || candle_area_h <= 0.f) return;
|
|
600
|
+
opacity = std::clamp(opacity, 0.f, 1.f);
|
|
601
|
+
if (opacity <= 0.f) return;
|
|
602
|
+
morph_t = std::clamp(morph_t, 0.f, 1.f);
|
|
603
|
+
|
|
604
|
+
const std::size_t slots = std::max(series_slots(n, a_from, morph_t),
|
|
605
|
+
series_slots(n, b_from, morph_t));
|
|
606
|
+
if (slots == 0) return;
|
|
607
|
+
|
|
608
|
+
static thread_local std::vector<MorphVertex> edge_a;
|
|
609
|
+
static thread_local std::vector<MorphVertex> edge_b;
|
|
610
|
+
resolve_series(lay, bounds, visible, n, a_visible, window_ms,
|
|
611
|
+
visible_start_ms, candle_duration_ms, time_shift_ms, a_from,
|
|
612
|
+
morph_t, slots, edge_a);
|
|
613
|
+
resolve_series(lay, bounds, visible, n, b_visible, window_ms,
|
|
614
|
+
visible_start_ms, candle_duration_ms, time_shift_ms, b_from,
|
|
615
|
+
morph_t, slots, edge_b);
|
|
616
|
+
|
|
617
|
+
// A vertex of the cloud: one x with the two edge heights there. At a
|
|
618
|
+
// crossover the two collapse onto the same y, which is what lets the
|
|
619
|
+
// neighboring contours meet on a point instead of overlapping.
|
|
620
|
+
struct Vertex {
|
|
621
|
+
float x, ya, yb;
|
|
622
|
+
};
|
|
623
|
+
|
|
624
|
+
SkPathBuilder above_path;
|
|
625
|
+
SkPathBuilder below_path;
|
|
626
|
+
std::vector<Vertex> contour;
|
|
627
|
+
|
|
628
|
+
// Closes the accumulated vertices into the path for whichever side is on
|
|
629
|
+
// top: forward along edge a, back along edge b.
|
|
630
|
+
const auto flush = [&](int sign) {
|
|
631
|
+
if (contour.size() >= 2) {
|
|
632
|
+
SkPathBuilder& into = sign >= 0 ? above_path : below_path;
|
|
633
|
+
into.moveTo(contour[0].x, contour[0].ya);
|
|
634
|
+
for (std::size_t k = 1; k < contour.size(); ++k) {
|
|
635
|
+
into.lineTo(contour[k].x, contour[k].ya);
|
|
636
|
+
}
|
|
637
|
+
for (std::size_t k = contour.size(); k-- > 0;) {
|
|
638
|
+
into.lineTo(contour[k].x, contour[k].yb);
|
|
639
|
+
}
|
|
640
|
+
into.close();
|
|
641
|
+
}
|
|
642
|
+
contour.clear();
|
|
643
|
+
};
|
|
644
|
+
|
|
645
|
+
// Which tone a run takes, read off the drawn geometry rather than the raw
|
|
646
|
+
// values: y grows downward, so a above b means ya is the smaller. Mid-morph
|
|
647
|
+
// the values belong to two different resolutions and only the interpolated
|
|
648
|
+
// positions say where the edges actually sit.
|
|
649
|
+
const auto separation = [&](std::size_t k) {
|
|
650
|
+
return static_cast<double>(edge_b[k].y - edge_a[k].y);
|
|
651
|
+
};
|
|
652
|
+
|
|
653
|
+
std::size_t i = 0;
|
|
654
|
+
while (i < slots) {
|
|
655
|
+
if (!edge_a[i].valid || !edge_b[i].valid) {
|
|
656
|
+
++i;
|
|
657
|
+
continue;
|
|
658
|
+
}
|
|
659
|
+
std::size_t e = i;
|
|
660
|
+
while (e + 1 < slots && edge_a[e + 1].valid && edge_b[e + 1].valid) ++e;
|
|
661
|
+
|
|
662
|
+
int sign = 0;
|
|
663
|
+
Vertex prev{};
|
|
664
|
+
for (std::size_t k = i; k <= e; ++k) {
|
|
665
|
+
const Vertex v = Vertex{edge_a[k].x, edge_a[k].y, edge_b[k].y};
|
|
666
|
+
const double d = separation(k);
|
|
667
|
+
const int s = d > 0.0 ? 1 : (d < 0.0 ? -1 : 0);
|
|
668
|
+
if (sign != 0 && s != 0 && s != sign) {
|
|
669
|
+
// The spans swapped between the previous bar and this one. Meet
|
|
670
|
+
// them at the crossing so both tones end on the same point.
|
|
671
|
+
const double d0 = separation(k - 1);
|
|
672
|
+
const float t = static_cast<float>(d0 / (d0 - d));
|
|
673
|
+
const float xc = prev.x + (v.x - prev.x) * t;
|
|
674
|
+
const float yc = prev.ya + (v.ya - prev.ya) * t;
|
|
675
|
+
contour.push_back(Vertex{xc, yc, yc});
|
|
676
|
+
flush(sign);
|
|
677
|
+
contour.push_back(Vertex{xc, yc, yc});
|
|
678
|
+
sign = s;
|
|
679
|
+
} else if (sign == 0) {
|
|
680
|
+
sign = s;
|
|
681
|
+
}
|
|
682
|
+
contour.push_back(v);
|
|
683
|
+
prev = v;
|
|
684
|
+
}
|
|
685
|
+
flush(sign);
|
|
686
|
+
i = e + 1;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
SkPaint fill;
|
|
690
|
+
fill.setAntiAlias(true);
|
|
691
|
+
fill.setStyle(SkPaint::kFill_Style);
|
|
692
|
+
|
|
693
|
+
canvas->save();
|
|
694
|
+
canvas->clipRect(SkRect::MakeLTRB(0.f, 0.f, candle_right, candle_area_h));
|
|
695
|
+
fill.setColor(static_cast<SkColor>(above_color));
|
|
696
|
+
fill.setAlphaf(fill.getAlphaf() * opacity);
|
|
697
|
+
canvas->drawPath(above_path.detach(), fill);
|
|
698
|
+
fill.setColor(static_cast<SkColor>(below_color));
|
|
699
|
+
fill.setAlphaf(fill.getAlphaf() * opacity);
|
|
700
|
+
canvas->drawPath(below_path.detach(), fill);
|
|
701
|
+
canvas->restore();
|
|
702
|
+
}
|
|
703
|
+
|
|
476
704
|
} // namespace vroom::ma_overlay
|
|
@@ -13,6 +13,7 @@ class SkCanvas;
|
|
|
13
13
|
namespace vroom {
|
|
14
14
|
struct CandleSnapshot;
|
|
15
15
|
struct Layout;
|
|
16
|
+
struct LineMorph;
|
|
16
17
|
struct PriceBounds;
|
|
17
18
|
} // namespace vroom
|
|
18
19
|
|
|
@@ -25,6 +26,18 @@ namespace vroom::ma_overlay {
|
|
|
25
26
|
// `break_before` (optional, aligned with `visible`) forces a new subpath at any
|
|
26
27
|
// index where it is non-zero — used by VWAP to break the line at session
|
|
27
28
|
// resets. Pass nullptr for continuous lines (SMA/EMA).
|
|
29
|
+
//
|
|
30
|
+
// `time_shift_ms` offsets where each value lands on the time axis, for a series
|
|
31
|
+
// drawn away from the bar it was computed on (Ichimoku's leading and lagging
|
|
32
|
+
// spans). The shifted time need not be a bar that exists: projection is linear,
|
|
33
|
+
// so a leading span runs past the newest candle into empty time. `visible` is
|
|
34
|
+
// then the *source* slice — see ichimoku::shifted_source_range.
|
|
35
|
+
//
|
|
36
|
+
// `from` / `morph_t` are the interval morph, the indicator counterpart of what
|
|
37
|
+
// draw_close_line does for the price series: the capture holds this series'
|
|
38
|
+
// outgoing shape indexed from the right (slot 0 = newest) and each vertex
|
|
39
|
+
// slides from where it sat before the timeframe switch to where it sits now.
|
|
40
|
+
// A null capture (the default) draws `values_visible` alone.
|
|
28
41
|
void draw(SkCanvas* canvas,
|
|
29
42
|
const Layout& lay,
|
|
30
43
|
const PriceBounds& bounds,
|
|
@@ -39,7 +52,10 @@ void draw(SkCanvas* canvas,
|
|
|
39
52
|
uint32_t color,
|
|
40
53
|
float width,
|
|
41
54
|
const unsigned char* break_before = nullptr,
|
|
42
|
-
float opacity = 1.f
|
|
55
|
+
float opacity = 1.f,
|
|
56
|
+
int64_t time_shift_ms = 0,
|
|
57
|
+
const LineMorph* from = nullptr,
|
|
58
|
+
float morph_t = 1.f);
|
|
43
59
|
|
|
44
60
|
// The close-price polyline of line-chart mode. Equivalent to draw() fed the
|
|
45
61
|
// visible closes, plus the interval morph: `from` / `from_n` is the outgoing
|
|
@@ -115,6 +131,12 @@ void draw_close_gradient(SkCanvas* canvas,
|
|
|
115
131
|
//
|
|
116
132
|
// `pulse_phase` is in cycles and wraps, so the caller can hand over elapsed time
|
|
117
133
|
// divided by tip_pulse::kPeriodSeconds. Ignored unless `pulse`.
|
|
134
|
+
//
|
|
135
|
+
// `candle_right` bounds where the marked candle may be for the marker to draw at
|
|
136
|
+
// all; `clip_right` bounds the paint, and is wider — the dot overhangs the plot
|
|
137
|
+
// when the newest candle is at the right edge, so it paints into the gutter the
|
|
138
|
+
// layout reserves for it. Callers must therefore draw the tip *after* the axis
|
|
139
|
+
// backgrounds, which mask that gutter.
|
|
118
140
|
void draw_close_tip(SkCanvas* canvas,
|
|
119
141
|
const Layout& lay,
|
|
120
142
|
const PriceBounds& bounds,
|
|
@@ -124,6 +146,7 @@ void draw_close_tip(SkCanvas* canvas,
|
|
|
124
146
|
int64_t visible_start_ms,
|
|
125
147
|
int64_t candle_duration_ms,
|
|
126
148
|
float candle_right,
|
|
149
|
+
float clip_right,
|
|
127
150
|
float candle_area_h,
|
|
128
151
|
uint32_t line_color,
|
|
129
152
|
uint32_t bg_color,
|
|
@@ -140,6 +163,9 @@ void draw_close_tip(SkCanvas* canvas,
|
|
|
140
163
|
// Runs where either series is NaN are skipped, so the fill never bridges the
|
|
141
164
|
// warmup gap. Plain-alpha SkPaint fill, no gradient ramp (see gradient.h).
|
|
142
165
|
// Clipped to the candle area like draw().
|
|
166
|
+
//
|
|
167
|
+
// Takes a capture per edge so the fill is built from the same interpolated
|
|
168
|
+
// vertices its two lines are, and can't detach from them mid-morph.
|
|
143
169
|
void fill_between(SkCanvas* canvas,
|
|
144
170
|
const Layout& lay,
|
|
145
171
|
const PriceBounds& bounds,
|
|
@@ -153,6 +179,42 @@ void fill_between(SkCanvas* canvas,
|
|
|
153
179
|
float candle_right,
|
|
154
180
|
float candle_area_h,
|
|
155
181
|
uint32_t color,
|
|
156
|
-
float opacity
|
|
182
|
+
float opacity,
|
|
183
|
+
const LineMorph* upper_from = nullptr,
|
|
184
|
+
const LineMorph* lower_from = nullptr,
|
|
185
|
+
float morph_t = 1.f);
|
|
186
|
+
|
|
187
|
+
// Ichimoku's cloud: the region between two aligned series, filled with
|
|
188
|
+
// `above_color` where `a` is over `b` and `below_color` where it is under, both
|
|
189
|
+
// at their alpha × `opacity`.
|
|
190
|
+
//
|
|
191
|
+
// Each run is split at every crossover, with the interpolated meeting point
|
|
192
|
+
// added to the contour on both sides, so the two tones abut exactly instead of
|
|
193
|
+
// overlapping or leaving a seam. NaN in either series breaks the run, as in
|
|
194
|
+
// fill_between. `time_shift_ms` shifts the whole cloud along the time axis the
|
|
195
|
+
// same way draw() does.
|
|
196
|
+
//
|
|
197
|
+
// Morphs per edge like fill_between. Which tone a run takes is read off the
|
|
198
|
+
// interpolated vertices rather than the raw values, so a crossover that only
|
|
199
|
+
// exists mid-morph still splits the cloud where the two edges actually meet.
|
|
200
|
+
void fill_cloud(SkCanvas* canvas,
|
|
201
|
+
const Layout& lay,
|
|
202
|
+
const PriceBounds& bounds,
|
|
203
|
+
const ::VroomCandle* visible,
|
|
204
|
+
std::size_t n,
|
|
205
|
+
const double* a_visible,
|
|
206
|
+
const double* b_visible,
|
|
207
|
+
int64_t window_ms,
|
|
208
|
+
int64_t visible_start_ms,
|
|
209
|
+
int64_t candle_duration_ms,
|
|
210
|
+
float candle_right,
|
|
211
|
+
float candle_area_h,
|
|
212
|
+
uint32_t above_color,
|
|
213
|
+
uint32_t below_color,
|
|
214
|
+
float opacity,
|
|
215
|
+
int64_t time_shift_ms = 0,
|
|
216
|
+
const LineMorph* a_from = nullptr,
|
|
217
|
+
const LineMorph* b_from = nullptr,
|
|
218
|
+
float morph_t = 1.f);
|
|
157
219
|
|
|
158
220
|
} // namespace vroom::ma_overlay
|
package/cpp/_core_src/macd.cpp
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#include "macd.h"
|
|
2
2
|
|
|
3
|
-
#include <
|
|
3
|
+
#include <algorithm> // std::max
|
|
4
|
+
#include <cmath> // std::nan, std::isfinite, std::abs
|
|
4
5
|
|
|
5
6
|
#include "ma.h"
|
|
6
7
|
#include "series_ma.h"
|
|
@@ -36,4 +37,26 @@ void compute(const ::VroomCandle* candles, std::size_t n, int fast, int slow,
|
|
|
36
37
|
}
|
|
37
38
|
}
|
|
38
39
|
|
|
40
|
+
double autoscale(const double* macd, const double* signal, const double* hist,
|
|
41
|
+
std::size_t n) {
|
|
42
|
+
double scale = 0.0;
|
|
43
|
+
const auto track = [&](const double* s) {
|
|
44
|
+
if (!s) return;
|
|
45
|
+
for (std::size_t i = 0; i < n; ++i) {
|
|
46
|
+
if (std::isfinite(s[i])) scale = std::max(scale, std::abs(s[i]));
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
track(macd);
|
|
50
|
+
track(signal);
|
|
51
|
+
track(hist);
|
|
52
|
+
return scale;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
double band_fraction(double v, double scale, double y_scale) {
|
|
56
|
+
// Nothing on show yet — everything collapses onto the zero line rather than
|
|
57
|
+
// dividing by zero.
|
|
58
|
+
if (!(scale > 0.0)) return 0.5;
|
|
59
|
+
return 0.5 + (v / scale) * 0.5 * kBandPadFraction * y_scale;
|
|
60
|
+
}
|
|
61
|
+
|
|
39
62
|
} // namespace vroom::macd
|
package/cpp/_core_src/macd.h
CHANGED
|
@@ -16,6 +16,10 @@
|
|
|
16
16
|
|
|
17
17
|
namespace vroom::macd {
|
|
18
18
|
|
|
19
|
+
// How much of the pane's half-band an extreme is allowed to fill, so a peak
|
|
20
|
+
// stops short of the separator instead of touching it.
|
|
21
|
+
constexpr double kBandPadFraction = 0.85;
|
|
22
|
+
|
|
19
23
|
// Computes MACD over [candles, candles+n). fast/slow/signal are clamped to >= 1
|
|
20
24
|
// (callers pass 12/26/9 by default; slow should exceed fast). `source` selects
|
|
21
25
|
// the price series (vroom::ma::Source); `ma_kind` picks the averaging for the
|
|
@@ -29,4 +33,16 @@ void compute(const ::VroomCandle* candles, std::size_t n, int fast, int slow,
|
|
|
29
33
|
std::vector<double>& macd_out, std::vector<double>& signal_out,
|
|
30
34
|
std::vector<double>& hist_out);
|
|
31
35
|
|
|
36
|
+
// The amplitude the pane band is fitted to, symmetric about zero: the largest
|
|
37
|
+
// finite magnitude across the series on show, or 0 when there is nothing to
|
|
38
|
+
// plot. Pass nullptr for a hidden series so the rest fill the band.
|
|
39
|
+
double autoscale(const double* macd, const double* signal, const double* hist,
|
|
40
|
+
std::size_t n);
|
|
41
|
+
|
|
42
|
+
// Where a value sits in the pane band, as a fraction of its height — 0 at the
|
|
43
|
+
// bottom edge, 1 at the top, 0.5 on the zero line. `y_scale` is the user's
|
|
44
|
+
// y-axis zoom (1 = the default fit). Split out of the renderer so the
|
|
45
|
+
// interval-morph capture maps its geometry through the same math.
|
|
46
|
+
double band_fraction(double v, double scale, double y_scale);
|
|
47
|
+
|
|
32
48
|
} // namespace vroom::macd
|