react-native-vroom-chart 0.14.0 → 0.16.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 +289 -2
- package/cpp/_core_include/vroom/vroom_chart.h +221 -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/chart.cpp +477 -172
- package/cpp/_core_src/chart.h +173 -1
- package/cpp/_core_src/chart_facade.cpp +317 -23
- package/cpp/_core_src/fair_value_gaps.cpp +76 -0
- package/cpp/_core_src/fair_value_gaps.h +54 -0
- package/cpp/_core_src/footprints.cpp +226 -0
- package/cpp/_core_src/footprints.h +45 -0
- package/cpp/_core_src/footprints_layout.cpp +140 -0
- package/cpp/_core_src/footprints_layout.h +94 -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/ma_overlay.cpp +259 -36
- package/cpp/_core_src/ma_overlay.h +57 -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/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/tip_pulse.h +4 -4
- package/cpp/_core_src/viewport.h +35 -0
- package/lib/index.d.mts +385 -3
- package/lib/index.d.ts +385 -3
- package/lib/index.js +305 -7
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +305 -7
- package/lib/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/VroomChart.tsx +108 -7
- package/src/dataTransitions.ts +47 -0
- package/src/index.ts +10 -0
- package/src/jsi.d.ts +136 -1
- package/src/types.ts +10 -0
- package/src/useChartCore.ts +330 -5
package/cpp/_core_src/chart.cpp
CHANGED
|
@@ -20,11 +20,16 @@
|
|
|
20
20
|
#include "include/core/SkRect.h"
|
|
21
21
|
#pragma clang diagnostic pop
|
|
22
22
|
|
|
23
|
+
#include "atr.h"
|
|
24
|
+
#include "atr_pane.h"
|
|
23
25
|
#include "bollinger.h"
|
|
24
26
|
#include "candles.h"
|
|
25
27
|
#include "chart_internal.h"
|
|
26
28
|
#include "crosshair.h"
|
|
27
29
|
#include "drawings.h"
|
|
30
|
+
#include "footprints.h"
|
|
31
|
+
#include "fvg_overlay.h"
|
|
32
|
+
#include "ichimoku.h"
|
|
28
33
|
#include "labels.h"
|
|
29
34
|
#include "liquidity.h"
|
|
30
35
|
#include "ma.h"
|
|
@@ -58,7 +63,8 @@ vroom::Layout VroomChart::layout() const {
|
|
|
58
63
|
const float axis_w = axis_width_px > 0.f
|
|
59
64
|
? axis_width_px
|
|
60
65
|
: width_px * theme.floats[VROOM_FLOAT_Y_AXIS_WIDTH_RATIO];
|
|
61
|
-
const int pane_count =
|
|
66
|
+
const int pane_count =
|
|
67
|
+
(rsi.enabled ? 1 : 0) + (macd.enabled ? 1 : 0) + (atr.enabled ? 1 : 0);
|
|
62
68
|
const float indicator_h = static_cast<float>(pane_count) * height_px *
|
|
63
69
|
theme.floats[VROOM_FLOAT_INDICATOR_HEIGHT_FRAC];
|
|
64
70
|
return vroom::Layout{
|
|
@@ -98,6 +104,90 @@ void VroomChart::ensure_macd() {
|
|
|
98
104
|
macd_dirty = false;
|
|
99
105
|
}
|
|
100
106
|
|
|
107
|
+
void VroomChart::ensure_atr() {
|
|
108
|
+
if (!atr.enabled || !atr_dirty) return;
|
|
109
|
+
vroom::atr::compute(candles.data(), candles.size(), atr.period,
|
|
110
|
+
atr.smoothing, atr_cache);
|
|
111
|
+
atr_dirty = false;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
int VroomChart::indicator_panes(vroom::IndicatorPane out[vroom::kMaxPanes]) const {
|
|
115
|
+
int count = 0;
|
|
116
|
+
if (rsi.enabled) out[count++] = {rsi_order, vroom::PaneKind::Rsi};
|
|
117
|
+
if (macd.enabled) out[count++] = {macd_order, vroom::PaneKind::Macd};
|
|
118
|
+
if (atr.enabled) out[count++] = {atr_order, vroom::PaneKind::Atr};
|
|
119
|
+
// Insertion sort: at most three entries, already nearly ordered.
|
|
120
|
+
for (int i = 1; i < count; ++i) {
|
|
121
|
+
const vroom::IndicatorPane key = out[i];
|
|
122
|
+
int j = i - 1;
|
|
123
|
+
for (; j >= 0 && out[j].order > key.order; --j) out[j + 1] = out[j];
|
|
124
|
+
out[j + 1] = key;
|
|
125
|
+
}
|
|
126
|
+
return count;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
void VroomChart::draw_indicator_panes(SkCanvas* canvas,
|
|
130
|
+
const vroom::Layout& lay,
|
|
131
|
+
const VroomCandle* visible,
|
|
132
|
+
std::size_t n, std::size_t first,
|
|
133
|
+
int64_t window_ms, float candle_right,
|
|
134
|
+
float pane_top, bool use_capture,
|
|
135
|
+
float morph_t) {
|
|
136
|
+
vroom::IndicatorPane panes[vroom::kMaxPanes];
|
|
137
|
+
const int count = indicator_panes(panes);
|
|
138
|
+
const float pane_h =
|
|
139
|
+
height_px * theme.floats[VROOM_FLOAT_INDICATOR_HEIGHT_FRAC];
|
|
140
|
+
|
|
141
|
+
// A cache only lines up with the candles once its ensure_* has run against
|
|
142
|
+
// the current series; until then there is nothing to plot.
|
|
143
|
+
const auto visible_slice = [&](const std::vector<double>& cache) -> const double* {
|
|
144
|
+
return cache.size() == candles.size() ? cache.data() + first : nullptr;
|
|
145
|
+
};
|
|
146
|
+
const auto capture = [&](vroom::LineKey key) -> const vroom::LineMorph* {
|
|
147
|
+
return use_capture ? vroom::find_line_morph(morph_lines, key) : nullptr;
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
for (int i = 0; i < count; ++i) {
|
|
151
|
+
const float pane_bottom = pane_top + pane_h;
|
|
152
|
+
switch (panes[i].kind) {
|
|
153
|
+
case vroom::PaneKind::Rsi: {
|
|
154
|
+
ensure_rsi();
|
|
155
|
+
vroom::rsi_pane::draw(
|
|
156
|
+
canvas, *this, lay, visible, n, visible_slice(rsi_cache),
|
|
157
|
+
rsi.ma_visible ? visible_slice(rsi_ma_cache) : nullptr,
|
|
158
|
+
window_ms, visible_start_ms, candle_duration_ms,
|
|
159
|
+
candle_right, pane_top, pane_bottom,
|
|
160
|
+
capture({vroom::LineKind::Rsi}),
|
|
161
|
+
rsi.ma_visible ? capture({vroom::LineKind::RsiMa}) : nullptr,
|
|
162
|
+
morph_t);
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
case vroom::PaneKind::Macd: {
|
|
166
|
+
ensure_macd();
|
|
167
|
+
vroom::macd_pane::draw(
|
|
168
|
+
canvas, *this, lay, visible, n, visible_slice(macd_cache),
|
|
169
|
+
visible_slice(macd_signal_cache),
|
|
170
|
+
visible_slice(macd_hist_cache), window_ms, visible_start_ms,
|
|
171
|
+
candle_duration_ms, candle_right, pane_top, pane_bottom,
|
|
172
|
+
capture({vroom::LineKind::Macd}),
|
|
173
|
+
capture({vroom::LineKind::MacdSignal}),
|
|
174
|
+
capture({vroom::LineKind::MacdHistogram}), morph_t);
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
case vroom::PaneKind::Atr: {
|
|
178
|
+
ensure_atr();
|
|
179
|
+
vroom::atr_pane::draw(canvas, *this, lay, visible, n,
|
|
180
|
+
visible_slice(atr_cache), window_ms,
|
|
181
|
+
visible_start_ms, candle_duration_ms,
|
|
182
|
+
candle_right, pane_top, pane_bottom,
|
|
183
|
+
capture({vroom::LineKind::Atr}), morph_t);
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
pane_top = pane_bottom;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
101
191
|
void VroomChart::ensure_overlays() {
|
|
102
192
|
if (!overlays_dirty) return;
|
|
103
193
|
overlay_caches.resize(overlays.size());
|
|
@@ -125,6 +215,326 @@ void VroomChart::ensure_bollinger() {
|
|
|
125
215
|
bollinger_dirty = false;
|
|
126
216
|
}
|
|
127
217
|
|
|
218
|
+
void VroomChart::ensure_ichimoku() {
|
|
219
|
+
if (!ichimoku.enabled || !ichimoku_dirty) return;
|
|
220
|
+
vroom::ichimoku::compute(candles.data(), candles.size(),
|
|
221
|
+
ichimoku.tenkan_period, ichimoku.kijun_period,
|
|
222
|
+
ichimoku.senkou_b_period, ich_tenkan_cache,
|
|
223
|
+
ich_kijun_cache, ich_senkou_a_cache,
|
|
224
|
+
ich_senkou_b_cache, ich_chikou_cache);
|
|
225
|
+
ichimoku_dirty = false;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
void VroomChart::ensure_fvg() {
|
|
229
|
+
if (!fvg.enabled || !fvg_dirty) return;
|
|
230
|
+
vroom::fvg::compute(candles.data(), candles.size(), fvg.max_bars_back,
|
|
231
|
+
fvg.wait_for_close != 0, fvg.fill_type, fvg_cache);
|
|
232
|
+
fvg_dirty = false;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
void VroomChart::ensure_footprint_buckets() {
|
|
236
|
+
if (!footprint_buckets_dirty) return;
|
|
237
|
+
footprint_buckets = vroom::footprints::build_buckets(
|
|
238
|
+
candles.data(), candles.size(), candle_duration_ms, footprints.data(),
|
|
239
|
+
footprints.size());
|
|
240
|
+
footprint_buckets_dirty = false;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
vroom::IndexRange VroomChart::ichimoku_source_range(int64_t shift_ms) const {
|
|
244
|
+
return vroom::ichimoku::shifted_source_range(
|
|
245
|
+
candles.data(), candles.size(), visible_start_ms - candle_duration_ms,
|
|
246
|
+
visible_end_ms + candle_duration_ms, shift_ms);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
void VroomChart::draw_overlay_fills(SkCanvas* canvas, const vroom::Layout& lay,
|
|
250
|
+
const vroom::PriceBounds& bounds,
|
|
251
|
+
const VroomCandle* visible, std::size_t n,
|
|
252
|
+
std::size_t first, int64_t window_ms,
|
|
253
|
+
float candle_right, float candle_area_h,
|
|
254
|
+
bool use_capture, float morph_t) {
|
|
255
|
+
const std::size_t sz = candles.size();
|
|
256
|
+
const auto capture = [&](vroom::LineKey key) -> const vroom::LineMorph* {
|
|
257
|
+
return use_capture ? vroom::find_line_morph(morph_lines, key) : nullptr;
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
if (bollinger.enabled) {
|
|
261
|
+
ensure_bollinger();
|
|
262
|
+
if (bollinger.fill_enabled && bb_upper_cache.size() == sz &&
|
|
263
|
+
bb_lower_cache.size() == sz) {
|
|
264
|
+
vroom::ma_overlay::fill_between(
|
|
265
|
+
canvas, lay, bounds, visible, n, bb_upper_cache.data() + first,
|
|
266
|
+
bb_lower_cache.data() + first, window_ms, visible_start_ms,
|
|
267
|
+
candle_duration_ms, candle_right, candle_area_h,
|
|
268
|
+
bollinger.upper_color, bollinger.fill_opacity,
|
|
269
|
+
capture({vroom::LineKind::BollingerUpper}),
|
|
270
|
+
capture({vroom::LineKind::BollingerLower}), morph_t);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (ichimoku.enabled && ichimoku.cloud_enabled) {
|
|
275
|
+
ensure_ichimoku();
|
|
276
|
+
if (ich_senkou_a_cache.size() == sz && ich_senkou_b_cache.size() == sz) {
|
|
277
|
+
const int64_t shift_ms =
|
|
278
|
+
static_cast<int64_t>(ichimoku.displacement) * candle_duration_ms;
|
|
279
|
+
const auto r = ichimoku_source_range(shift_ms);
|
|
280
|
+
// A fade's outgoing half has no new data to slice, but its capture
|
|
281
|
+
// still has to paint.
|
|
282
|
+
const bool has_new = n > 0 && r.end > r.start;
|
|
283
|
+
vroom::ma_overlay::fill_cloud(
|
|
284
|
+
canvas, lay, bounds,
|
|
285
|
+
has_new ? candles.data() + r.start : candles.data(),
|
|
286
|
+
has_new ? r.end - r.start : 0,
|
|
287
|
+
has_new ? ich_senkou_a_cache.data() + r.start : nullptr,
|
|
288
|
+
has_new ? ich_senkou_b_cache.data() + r.start : nullptr,
|
|
289
|
+
window_ms, visible_start_ms, candle_duration_ms, candle_right,
|
|
290
|
+
candle_area_h, ichimoku.bullish_cloud_color,
|
|
291
|
+
ichimoku.bearish_cloud_color, ichimoku.cloud_opacity, shift_ms,
|
|
292
|
+
capture({vroom::LineKind::IchimokuSenkouA}),
|
|
293
|
+
capture({vroom::LineKind::IchimokuSenkouB}), morph_t);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
void VroomChart::draw_overlay_lines(SkCanvas* canvas, const vroom::Layout& lay,
|
|
299
|
+
const vroom::PriceBounds& bounds,
|
|
300
|
+
const VroomCandle* visible, std::size_t n,
|
|
301
|
+
std::size_t first, int64_t window_ms,
|
|
302
|
+
float candle_right, float candle_area_h,
|
|
303
|
+
bool use_capture, float morph_t) {
|
|
304
|
+
const std::size_t sz = candles.size();
|
|
305
|
+
const auto capture = [&](vroom::LineKey key) -> const vroom::LineMorph* {
|
|
306
|
+
return use_capture ? vroom::find_line_morph(morph_lines, key) : nullptr;
|
|
307
|
+
};
|
|
308
|
+
// One price-pane line against the visible slice. `n == 0` (a fade's
|
|
309
|
+
// outgoing half) leaves only the capture to draw.
|
|
310
|
+
const auto stroke = [&](vroom::LineKey key, const std::vector<double>& cache,
|
|
311
|
+
uint32_t color, float width,
|
|
312
|
+
const unsigned char* breaks) {
|
|
313
|
+
if (cache.size() != sz) return;
|
|
314
|
+
vroom::ma_overlay::draw(canvas, lay, bounds, visible, n,
|
|
315
|
+
cache.data() + first, window_ms,
|
|
316
|
+
visible_start_ms, candle_duration_ms,
|
|
317
|
+
candle_right, candle_area_h, color, width,
|
|
318
|
+
breaks, 1.f, 0, capture(key), morph_t);
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
if (!overlays.empty()) {
|
|
322
|
+
ensure_overlays();
|
|
323
|
+
for (std::size_t k = 0; k < overlays.size(); ++k) {
|
|
324
|
+
stroke(vroom::overlay_line_key(overlays[k], k), overlay_caches[k],
|
|
325
|
+
overlays[k].color, overlays[k].width, nullptr);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (vwap.enabled) {
|
|
330
|
+
ensure_vwap();
|
|
331
|
+
stroke({vroom::LineKind::Vwap}, vwap_cache,
|
|
332
|
+
vroom::style::color_or(vwap.color, kVwapLine),
|
|
333
|
+
vroom::style::width_or(vwap.width, 1.5f),
|
|
334
|
+
vwap_breaks.size() == sz ? vwap_breaks.data() + first : nullptr);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (bollinger.enabled) {
|
|
338
|
+
ensure_bollinger();
|
|
339
|
+
stroke({vroom::LineKind::BollingerUpper}, bb_upper_cache,
|
|
340
|
+
bollinger.upper_color, bollinger.upper_width, nullptr);
|
|
341
|
+
stroke({vroom::LineKind::BollingerLower}, bb_lower_cache,
|
|
342
|
+
bollinger.lower_color, bollinger.lower_width, nullptr);
|
|
343
|
+
stroke({vroom::LineKind::BollingerMiddle}, bb_middle_cache,
|
|
344
|
+
bollinger.middle_color, bollinger.middle_width, nullptr);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (ichimoku.enabled) {
|
|
348
|
+
ensure_ichimoku();
|
|
349
|
+
const int64_t shift_ms =
|
|
350
|
+
static_cast<int64_t>(ichimoku.displacement) * candle_duration_ms;
|
|
351
|
+
// The displaced spans read from their own source slice, so they can't
|
|
352
|
+
// go through `stroke`: the query window shifts back by however far the
|
|
353
|
+
// drawing shifts forward.
|
|
354
|
+
const auto span = [&](vroom::LineKey key,
|
|
355
|
+
const std::vector<double>& cache, uint32_t color,
|
|
356
|
+
float width, int64_t line_shift_ms) {
|
|
357
|
+
if (cache.size() != sz) return;
|
|
358
|
+
const auto r = ichimoku_source_range(line_shift_ms);
|
|
359
|
+
const bool has_new = n > 0 && r.end > r.start;
|
|
360
|
+
vroom::ma_overlay::draw(
|
|
361
|
+
canvas, lay, bounds,
|
|
362
|
+
has_new ? candles.data() + r.start : candles.data(),
|
|
363
|
+
has_new ? r.end - r.start : 0,
|
|
364
|
+
has_new ? cache.data() + r.start : nullptr, window_ms,
|
|
365
|
+
visible_start_ms, candle_duration_ms, candle_right,
|
|
366
|
+
candle_area_h, color, width, nullptr, 1.f, line_shift_ms,
|
|
367
|
+
capture(key), morph_t);
|
|
368
|
+
};
|
|
369
|
+
// Span B under span A where the two edges touch, then the unshifted
|
|
370
|
+
// signal lines, then chikou on top of everything.
|
|
371
|
+
if (ichimoku.senkou_b_enabled) {
|
|
372
|
+
span({vroom::LineKind::IchimokuSenkouB}, ich_senkou_b_cache,
|
|
373
|
+
ichimoku.senkou_b_color, ichimoku.senkou_b_width, shift_ms);
|
|
374
|
+
}
|
|
375
|
+
if (ichimoku.senkou_a_enabled) {
|
|
376
|
+
span({vroom::LineKind::IchimokuSenkouA}, ich_senkou_a_cache,
|
|
377
|
+
ichimoku.senkou_a_color, ichimoku.senkou_a_width, shift_ms);
|
|
378
|
+
}
|
|
379
|
+
if (ichimoku.kijun_enabled) {
|
|
380
|
+
span({vroom::LineKind::IchimokuKijun}, ich_kijun_cache,
|
|
381
|
+
ichimoku.kijun_color, ichimoku.kijun_width, 0);
|
|
382
|
+
}
|
|
383
|
+
if (ichimoku.tenkan_enabled) {
|
|
384
|
+
span({vroom::LineKind::IchimokuTenkan}, ich_tenkan_cache,
|
|
385
|
+
ichimoku.tenkan_color, ichimoku.tenkan_width, 0);
|
|
386
|
+
}
|
|
387
|
+
if (ichimoku.chikou_enabled) {
|
|
388
|
+
span({vroom::LineKind::IchimokuChikou}, ich_chikou_cache,
|
|
389
|
+
ichimoku.chikou_color, ichimoku.chikou_width, -shift_ms);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
void VroomChart::capture_morph_lines(const vroom::Layout& lay,
|
|
395
|
+
const vroom::PriceBounds& bounds,
|
|
396
|
+
vroom::IndexRange range,
|
|
397
|
+
int64_t window_ms) {
|
|
398
|
+
morph_lines.clear();
|
|
399
|
+
const float area_w = vroom::candle_area_width(lay);
|
|
400
|
+
const std::size_t n = range.end - range.start;
|
|
401
|
+
if (area_w <= 0.f || window_ms <= 0 || n == 0) return;
|
|
402
|
+
const ::VroomCandle* visible = candles.data() + range.start;
|
|
403
|
+
const std::size_t sz = candles.size();
|
|
404
|
+
|
|
405
|
+
// Captures one series newest-first, matching the slot indexing every morph
|
|
406
|
+
// draw pairs on. `src` is the slice the values were drawn against — the
|
|
407
|
+
// visible one for everything except Ichimoku's displaced spans, which plot
|
|
408
|
+
// at `time_ms + shift_ms` from a window of their own. `frac` maps a value
|
|
409
|
+
// into its band; it's the only part that differs between the price pane and
|
|
410
|
+
// an indicator pane.
|
|
411
|
+
const auto capture = [&](vroom::LineKey key, const ::VroomCandle* src,
|
|
412
|
+
std::size_t src_n, const double* values,
|
|
413
|
+
int64_t shift_ms, double scale, auto&& frac) {
|
|
414
|
+
if (!values || src_n == 0) return;
|
|
415
|
+
vroom::LineMorph line;
|
|
416
|
+
line.key = key;
|
|
417
|
+
line.scale = scale;
|
|
418
|
+
line.pts.resize(src_n);
|
|
419
|
+
for (std::size_t k = 0; k < src_n; ++k) {
|
|
420
|
+
const std::size_t i = src_n - 1 - k;
|
|
421
|
+
const double v = values[i];
|
|
422
|
+
const bool defined = std::isfinite(v);
|
|
423
|
+
line.pts[k] = vroom::LineSnapshot{
|
|
424
|
+
vroom::candle_center_x(lay, src[i].time_ms + shift_ms,
|
|
425
|
+
candle_duration_ms, visible_start_ms,
|
|
426
|
+
window_ms) /
|
|
427
|
+
area_w,
|
|
428
|
+
defined ? static_cast<float>(frac(v)) : 0.f,
|
|
429
|
+
defined,
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
morph_lines.push_back(std::move(line));
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
// Price-pane series share the band the candle capture was taken against, so
|
|
436
|
+
// they keep their position relative to the candles through the switch.
|
|
437
|
+
const auto price_frac = [&](double v) {
|
|
438
|
+
return vroom::price_fraction(bounds, v);
|
|
439
|
+
};
|
|
440
|
+
const auto price_series = [&](vroom::LineKey key,
|
|
441
|
+
const std::vector<double>& cache) {
|
|
442
|
+
if (cache.size() != sz) return;
|
|
443
|
+
capture(key, visible, n, cache.data() + range.start, 0, 0.0, price_frac);
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
if (!overlays.empty()) {
|
|
447
|
+
ensure_overlays();
|
|
448
|
+
for (std::size_t k = 0; k < overlays.size(); ++k) {
|
|
449
|
+
price_series(vroom::overlay_line_key(overlays[k], k),
|
|
450
|
+
overlay_caches[k]);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
if (vwap.enabled) {
|
|
455
|
+
ensure_vwap();
|
|
456
|
+
price_series({vroom::LineKind::Vwap}, vwap_cache);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
if (bollinger.enabled) {
|
|
460
|
+
ensure_bollinger();
|
|
461
|
+
price_series({vroom::LineKind::BollingerUpper}, bb_upper_cache);
|
|
462
|
+
price_series({vroom::LineKind::BollingerMiddle}, bb_middle_cache);
|
|
463
|
+
price_series({vroom::LineKind::BollingerLower}, bb_lower_cache);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
if (ichimoku.enabled) {
|
|
467
|
+
ensure_ichimoku();
|
|
468
|
+
const int64_t shift_ms =
|
|
469
|
+
static_cast<int64_t>(ichimoku.displacement) * candle_duration_ms;
|
|
470
|
+
// Same source windows draw_chart uses, so slot 0 is the same rightmost
|
|
471
|
+
// vertex on both sides of the switch even for a span running past the
|
|
472
|
+
// newest candle into empty time.
|
|
473
|
+
const auto span = [&](vroom::LineKey key,
|
|
474
|
+
const std::vector<double>& cache,
|
|
475
|
+
int64_t line_shift_ms) {
|
|
476
|
+
if (cache.size() != sz) return;
|
|
477
|
+
const auto r = ichimoku_source_range(line_shift_ms);
|
|
478
|
+
if (r.end <= r.start) return;
|
|
479
|
+
capture(key, candles.data() + r.start, r.end - r.start,
|
|
480
|
+
cache.data() + r.start, line_shift_ms, 0.0, price_frac);
|
|
481
|
+
};
|
|
482
|
+
span({vroom::LineKind::IchimokuTenkan}, ich_tenkan_cache, 0);
|
|
483
|
+
span({vroom::LineKind::IchimokuKijun}, ich_kijun_cache, 0);
|
|
484
|
+
span({vroom::LineKind::IchimokuSenkouA}, ich_senkou_a_cache, shift_ms);
|
|
485
|
+
span({vroom::LineKind::IchimokuSenkouB}, ich_senkou_b_cache, shift_ms);
|
|
486
|
+
span({vroom::LineKind::IchimokuChikou}, ich_chikou_cache, -shift_ms);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Pane series normalize against their pane's own band rather than the price
|
|
490
|
+
// one, so each pane's auto-fit is free to change across the switch without
|
|
491
|
+
// the capture moving.
|
|
492
|
+
const auto pane_slice = [&](const std::vector<double>& cache) -> const double* {
|
|
493
|
+
return cache.size() == sz ? cache.data() + range.start : nullptr;
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
if (rsi.enabled) {
|
|
497
|
+
ensure_rsi();
|
|
498
|
+
const auto frac = [&](double v) {
|
|
499
|
+
return vroom::rsi::band_fraction(v, rsi_y_scale);
|
|
500
|
+
};
|
|
501
|
+
capture({vroom::LineKind::Rsi}, visible, n, pane_slice(rsi_cache), 0, 0.0,
|
|
502
|
+
frac);
|
|
503
|
+
if (rsi.ma_visible) {
|
|
504
|
+
capture({vroom::LineKind::RsiMa}, visible, n,
|
|
505
|
+
pane_slice(rsi_ma_cache), 0, 0.0, frac);
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
if (macd.enabled) {
|
|
510
|
+
ensure_macd();
|
|
511
|
+
const double* line = pane_slice(macd_cache);
|
|
512
|
+
const double* signal = pane_slice(macd_signal_cache);
|
|
513
|
+
const double* hist = pane_slice(macd_hist_cache);
|
|
514
|
+
const double scale = vroom::macd::autoscale(
|
|
515
|
+
macd.line_visible ? line : nullptr,
|
|
516
|
+
macd.signal_visible ? signal : nullptr,
|
|
517
|
+
macd.hist_visible ? hist : nullptr, n);
|
|
518
|
+
const auto frac = [&](double v) {
|
|
519
|
+
return vroom::macd::band_fraction(v, scale, macd_y_scale);
|
|
520
|
+
};
|
|
521
|
+
capture({vroom::LineKind::Macd}, visible, n, line, 0, scale, frac);
|
|
522
|
+
capture({vroom::LineKind::MacdSignal}, visible, n, signal, 0, scale, frac);
|
|
523
|
+
capture({vroom::LineKind::MacdHistogram}, visible, n, hist, 0, scale,
|
|
524
|
+
frac);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
if (atr.enabled) {
|
|
528
|
+
ensure_atr();
|
|
529
|
+
const double* line = pane_slice(atr_cache);
|
|
530
|
+
const double scale = vroom::atr::autoscale(line, n);
|
|
531
|
+
capture({vroom::LineKind::Atr}, visible, n, line, 0, scale,
|
|
532
|
+
[&](double v) {
|
|
533
|
+
return vroom::atr::band_fraction(v, scale, atr_y_scale);
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
128
538
|
void VroomChart::draw_chart(SkCanvas* canvas) {
|
|
129
539
|
begin_frame();
|
|
130
540
|
const auto lay = layout();
|
|
@@ -197,12 +607,28 @@ void VroomChart::draw_chart(SkCanvas* canvas) {
|
|
|
197
607
|
const float morph_t = reshape ? interval_morph_t : 1.f;
|
|
198
608
|
|
|
199
609
|
if (fade_out) {
|
|
200
|
-
// First half: only
|
|
201
|
-
// Layers with no snapshot (volume,
|
|
202
|
-
//
|
|
203
|
-
//
|
|
610
|
+
// First half: only what was captured, at the axis envelope opacity.
|
|
611
|
+
// Layers with no snapshot (volume, liquidity, FVG, drawings) would
|
|
612
|
+
// already be the new data, so they stay hidden until the incoming half.
|
|
613
|
+
// morph_t = 0 draws every capture pixel-identically.
|
|
204
614
|
const float layer = axis_phase.opacity;
|
|
615
|
+
// Groups the captured indicators into one layer rather than threading
|
|
616
|
+
// the envelope through as a per-paint alpha, so overlapping strokes
|
|
617
|
+
// composite once and the panes keep their signatures. The price series
|
|
618
|
+
// below fades per-paint, as it always has.
|
|
619
|
+
const auto push_fade_layer = [&]() -> bool {
|
|
620
|
+
if (layer >= 0.999f) return false;
|
|
621
|
+
canvas->saveLayerAlpha(nullptr,
|
|
622
|
+
static_cast<U8CPU>(layer * 255.f + 0.5f));
|
|
623
|
+
return true;
|
|
624
|
+
};
|
|
205
625
|
if (layer > 0.f) {
|
|
626
|
+
// Behind the candles, as in the settled z-order.
|
|
627
|
+
const bool fills_wrapped = push_fade_layer();
|
|
628
|
+
draw_overlay_fills(canvas, lay, bounds, visible, 0, range.start,
|
|
629
|
+
window_ms, candle_right, candle_area_h, true, 0.f);
|
|
630
|
+
if (fills_wrapped) canvas->restore();
|
|
631
|
+
|
|
206
632
|
if (fade > 0.f) {
|
|
207
633
|
vroom::ma_overlay::draw_close_gradient(
|
|
208
634
|
canvas, lay, bounds, visible, 0, window_ms,
|
|
@@ -239,6 +665,18 @@ void VroomChart::draw_chart(SkCanvas* canvas) {
|
|
|
239
665
|
morph_from.data(), morph_n, 0.f);
|
|
240
666
|
}
|
|
241
667
|
}
|
|
668
|
+
|
|
669
|
+
// Over the candles, and the panes below them. One layer for both:
|
|
670
|
+
// they occupy disjoint bands, so nothing overlaps across the two.
|
|
671
|
+
const bool lines_wrapped = push_fade_layer();
|
|
672
|
+
draw_overlay_lines(canvas, lay, bounds, visible, 0, range.start,
|
|
673
|
+
window_ms, candle_right, candle_area_h, true, 0.f);
|
|
674
|
+
if (lay.indicator_area_h > 0.f) {
|
|
675
|
+
draw_indicator_panes(canvas, lay, visible, 0, range.start,
|
|
676
|
+
window_ms, candle_right, candle_area_h,
|
|
677
|
+
true, 0.f);
|
|
678
|
+
}
|
|
679
|
+
if (lines_wrapped) canvas->restore();
|
|
242
680
|
}
|
|
243
681
|
} else {
|
|
244
682
|
// Incoming fade: the new scene at envelope opacity, no slot lerp
|
|
@@ -275,22 +713,20 @@ void VroomChart::draw_chart(SkCanvas* canvas) {
|
|
|
275
713
|
vroom::liquidity::draw(canvas, *this, lay, bounds, candle_right,
|
|
276
714
|
candle_area_h);
|
|
277
715
|
|
|
278
|
-
// 4.
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
canvas, lay, bounds, visible, n,
|
|
286
|
-
bb_upper_cache.data() + range.start,
|
|
287
|
-
bb_lower_cache.data() + range.start, window_ms,
|
|
288
|
-
visible_start_ms, candle_duration_ms, candle_right,
|
|
289
|
-
candle_area_h, bollinger.upper_color,
|
|
290
|
-
bollinger.fill_opacity);
|
|
291
|
-
}
|
|
716
|
+
// 4.65. Fair Value Gap boxes — behind the candles, so the bars that
|
|
717
|
+
// formed each imbalance still read over their own shading.
|
|
718
|
+
if (fvg.enabled) {
|
|
719
|
+
ensure_fvg();
|
|
720
|
+
vroom::fvg_overlay::draw_boxes(canvas, *this, lay, bounds,
|
|
721
|
+
window_ms, candle_right,
|
|
722
|
+
candle_area_h);
|
|
292
723
|
}
|
|
293
724
|
|
|
725
|
+
// 4.7 / 4.75. Bollinger fill and Ichimoku cloud, behind the candles.
|
|
726
|
+
draw_overlay_fills(canvas, lay, bounds, visible, n, range.start,
|
|
727
|
+
window_ms, candle_right, candle_area_h, reshape,
|
|
728
|
+
morph_t);
|
|
729
|
+
|
|
294
730
|
// 5. Price series — candles, a close-price line, or a blend.
|
|
295
731
|
if (fade < 1.f) {
|
|
296
732
|
vroom::candles::draw(canvas, visible, n, lay, theme, bounds,
|
|
@@ -308,59 +744,10 @@ void VroomChart::draw_chart(SkCanvas* canvas) {
|
|
|
308
744
|
theme.floats[VROOM_FLOAT_LINE_TENSION]);
|
|
309
745
|
}
|
|
310
746
|
|
|
311
|
-
// 5.5. Moving
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
if (overlay_caches[k].size() != candles.size()) continue;
|
|
316
|
-
const double* vis = overlay_caches[k].data() + range.start;
|
|
317
|
-
vroom::ma_overlay::draw(
|
|
318
|
-
canvas, lay, bounds, visible, n, vis, window_ms,
|
|
319
|
-
visible_start_ms, candle_duration_ms, candle_right,
|
|
320
|
-
candle_area_h, overlays[k].color, overlays[k].width);
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
// 5.6. VWAP overlay.
|
|
325
|
-
if (vwap.enabled) {
|
|
326
|
-
ensure_vwap();
|
|
327
|
-
if (vwap_cache.size() == candles.size()) {
|
|
328
|
-
const double* vis = vwap_cache.data() + range.start;
|
|
329
|
-
const unsigned char* brk =
|
|
330
|
-
vwap_breaks.size() == candles.size()
|
|
331
|
-
? vwap_breaks.data() + range.start
|
|
332
|
-
: nullptr;
|
|
333
|
-
vroom::ma_overlay::draw(
|
|
334
|
-
canvas, lay, bounds, visible, n, vis, window_ms,
|
|
335
|
-
visible_start_ms, candle_duration_ms, candle_right,
|
|
336
|
-
candle_area_h,
|
|
337
|
-
vroom::style::color_or(vwap.color, kVwapLine),
|
|
338
|
-
vroom::style::width_or(vwap.width, 1.5f), brk);
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
// 5.65. Bollinger Band lines.
|
|
343
|
-
if (bollinger.enabled) {
|
|
344
|
-
ensure_bollinger();
|
|
345
|
-
const std::size_t sz = candles.size();
|
|
346
|
-
if (bb_upper_cache.size() == sz && bb_lower_cache.size() == sz &&
|
|
347
|
-
bb_middle_cache.size() == sz) {
|
|
348
|
-
const auto stroke = [&](const std::vector<double>& cache,
|
|
349
|
-
uint32_t color, float width) {
|
|
350
|
-
vroom::ma_overlay::draw(
|
|
351
|
-
canvas, lay, bounds, visible, n,
|
|
352
|
-
cache.data() + range.start, window_ms,
|
|
353
|
-
visible_start_ms, candle_duration_ms, candle_right,
|
|
354
|
-
candle_area_h, color, width);
|
|
355
|
-
};
|
|
356
|
-
stroke(bb_upper_cache, bollinger.upper_color,
|
|
357
|
-
bollinger.upper_width);
|
|
358
|
-
stroke(bb_lower_cache, bollinger.lower_color,
|
|
359
|
-
bollinger.lower_width);
|
|
360
|
-
stroke(bb_middle_cache, bollinger.middle_color,
|
|
361
|
-
bollinger.middle_width);
|
|
362
|
-
}
|
|
363
|
-
}
|
|
747
|
+
// 5.5 - 5.67. Moving averages, VWAP, Bollinger and Ichimoku lines.
|
|
748
|
+
draw_overlay_lines(canvas, lay, bounds, visible, n, range.start,
|
|
749
|
+
window_ms, candle_right, candle_area_h, reshape,
|
|
750
|
+
morph_t);
|
|
364
751
|
|
|
365
752
|
// 5.7. Drawing annotations.
|
|
366
753
|
vroom::drawings::draw(canvas, *this, lay, bounds, candle_right,
|
|
@@ -386,62 +773,9 @@ void VroomChart::draw_chart(SkCanvas* canvas) {
|
|
|
386
773
|
// paint before axis masks so overflow still clips). Transforms
|
|
387
774
|
// keep the settled z-order after the price indicator.
|
|
388
775
|
if (fade_in && lay.indicator_area_h > 0.f) {
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
};
|
|
393
|
-
ActivePane panes[2];
|
|
394
|
-
int count = 0;
|
|
395
|
-
if (rsi.enabled) panes[count++] = {rsi_order, 0};
|
|
396
|
-
if (macd.enabled) panes[count++] = {macd_order, 1};
|
|
397
|
-
if (count == 2 && panes[0].order > panes[1].order) {
|
|
398
|
-
const ActivePane tmp = panes[0];
|
|
399
|
-
panes[0] = panes[1];
|
|
400
|
-
panes[1] = tmp;
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
const float pane_h =
|
|
404
|
-
height_px * theme.floats[VROOM_FLOAT_INDICATOR_HEIGHT_FRAC];
|
|
405
|
-
float pane_top = candle_area_h;
|
|
406
|
-
for (int i = 0; i < count; ++i) {
|
|
407
|
-
const float pane_bottom = pane_top + pane_h;
|
|
408
|
-
if (panes[i].type == 0) {
|
|
409
|
-
ensure_rsi();
|
|
410
|
-
const double* rsi_vis =
|
|
411
|
-
rsi_cache.size() == candles.size()
|
|
412
|
-
? rsi_cache.data() + range.start
|
|
413
|
-
: nullptr;
|
|
414
|
-
const double* rsi_ma_vis =
|
|
415
|
-
(rsi.ma_visible &&
|
|
416
|
-
rsi_ma_cache.size() == candles.size())
|
|
417
|
-
? rsi_ma_cache.data() + range.start
|
|
418
|
-
: nullptr;
|
|
419
|
-
vroom::rsi_pane::draw(
|
|
420
|
-
canvas, *this, lay, visible, n, rsi_vis, rsi_ma_vis,
|
|
421
|
-
window_ms, visible_start_ms, candle_duration_ms,
|
|
422
|
-
candle_right, pane_top, pane_bottom);
|
|
423
|
-
} else {
|
|
424
|
-
ensure_macd();
|
|
425
|
-
const double* macd_vis =
|
|
426
|
-
macd_cache.size() == candles.size()
|
|
427
|
-
? macd_cache.data() + range.start
|
|
428
|
-
: nullptr;
|
|
429
|
-
const double* sig_vis =
|
|
430
|
-
macd_signal_cache.size() == candles.size()
|
|
431
|
-
? macd_signal_cache.data() + range.start
|
|
432
|
-
: nullptr;
|
|
433
|
-
const double* hist_vis =
|
|
434
|
-
macd_hist_cache.size() == candles.size()
|
|
435
|
-
? macd_hist_cache.data() + range.start
|
|
436
|
-
: nullptr;
|
|
437
|
-
vroom::macd_pane::draw(
|
|
438
|
-
canvas, *this, lay, visible, n, macd_vis, sig_vis,
|
|
439
|
-
hist_vis, window_ms, visible_start_ms,
|
|
440
|
-
candle_duration_ms, candle_right, pane_top,
|
|
441
|
-
pane_bottom);
|
|
442
|
-
}
|
|
443
|
-
pane_top = pane_bottom;
|
|
444
|
-
}
|
|
776
|
+
draw_indicator_panes(canvas, lay, visible, n, range.start,
|
|
777
|
+
window_ms, candle_right, candle_area_h,
|
|
778
|
+
false, 1.f);
|
|
445
779
|
}
|
|
446
780
|
}
|
|
447
781
|
|
|
@@ -474,12 +808,24 @@ void VroomChart::draw_chart(SkCanvas* canvas) {
|
|
|
474
808
|
vroom::price_indicator::draw(canvas, *this, lay, bounds,
|
|
475
809
|
candle_right, candle_area_h);
|
|
476
810
|
|
|
811
|
+
// 7.54. Fair Value Gap labels — the boxes themselves are back behind
|
|
812
|
+
// the candles, but their text has to clear the bars it sits over.
|
|
813
|
+
vroom::fvg_overlay::draw_labels(canvas, *this, lay, bounds, window_ms,
|
|
814
|
+
candle_right, candle_area_h);
|
|
815
|
+
|
|
477
816
|
// 7.55. Consumer-supplied price status lines — same tier as the
|
|
478
817
|
// current-price indicator (their badges must cover the labels
|
|
479
818
|
// underneath), but after it so a resting order at the last close
|
|
480
819
|
// stays readable.
|
|
481
820
|
vroom::price_lines::draw(canvas, *this, lay, bounds, candle_right,
|
|
482
821
|
candle_area_h);
|
|
822
|
+
|
|
823
|
+
// 7.56. Footprint badges — data-anchored chrome that must not be hidden
|
|
824
|
+
// by candles or overlays, so it draws with the price lines rather
|
|
825
|
+
// than back at the drawings layer. Below the crosshair, which the
|
|
826
|
+
// user is actively pointing with.
|
|
827
|
+
ensure_footprint_buckets();
|
|
828
|
+
vroom::footprints::draw(canvas, *this, lay, bounds, window_ms);
|
|
483
829
|
}
|
|
484
830
|
|
|
485
831
|
// 7.6. Indicator panes stacked below the candles, ordered by enable
|
|
@@ -487,50 +833,9 @@ void VroomChart::draw_chart(SkCanvas* canvas) {
|
|
|
487
833
|
// INDICATOR_HEIGHT_FRAC of the height; the candle pane already shrank
|
|
488
834
|
// to fit them (see layout()). A fade already drew them with the scene.
|
|
489
835
|
if (!fade_swap && lay.indicator_area_h > 0.f) {
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
if (rsi.enabled) panes[count++] = {rsi_order, 0};
|
|
494
|
-
if (macd.enabled) panes[count++] = {macd_order, 1};
|
|
495
|
-
if (count == 2 && panes[0].order > panes[1].order) {
|
|
496
|
-
const ActivePane tmp = panes[0];
|
|
497
|
-
panes[0] = panes[1];
|
|
498
|
-
panes[1] = tmp;
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
const float pane_h =
|
|
502
|
-
height_px * theme.floats[VROOM_FLOAT_INDICATOR_HEIGHT_FRAC];
|
|
503
|
-
float pane_top = candle_area_h; // == price_pane_bottom(lay)
|
|
504
|
-
for (int i = 0; i < count; ++i) {
|
|
505
|
-
const float pane_bottom = pane_top + pane_h;
|
|
506
|
-
if (panes[i].type == 0) {
|
|
507
|
-
ensure_rsi();
|
|
508
|
-
const double* rsi_vis = rsi_cache.size() == candles.size()
|
|
509
|
-
? rsi_cache.data() + range.start : nullptr;
|
|
510
|
-
const double* rsi_ma_vis =
|
|
511
|
-
(rsi.ma_visible && rsi_ma_cache.size() == candles.size())
|
|
512
|
-
? rsi_ma_cache.data() + range.start : nullptr;
|
|
513
|
-
vroom::rsi_pane::draw(canvas, *this, lay, visible, n, rsi_vis,
|
|
514
|
-
rsi_ma_vis, window_ms, visible_start_ms,
|
|
515
|
-
candle_duration_ms, candle_right, pane_top,
|
|
516
|
-
pane_bottom);
|
|
517
|
-
} else {
|
|
518
|
-
ensure_macd();
|
|
519
|
-
const double* macd_vis = macd_cache.size() == candles.size()
|
|
520
|
-
? macd_cache.data() + range.start : nullptr;
|
|
521
|
-
const double* sig_vis =
|
|
522
|
-
macd_signal_cache.size() == candles.size()
|
|
523
|
-
? macd_signal_cache.data() + range.start : nullptr;
|
|
524
|
-
const double* hist_vis =
|
|
525
|
-
macd_hist_cache.size() == candles.size()
|
|
526
|
-
? macd_hist_cache.data() + range.start : nullptr;
|
|
527
|
-
vroom::macd_pane::draw(canvas, *this, lay, visible, n, macd_vis,
|
|
528
|
-
sig_vis, hist_vis, window_ms,
|
|
529
|
-
visible_start_ms, candle_duration_ms,
|
|
530
|
-
candle_right, pane_top, pane_bottom);
|
|
531
|
-
}
|
|
532
|
-
pane_top = pane_bottom;
|
|
533
|
-
}
|
|
836
|
+
// candle_area_h == price_pane_bottom(lay), the top of the band.
|
|
837
|
+
draw_indicator_panes(canvas, lay, visible, n, range.start, window_ms,
|
|
838
|
+
candle_right, candle_area_h, reshape, morph_t);
|
|
534
839
|
}
|
|
535
840
|
|
|
536
841
|
// 7.7. Crosshair — drawn last so it sits on top of everything, including the
|