react-native-vroom-chart 0.9.0 → 0.9.1
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/_core_include/vroom/vroom_chart.h +2 -1
- package/cpp/_core_src/chart_facade.cpp +61 -55
- package/cpp/_core_src/crosshair.cpp +5 -1
- package/cpp/_core_src/labels.cpp +8 -3
- package/cpp/_core_src/price_indicator.cpp +4 -1
- package/cpp/_core_src/price_lines.cpp +7 -3
- package/cpp/_core_src/ticks.cpp +14 -0
- package/cpp/_core_src/ticks.h +8 -0
- package/cpp/_core_src/viewport.cpp +45 -0
- package/cpp/_core_src/viewport.h +30 -0
- package/lib/index.d.mts +2 -1
- package/lib/index.d.ts +2 -1
- package/package.json +1 -1
|
@@ -345,7 +345,8 @@ void vroom_chart_set_visible_range(VroomChart* chart, int64_t start_ms, int64_t
|
|
|
345
345
|
// Target candle *body* width in px for the default/reset framing. Larger = more
|
|
346
346
|
// zoomed in (fewer candles), smaller = more zoomed out. 0 restores the legacy
|
|
347
347
|
// "most recent ~80 candles" default. Affects initial framing only; an explicit
|
|
348
|
-
// set_visible_range still overrides it.
|
|
348
|
+
// set_visible_range still overrides it. A series shorter than the implied window
|
|
349
|
+
// is left-padded with empty slots so bars stay this wide instead of stretching.
|
|
349
350
|
void vroom_chart_set_default_candle_width(VroomChart* chart, float px);
|
|
350
351
|
|
|
351
352
|
// Chart render mode: 0 = candlesticks (default), 1 = line chart (a polyline
|
|
@@ -50,6 +50,21 @@ int64_t max_future_gap(int64_t window_ms, int64_t cur_future) {
|
|
|
50
50
|
return std::max<int64_t>((window_ms * 3) / 4, cur_future);
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
// Pan/translate future allowance. A window longer than the data has nowhere
|
|
54
|
+
// useful to scroll, so don't grow the future gap — growing it used to combine
|
|
55
|
+
// with a first-candle start pin and latch the view. Dense series keep the
|
|
56
|
+
// usual 3/4-window rubber-band.
|
|
57
|
+
int64_t pan_max_future(int64_t window_ms, int64_t cur_future,
|
|
58
|
+
int64_t first_time, int64_t last_time,
|
|
59
|
+
int64_t candle_duration_ms) {
|
|
60
|
+
const int64_t dur = candle_duration_ms > 0 ? candle_duration_ms : 0;
|
|
61
|
+
const int64_t data_extent = last_time + dur - first_time;
|
|
62
|
+
if (data_extent >= 0 && window_ms > data_extent) {
|
|
63
|
+
return std::max<int64_t>(0, cur_future);
|
|
64
|
+
}
|
|
65
|
+
return max_future_gap(window_ms, cur_future);
|
|
66
|
+
}
|
|
67
|
+
|
|
53
68
|
// Rubber-band a forward (into-future) pan delta: once the current future gap is
|
|
54
69
|
// past the soft cap (window/2), damp the delta quadratically toward zero as it
|
|
55
70
|
// nears the hard cap (3*window/4). Backward (into-past) deltas are unaffected.
|
|
@@ -109,13 +124,20 @@ void apply_default_framing(VroomChart* chart) {
|
|
|
109
124
|
const double body = std::clamp(static_cast<double>(chart->default_candle_px),
|
|
110
125
|
kMinCandleBodyPx, kMaxCandleBodyPx);
|
|
111
126
|
if (usable > 0.0 && ratio > 0.0 && dur > 0.0 && body > 0.0) {
|
|
112
|
-
int64_t window_ms =
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
127
|
+
int64_t window_ms = vroom::window_for_body_width(
|
|
128
|
+
lay, chart->candle_duration_ms, body);
|
|
129
|
+
// Floor at one slot so the window is valid. Do NOT clamp to the
|
|
130
|
+
// data span: a short series would then stretch each bar to fill
|
|
131
|
+
// the plot, the opposite of a target body width. Empty space on
|
|
132
|
+
// the left is intentional — same rule as timeframeWindow (width
|
|
133
|
+
// wins).
|
|
134
|
+
if (window_ms > 0) {
|
|
135
|
+
if (window_ms < chart->candle_duration_ms) {
|
|
136
|
+
window_ms = chart->candle_duration_ms;
|
|
137
|
+
}
|
|
138
|
+
frame(window_ms);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
119
141
|
}
|
|
120
142
|
}
|
|
121
143
|
|
|
@@ -404,30 +426,23 @@ extern "C" void vroom_chart_pan(VroomChart* chart, float dx_px, float /*dy_px*/)
|
|
|
404
426
|
(-dx_px / usable_px) * static_cast<float>(window_ms));
|
|
405
427
|
if (delta_ms == 0) return;
|
|
406
428
|
|
|
407
|
-
//
|
|
408
|
-
//
|
|
409
|
-
//
|
|
410
|
-
//
|
|
411
|
-
// larger than the cap (it zooms in until ~one candle remains), so the cap
|
|
412
|
-
// never forces it back — it just shrinks naturally as the user scrolls.
|
|
413
|
-
// Left edge still hard-clamps at the first candle.
|
|
429
|
+
// Forward pans rubber-band between half and 3/4 of a window of empty
|
|
430
|
+
// future (see damp_future_delta / max_future_gap). Past clamping lives in
|
|
431
|
+
// clamp_shifted_time_window: dense series pin start at the first candle;
|
|
432
|
+
// a window longer than the data keeps empty past instead of latching.
|
|
414
433
|
const int64_t first_time = chart->candles.front().time_ms;
|
|
415
434
|
const int64_t last_time = chart->candles.back().time_ms;
|
|
416
435
|
const int64_t cur_future = chart->visible_end_ms - last_time;
|
|
417
436
|
const int64_t eff_delta = damp_future_delta(delta_ms, cur_future, window_ms);
|
|
418
437
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
if (new_start < first_time) {
|
|
428
|
-
new_start = first_time;
|
|
429
|
-
new_end = new_start + window_ms;
|
|
430
|
-
}
|
|
438
|
+
const auto clamped = vroom::clamp_shifted_time_window(
|
|
439
|
+
chart->visible_start_ms + eff_delta,
|
|
440
|
+
chart->visible_end_ms + eff_delta,
|
|
441
|
+
first_time, last_time, chart->candle_duration_ms,
|
|
442
|
+
pan_max_future(window_ms, cur_future, first_time, last_time,
|
|
443
|
+
chart->candle_duration_ms));
|
|
444
|
+
const int64_t new_start = clamped.start_ms;
|
|
445
|
+
const int64_t new_end = clamped.end_ms;
|
|
431
446
|
|
|
432
447
|
if (new_start == chart->visible_start_ms &&
|
|
433
448
|
new_end == chart->visible_end_ms) {
|
|
@@ -467,22 +482,16 @@ extern "C" void vroom_chart_translate(VroomChart* chart, float dx_px, float dy_p
|
|
|
467
482
|
const int64_t cur_future = chart->visible_end_ms - last_time;
|
|
468
483
|
const int64_t eff_delta =
|
|
469
484
|
damp_future_delta(delta_ms, cur_future, window_ms);
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
new_end = new_start + window_ms;
|
|
481
|
-
}
|
|
482
|
-
if (new_start != chart->visible_start_ms ||
|
|
483
|
-
new_end != chart->visible_end_ms) {
|
|
484
|
-
chart->visible_start_ms = new_start;
|
|
485
|
-
chart->visible_end_ms = new_end;
|
|
485
|
+
const auto clamped = vroom::clamp_shifted_time_window(
|
|
486
|
+
chart->visible_start_ms + eff_delta,
|
|
487
|
+
chart->visible_end_ms + eff_delta,
|
|
488
|
+
first_time, last_time, chart->candle_duration_ms,
|
|
489
|
+
pan_max_future(window_ms, cur_future, first_time, last_time,
|
|
490
|
+
chart->candle_duration_ms));
|
|
491
|
+
if (clamped.start_ms != chart->visible_start_ms ||
|
|
492
|
+
clamped.end_ms != chart->visible_end_ms) {
|
|
493
|
+
chart->visible_start_ms = clamped.start_ms;
|
|
494
|
+
chart->visible_end_ms = clamped.end_ms;
|
|
486
495
|
changed = true;
|
|
487
496
|
}
|
|
488
497
|
}
|
|
@@ -597,9 +606,9 @@ extern "C" void vroom_chart_scale_time_axis(VroomChart* chart, float dx_px) {
|
|
|
597
606
|
if (new_window_ms > max_window) new_window_ms = max_window;
|
|
598
607
|
|
|
599
608
|
// Pivot around the right edge — most-recent visible candle stays put.
|
|
609
|
+
// A window longer than the data may start before the first candle; that
|
|
610
|
+
// empty past is how defaultCandleWidth wins, so don't pin start to first.
|
|
600
611
|
int64_t new_start = chart->visible_end_ms - new_window_ms;
|
|
601
|
-
const int64_t first_time = chart->candles.front().time_ms;
|
|
602
|
-
if (new_start < first_time) new_start = first_time;
|
|
603
612
|
|
|
604
613
|
if (new_start == chart->visible_start_ms) return;
|
|
605
614
|
chart->visible_start_ms = new_start;
|
|
@@ -779,17 +788,14 @@ extern "C" void vroom_chart_zoom(VroomChart* chart, float scale_x, float scale_y
|
|
|
779
788
|
const int64_t last_time = chart->candles.back().time_ms;
|
|
780
789
|
// Cap the future gap at 3/4 of the (new) window so >= 25% of the
|
|
781
790
|
// chart still shows candles, without snapping back an existing
|
|
782
|
-
// larger gap (e.g. from the x-axis drag zoom).
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
new_start = first_time;
|
|
791
|
-
new_end = new_start + new_window;
|
|
792
|
-
}
|
|
791
|
+
// larger gap (e.g. from the x-axis drag zoom). Past clamping
|
|
792
|
+
// matches pan: a window longer than the data keeps empty left.
|
|
793
|
+
const auto clamped = vroom::clamp_shifted_time_window(
|
|
794
|
+
new_start, new_end, first_time, last_time,
|
|
795
|
+
chart->candle_duration_ms,
|
|
796
|
+
max_future_gap(new_window, chart->visible_end_ms - last_time));
|
|
797
|
+
new_start = clamped.start_ms;
|
|
798
|
+
new_end = clamped.end_ms;
|
|
793
799
|
if (new_start != chart->visible_start_ms ||
|
|
794
800
|
new_end != chart->visible_end_ms) {
|
|
795
801
|
chart->visible_start_ms = new_start;
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
#include "chart.h"
|
|
22
22
|
#include "fonts.h"
|
|
23
23
|
#include "theme.h"
|
|
24
|
+
#include "ticks.h"
|
|
24
25
|
|
|
25
26
|
namespace vroom::crosshair {
|
|
26
27
|
|
|
@@ -146,7 +147,10 @@ void draw(SkCanvas* canvas,
|
|
|
146
147
|
if (lay.y_axis_width_px > 0.f) {
|
|
147
148
|
const double price = vroom::y_to_price(lay, bounds, cy);
|
|
148
149
|
char buf[32];
|
|
149
|
-
|
|
150
|
+
const int decimals = vroom::price_decimals(
|
|
151
|
+
vroom::pick_price_interval(bounds.max - bounds.min,
|
|
152
|
+
vroom::price_pane_bottom(lay)));
|
|
153
|
+
vroom::format_price(buf, sizeof(buf), price, decimals);
|
|
150
154
|
const float axis_center_x = lay.width_px - lay.y_axis_width_px * 0.5f;
|
|
151
155
|
draw_badge(canvas, font, buf, axis_center_x, cy, badge_fill,
|
|
152
156
|
SK_ColorWHITE);
|
package/cpp/_core_src/labels.cpp
CHANGED
|
@@ -153,13 +153,15 @@ void draw_y_labels(SkCanvas* canvas,
|
|
|
153
153
|
// Horizontal center of the y-axis container ([width - y_axis_width, width]).
|
|
154
154
|
// Labels (and the price box) center on this so their text shares a column.
|
|
155
155
|
const float axis_center_x = lay.width_px - lay.y_axis_width_px * 0.5f;
|
|
156
|
+
const int decimals = vroom::price_decimals(
|
|
157
|
+
vroom::pick_price_interval(bounds.max - bounds.min, candle_area_h));
|
|
156
158
|
|
|
157
159
|
for (const auto& f : chart.y_fades) {
|
|
158
160
|
if (f.opacity <= 1e-3f) continue;
|
|
159
161
|
const float y = vroom::price_to_y(lay, bounds, f.price);
|
|
160
162
|
|
|
161
163
|
char buf[32];
|
|
162
|
-
|
|
164
|
+
vroom::format_price(buf, sizeof(buf), f.price, decimals);
|
|
163
165
|
const size_t len = std::strlen(buf);
|
|
164
166
|
const float text_w = font.measureText(
|
|
165
167
|
buf, len, SkTextEncoding::kUTF8);
|
|
@@ -367,9 +369,12 @@ void recompute_axis_width(VroomChart& chart) {
|
|
|
367
369
|
}
|
|
368
370
|
|
|
369
371
|
SkFont font(tf, chart.theme.floats[VROOM_FLOAT_AXIS_FONT_SIZE_PX]);
|
|
372
|
+
const auto lay = chart.layout();
|
|
373
|
+
const int decimals = vroom::price_decimals(
|
|
374
|
+
vroom::pick_price_interval(hi - lo, vroom::price_pane_bottom(lay)));
|
|
370
375
|
char buf_hi[32], buf_lo[32];
|
|
371
|
-
|
|
372
|
-
|
|
376
|
+
vroom::format_price(buf_hi, sizeof(buf_hi), hi, decimals);
|
|
377
|
+
vroom::format_price(buf_lo, sizeof(buf_lo), lo, decimals);
|
|
373
378
|
const float w_hi = font.measureText(
|
|
374
379
|
buf_hi, std::strlen(buf_hi), SkTextEncoding::kUTF8);
|
|
375
380
|
const float w_lo = font.measureText(
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
#include "chart.h"
|
|
20
20
|
#include "fonts.h"
|
|
21
21
|
#include "theme.h"
|
|
22
|
+
#include "ticks.h"
|
|
22
23
|
#include "viewport.h"
|
|
23
24
|
|
|
24
25
|
namespace vroom::price_indicator {
|
|
@@ -66,7 +67,9 @@ void draw(SkCanvas* canvas,
|
|
|
66
67
|
font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
|
|
67
68
|
|
|
68
69
|
char buf[32];
|
|
69
|
-
|
|
70
|
+
const int decimals = vroom::price_decimals(
|
|
71
|
+
vroom::pick_price_interval(bounds.max - bounds.min, candle_area_h));
|
|
72
|
+
vroom::format_price(buf, sizeof(buf), last.close, decimals);
|
|
70
73
|
const size_t len = std::strlen(buf);
|
|
71
74
|
|
|
72
75
|
// Measure the tight glyph bounds (origin at the baseline) so we can center
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
#include "fonts.h"
|
|
24
24
|
#include "price_line_layout.h"
|
|
25
25
|
#include "theme.h"
|
|
26
|
+
#include "ticks.h"
|
|
26
27
|
#include "viewport.h"
|
|
27
28
|
|
|
28
29
|
namespace vroom::price_lines {
|
|
@@ -212,11 +213,12 @@ void draw_axis_badge(SkCanvas* canvas,
|
|
|
212
213
|
const Layout& lay,
|
|
213
214
|
double price,
|
|
214
215
|
float y,
|
|
215
|
-
SkColor fill
|
|
216
|
+
SkColor fill,
|
|
217
|
+
int decimals) {
|
|
216
218
|
if (lay.y_axis_width_px <= 0.f) return;
|
|
217
219
|
|
|
218
220
|
char buf[32];
|
|
219
|
-
|
|
221
|
+
vroom::format_price(buf, sizeof(buf), price, decimals);
|
|
220
222
|
const size_t len = std::strlen(buf);
|
|
221
223
|
|
|
222
224
|
SkRect tb;
|
|
@@ -253,6 +255,8 @@ void draw(SkCanvas* canvas,
|
|
|
253
255
|
const VroomPriceLineStyle& style = chart.price_line_style;
|
|
254
256
|
SkFont font;
|
|
255
257
|
const bool has_font = label_font(chart, &font);
|
|
258
|
+
const int decimals = vroom::price_decimals(
|
|
259
|
+
vroom::pick_price_interval(bounds.max - bounds.min, candle_area_h));
|
|
256
260
|
|
|
257
261
|
for (size_t i = 0; i < chart.price_lines.size(); ++i) {
|
|
258
262
|
const VroomChart::StoredPriceLine& pl = chart.price_lines[i];
|
|
@@ -351,7 +355,7 @@ void draw(SkCanvas* canvas,
|
|
|
351
355
|
|
|
352
356
|
if (has_font && (pl.flags & VROOM_PRICE_LINE_AXIS_LABEL) != 0) {
|
|
353
357
|
draw_axis_badge(canvas, font, lay, render_price(chart, i), y,
|
|
354
|
-
line_color);
|
|
358
|
+
line_color, decimals);
|
|
355
359
|
}
|
|
356
360
|
}
|
|
357
361
|
}
|
package/cpp/_core_src/ticks.cpp
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
#include <algorithm>
|
|
4
4
|
#include <cmath>
|
|
5
|
+
#include <cstdio>
|
|
5
6
|
#include <ctime>
|
|
6
7
|
|
|
7
8
|
namespace vroom {
|
|
@@ -145,4 +146,17 @@ double pick_price_interval(double range, float candle_area_h) {
|
|
|
145
146
|
return nice * magnitude;
|
|
146
147
|
}
|
|
147
148
|
|
|
149
|
+
int price_decimals(double interval) {
|
|
150
|
+
if (!(interval > 0.0) || !std::isfinite(interval)) return 2;
|
|
151
|
+
const double d = std::ceil(-std::log10(interval) - 1e-12);
|
|
152
|
+
if (d < 0.0) return 0;
|
|
153
|
+
if (d > 12.0) return 12;
|
|
154
|
+
return static_cast<int>(d);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
void format_price(char* buf, size_t buf_size, double price, int decimals) {
|
|
158
|
+
if (!buf || buf_size == 0) return;
|
|
159
|
+
std::snprintf(buf, buf_size, "%.*f", decimals, price);
|
|
160
|
+
}
|
|
161
|
+
|
|
148
162
|
} // namespace vroom
|
package/cpp/_core_src/ticks.h
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
#pragma once
|
|
8
8
|
|
|
9
|
+
#include <cstddef>
|
|
9
10
|
#include <cstdint>
|
|
10
11
|
|
|
11
12
|
namespace vroom {
|
|
@@ -50,4 +51,11 @@ int64_t next_tick(int64_t t, const TimeTick& tick);
|
|
|
50
51
|
// in the same units as the data (e.g. dollars).
|
|
51
52
|
double pick_price_interval(double range, float candle_area_h);
|
|
52
53
|
|
|
54
|
+
// Decimal places needed so adjacent ticks `interval` apart don't collapse to
|
|
55
|
+
// the same string. 0.01 → 2, 0.005 → 3, 1e-8 → 8. Clamped to [0, 12].
|
|
56
|
+
int price_decimals(double interval);
|
|
57
|
+
|
|
58
|
+
// Writes `price` into `buf` using `decimals` (from price_decimals).
|
|
59
|
+
void format_price(char* buf, size_t buf_size, double price, int decimals);
|
|
60
|
+
|
|
53
61
|
} // namespace vroom
|
|
@@ -17,6 +17,16 @@ float candle_body_width(const Layout& layout,
|
|
|
17
17
|
return static_cast<float>(slot * layout.candle_width_ratio);
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
int64_t window_for_body_width(const Layout& layout,
|
|
21
|
+
int64_t candle_duration_ms,
|
|
22
|
+
double body_px) {
|
|
23
|
+
const double usable = candle_area_width(layout);
|
|
24
|
+
const double ratio = static_cast<double>(layout.candle_width_ratio);
|
|
25
|
+
const double dur = static_cast<double>(candle_duration_ms);
|
|
26
|
+
if (usable <= 0.0 || ratio <= 0.0 || dur <= 0.0 || body_px <= 0.0) return 0;
|
|
27
|
+
return static_cast<int64_t>((usable * dur * ratio) / body_px);
|
|
28
|
+
}
|
|
29
|
+
|
|
20
30
|
float candle_center_x(const Layout& layout,
|
|
21
31
|
int64_t time_ms,
|
|
22
32
|
int64_t candle_duration_ms,
|
|
@@ -265,4 +275,39 @@ double y_to_price(const Layout& layout,
|
|
|
265
275
|
return bounds.min + t * range;
|
|
266
276
|
}
|
|
267
277
|
|
|
278
|
+
TimeWindow clamp_shifted_time_window(int64_t start_ms, int64_t end_ms,
|
|
279
|
+
int64_t first_time, int64_t last_time,
|
|
280
|
+
int64_t candle_duration_ms,
|
|
281
|
+
int64_t max_future) {
|
|
282
|
+
const int64_t window = end_ms - start_ms;
|
|
283
|
+
if (window <= 0) return {start_ms, end_ms};
|
|
284
|
+
|
|
285
|
+
const int64_t dur = candle_duration_ms > 0 ? candle_duration_ms : 0;
|
|
286
|
+
const int64_t last_slot_end = last_time + dur;
|
|
287
|
+
const int64_t data_extent = last_slot_end - first_time;
|
|
288
|
+
|
|
289
|
+
if (max_future >= 0 && end_ms > last_time + max_future) {
|
|
290
|
+
end_ms = last_time + max_future;
|
|
291
|
+
start_ms = end_ms - window;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Window longer than the series: empty past is how width wins. Floor the
|
|
295
|
+
// right edge at last_time so a pan into the past is a no-op when the
|
|
296
|
+
// candles are already right-aligned. Do not pin start to first_time — that
|
|
297
|
+
// throws the extra length into the future and the two caps latch.
|
|
298
|
+
if (data_extent >= 0 && window > data_extent) {
|
|
299
|
+
if (end_ms < last_time) {
|
|
300
|
+
end_ms = last_time;
|
|
301
|
+
start_ms = end_ms - window;
|
|
302
|
+
}
|
|
303
|
+
return {start_ms, end_ms};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (start_ms < first_time) {
|
|
307
|
+
start_ms = first_time;
|
|
308
|
+
end_ms = start_ms + window;
|
|
309
|
+
}
|
|
310
|
+
return {start_ms, end_ms};
|
|
311
|
+
}
|
|
312
|
+
|
|
268
313
|
} // namespace vroom
|
package/cpp/_core_src/viewport.h
CHANGED
|
@@ -86,6 +86,36 @@ float candle_body_width(const Layout& layout,
|
|
|
86
86
|
int64_t window_ms,
|
|
87
87
|
int64_t candle_duration_ms);
|
|
88
88
|
|
|
89
|
+
// Inverse of candle_body_width: the time window that makes each candle body
|
|
90
|
+
// `body_px` wide. Returns 0 if any input is degenerate. Does not consult the
|
|
91
|
+
// data span — a short series leaves empty space on the left (width wins).
|
|
92
|
+
int64_t window_for_body_width(const Layout& layout,
|
|
93
|
+
int64_t candle_duration_ms,
|
|
94
|
+
double body_px);
|
|
95
|
+
|
|
96
|
+
// A [start, end] time window of fixed length, used by pan/zoom clamping.
|
|
97
|
+
struct TimeWindow {
|
|
98
|
+
int64_t start_ms;
|
|
99
|
+
int64_t end_ms;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
// Clamp a same-length time window after a pan or pinch-zoom shift.
|
|
103
|
+
//
|
|
104
|
+
// The future cap (`max_future`, typically 3/4 of the window) is unchanged: the
|
|
105
|
+
// right edge cannot run past last_time + max_future.
|
|
106
|
+
//
|
|
107
|
+
// The past cap depends on whether the window fits in the data:
|
|
108
|
+
// - Window ≤ data extent: start cannot precede first_time (no empty past).
|
|
109
|
+
// - Window > data extent: empty past is intentional (width wins). start MAY
|
|
110
|
+
// precede first_time. Instead, the last candle cannot leave the right edge
|
|
111
|
+
// (end >= last_time). Pinning start to first_time in this case would shove
|
|
112
|
+
// the extra length into the future and latch, because the future cap then
|
|
113
|
+
// fights the past cap on every subsequent pan.
|
|
114
|
+
TimeWindow clamp_shifted_time_window(int64_t start_ms, int64_t end_ms,
|
|
115
|
+
int64_t first_time, int64_t last_time,
|
|
116
|
+
int64_t candle_duration_ms,
|
|
117
|
+
int64_t max_future);
|
|
118
|
+
|
|
89
119
|
// Center-x of a candle whose period starts at time_ms and lasts
|
|
90
120
|
// candle_duration_ms, given the current visible time window.
|
|
91
121
|
float candle_center_x(const Layout& layout,
|
package/lib/index.d.mts
CHANGED
|
@@ -627,7 +627,8 @@ type VroomChartCoreProps = {
|
|
|
627
627
|
* more zoomed in (fewer candles); smaller → more zoomed out. Only affects
|
|
628
628
|
* first load / reset; an explicit `visibleRange` takes precedence. Omit for
|
|
629
629
|
* the default (~80 candles). Useful to keep candles a consistent size across
|
|
630
|
-
* devices of different widths.
|
|
630
|
+
* devices of different widths. A series shorter than the window is left-padded
|
|
631
|
+
* with empty slots — bars stay this wide rather than stretching to fill.
|
|
631
632
|
*/
|
|
632
633
|
defaultCandleWidth?: number;
|
|
633
634
|
/**
|
package/lib/index.d.ts
CHANGED
|
@@ -627,7 +627,8 @@ type VroomChartCoreProps = {
|
|
|
627
627
|
* more zoomed in (fewer candles); smaller → more zoomed out. Only affects
|
|
628
628
|
* first load / reset; an explicit `visibleRange` takes precedence. Omit for
|
|
629
629
|
* the default (~80 candles). Useful to keep candles a consistent size across
|
|
630
|
-
* devices of different widths.
|
|
630
|
+
* devices of different widths. A series shorter than the window is left-padded
|
|
631
|
+
* with empty slots — bars stay this wide rather than stretching to fill.
|
|
631
632
|
*/
|
|
632
633
|
defaultCandleWidth?: number;
|
|
633
634
|
/**
|