react-native-vroom-chart 0.13.1 → 0.14.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 +15 -7
- package/cpp/_core_include/vroom/vroom_chart.h +6 -7
- package/cpp/_core_src/candles.h +7 -5
- package/cpp/_core_src/chart.cpp +271 -152
- package/cpp/_core_src/chart.h +6 -3
- package/cpp/_core_src/chart_facade.cpp +5 -1
- package/cpp/_core_src/viewport.h +4 -0
- package/lib/index.d.mts +52 -6
- package/lib/index.d.ts +52 -6
- package/lib/index.js +6 -4
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +6 -4
- package/lib/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/VroomChart.tsx +2 -1
- package/src/index.ts +2 -0
- package/src/jsi.d.ts +6 -8
- package/src/types.ts +2 -0
- package/src/useChartCore.ts +7 -2
|
@@ -484,17 +484,25 @@ jsi::Value ChartHostObject::get(jsi::Runtime& rt,
|
|
|
484
484
|
}
|
|
485
485
|
|
|
486
486
|
if (name == "beginIntervalMorph") {
|
|
487
|
-
// beginIntervalMorph() — capture the visible candle geometry so the
|
|
488
|
-
// setCandles can
|
|
487
|
+
// beginIntervalMorph(mode?) — capture the visible candle geometry so the
|
|
488
|
+
// next setCandles can animate. Optional string `'fade'` or `'transform'`
|
|
489
|
+
// (default). Call before setCandles.
|
|
489
490
|
return jsi::Function::createFromHostFunction(
|
|
490
491
|
rt,
|
|
491
492
|
jsi::PropNameID::forAscii(rt, "beginIntervalMorph"),
|
|
492
|
-
|
|
493
|
-
[this](jsi::Runtime&
|
|
493
|
+
1,
|
|
494
|
+
[this](jsi::Runtime& rt2,
|
|
494
495
|
const jsi::Value& /*thisVal*/,
|
|
495
|
-
const jsi::Value*
|
|
496
|
-
size_t
|
|
497
|
-
|
|
496
|
+
const jsi::Value* args,
|
|
497
|
+
size_t count) -> jsi::Value {
|
|
498
|
+
int32_t mode = 0;
|
|
499
|
+
if (count >= 1 && args[0].isString()) {
|
|
500
|
+
const auto s = args[0].asString(rt2).utf8(rt2);
|
|
501
|
+
mode = s == "fade" ? 1 : 0;
|
|
502
|
+
} else if (count >= 1 && args[0].isNumber()) {
|
|
503
|
+
mode = args[0].asNumber() != 0 ? 1 : 0;
|
|
504
|
+
}
|
|
505
|
+
vroom_chart_begin_interval_morph(chart_, mode);
|
|
498
506
|
return jsi::Value::undefined();
|
|
499
507
|
});
|
|
500
508
|
}
|
|
@@ -420,14 +420,13 @@ void vroom_chart_preserve_price_envelope(VroomChart* chart,
|
|
|
420
420
|
double prev_low, double prev_high);
|
|
421
421
|
|
|
422
422
|
// Captures the currently visible candle geometry so the next data swap can be
|
|
423
|
-
// animated
|
|
424
|
-
//
|
|
423
|
+
// animated. `mode` 0 (transform) lerps each visible column into its counterpart,
|
|
424
|
+
// paired by slot counting back from the right edge. `mode` 1 (fade) fades the
|
|
425
|
+
// capture out then the new scene in on the same envelope the axes already use.
|
|
425
426
|
//
|
|
426
|
-
//
|
|
427
|
-
//
|
|
428
|
-
|
|
429
|
-
// No-op when nothing is visible.
|
|
430
|
-
void vroom_chart_begin_interval_morph(VroomChart* chart);
|
|
427
|
+
// Call before set_candles, then drive vroom_chart_set_interval_morph from 0
|
|
428
|
+
// to 1. No-op when nothing is visible.
|
|
429
|
+
void vroom_chart_begin_interval_morph(VroomChart* chart, int32_t mode);
|
|
431
430
|
|
|
432
431
|
// Advances the interval morph started by vroom_chart_begin_interval_morph. `t`
|
|
433
432
|
// (clamped to 0..1) is the eased progress: 0 renders the captured geometry
|
package/cpp/_core_src/candles.h
CHANGED
|
@@ -28,11 +28,13 @@ namespace vroom::candles {
|
|
|
28
28
|
// body/wick heights and width shrink to the line). `opacity` (0..1) fades the
|
|
29
29
|
// whole candle layer out as the line fades in. Both default to a no-op.
|
|
30
30
|
//
|
|
31
|
-
// `from` / `from_n` is the outgoing geometry of an interval
|
|
32
|
-
// the right of the visible slice (slot 0 = newest), and `morph_t` (0..1)
|
|
33
|
-
// eased progress toward `visible`. Each slot's wick and body interpolate
|
|
34
|
-
// the two; slots present on only one side fade in or out.
|
|
35
|
-
//
|
|
31
|
+
// `from` / `from_n` is the outgoing geometry of an interval *transform*, indexed
|
|
32
|
+
// from the right of the visible slice (slot 0 = newest), and `morph_t` (0..1)
|
|
33
|
+
// is the eased progress toward `visible`. Each slot's wick and body interpolate
|
|
34
|
+
// between the two; slots present on only one side fade in or out. A host-chosen
|
|
35
|
+
// fade (see interval_morph_fade) skips this lerp: the chart draws the capture
|
|
36
|
+
// then the new scene via interval_phase instead. `morph_t == 1` (the default)
|
|
37
|
+
// draws `visible` alone.
|
|
36
38
|
void draw(SkCanvas* canvas,
|
|
37
39
|
const ::VroomCandle* visible,
|
|
38
40
|
std::size_t n,
|
package/cpp/_core_src/chart.cpp
CHANGED
|
@@ -178,160 +178,274 @@ void VroomChart::draw_chart(SkCanvas* canvas) {
|
|
|
178
178
|
// 4.35. Price-series morph state, shared by the gradient fill below and the
|
|
179
179
|
// series itself (5). `morph_fade` crossfades candles→line and
|
|
180
180
|
// `morph_collapse` folds each candle toward its close (the line vertex);
|
|
181
|
-
// fade 0 = pure candles, fade 1 = pure line.
|
|
182
|
-
//
|
|
183
|
-
//
|
|
184
|
-
// the
|
|
181
|
+
// fade 0 = pure candles, fade 1 = pure line.
|
|
182
|
+
//
|
|
183
|
+
// An interval *transform* reshapes each slot from the geometry it held
|
|
184
|
+
// before the timeframe switch (see `morph_from`). A host-chosen
|
|
185
|
+
// *fade* skips the pairing and uses the same envelope as the axes
|
|
186
|
+
// (`labels::interval_phase`): outgoing snapshot out, then new scene in.
|
|
185
187
|
const float fade = morph_fade;
|
|
186
188
|
const float collapse = morph_collapse;
|
|
187
189
|
const bool morphing = interval_morph_t < 1.f && !morph_from.empty();
|
|
188
|
-
const vroom::CandleSnapshot* morph_src = morphing ? morph_from.data() : nullptr;
|
|
189
190
|
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
|
-
|
|
191
|
+
const bool fade_swap = morphing && interval_morph_fade;
|
|
192
|
+
const bool fade_out = fade_swap && axis_phase.outgoing;
|
|
193
|
+
const bool fade_in = fade_swap && !axis_phase.outgoing;
|
|
194
|
+
const bool reshape = morphing && !fade_swap;
|
|
195
|
+
const vroom::CandleSnapshot* morph_src = reshape ? morph_from.data() : nullptr;
|
|
196
|
+
const std::size_t morph_n_draw = reshape ? morph_n : 0;
|
|
197
|
+
const float morph_t = reshape ? interval_morph_t : 1.f;
|
|
198
|
+
|
|
199
|
+
if (fade_out) {
|
|
200
|
+
// First half: only the captured series, at the axis envelope opacity.
|
|
201
|
+
// Layers with no snapshot (volume, overlays, indicators, drawings)
|
|
202
|
+
// would already be the new data, so they stay hidden until the incoming
|
|
203
|
+
// half. morph_t = 0 draws the capture pixel-identically.
|
|
204
|
+
const float layer = axis_phase.opacity;
|
|
205
|
+
if (layer > 0.f) {
|
|
206
|
+
if (fade > 0.f) {
|
|
207
|
+
vroom::ma_overlay::draw_close_gradient(
|
|
208
|
+
canvas, lay, bounds, visible, 0, window_ms,
|
|
209
|
+
visible_start_ms, candle_duration_ms, candle_right,
|
|
210
|
+
candle_area_h, theme.colors[VROOM_COLOR_LINE],
|
|
211
|
+
theme.floats[VROOM_FLOAT_LINE_GRADIENT_OPACITY],
|
|
212
|
+
fade * layer, morph_from.data(), morph_n, 0.f,
|
|
213
|
+
theme.floats[VROOM_FLOAT_LINE_TENSION]);
|
|
214
|
+
}
|
|
215
|
+
if (fade < 1.f) {
|
|
216
|
+
vroom::candles::draw(canvas, visible, 0, lay, theme, bounds,
|
|
217
|
+
window_ms, visible_start_ms,
|
|
218
|
+
candle_duration_ms, collapse,
|
|
219
|
+
(1.f - fade) * layer, morph_from.data(),
|
|
220
|
+
morph_n, 0.f);
|
|
221
|
+
}
|
|
222
|
+
if (fade > 0.f) {
|
|
223
|
+
vroom::ma_overlay::draw_close_line(
|
|
224
|
+
canvas, lay, bounds, visible, 0, window_ms,
|
|
225
|
+
visible_start_ms, candle_duration_ms, candle_right,
|
|
226
|
+
candle_area_h, theme.colors[VROOM_COLOR_LINE],
|
|
227
|
+
theme.floats[VROOM_FLOAT_LINE_WIDTH_PX], fade * layer,
|
|
228
|
+
morph_from.data(), morph_n, 0.f,
|
|
229
|
+
theme.floats[VROOM_FLOAT_LINE_TENSION]);
|
|
230
|
+
if (theme.floats[VROOM_FLOAT_LINE_TIP_DOT] > 0.5f) {
|
|
231
|
+
vroom::ma_overlay::draw_close_tip(
|
|
232
|
+
canvas, lay, bounds, visible, 0, window_ms,
|
|
233
|
+
visible_start_ms, candle_duration_ms, candle_right,
|
|
234
|
+
candle_area_h, theme.colors[VROOM_COLOR_LINE],
|
|
235
|
+
theme.colors[VROOM_COLOR_BACKGROUND],
|
|
236
|
+
theme.floats[VROOM_FLOAT_LINE_WIDTH_PX], fade * layer,
|
|
237
|
+
theme.floats[VROOM_FLOAT_LINE_TIP_PULSE] > 0.5f,
|
|
238
|
+
tip_pulse_elapsed_s / vroom::tip_pulse::kPeriodSeconds,
|
|
239
|
+
morph_from.data(), morph_n, 0.f);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
} else {
|
|
244
|
+
// Incoming fade: the new scene at envelope opacity, no slot lerp
|
|
245
|
+
// (`morph_t` is already 1). Transforms use the reshape path above.
|
|
246
|
+
const bool wrap = fade_in && axis_phase.opacity > 0.f &&
|
|
247
|
+
axis_phase.opacity < 0.999f;
|
|
248
|
+
if (wrap) {
|
|
249
|
+
canvas->saveLayerAlpha(
|
|
250
|
+
nullptr, static_cast<U8CPU>(axis_phase.opacity * 255.f + 0.5f));
|
|
251
|
+
}
|
|
252
|
+
const bool draw_new = !fade_in || axis_phase.opacity > 0.f;
|
|
253
|
+
|
|
254
|
+
if (draw_new) {
|
|
255
|
+
// 4.4. Line-mode gradient fill — the wash under the close polyline.
|
|
256
|
+
if (fade > 0.f) {
|
|
257
|
+
vroom::ma_overlay::draw_close_gradient(
|
|
258
|
+
canvas, lay, bounds, visible, n, window_ms,
|
|
259
|
+
visible_start_ms, candle_duration_ms, candle_right,
|
|
260
|
+
candle_area_h, theme.colors[VROOM_COLOR_LINE],
|
|
261
|
+
theme.floats[VROOM_FLOAT_LINE_GRADIENT_OPACITY], fade,
|
|
262
|
+
morph_src, morph_n_draw, morph_t,
|
|
263
|
+
theme.floats[VROOM_FLOAT_LINE_TENSION]);
|
|
264
|
+
}
|
|
204
265
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
}
|
|
266
|
+
// 4.5. Volume bars — drawn under the candles so candles z-index above.
|
|
267
|
+
if (volume_collapse_t < 1.f) {
|
|
268
|
+
vroom::volume::draw(canvas, visible, n, lay, theme, volume,
|
|
269
|
+
volume_collapse_t, volume_collapse_easing,
|
|
270
|
+
window_ms, visible_start_ms,
|
|
271
|
+
candle_duration_ms);
|
|
272
|
+
}
|
|
213
273
|
|
|
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
|
-
}
|
|
274
|
+
// 4.6. Liquidity bands (resting-order depth).
|
|
275
|
+
vroom::liquidity::draw(canvas, *this, lay, bounds, candle_right,
|
|
276
|
+
candle_area_h);
|
|
277
|
+
|
|
278
|
+
// 4.7. Bollinger Band fill.
|
|
279
|
+
if (bollinger.enabled) {
|
|
280
|
+
ensure_bollinger();
|
|
281
|
+
if (bollinger.fill_enabled &&
|
|
282
|
+
bb_upper_cache.size() == candles.size() &&
|
|
283
|
+
bb_lower_cache.size() == candles.size()) {
|
|
284
|
+
vroom::ma_overlay::fill_between(
|
|
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
|
+
}
|
|
292
|
+
}
|
|
236
293
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
294
|
+
// 5. Price series — candles, a close-price line, or a blend.
|
|
295
|
+
if (fade < 1.f) {
|
|
296
|
+
vroom::candles::draw(canvas, visible, n, lay, theme, bounds,
|
|
297
|
+
window_ms, visible_start_ms,
|
|
298
|
+
candle_duration_ms, collapse, 1.f - fade,
|
|
299
|
+
morph_src, morph_n_draw, morph_t);
|
|
300
|
+
}
|
|
301
|
+
if (fade > 0.f) {
|
|
302
|
+
vroom::ma_overlay::draw_close_line(
|
|
303
|
+
canvas, lay, bounds, visible, n, window_ms,
|
|
304
|
+
visible_start_ms, candle_duration_ms, candle_right,
|
|
305
|
+
candle_area_h, theme.colors[VROOM_COLOR_LINE],
|
|
306
|
+
theme.floats[VROOM_FLOAT_LINE_WIDTH_PX], fade, morph_src,
|
|
307
|
+
morph_n_draw, morph_t,
|
|
308
|
+
theme.floats[VROOM_FLOAT_LINE_TENSION]);
|
|
309
|
+
}
|
|
253
310
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
}
|
|
267
|
-
}
|
|
311
|
+
// 5.5. Moving-average overlay lines.
|
|
312
|
+
if (!overlays.empty()) {
|
|
313
|
+
ensure_overlays();
|
|
314
|
+
for (std::size_t k = 0; k < overlays.size(); ++k) {
|
|
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
|
+
}
|
|
268
323
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
}
|
|
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
|
+
}
|
|
287
341
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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
|
+
}
|
|
309
364
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
365
|
+
// 5.7. Drawing annotations.
|
|
366
|
+
vroom::drawings::draw(canvas, *this, lay, bounds, candle_right,
|
|
367
|
+
candle_area_h);
|
|
368
|
+
|
|
369
|
+
// 5.8. Line-mode tip marker.
|
|
370
|
+
if (fade > 0.f && theme.floats[VROOM_FLOAT_LINE_TIP_DOT] > 0.5f) {
|
|
371
|
+
const auto anchor = vroom::tip_anchor::at(
|
|
372
|
+
range.start, range.end, candles.size(), reshape);
|
|
373
|
+
vroom::ma_overlay::draw_close_tip(
|
|
374
|
+
canvas, lay, bounds, visible, anchor.slot_count, window_ms,
|
|
375
|
+
visible_start_ms, candle_duration_ms, candle_right,
|
|
376
|
+
candle_area_h, theme.colors[VROOM_COLOR_LINE],
|
|
377
|
+
theme.colors[VROOM_COLOR_BACKGROUND],
|
|
378
|
+
theme.floats[VROOM_FLOAT_LINE_WIDTH_PX], fade,
|
|
379
|
+
theme.floats[VROOM_FLOAT_LINE_TIP_PULSE] > 0.5f,
|
|
380
|
+
tip_pulse_elapsed_s / vroom::tip_pulse::kPeriodSeconds,
|
|
381
|
+
anchor.use_morph ? morph_src : nullptr,
|
|
382
|
+
anchor.use_morph ? morph_n_draw : 0, morph_t);
|
|
383
|
+
}
|
|
314
384
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
385
|
+
// Incoming fade: indicator panes share the scene opacity (and
|
|
386
|
+
// paint before axis masks so overflow still clips). Transforms
|
|
387
|
+
// keep the settled z-order after the price indicator.
|
|
388
|
+
if (fade_in && lay.indicator_area_h > 0.f) {
|
|
389
|
+
struct ActivePane {
|
|
390
|
+
int order;
|
|
391
|
+
int type;
|
|
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
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
if (wrap) canvas->restore();
|
|
335
449
|
}
|
|
336
450
|
|
|
337
451
|
// 6. Axis backgrounds (mask any candle overflow). The x-axis separator
|
|
@@ -354,20 +468,25 @@ void VroomChart::draw_chart(SkCanvas* canvas) {
|
|
|
354
468
|
|
|
355
469
|
// 7.5. Current-price line + box — above labels so the box covers any label
|
|
356
470
|
// it overlaps; tracks the latest close as the price scale moves.
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
471
|
+
// Hidden during a fade's outgoing half: the close is already the new
|
|
472
|
+
// series and there is no snapshot to fade.
|
|
473
|
+
if (!fade_out) {
|
|
474
|
+
vroom::price_indicator::draw(canvas, *this, lay, bounds,
|
|
475
|
+
candle_right, candle_area_h);
|
|
476
|
+
|
|
477
|
+
// 7.55. Consumer-supplied price status lines — same tier as the
|
|
478
|
+
// current-price indicator (their badges must cover the labels
|
|
479
|
+
// underneath), but after it so a resting order at the last close
|
|
480
|
+
// stays readable.
|
|
481
|
+
vroom::price_lines::draw(canvas, *this, lay, bounds, candle_right,
|
|
482
|
+
candle_area_h);
|
|
483
|
+
}
|
|
365
484
|
|
|
366
485
|
// 7.6. Indicator panes stacked below the candles, ordered by enable
|
|
367
486
|
// sequence (most recently enabled at the bottom). Each pane is
|
|
368
487
|
// 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) {
|
|
488
|
+
// to fit them (see layout()). A fade already drew them with the scene.
|
|
489
|
+
if (!fade_swap && lay.indicator_area_h > 0.f) {
|
|
371
490
|
struct ActivePane { int order; int type; }; // type: 0 = RSI, 1 = MACD
|
|
372
491
|
ActivePane panes[2];
|
|
373
492
|
int count = 0;
|
package/cpp/_core_src/chart.h
CHANGED
|
@@ -73,11 +73,14 @@ struct VroomChart {
|
|
|
73
73
|
|
|
74
74
|
// Interval morph: the outgoing candle geometry captured when a timeframe
|
|
75
75
|
// switch begins, indexed from the right of the visible slice (slot 0 =
|
|
76
|
-
// newest)
|
|
77
|
-
//
|
|
78
|
-
//
|
|
76
|
+
// newest). `interval_morph_fade` is the host's choice at capture time:
|
|
77
|
+
// false = slot-lerp each column into its counterpart; true = fade the
|
|
78
|
+
// snapshot out then the new scene in (see labels::interval_phase).
|
|
79
|
+
// Stored as normalized fractions so it survives the new bounds and a
|
|
80
|
+
// resize. Empty when not morphing.
|
|
79
81
|
std::vector<vroom::CandleSnapshot> morph_from;
|
|
80
82
|
float interval_morph_t = 1.f; // 1 = not morphing
|
|
83
|
+
bool interval_morph_fade = false;
|
|
81
84
|
// The pre-switch price scale and time window. The candle capture above is
|
|
82
85
|
// normalized against these, and the axes keep rendering their old ticks from
|
|
83
86
|
// them while fading out (see labels::interval_phase), so a label is never
|
|
@@ -299,6 +299,7 @@ extern "C" void vroom_chart_reset_view(VroomChart* chart) {
|
|
|
299
299
|
// here — drop any capture rather than leaving it to reshape the wrong data.
|
|
300
300
|
chart->morph_from.clear();
|
|
301
301
|
chart->interval_morph_t = 1.f;
|
|
302
|
+
chart->interval_morph_fade = false;
|
|
302
303
|
apply_default_framing(chart);
|
|
303
304
|
vroom::labels::recompute_axis_width(*chart);
|
|
304
305
|
if (chart->cb.on_viewport_changed) {
|
|
@@ -352,10 +353,11 @@ extern "C" void vroom_chart_preserve_price_envelope(VroomChart* chart,
|
|
|
352
353
|
chart->mark_dirty();
|
|
353
354
|
}
|
|
354
355
|
|
|
355
|
-
extern "C" void vroom_chart_begin_interval_morph(VroomChart* chart) {
|
|
356
|
+
extern "C" void vroom_chart_begin_interval_morph(VroomChart* chart, int32_t mode) {
|
|
356
357
|
if (!chart) return;
|
|
357
358
|
chart->morph_from.clear();
|
|
358
359
|
chart->interval_morph_t = 1.f;
|
|
360
|
+
chart->interval_morph_fade = false;
|
|
359
361
|
|
|
360
362
|
const auto lay = chart->layout();
|
|
361
363
|
const float area_w = vroom::candle_area_width(lay);
|
|
@@ -400,6 +402,7 @@ extern "C" void vroom_chart_begin_interval_morph(VroomChart* chart) {
|
|
|
400
402
|
// Open the morph at 0 rather than leaving it at 1: the caller still has to
|
|
401
403
|
// push the new candles, and any frame painted in between should show the
|
|
402
404
|
// captured geometry — which is what the pre-switch frame looked like.
|
|
405
|
+
chart->interval_morph_fade = vroom::interval_morph_is_fade(mode);
|
|
403
406
|
chart->interval_morph_t = 0.f;
|
|
404
407
|
}
|
|
405
408
|
|
|
@@ -409,6 +412,7 @@ extern "C" void vroom_chart_set_interval_morph(VroomChart* chart, float t) {
|
|
|
409
412
|
if (chart->interval_morph_t >= 1.f) {
|
|
410
413
|
chart->morph_from.clear();
|
|
411
414
|
chart->morph_from.shrink_to_fit();
|
|
415
|
+
chart->interval_morph_fade = false;
|
|
412
416
|
}
|
|
413
417
|
chart->mark_dirty();
|
|
414
418
|
}
|
package/cpp/_core_src/viewport.h
CHANGED
|
@@ -104,6 +104,10 @@ inline std::size_t morph_from_count(const CandleSnapshot* from,
|
|
|
104
104
|
return (from && morph_t < 1.f) ? from_n : 0;
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
+
// C facade `vroom_chart_begin_interval_morph` mode: 0 = slot-lerp transform,
|
|
108
|
+
// anything else = fade the outgoing snapshot out then the new scene in.
|
|
109
|
+
inline bool interval_morph_is_fade(int32_t mode) { return mode != 0; }
|
|
110
|
+
|
|
107
111
|
// Returns the indices of candles whose time_ms falls in [start_ms, end_ms].
|
|
108
112
|
// When both are 0, returns the full range (Phase 1 default-everything behavior).
|
|
109
113
|
// Candles must be sorted ascending by time_ms (invariant of the public API).
|