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
|
@@ -9,17 +9,24 @@
|
|
|
9
9
|
#include "include/core/SkPaint.h"
|
|
10
10
|
#include "include/core/SkPath.h"
|
|
11
11
|
#include "include/core/SkPathBuilder.h"
|
|
12
|
+
#include "include/core/SkPoint.h"
|
|
12
13
|
#include "include/core/SkRect.h"
|
|
14
|
+
#include "include/core/SkShader.h"
|
|
13
15
|
#include "include/core/SkTypeface.h"
|
|
14
16
|
#include "include/effects/SkDashPathEffect.h"
|
|
15
17
|
#pragma clang diagnostic pop
|
|
16
18
|
|
|
19
|
+
#include <algorithm>
|
|
17
20
|
#include <cmath>
|
|
18
21
|
#include <cstdio>
|
|
19
22
|
#include <cstring>
|
|
20
23
|
|
|
21
24
|
#include "chart.h"
|
|
22
25
|
#include "fonts.h"
|
|
26
|
+
#include "gradient.h"
|
|
27
|
+
#include "line_morph.h"
|
|
28
|
+
#include "pane_series.h"
|
|
29
|
+
#include "rsi.h"
|
|
23
30
|
#include "style_inherit.h"
|
|
24
31
|
#include "theme.h"
|
|
25
32
|
#include "viewport.h"
|
|
@@ -36,6 +43,11 @@ constexpr SkColor kRefLine = 0xff30363d; // band reference lines
|
|
|
36
43
|
constexpr SkColor kDivider = 0xff21262d; // pane separator
|
|
37
44
|
constexpr SkScalar kDash[2] = {3.f, 3.f};
|
|
38
45
|
constexpr float kLineWidth = 1.5f; // default stroke for both lines
|
|
46
|
+
// Alpha the extreme shading reaches at the end of the scale. Only a reading
|
|
47
|
+
// pinned at 100 or 0 collects all of it, which is why this sits higher than the
|
|
48
|
+
// wash typically paints. Still short of opaque: the RSI line and the band rule
|
|
49
|
+
// sit on top of it and have to stay readable at its deepest.
|
|
50
|
+
constexpr float kExtremeFillAlpha = 0.7f;
|
|
39
51
|
} // namespace
|
|
40
52
|
|
|
41
53
|
void draw(SkCanvas* canvas,
|
|
@@ -50,19 +62,31 @@ void draw(SkCanvas* canvas,
|
|
|
50
62
|
int64_t candle_duration_ms,
|
|
51
63
|
float candle_right,
|
|
52
64
|
float pane_top,
|
|
53
|
-
float pane_bottom
|
|
54
|
-
|
|
65
|
+
float pane_bottom,
|
|
66
|
+
const vroom::LineMorph* rsi_from,
|
|
67
|
+
const vroom::LineMorph* rsi_ma_from,
|
|
68
|
+
float morph_t) {
|
|
69
|
+
if (!canvas || candle_right <= 0.f) return;
|
|
55
70
|
const float band_h = pane_bottom - pane_top;
|
|
56
71
|
if (band_h <= 0.f) return;
|
|
72
|
+
morph_t = std::clamp(morph_t, 0.f, 1.f);
|
|
73
|
+
// A fade's outgoing half has no new data at all, so the captures are the
|
|
74
|
+
// whole frame. Nothing on either side means nothing to paint, shell
|
|
75
|
+
// included.
|
|
76
|
+
if (n == 0 && vroom::morph_line_count(rsi_from, morph_t) == 0 &&
|
|
77
|
+
vroom::morph_line_count(rsi_ma_from, morph_t) == 0) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
57
80
|
|
|
58
81
|
// Map 0..100 about the pane center so the user y-zoom scales symmetrically
|
|
59
82
|
// around 50. z == 1 reproduces the default fit (0 -> pane_bottom, 100 ->
|
|
60
83
|
// pane_top); z > 1 zooms in (taller), z < 1 zooms out (flatter).
|
|
61
84
|
const VroomRSI& cfg = chart.rsi;
|
|
62
|
-
const float center_y = (pane_top + pane_bottom) * 0.5f;
|
|
63
|
-
const float z = static_cast<float>(chart.rsi_y_scale);
|
|
64
85
|
auto y_for = [&](double v) -> float {
|
|
65
|
-
return
|
|
86
|
+
return pane_bottom -
|
|
87
|
+
static_cast<float>(
|
|
88
|
+
vroom::rsi::band_fraction(v, chart.rsi_y_scale)) *
|
|
89
|
+
band_h;
|
|
66
90
|
};
|
|
67
91
|
|
|
68
92
|
// Mask the band with the background so candles/volume that overflow below
|
|
@@ -86,6 +110,52 @@ void draw(SkCanvas* canvas,
|
|
|
86
110
|
// Configurable band reference lines (overbought / oversold).
|
|
87
111
|
const float y_upper = y_for(cfg.upper_band);
|
|
88
112
|
const float y_lower = y_for(cfg.lower_band);
|
|
113
|
+
|
|
114
|
+
// Shade the stretches that reach past a band, fading out at the rule itself
|
|
115
|
+
// and deepening toward the end of the scale — so how far the reading went
|
|
116
|
+
// past the threshold reads at a glance, not just that it did.
|
|
117
|
+
//
|
|
118
|
+
// Drawn before the rules and the lines so both stay legible on top. The
|
|
119
|
+
// region is built across the whole series and then clipped to the far side
|
|
120
|
+
// of the rule, which lands the crossings exactly without solving for them.
|
|
121
|
+
auto fill_extreme = [&](double band, double limit, SkColor base,
|
|
122
|
+
bool above) {
|
|
123
|
+
// The ramp spans band -> limit. Pinned together (upper at 100, lower at
|
|
124
|
+
// 0) there is nothing to ramp through, and Skia would clamp the
|
|
125
|
+
// zero-length gradient to its end color: a flat slab at full alpha.
|
|
126
|
+
if (band == limit) return;
|
|
127
|
+
const float y_band = y_for(band);
|
|
128
|
+
const SkPath area = vroom::pane_series::build_band_area(
|
|
129
|
+
lay, visible, n, rsi_visible, window_ms, visible_start_ms,
|
|
130
|
+
candle_duration_ms, pane_top, pane_bottom, rsi_from, morph_t,
|
|
131
|
+
y_band, y_for);
|
|
132
|
+
if (area.isEmpty()) return;
|
|
133
|
+
|
|
134
|
+
// Anchored to the RSI value rather than the pane edge, so the shading
|
|
135
|
+
// means the same thing at any y-zoom and a shallow excursion stays
|
|
136
|
+
// faint instead of saturating on a short pane.
|
|
137
|
+
const SkPoint pts[2] = {SkPoint::Make(0.f, y_band),
|
|
138
|
+
SkPoint::Make(0.f, y_for(limit))};
|
|
139
|
+
SkPaint fill;
|
|
140
|
+
fill.setAntiAlias(true);
|
|
141
|
+
fill.setStyle(SkPaint::kFill_Style);
|
|
142
|
+
fill.setShader(
|
|
143
|
+
vroom::linear_alpha_ramp(pts, base, 0.f, kExtremeFillAlpha));
|
|
144
|
+
|
|
145
|
+
canvas->save();
|
|
146
|
+
canvas->clipRect(
|
|
147
|
+
above ? SkRect::MakeLTRB(0.f, pane_top, candle_right, y_band)
|
|
148
|
+
: SkRect::MakeLTRB(0.f, y_band, candle_right, pane_bottom));
|
|
149
|
+
canvas->drawPath(area, fill);
|
|
150
|
+
canvas->restore();
|
|
151
|
+
};
|
|
152
|
+
if (cfg.extreme_fill) {
|
|
153
|
+
fill_extreme(cfg.upper_band, 100.0,
|
|
154
|
+
chart.theme.colors[VROOM_COLOR_ACCENT_BULL], true);
|
|
155
|
+
fill_extreme(cfg.lower_band, 0.0,
|
|
156
|
+
chart.theme.colors[VROOM_COLOR_ACCENT_BEAR], false);
|
|
157
|
+
}
|
|
158
|
+
|
|
89
159
|
if (cfg.bands_visible) {
|
|
90
160
|
SkPaint ref;
|
|
91
161
|
ref.setAntiAlias(true);
|
|
@@ -98,41 +168,27 @@ void draw(SkCanvas* canvas,
|
|
|
98
168
|
|
|
99
169
|
// Strokes a value series as a polyline; a NaN lifts the pen so undefined
|
|
100
170
|
// leading values (i < period) leave a gap.
|
|
101
|
-
auto stroke_series = [&](const double* series,
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
bool pen_down = false;
|
|
105
|
-
for (std::size_t i = 0; i < n; ++i) {
|
|
106
|
-
const double v = series[i];
|
|
107
|
-
if (!std::isfinite(v)) {
|
|
108
|
-
pen_down = false;
|
|
109
|
-
continue;
|
|
110
|
-
}
|
|
111
|
-
const float x = vroom::candle_center_x(
|
|
112
|
-
lay, visible[i].time_ms, candle_duration_ms, visible_start_ms,
|
|
113
|
-
window_ms);
|
|
114
|
-
const float y = y_for(v);
|
|
115
|
-
if (pen_down) {
|
|
116
|
-
path.lineTo(x, y);
|
|
117
|
-
} else {
|
|
118
|
-
path.moveTo(x, y);
|
|
119
|
-
pen_down = true;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
171
|
+
auto stroke_series = [&](const double* series, const vroom::LineMorph* from,
|
|
172
|
+
SkColor color, float width) {
|
|
173
|
+
if (!series && !from) return;
|
|
122
174
|
SkPaint line;
|
|
123
175
|
line.setAntiAlias(true);
|
|
124
176
|
line.setColor(color);
|
|
125
177
|
line.setStyle(SkPaint::kStroke_Style);
|
|
126
178
|
line.setStrokeWidth(width);
|
|
127
|
-
canvas->drawPath(
|
|
179
|
+
canvas->drawPath(
|
|
180
|
+
vroom::pane_series::build_path(
|
|
181
|
+
lay, visible, n, series, window_ms, visible_start_ms,
|
|
182
|
+
candle_duration_ms, pane_top, pane_bottom, from, morph_t, y_for),
|
|
183
|
+
line);
|
|
128
184
|
};
|
|
129
185
|
if (cfg.line_visible) {
|
|
130
|
-
stroke_series(rsi_visible, color_or(cfg.line_color, kRsiLine),
|
|
186
|
+
stroke_series(rsi_visible, rsi_from, color_or(cfg.line_color, kRsiLine),
|
|
131
187
|
width_or(cfg.line_width, kLineWidth));
|
|
132
188
|
}
|
|
133
189
|
// Trendline on top. Its own visibility is handled upstream: a hidden
|
|
134
190
|
// trendline isn't computed, so the caller passes null.
|
|
135
|
-
stroke_series(rsi_ma_visible, color_or(cfg.ma_color, kRsiMaLine),
|
|
191
|
+
stroke_series(rsi_ma_visible, rsi_ma_from, color_or(cfg.ma_color, kRsiMaLine),
|
|
136
192
|
width_or(cfg.ma_width, kLineWidth));
|
|
137
193
|
|
|
138
194
|
// Caption, top-left of the pane.
|
package/cpp/_core_src/rsi_pane.h
CHANGED
|
@@ -13,6 +13,7 @@ class SkCanvas;
|
|
|
13
13
|
|
|
14
14
|
namespace vroom {
|
|
15
15
|
struct Layout;
|
|
16
|
+
struct LineMorph;
|
|
16
17
|
} // namespace vroom
|
|
17
18
|
|
|
18
19
|
struct VroomChart;
|
|
@@ -22,6 +23,12 @@ namespace vroom::rsi_pane {
|
|
|
22
23
|
// Draws the RSI pane spanning vertically [pane_top, pane_bottom]. `rsi_visible`
|
|
23
24
|
// is the cached RSI series aligned with `visible` (NaN where undefined). Shares
|
|
24
25
|
// the candles' horizontal mapping (candle_center_x) so it scrolls in lock-step.
|
|
26
|
+
//
|
|
27
|
+
// `rsi_from` / `rsi_ma_from` / `morph_t` are the interval morph: each capture
|
|
28
|
+
// holds its line's outgoing shape indexed from the right (slot 0 = newest) and
|
|
29
|
+
// every vertex slides from where it sat before the timeframe switch to where it
|
|
30
|
+
// sits now. A fade's outgoing half passes n = 0 with morph_t = 0, which paints
|
|
31
|
+
// the captures alone.
|
|
25
32
|
void draw(SkCanvas* canvas,
|
|
26
33
|
const VroomChart& chart,
|
|
27
34
|
const Layout& lay,
|
|
@@ -34,6 +41,9 @@ void draw(SkCanvas* canvas,
|
|
|
34
41
|
int64_t candle_duration_ms,
|
|
35
42
|
float candle_right,
|
|
36
43
|
float pane_top,
|
|
37
|
-
float pane_bottom
|
|
44
|
+
float pane_bottom,
|
|
45
|
+
const LineMorph* rsi_from = nullptr,
|
|
46
|
+
const LineMorph* rsi_ma_from = nullptr,
|
|
47
|
+
float morph_t = 1.f);
|
|
38
48
|
|
|
39
49
|
} // namespace vroom::rsi_pane
|
package/cpp/_core_src/theme.cpp
CHANGED
|
@@ -23,6 +23,11 @@ constexpr uint32_t kDefaultColors[VROOM_COLOR_COUNT_] = {
|
|
|
23
23
|
0xff26a69a, // ACCENT_BULL — classic teal-green (price indicator, volume, MACD)
|
|
24
24
|
0xffef5350, // ACCENT_BEAR — classic red
|
|
25
25
|
0xff8957e5, // LINE — line-chart close polyline; violet, matching the RSI line
|
|
26
|
+
// SKELETON — transparent sentinel: inherit GRID, the way BORDER_BULL and
|
|
27
|
+
// the wick colors inherit their fills. The gridlines are already the
|
|
28
|
+
// chart's tone for structure rather than data, which is what the loading
|
|
29
|
+
// line is, so matching them keeps it from being read as a series.
|
|
30
|
+
0x00000000,
|
|
26
31
|
};
|
|
27
32
|
|
|
28
33
|
constexpr float kDefaultFloats[VROOM_FLOAT_COUNT_] = {
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// How big the line chart's tip marker is, and how much gutter it needs.
|
|
2
|
+
//
|
|
3
|
+
// The dot marks the newest close, which on a view pinned to the latest bar sits
|
|
4
|
+
// half a slot from the right edge of the plot — a few pixels. The dot's own
|
|
5
|
+
// radius is larger than that, so it can only be drawn in full if it is allowed
|
|
6
|
+
// to spill into the gutter between the plot and the y-axis strip. That makes the
|
|
7
|
+
// marker's size a layout input, not just a paint detail, so the renderer
|
|
8
|
+
// (draw_close_tip in ma_overlay.cpp) and the layout (VroomChart::layout) read it
|
|
9
|
+
// from here rather than each carrying its own copy.
|
|
10
|
+
//
|
|
11
|
+
// The pulse ring is deliberately not part of this. At its widest it is 3.5x the
|
|
12
|
+
// border radius, and reserving that much gutter would eat the plot; it is
|
|
13
|
+
// allowed to clip against the axis.
|
|
14
|
+
//
|
|
15
|
+
// Skia-free and header-only so the unit tests can cover it; see
|
|
16
|
+
// tests/test_tip_geometry.cpp.
|
|
17
|
+
|
|
18
|
+
#pragma once
|
|
19
|
+
|
|
20
|
+
#include <algorithm>
|
|
21
|
+
|
|
22
|
+
namespace vroom::tip_geometry {
|
|
23
|
+
|
|
24
|
+
// Width of the background-colored ring that separates the tip dot from the line
|
|
25
|
+
// and from the pulse expanding out behind it.
|
|
26
|
+
constexpr float kBorderPx = 2.f;
|
|
27
|
+
|
|
28
|
+
// Gap left between the dot's outer edge and the y-axis strip. The dot touching
|
|
29
|
+
// the price labels reads as a rendering fault even when nothing is clipped.
|
|
30
|
+
constexpr float kClearPx = 4.f;
|
|
31
|
+
|
|
32
|
+
struct Geometry {
|
|
33
|
+
float dot_r; // the filled dot in the line's color
|
|
34
|
+
float border_r; // the dot plus its background-colored halo
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// Scaling off the stroke keeps the marker proportionate at any line width; the
|
|
38
|
+
// floor stops a hairline chart from getting an invisible dot.
|
|
39
|
+
inline Geometry of(float line_width) {
|
|
40
|
+
const float w = line_width > 0.f ? line_width : 1.5f;
|
|
41
|
+
const float dot_r = std::max(2.f, w * 1.5f);
|
|
42
|
+
return Geometry{dot_r, dot_r + kBorderPx};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Gutter width that lets the dot draw in full with `kClearPx` to spare.
|
|
46
|
+
//
|
|
47
|
+
// draw_close_tip drops the marker once its center passes the plot's right edge,
|
|
48
|
+
// so the center is at worst flush with that edge and the dot overhangs it by
|
|
49
|
+
// exactly `border_r`. A gutter of that plus the clearance therefore makes "the
|
|
50
|
+
// dot never touches the axis strip" true by construction, whatever the window.
|
|
51
|
+
inline float gutter_px(float line_width) {
|
|
52
|
+
return of(line_width).border_r + kClearPx;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
} // namespace vroom::tip_geometry
|
package/cpp/_core_src/viewport.h
CHANGED
|
@@ -92,6 +92,30 @@ struct CandleSnapshot {
|
|
|
92
92
|
float x;
|
|
93
93
|
float open, high, low, close;
|
|
94
94
|
bool bull; // close >= open; selects the fill / wick / border color
|
|
95
|
+
// Set when the capture came from the loading skeleton. Such a slot starts
|
|
96
|
+
// out grey and semi-transparent rather than in its own bull/bear color, so
|
|
97
|
+
// the draw path has to blend from `skeleton_alpha`-scaled grey instead —
|
|
98
|
+
// that color blend is what makes the hand-off read as the placeholder
|
|
99
|
+
// *becoming* the data (see candles::draw).
|
|
100
|
+
bool skeleton = false;
|
|
101
|
+
// The wave's alpha for this bar at capture time. Carried so the morph's
|
|
102
|
+
// first frame matches the skeleton frame it replaced; without it every bar
|
|
103
|
+
// would jump to full opacity the instant data landed.
|
|
104
|
+
float skeleton_alpha = 1.f;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// One vertex of the loading line's morph, holding both ends of the animation
|
|
108
|
+
// so a frame is a plain lerp between them.
|
|
109
|
+
//
|
|
110
|
+
// The two y's are in different spaces on purpose. The sine knows nothing about
|
|
111
|
+
// prices, so it's a fraction of the pane; the candle centre is a fraction of
|
|
112
|
+
// the price band, the same space CandleSnapshot uses — which is what keeps the
|
|
113
|
+
// line sitting exactly where the bars emerge from, since both resolve through
|
|
114
|
+
// y_at_fraction.
|
|
115
|
+
struct LinePoint {
|
|
116
|
+
float x; // fraction of the candle-area width
|
|
117
|
+
float from_y; // fraction of the pane height — the sine, frozen at capture
|
|
118
|
+
float to_y; // fraction of the price band — the candle's vertical centre
|
|
95
119
|
};
|
|
96
120
|
|
|
97
121
|
// How many captured slots still contribute to a frame — 0 once the morph is
|
|
@@ -108,6 +132,50 @@ inline std::size_t morph_from_count(const CandleSnapshot* from,
|
|
|
108
132
|
// anything else = fade the outgoing snapshot out then the new scene in.
|
|
109
133
|
inline bool interval_morph_is_fade(int32_t mode) { return mode != 0; }
|
|
110
134
|
|
|
135
|
+
// Rewrites a fresh capture so it starts from the shape currently on screen
|
|
136
|
+
// rather than from the data underneath it. Live ticks arrive faster than a
|
|
137
|
+
// morph lands, so every restart interrupts one; without this the bar would jump
|
|
138
|
+
// back to its un-morphed position on each tick, which is the snap the animation
|
|
139
|
+
// exists to remove.
|
|
140
|
+
//
|
|
141
|
+
// `interrupted` is the capture being replaced and `morph_t` the progress it had
|
|
142
|
+
// reached, so `dst` becomes exactly the frame that was being painted: the draw
|
|
143
|
+
// path interpolates in this same fraction space (see candles::draw), which is
|
|
144
|
+
// what makes a plain lerp here land pixel-exact.
|
|
145
|
+
//
|
|
146
|
+
// Slot 0 is the newest in both, so a differing visible count only drops the
|
|
147
|
+
// oldest slots — those keep the fresh capture, which is where they already are.
|
|
148
|
+
inline void blend_candle_snapshots(CandleSnapshot* dst, std::size_t dst_n,
|
|
149
|
+
const CandleSnapshot* interrupted,
|
|
150
|
+
std::size_t interrupted_n, float morph_t) {
|
|
151
|
+
if (!dst || !interrupted) return;
|
|
152
|
+
const std::size_t n = dst_n < interrupted_n ? dst_n : interrupted_n;
|
|
153
|
+
for (std::size_t k = 0; k < n; ++k) {
|
|
154
|
+
const CandleSnapshot& from = interrupted[k];
|
|
155
|
+
CandleSnapshot& to = dst[k];
|
|
156
|
+
const auto mix = [morph_t](float a, float b) {
|
|
157
|
+
return a + (b - a) * morph_t;
|
|
158
|
+
};
|
|
159
|
+
to.open = mix(from.open, to.open);
|
|
160
|
+
to.high = mix(from.high, to.high);
|
|
161
|
+
to.low = mix(from.low, to.low);
|
|
162
|
+
to.close = mix(from.close, to.close);
|
|
163
|
+
to.x = mix(from.x, to.x);
|
|
164
|
+
// `bull` stays the fresh one: the draw path colors a paired slot from
|
|
165
|
+
// the live candle and only reads the capture's flag for a slot the next
|
|
166
|
+
// update drops, where the newer direction is the better answer.
|
|
167
|
+
//
|
|
168
|
+
// The skeleton state, by contrast, carries over from the interrupted
|
|
169
|
+
// capture: a tick landing mid-hand-off must not abandon the grey blend
|
|
170
|
+
// partway, or the bar would snap to full color while its geometry is
|
|
171
|
+
// still moving. Its alpha follows the same lerp as the geometry.
|
|
172
|
+
if (from.skeleton) {
|
|
173
|
+
to.skeleton = true;
|
|
174
|
+
to.skeleton_alpha = mix(from.skeleton_alpha, 1.f);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
111
179
|
// Returns the indices of candles whose time_ms falls in [start_ms, end_ms].
|
|
112
180
|
// When both are 0, returns the full range (Phase 1 default-everything behavior).
|
|
113
181
|
// Candles must be sorted ascending by time_ms (invariant of the public API).
|