react-native-vroom-chart 0.13.1 → 0.15.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 +133 -8
- package/cpp/_core_include/vroom/vroom_chart.h +90 -7
- package/cpp/_core_src/candles.h +7 -5
- package/cpp/_core_src/chart.cpp +287 -152
- package/cpp/_core_src/chart.h +29 -3
- package/cpp/_core_src/chart_facade.cpp +113 -1
- 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/tip_pulse.h +4 -4
- package/cpp/_core_src/viewport.h +4 -0
- package/lib/index.d.mts +186 -6
- package/lib/index.d.ts +186 -6
- package/lib/index.js +89 -7
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +89 -7
- package/lib/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/VroomChart.tsx +89 -5
- package/src/index.ts +7 -0
- package/src/jsi.d.ts +45 -8
- package/src/types.ts +7 -0
- package/src/useChartCore.ts +56 -5
package/cpp/_core_src/chart.cpp
CHANGED
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
#include "chart_internal.h"
|
|
26
26
|
#include "crosshair.h"
|
|
27
27
|
#include "drawings.h"
|
|
28
|
+
#include "footprints.h"
|
|
28
29
|
#include "labels.h"
|
|
29
30
|
#include "liquidity.h"
|
|
30
31
|
#include "ma.h"
|
|
@@ -125,6 +126,14 @@ void VroomChart::ensure_bollinger() {
|
|
|
125
126
|
bollinger_dirty = false;
|
|
126
127
|
}
|
|
127
128
|
|
|
129
|
+
void VroomChart::ensure_footprint_buckets() {
|
|
130
|
+
if (!footprint_buckets_dirty) return;
|
|
131
|
+
footprint_buckets = vroom::footprints::build_buckets(
|
|
132
|
+
candles.data(), candles.size(), candle_duration_ms, footprints.data(),
|
|
133
|
+
footprints.size());
|
|
134
|
+
footprint_buckets_dirty = false;
|
|
135
|
+
}
|
|
136
|
+
|
|
128
137
|
void VroomChart::draw_chart(SkCanvas* canvas) {
|
|
129
138
|
begin_frame();
|
|
130
139
|
const auto lay = layout();
|
|
@@ -178,160 +187,274 @@ void VroomChart::draw_chart(SkCanvas* canvas) {
|
|
|
178
187
|
// 4.35. Price-series morph state, shared by the gradient fill below and the
|
|
179
188
|
// series itself (5). `morph_fade` crossfades candles→line and
|
|
180
189
|
// `morph_collapse` folds each candle toward its close (the line vertex);
|
|
181
|
-
// fade 0 = pure candles, fade 1 = pure line.
|
|
182
|
-
//
|
|
183
|
-
//
|
|
184
|
-
// the
|
|
190
|
+
// fade 0 = pure candles, fade 1 = pure line.
|
|
191
|
+
//
|
|
192
|
+
// An interval *transform* reshapes each slot from the geometry it held
|
|
193
|
+
// before the timeframe switch (see `morph_from`). A host-chosen
|
|
194
|
+
// *fade* skips the pairing and uses the same envelope as the axes
|
|
195
|
+
// (`labels::interval_phase`): outgoing snapshot out, then new scene in.
|
|
185
196
|
const float fade = morph_fade;
|
|
186
197
|
const float collapse = morph_collapse;
|
|
187
198
|
const bool morphing = interval_morph_t < 1.f && !morph_from.empty();
|
|
188
|
-
const vroom::CandleSnapshot* morph_src = morphing ? morph_from.data() : nullptr;
|
|
189
199
|
const std::size_t morph_n = morphing ? morph_from.size() : 0;
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
200
|
+
const bool fade_swap = morphing && interval_morph_fade;
|
|
201
|
+
const bool fade_out = fade_swap && axis_phase.outgoing;
|
|
202
|
+
const bool fade_in = fade_swap && !axis_phase.outgoing;
|
|
203
|
+
const bool reshape = morphing && !fade_swap;
|
|
204
|
+
const vroom::CandleSnapshot* morph_src = reshape ? morph_from.data() : nullptr;
|
|
205
|
+
const std::size_t morph_n_draw = reshape ? morph_n : 0;
|
|
206
|
+
const float morph_t = reshape ? interval_morph_t : 1.f;
|
|
207
|
+
|
|
208
|
+
if (fade_out) {
|
|
209
|
+
// First half: only the captured series, at the axis envelope opacity.
|
|
210
|
+
// Layers with no snapshot (volume, overlays, indicators, drawings)
|
|
211
|
+
// would already be the new data, so they stay hidden until the incoming
|
|
212
|
+
// half. morph_t = 0 draws the capture pixel-identically.
|
|
213
|
+
const float layer = axis_phase.opacity;
|
|
214
|
+
if (layer > 0.f) {
|
|
215
|
+
if (fade > 0.f) {
|
|
216
|
+
vroom::ma_overlay::draw_close_gradient(
|
|
217
|
+
canvas, lay, bounds, visible, 0, window_ms,
|
|
218
|
+
visible_start_ms, candle_duration_ms, candle_right,
|
|
219
|
+
candle_area_h, theme.colors[VROOM_COLOR_LINE],
|
|
220
|
+
theme.floats[VROOM_FLOAT_LINE_GRADIENT_OPACITY],
|
|
221
|
+
fade * layer, morph_from.data(), morph_n, 0.f,
|
|
222
|
+
theme.floats[VROOM_FLOAT_LINE_TENSION]);
|
|
223
|
+
}
|
|
224
|
+
if (fade < 1.f) {
|
|
225
|
+
vroom::candles::draw(canvas, visible, 0, lay, theme, bounds,
|
|
226
|
+
window_ms, visible_start_ms,
|
|
227
|
+
candle_duration_ms, collapse,
|
|
228
|
+
(1.f - fade) * layer, morph_from.data(),
|
|
229
|
+
morph_n, 0.f);
|
|
230
|
+
}
|
|
231
|
+
if (fade > 0.f) {
|
|
232
|
+
vroom::ma_overlay::draw_close_line(
|
|
233
|
+
canvas, lay, bounds, visible, 0, window_ms,
|
|
234
|
+
visible_start_ms, candle_duration_ms, candle_right,
|
|
235
|
+
candle_area_h, theme.colors[VROOM_COLOR_LINE],
|
|
236
|
+
theme.floats[VROOM_FLOAT_LINE_WIDTH_PX], fade * layer,
|
|
237
|
+
morph_from.data(), morph_n, 0.f,
|
|
238
|
+
theme.floats[VROOM_FLOAT_LINE_TENSION]);
|
|
239
|
+
if (theme.floats[VROOM_FLOAT_LINE_TIP_DOT] > 0.5f) {
|
|
240
|
+
vroom::ma_overlay::draw_close_tip(
|
|
241
|
+
canvas, lay, bounds, visible, 0, window_ms,
|
|
242
|
+
visible_start_ms, candle_duration_ms, candle_right,
|
|
243
|
+
candle_area_h, theme.colors[VROOM_COLOR_LINE],
|
|
244
|
+
theme.colors[VROOM_COLOR_BACKGROUND],
|
|
245
|
+
theme.floats[VROOM_FLOAT_LINE_WIDTH_PX], fade * layer,
|
|
246
|
+
theme.floats[VROOM_FLOAT_LINE_TIP_PULSE] > 0.5f,
|
|
247
|
+
tip_pulse_elapsed_s / vroom::tip_pulse::kPeriodSeconds,
|
|
248
|
+
morph_from.data(), morph_n, 0.f);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
} else {
|
|
253
|
+
// Incoming fade: the new scene at envelope opacity, no slot lerp
|
|
254
|
+
// (`morph_t` is already 1). Transforms use the reshape path above.
|
|
255
|
+
const bool wrap = fade_in && axis_phase.opacity > 0.f &&
|
|
256
|
+
axis_phase.opacity < 0.999f;
|
|
257
|
+
if (wrap) {
|
|
258
|
+
canvas->saveLayerAlpha(
|
|
259
|
+
nullptr, static_cast<U8CPU>(axis_phase.opacity * 255.f + 0.5f));
|
|
260
|
+
}
|
|
261
|
+
const bool draw_new = !fade_in || axis_phase.opacity > 0.f;
|
|
262
|
+
|
|
263
|
+
if (draw_new) {
|
|
264
|
+
// 4.4. Line-mode gradient fill — the wash under the close polyline.
|
|
265
|
+
if (fade > 0.f) {
|
|
266
|
+
vroom::ma_overlay::draw_close_gradient(
|
|
267
|
+
canvas, lay, bounds, visible, n, window_ms,
|
|
268
|
+
visible_start_ms, candle_duration_ms, candle_right,
|
|
269
|
+
candle_area_h, theme.colors[VROOM_COLOR_LINE],
|
|
270
|
+
theme.floats[VROOM_FLOAT_LINE_GRADIENT_OPACITY], fade,
|
|
271
|
+
morph_src, morph_n_draw, morph_t,
|
|
272
|
+
theme.floats[VROOM_FLOAT_LINE_TENSION]);
|
|
273
|
+
}
|
|
204
274
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
}
|
|
275
|
+
// 4.5. Volume bars — drawn under the candles so candles z-index above.
|
|
276
|
+
if (volume_collapse_t < 1.f) {
|
|
277
|
+
vroom::volume::draw(canvas, visible, n, lay, theme, volume,
|
|
278
|
+
volume_collapse_t, volume_collapse_easing,
|
|
279
|
+
window_ms, visible_start_ms,
|
|
280
|
+
candle_duration_ms);
|
|
281
|
+
}
|
|
213
282
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
candle_area_h, bollinger.upper_color, bollinger.fill_opacity);
|
|
234
|
-
}
|
|
235
|
-
}
|
|
283
|
+
// 4.6. Liquidity bands (resting-order depth).
|
|
284
|
+
vroom::liquidity::draw(canvas, *this, lay, bounds, candle_right,
|
|
285
|
+
candle_area_h);
|
|
286
|
+
|
|
287
|
+
// 4.7. Bollinger Band fill.
|
|
288
|
+
if (bollinger.enabled) {
|
|
289
|
+
ensure_bollinger();
|
|
290
|
+
if (bollinger.fill_enabled &&
|
|
291
|
+
bb_upper_cache.size() == candles.size() &&
|
|
292
|
+
bb_lower_cache.size() == candles.size()) {
|
|
293
|
+
vroom::ma_overlay::fill_between(
|
|
294
|
+
canvas, lay, bounds, visible, n,
|
|
295
|
+
bb_upper_cache.data() + range.start,
|
|
296
|
+
bb_lower_cache.data() + range.start, window_ms,
|
|
297
|
+
visible_start_ms, candle_duration_ms, candle_right,
|
|
298
|
+
candle_area_h, bollinger.upper_color,
|
|
299
|
+
bollinger.fill_opacity);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
236
302
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
303
|
+
// 5. Price series — candles, a close-price line, or a blend.
|
|
304
|
+
if (fade < 1.f) {
|
|
305
|
+
vroom::candles::draw(canvas, visible, n, lay, theme, bounds,
|
|
306
|
+
window_ms, visible_start_ms,
|
|
307
|
+
candle_duration_ms, collapse, 1.f - fade,
|
|
308
|
+
morph_src, morph_n_draw, morph_t);
|
|
309
|
+
}
|
|
310
|
+
if (fade > 0.f) {
|
|
311
|
+
vroom::ma_overlay::draw_close_line(
|
|
312
|
+
canvas, lay, bounds, visible, n, window_ms,
|
|
313
|
+
visible_start_ms, candle_duration_ms, candle_right,
|
|
314
|
+
candle_area_h, theme.colors[VROOM_COLOR_LINE],
|
|
315
|
+
theme.floats[VROOM_FLOAT_LINE_WIDTH_PX], fade, morph_src,
|
|
316
|
+
morph_n_draw, morph_t,
|
|
317
|
+
theme.floats[VROOM_FLOAT_LINE_TENSION]);
|
|
318
|
+
}
|
|
253
319
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
}
|
|
267
|
-
}
|
|
320
|
+
// 5.5. Moving-average overlay lines.
|
|
321
|
+
if (!overlays.empty()) {
|
|
322
|
+
ensure_overlays();
|
|
323
|
+
for (std::size_t k = 0; k < overlays.size(); ++k) {
|
|
324
|
+
if (overlay_caches[k].size() != candles.size()) continue;
|
|
325
|
+
const double* vis = overlay_caches[k].data() + range.start;
|
|
326
|
+
vroom::ma_overlay::draw(
|
|
327
|
+
canvas, lay, bounds, visible, n, vis, window_ms,
|
|
328
|
+
visible_start_ms, candle_duration_ms, candle_right,
|
|
329
|
+
candle_area_h, overlays[k].color, overlays[k].width);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
268
332
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
}
|
|
333
|
+
// 5.6. VWAP overlay.
|
|
334
|
+
if (vwap.enabled) {
|
|
335
|
+
ensure_vwap();
|
|
336
|
+
if (vwap_cache.size() == candles.size()) {
|
|
337
|
+
const double* vis = vwap_cache.data() + range.start;
|
|
338
|
+
const unsigned char* brk =
|
|
339
|
+
vwap_breaks.size() == candles.size()
|
|
340
|
+
? vwap_breaks.data() + range.start
|
|
341
|
+
: nullptr;
|
|
342
|
+
vroom::ma_overlay::draw(
|
|
343
|
+
canvas, lay, bounds, visible, n, vis, window_ms,
|
|
344
|
+
visible_start_ms, candle_duration_ms, candle_right,
|
|
345
|
+
candle_area_h,
|
|
346
|
+
vroom::style::color_or(vwap.color, kVwapLine),
|
|
347
|
+
vroom::style::width_or(vwap.width, 1.5f), brk);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
287
350
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
351
|
+
// 5.65. Bollinger Band lines.
|
|
352
|
+
if (bollinger.enabled) {
|
|
353
|
+
ensure_bollinger();
|
|
354
|
+
const std::size_t sz = candles.size();
|
|
355
|
+
if (bb_upper_cache.size() == sz && bb_lower_cache.size() == sz &&
|
|
356
|
+
bb_middle_cache.size() == sz) {
|
|
357
|
+
const auto stroke = [&](const std::vector<double>& cache,
|
|
358
|
+
uint32_t color, float width) {
|
|
359
|
+
vroom::ma_overlay::draw(
|
|
360
|
+
canvas, lay, bounds, visible, n,
|
|
361
|
+
cache.data() + range.start, window_ms,
|
|
362
|
+
visible_start_ms, candle_duration_ms, candle_right,
|
|
363
|
+
candle_area_h, color, width);
|
|
364
|
+
};
|
|
365
|
+
stroke(bb_upper_cache, bollinger.upper_color,
|
|
366
|
+
bollinger.upper_width);
|
|
367
|
+
stroke(bb_lower_cache, bollinger.lower_color,
|
|
368
|
+
bollinger.lower_width);
|
|
369
|
+
stroke(bb_middle_cache, bollinger.middle_color,
|
|
370
|
+
bollinger.middle_width);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
309
373
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
374
|
+
// 5.7. Drawing annotations.
|
|
375
|
+
vroom::drawings::draw(canvas, *this, lay, bounds, candle_right,
|
|
376
|
+
candle_area_h);
|
|
377
|
+
|
|
378
|
+
// 5.8. Line-mode tip marker.
|
|
379
|
+
if (fade > 0.f && theme.floats[VROOM_FLOAT_LINE_TIP_DOT] > 0.5f) {
|
|
380
|
+
const auto anchor = vroom::tip_anchor::at(
|
|
381
|
+
range.start, range.end, candles.size(), reshape);
|
|
382
|
+
vroom::ma_overlay::draw_close_tip(
|
|
383
|
+
canvas, lay, bounds, visible, anchor.slot_count, window_ms,
|
|
384
|
+
visible_start_ms, candle_duration_ms, candle_right,
|
|
385
|
+
candle_area_h, theme.colors[VROOM_COLOR_LINE],
|
|
386
|
+
theme.colors[VROOM_COLOR_BACKGROUND],
|
|
387
|
+
theme.floats[VROOM_FLOAT_LINE_WIDTH_PX], fade,
|
|
388
|
+
theme.floats[VROOM_FLOAT_LINE_TIP_PULSE] > 0.5f,
|
|
389
|
+
tip_pulse_elapsed_s / vroom::tip_pulse::kPeriodSeconds,
|
|
390
|
+
anchor.use_morph ? morph_src : nullptr,
|
|
391
|
+
anchor.use_morph ? morph_n_draw : 0, morph_t);
|
|
392
|
+
}
|
|
314
393
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
394
|
+
// Incoming fade: indicator panes share the scene opacity (and
|
|
395
|
+
// paint before axis masks so overflow still clips). Transforms
|
|
396
|
+
// keep the settled z-order after the price indicator.
|
|
397
|
+
if (fade_in && lay.indicator_area_h > 0.f) {
|
|
398
|
+
struct ActivePane {
|
|
399
|
+
int order;
|
|
400
|
+
int type;
|
|
401
|
+
};
|
|
402
|
+
ActivePane panes[2];
|
|
403
|
+
int count = 0;
|
|
404
|
+
if (rsi.enabled) panes[count++] = {rsi_order, 0};
|
|
405
|
+
if (macd.enabled) panes[count++] = {macd_order, 1};
|
|
406
|
+
if (count == 2 && panes[0].order > panes[1].order) {
|
|
407
|
+
const ActivePane tmp = panes[0];
|
|
408
|
+
panes[0] = panes[1];
|
|
409
|
+
panes[1] = tmp;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const float pane_h =
|
|
413
|
+
height_px * theme.floats[VROOM_FLOAT_INDICATOR_HEIGHT_FRAC];
|
|
414
|
+
float pane_top = candle_area_h;
|
|
415
|
+
for (int i = 0; i < count; ++i) {
|
|
416
|
+
const float pane_bottom = pane_top + pane_h;
|
|
417
|
+
if (panes[i].type == 0) {
|
|
418
|
+
ensure_rsi();
|
|
419
|
+
const double* rsi_vis =
|
|
420
|
+
rsi_cache.size() == candles.size()
|
|
421
|
+
? rsi_cache.data() + range.start
|
|
422
|
+
: nullptr;
|
|
423
|
+
const double* rsi_ma_vis =
|
|
424
|
+
(rsi.ma_visible &&
|
|
425
|
+
rsi_ma_cache.size() == candles.size())
|
|
426
|
+
? rsi_ma_cache.data() + range.start
|
|
427
|
+
: nullptr;
|
|
428
|
+
vroom::rsi_pane::draw(
|
|
429
|
+
canvas, *this, lay, visible, n, rsi_vis, rsi_ma_vis,
|
|
430
|
+
window_ms, visible_start_ms, candle_duration_ms,
|
|
431
|
+
candle_right, pane_top, pane_bottom);
|
|
432
|
+
} else {
|
|
433
|
+
ensure_macd();
|
|
434
|
+
const double* macd_vis =
|
|
435
|
+
macd_cache.size() == candles.size()
|
|
436
|
+
? macd_cache.data() + range.start
|
|
437
|
+
: nullptr;
|
|
438
|
+
const double* sig_vis =
|
|
439
|
+
macd_signal_cache.size() == candles.size()
|
|
440
|
+
? macd_signal_cache.data() + range.start
|
|
441
|
+
: nullptr;
|
|
442
|
+
const double* hist_vis =
|
|
443
|
+
macd_hist_cache.size() == candles.size()
|
|
444
|
+
? macd_hist_cache.data() + range.start
|
|
445
|
+
: nullptr;
|
|
446
|
+
vroom::macd_pane::draw(
|
|
447
|
+
canvas, *this, lay, visible, n, macd_vis, sig_vis,
|
|
448
|
+
hist_vis, window_ms, visible_start_ms,
|
|
449
|
+
candle_duration_ms, candle_right, pane_top,
|
|
450
|
+
pane_bottom);
|
|
451
|
+
}
|
|
452
|
+
pane_top = pane_bottom;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
if (wrap) canvas->restore();
|
|
335
458
|
}
|
|
336
459
|
|
|
337
460
|
// 6. Axis backgrounds (mask any candle overflow). The x-axis separator
|
|
@@ -354,20 +477,32 @@ void VroomChart::draw_chart(SkCanvas* canvas) {
|
|
|
354
477
|
|
|
355
478
|
// 7.5. Current-price line + box — above labels so the box covers any label
|
|
356
479
|
// it overlaps; tracks the latest close as the price scale moves.
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
480
|
+
// Hidden during a fade's outgoing half: the close is already the new
|
|
481
|
+
// series and there is no snapshot to fade.
|
|
482
|
+
if (!fade_out) {
|
|
483
|
+
vroom::price_indicator::draw(canvas, *this, lay, bounds,
|
|
484
|
+
candle_right, candle_area_h);
|
|
485
|
+
|
|
486
|
+
// 7.55. Consumer-supplied price status lines — same tier as the
|
|
487
|
+
// current-price indicator (their badges must cover the labels
|
|
488
|
+
// underneath), but after it so a resting order at the last close
|
|
489
|
+
// stays readable.
|
|
490
|
+
vroom::price_lines::draw(canvas, *this, lay, bounds, candle_right,
|
|
491
|
+
candle_area_h);
|
|
492
|
+
|
|
493
|
+
// 7.56. Footprint badges — data-anchored chrome that must not be hidden
|
|
494
|
+
// by candles or overlays, so it draws with the price lines rather
|
|
495
|
+
// than back at the drawings layer. Below the crosshair, which the
|
|
496
|
+
// user is actively pointing with.
|
|
497
|
+
ensure_footprint_buckets();
|
|
498
|
+
vroom::footprints::draw(canvas, *this, lay, bounds, window_ms);
|
|
499
|
+
}
|
|
365
500
|
|
|
366
501
|
// 7.6. Indicator panes stacked below the candles, ordered by enable
|
|
367
502
|
// sequence (most recently enabled at the bottom). Each pane is
|
|
368
503
|
// INDICATOR_HEIGHT_FRAC of the height; the candle pane already shrank
|
|
369
|
-
// to fit them (see layout()).
|
|
370
|
-
if (lay.indicator_area_h > 0.f) {
|
|
504
|
+
// to fit them (see layout()). A fade already drew them with the scene.
|
|
505
|
+
if (!fade_swap && lay.indicator_area_h > 0.f) {
|
|
371
506
|
struct ActivePane { int order; int type; }; // type: 0 = RSI, 1 = MACD
|
|
372
507
|
ActivePane panes[2];
|
|
373
508
|
int count = 0;
|
package/cpp/_core_src/chart.h
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
#include "include/core/SkRefCnt.h"
|
|
23
23
|
#pragma clang diagnostic pop
|
|
24
24
|
|
|
25
|
+
#include "footprints_layout.h"
|
|
25
26
|
#include "labels.h"
|
|
26
27
|
#include "price_format.h"
|
|
27
28
|
#include "theme.h"
|
|
@@ -73,11 +74,14 @@ struct VroomChart {
|
|
|
73
74
|
|
|
74
75
|
// Interval morph: the outgoing candle geometry captured when a timeframe
|
|
75
76
|
// switch begins, indexed from the right of the visible slice (slot 0 =
|
|
76
|
-
// newest)
|
|
77
|
-
//
|
|
78
|
-
//
|
|
77
|
+
// newest). `interval_morph_fade` is the host's choice at capture time:
|
|
78
|
+
// false = slot-lerp each column into its counterpart; true = fade the
|
|
79
|
+
// snapshot out then the new scene in (see labels::interval_phase).
|
|
80
|
+
// Stored as normalized fractions so it survives the new bounds and a
|
|
81
|
+
// resize. Empty when not morphing.
|
|
79
82
|
std::vector<vroom::CandleSnapshot> morph_from;
|
|
80
83
|
float interval_morph_t = 1.f; // 1 = not morphing
|
|
84
|
+
bool interval_morph_fade = false;
|
|
81
85
|
// The pre-switch price scale and time window. The candle capture above is
|
|
82
86
|
// normalized against these, and the axes keep rendering their old ticks from
|
|
83
87
|
// them while fading out (see labels::interval_phase), so a label is never
|
|
@@ -277,6 +281,28 @@ struct VroomChart {
|
|
|
277
281
|
int32_t dragged_price_line = -1;
|
|
278
282
|
double dragged_price_line_price = 0.0;
|
|
279
283
|
|
|
284
|
+
// --- footprints (executed-trade badges) ---------------------------------
|
|
285
|
+
// Kept in the *host's* order so the indices reported by
|
|
286
|
+
// vroom_chart_footprints_at map straight back to its own trade objects.
|
|
287
|
+
std::vector<VroomFootprint> footprints;
|
|
288
|
+
VroomFootprintStyle footprint_style{};
|
|
289
|
+
|
|
290
|
+
// Footprints grouped onto candles, which is what the renderer and hit-test
|
|
291
|
+
// actually walk. Derived from `footprints` + `candles`, so it is rebuilt
|
|
292
|
+
// lazily (see ensure_footprint_buckets) whenever either changes — that
|
|
293
|
+
// rebuild is what re-groups badges onto the right bars after an interval
|
|
294
|
+
// switch, with no involvement from the host.
|
|
295
|
+
std::vector<vroom::footprints::Bucket> footprint_buckets;
|
|
296
|
+
bool footprint_buckets_dirty = true;
|
|
297
|
+
|
|
298
|
+
// Which badge renders highlighted, keyed the way the hit-test reports it.
|
|
299
|
+
// side -1 = none.
|
|
300
|
+
int64_t hovered_footprint_time_ms = 0;
|
|
301
|
+
int32_t hovered_footprint_side = -1;
|
|
302
|
+
|
|
303
|
+
// Rebuilds footprint_buckets if either input changed since the last call.
|
|
304
|
+
void ensure_footprint_buckets();
|
|
305
|
+
|
|
280
306
|
// --- theme --------------------------------------------------------------
|
|
281
307
|
vroom::Theme theme;
|
|
282
308
|
|