react-native-vroom-chart 0.11.1 → 0.12.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 +22 -0
- package/cpp/_core_include/vroom/vroom_chart.h +19 -2
- package/cpp/_core_src/chart.cpp +5 -2
- package/cpp/_core_src/chart.h +7 -0
- package/cpp/_core_src/chart_facade.cpp +9 -0
- package/cpp/_core_src/crosshair.cpp +10 -5
- package/cpp/_core_src/labels.cpp +7 -2
- package/cpp/_core_src/macd_pane.cpp +3 -2
- package/cpp/_core_src/price_indicator.cpp +9 -5
- package/cpp/_core_src/price_lines.cpp +9 -4
- package/cpp/_core_src/rsi_pane.cpp +3 -1
- package/cpp/_core_src/theme.cpp +2 -2
- package/cpp/_core_src/viewport.h +32 -0
- package/lib/index.d.mts +27 -0
- package/lib/index.d.ts +27 -0
- package/lib/index.js +102 -5
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +102 -5
- package/lib/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/VroomChart.tsx +129 -5
- package/src/jsi.d.ts +7 -0
- package/src/theme.ts +5 -0
|
@@ -63,6 +63,7 @@ std::vector<jsi::PropNameID> ChartHostObject::getPropertyNames(
|
|
|
63
63
|
out.push_back(jsi::PropNameID::forAscii(rt, "setBollinger"));
|
|
64
64
|
out.push_back(jsi::PropNameID::forAscii(rt, "setVolume"));
|
|
65
65
|
out.push_back(jsi::PropNameID::forAscii(rt, "setVolumeCollapse"));
|
|
66
|
+
out.push_back(jsi::PropNameID::forAscii(rt, "setAxisCollapse"));
|
|
66
67
|
out.push_back(jsi::PropNameID::forAscii(rt, "coordAt"));
|
|
67
68
|
out.push_back(jsi::PropNameID::forAscii(rt, "setPriceLines"));
|
|
68
69
|
out.push_back(jsi::PropNameID::forAscii(rt, "hitTestPriceLine"));
|
|
@@ -919,6 +920,27 @@ jsi::Value ChartHostObject::get(jsi::Runtime& rt,
|
|
|
919
920
|
});
|
|
920
921
|
}
|
|
921
922
|
|
|
923
|
+
if (name == "setAxisCollapse") {
|
|
924
|
+
// setAxisCollapse(yT, xT) — hides the price / time axis strips. Both take
|
|
925
|
+
// EASED progress (0 shown, 1 hidden); there is no per-element stagger for
|
|
926
|
+
// the core to distribute, so the host owns the curve.
|
|
927
|
+
return jsi::Function::createFromHostFunction(
|
|
928
|
+
rt,
|
|
929
|
+
jsi::PropNameID::forAscii(rt, "setAxisCollapse"),
|
|
930
|
+
2,
|
|
931
|
+
[this](jsi::Runtime& /*rt2*/,
|
|
932
|
+
const jsi::Value& /*thisVal*/,
|
|
933
|
+
const jsi::Value* args,
|
|
934
|
+
size_t count) -> jsi::Value {
|
|
935
|
+
if (count < 2) return jsi::Value::undefined();
|
|
936
|
+
vroom_chart_set_axis_collapse(
|
|
937
|
+
chart_,
|
|
938
|
+
static_cast<float>(args[0].asNumber()),
|
|
939
|
+
static_cast<float>(args[1].asNumber()));
|
|
940
|
+
return jsi::Value::undefined();
|
|
941
|
+
});
|
|
942
|
+
}
|
|
943
|
+
|
|
922
944
|
if (name == "coordAt") {
|
|
923
945
|
// coordAt(x, y) -> { timeMs, price } | null. The continuous data coordinate
|
|
924
946
|
// at a pixel — not snapped to a candle slot. Null when there are no candles
|
|
@@ -282,8 +282,12 @@ typedef enum {
|
|
|
282
282
|
VROOM_COLOR_GRID,
|
|
283
283
|
VROOM_COLOR_AXIS_TEXT,
|
|
284
284
|
VROOM_COLOR_CROSSHAIR,
|
|
285
|
-
VROOM_COLOR_TOOLTIP_BG,
|
|
286
|
-
|
|
285
|
+
// Retired (was VROOM_COLOR_TOOLTIP_BG, never rendered). Held open because
|
|
286
|
+
// the TypeScript theme maps hardcode the indices below it.
|
|
287
|
+
VROOM_COLOR_RESERVED_7_,
|
|
288
|
+
// Text drawn on a filled badge: the current-price indicator, the crosshair's
|
|
289
|
+
// axis badges, and price-line pills. Defaults to white.
|
|
290
|
+
VROOM_COLOR_BADGE_TEXT,
|
|
287
291
|
VROOM_COLOR_CROSSHAIR_TARGET, // the hollow ring/dot at the intersection
|
|
288
292
|
VROOM_COLOR_BORDER_BULL, // bull body 1px outline; 0 alpha => inherit BULL fill
|
|
289
293
|
VROOM_COLOR_BORDER_BEAR, // bear body 1px outline; 0 alpha => inherit BEAR fill
|
|
@@ -564,6 +568,19 @@ void vroom_chart_set_volume(VroomChart* chart, const VroomVolume* cfg);
|
|
|
564
568
|
// `enabled`, so the host only needs it while animating.
|
|
565
569
|
void vroom_chart_set_volume_collapse(VroomChart* chart, float t, int32_t easing);
|
|
566
570
|
|
|
571
|
+
// Collapses the axis strips: 0 leaves a strip at full size, 1 hides it. Driven
|
|
572
|
+
// per-frame by the host animation loop, which owns the eased clock — unlike the
|
|
573
|
+
// volume collapse there is no per-element stagger, so these take *eased*
|
|
574
|
+
// progress directly.
|
|
575
|
+
//
|
|
576
|
+
// Hiding is a collapse rather than a zeroed dimension because the y-axis width
|
|
577
|
+
// is measured from the widest price label and recomputed on most data changes,
|
|
578
|
+
// which would overwrite a zero. The price pane grows into whatever the strips
|
|
579
|
+
// give up, and each strip's contents (labels, price badge, crosshair and
|
|
580
|
+
// price-line badges) fade out over the first half of the collapse so text is
|
|
581
|
+
// never squeezed into a strip too narrow to hold it.
|
|
582
|
+
void vroom_chart_set_axis_collapse(VroomChart* chart, float y_t, float x_t);
|
|
583
|
+
|
|
567
584
|
// ---- Drawings (line annotations) ------------------------------------------
|
|
568
585
|
|
|
569
586
|
// Replaces the full set of committed line drawings (data-anchored, so they track
|
package/cpp/_core_src/chart.cpp
CHANGED
|
@@ -64,13 +64,16 @@ vroom::Layout VroomChart::layout() const {
|
|
|
64
64
|
return vroom::Layout{
|
|
65
65
|
width_px,
|
|
66
66
|
height_px,
|
|
67
|
-
axis_w,
|
|
68
|
-
theme.floats[VROOM_FLOAT_X_AXIS_HEIGHT_PX],
|
|
67
|
+
vroom::axis_extent(axis_w, y_axis_collapse_t),
|
|
68
|
+
vroom::axis_extent(theme.floats[VROOM_FLOAT_X_AXIS_HEIGHT_PX],
|
|
69
|
+
x_axis_collapse_t),
|
|
69
70
|
theme.floats[VROOM_FLOAT_RIGHT_PADDING_PX],
|
|
70
71
|
theme.floats[VROOM_FLOAT_CANDLE_WIDTH_RATIO],
|
|
71
72
|
0.05f,
|
|
72
73
|
0.05f,
|
|
73
74
|
indicator_h,
|
|
75
|
+
vroom::axis_opacity(y_axis_collapse_t),
|
|
76
|
+
vroom::axis_opacity(x_axis_collapse_t),
|
|
74
77
|
};
|
|
75
78
|
}
|
|
76
79
|
|
package/cpp/_core_src/chart.h
CHANGED
|
@@ -191,6 +191,13 @@ struct VroomChart {
|
|
|
191
191
|
float volume_collapse_t = 0.f;
|
|
192
192
|
int32_t volume_collapse_easing = VROOM_EASING_IN_OUT;
|
|
193
193
|
|
|
194
|
+
// Collapse of the axis strips, driven per-frame by the host animation loop:
|
|
195
|
+
// 0 = full strip, 1 = hidden. Unlike the volume collapse these change the
|
|
196
|
+
// layout, so the price pane grows into whatever the strips give up. The host
|
|
197
|
+
// pre-eases them (there is no per-element stagger to distribute here).
|
|
198
|
+
float y_axis_collapse_t = 0.f;
|
|
199
|
+
float x_axis_collapse_t = 0.f;
|
|
200
|
+
|
|
194
201
|
// --- drawings (annotations) --------------------------------------------
|
|
195
202
|
// Committed drawings, anchored in data space so they track the candles on
|
|
196
203
|
// pan/zoom. Drawn on the price pane above candles/overlays.
|
|
@@ -1401,6 +1401,15 @@ extern "C" void vroom_chart_set_volume_collapse(VroomChart* chart, float t,
|
|
|
1401
1401
|
chart->mark_dirty();
|
|
1402
1402
|
}
|
|
1403
1403
|
|
|
1404
|
+
extern "C" void vroom_chart_set_axis_collapse(VroomChart* chart, float y_t,
|
|
1405
|
+
float x_t) {
|
|
1406
|
+
if (!chart) return;
|
|
1407
|
+
chart->y_axis_collapse_t = std::clamp(y_t, 0.f, 1.f);
|
|
1408
|
+
chart->x_axis_collapse_t = std::clamp(x_t, 0.f, 1.f);
|
|
1409
|
+
// Changes the layout, so every cached pane geometry has to be re-derived.
|
|
1410
|
+
chart->mark_dirty();
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1404
1413
|
// ---- Direct draw (used by hosts that don't need the SkPicture cache) ------
|
|
1405
1414
|
|
|
1406
1415
|
extern "C" void vroom_chart_draw(VroomChart* chart, SkCanvas* canvas) {
|
|
@@ -42,7 +42,9 @@ void draw_badge(SkCanvas* canvas,
|
|
|
42
42
|
float cx,
|
|
43
43
|
float cy,
|
|
44
44
|
SkColor fill,
|
|
45
|
-
SkColor text_color
|
|
45
|
+
SkColor text_color,
|
|
46
|
+
float opacity) {
|
|
47
|
+
if (opacity <= 0.f) return;
|
|
46
48
|
const size_t len = std::strlen(text);
|
|
47
49
|
SkRect tb;
|
|
48
50
|
const float text_w = font.measureText(text, len, SkTextEncoding::kUTF8, &tb);
|
|
@@ -54,11 +56,13 @@ void draw_badge(SkCanvas* canvas,
|
|
|
54
56
|
SkPaint box;
|
|
55
57
|
box.setAntiAlias(true);
|
|
56
58
|
box.setColor(fill);
|
|
59
|
+
box.setAlphaf(box.getAlphaf() * opacity);
|
|
57
60
|
canvas->drawRRect(SkRRect::MakeRectXY(rect, kCorner, kCorner), box);
|
|
58
61
|
|
|
59
62
|
SkPaint text_paint;
|
|
60
63
|
text_paint.setAntiAlias(true);
|
|
61
64
|
text_paint.setColor(text_color);
|
|
65
|
+
text_paint.setAlphaf(text_paint.getAlphaf() * opacity);
|
|
62
66
|
canvas->drawString(text, cx - text_w * 0.5f,
|
|
63
67
|
cy - (tb.fTop + tb.fBottom) * 0.5f, font, text_paint);
|
|
64
68
|
}
|
|
@@ -108,8 +112,8 @@ void draw(SkCanvas* canvas,
|
|
|
108
112
|
ring.setStrokeWidth(2.f); // thicker border so the dot reads clearly
|
|
109
113
|
canvas->drawCircle(cx, cy, kRingRadius, ring);
|
|
110
114
|
|
|
111
|
-
// Axis badges
|
|
112
|
-
//
|
|
115
|
+
// Axis badges sit on top of the axis labels and the current-price indicator
|
|
116
|
+
// since the crosshair is the last draw step.
|
|
113
117
|
// They need the axis typeface; if it isn't loaded the lines alone suffice.
|
|
114
118
|
auto tf = vroom::axis_typeface();
|
|
115
119
|
if (!tf) return;
|
|
@@ -119,6 +123,7 @@ void draw(SkCanvas* canvas,
|
|
|
119
123
|
font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
|
|
120
124
|
|
|
121
125
|
const SkColor badge_fill = chart.theme.colors[VROOM_COLOR_CROSSHAIR_TARGET];
|
|
126
|
+
const SkColor badge_text = chart.theme.colors[VROOM_COLOR_BADGE_TEXT];
|
|
122
127
|
|
|
123
128
|
// Date/time badge over the x-axis strip, centered on the vertical line and
|
|
124
129
|
// vertically aligned with the time labels (centered in the strip).
|
|
@@ -140,7 +145,7 @@ void draw(SkCanvas* canvas,
|
|
|
140
145
|
const float badge_cx =
|
|
141
146
|
std::clamp(cx, half_w, std::max(half_w, candle_right - half_w));
|
|
142
147
|
draw_badge(canvas, font, buf, badge_cx, strip_center_y, badge_fill,
|
|
143
|
-
|
|
148
|
+
badge_text, lay.x_axis_opacity);
|
|
144
149
|
}
|
|
145
150
|
|
|
146
151
|
// Price badge over the y-axis strip, centered on the horizontal line and
|
|
@@ -155,7 +160,7 @@ void draw(SkCanvas* canvas,
|
|
|
155
160
|
vroom::format_price(buf, sizeof(buf), price, fmt);
|
|
156
161
|
const float axis_center_x = lay.width_px - lay.y_axis_width_px * 0.5f;
|
|
157
162
|
draw_badge(canvas, font, buf, axis_center_x, cy, badge_fill,
|
|
158
|
-
|
|
163
|
+
badge_text, lay.y_axis_opacity);
|
|
159
164
|
}
|
|
160
165
|
}
|
|
161
166
|
|
package/cpp/_core_src/labels.cpp
CHANGED
|
@@ -147,6 +147,9 @@ void draw_y_labels(SkCanvas* canvas,
|
|
|
147
147
|
const PriceBounds& bounds) {
|
|
148
148
|
auto tf = vroom::axis_typeface();
|
|
149
149
|
if (!tf) return;
|
|
150
|
+
// The fade state still advances in update_y_fades, so a hidden axis picks up
|
|
151
|
+
// wherever the ticks moved to once it is revealed again.
|
|
152
|
+
if (lay.y_axis_opacity <= 0.f) return;
|
|
150
153
|
|
|
151
154
|
const float candle_area_h = vroom::price_pane_bottom(lay);
|
|
152
155
|
|
|
@@ -186,7 +189,7 @@ void draw_y_labels(SkCanvas* canvas,
|
|
|
186
189
|
if (baseline_y - cap_h < 0.f) continue;
|
|
187
190
|
if (baseline_y > candle_area_h) continue;
|
|
188
191
|
|
|
189
|
-
text_paint.setAlphaf(f.opacity);
|
|
192
|
+
text_paint.setAlphaf(f.opacity * lay.y_axis_opacity);
|
|
190
193
|
canvas->drawString(buf, text_x, baseline_y, font, text_paint);
|
|
191
194
|
}
|
|
192
195
|
}
|
|
@@ -279,6 +282,8 @@ void draw_x_labels(SkCanvas* canvas,
|
|
|
279
282
|
auto tf = vroom::axis_typeface();
|
|
280
283
|
if (!tf || chart.candles.empty()) return;
|
|
281
284
|
if (end_ms <= start_ms) return;
|
|
285
|
+
// See draw_y_labels: update_x_fades keeps running while the strip is hidden.
|
|
286
|
+
if (lay.x_axis_opacity <= 0.f) return;
|
|
282
287
|
|
|
283
288
|
// X-axis labels live in the bottom strip, which stays anchored regardless
|
|
284
289
|
// of any indicator pane above it.
|
|
@@ -345,7 +350,7 @@ void draw_x_labels(SkCanvas* canvas,
|
|
|
345
350
|
if (text_x < 0.f) continue;
|
|
346
351
|
if (text_x + text_w > candle_area_w) continue;
|
|
347
352
|
|
|
348
|
-
text_paint.setAlphaf(f.opacity);
|
|
353
|
+
text_paint.setAlphaf(f.opacity * lay.x_axis_opacity);
|
|
349
354
|
canvas->drawString(buf, text_x, baseline_y, font, text_paint);
|
|
350
355
|
}
|
|
351
356
|
}
|
|
@@ -211,14 +211,15 @@ void draw(SkCanvas* canvas,
|
|
|
211
211
|
}
|
|
212
212
|
canvas->restore();
|
|
213
213
|
|
|
214
|
-
// "0" label in the y-axis strip at the zero line.
|
|
215
|
-
if (tf) {
|
|
214
|
+
// "0" label in the y-axis strip at the zero line — hidden with that strip.
|
|
215
|
+
if (tf && lay.y_axis_opacity > 0.f) {
|
|
216
216
|
SkFont font(tf, chart.theme.floats[VROOM_FLOAT_AXIS_FONT_SIZE_PX]);
|
|
217
217
|
font.setSubpixel(true);
|
|
218
218
|
font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
|
|
219
219
|
SkPaint text_paint;
|
|
220
220
|
text_paint.setAntiAlias(true);
|
|
221
221
|
text_paint.setColor(chart.theme.colors[VROOM_COLOR_AXIS_TEXT]);
|
|
222
|
+
text_paint.setAlphaf(text_paint.getAlphaf() * lay.y_axis_opacity);
|
|
222
223
|
SkRect tb;
|
|
223
224
|
const float tw =
|
|
224
225
|
font.measureText("0", 1, SkTextEncoding::kUTF8, &tb);
|
|
@@ -59,9 +59,11 @@ void draw(SkCanvas* canvas,
|
|
|
59
59
|
canvas->drawLine(0.f, y, candle_right, y, line);
|
|
60
60
|
|
|
61
61
|
// Price box in the y-axis strip. Needs the axis typeface; if it isn't
|
|
62
|
-
// loaded yet, the line alone still conveys the level.
|
|
62
|
+
// loaded yet, the line alone still conveys the level. Same when the y-axis
|
|
63
|
+
// is hidden — the level stays readable, it just loses its badge.
|
|
63
64
|
auto tf = vroom::axis_typeface();
|
|
64
65
|
if (!tf) return;
|
|
66
|
+
if (lay.y_axis_opacity <= 0.f) return;
|
|
65
67
|
|
|
66
68
|
SkFont font(tf, chart.theme.floats[VROOM_FLOAT_AXIS_FONT_SIZE_PX]);
|
|
67
69
|
font.setSubpixel(true);
|
|
@@ -95,14 +97,16 @@ void draw(SkCanvas* canvas,
|
|
|
95
97
|
SkPaint box;
|
|
96
98
|
box.setAntiAlias(true);
|
|
97
99
|
box.setColor(color);
|
|
100
|
+
box.setAlphaf(box.getAlphaf() * lay.y_axis_opacity);
|
|
98
101
|
canvas->drawRRect(SkRRect::MakeRectXY(rect, kCorner, kCorner), box);
|
|
99
102
|
|
|
100
|
-
//
|
|
101
|
-
//
|
|
102
|
-
//
|
|
103
|
+
// Price text horizontally centered on the same axis center, and vertically
|
|
104
|
+
// centered: shift the baseline so the glyph bounds' midpoint lands on `y`
|
|
105
|
+
// (= the box center and the dotted line).
|
|
103
106
|
SkPaint text_paint;
|
|
104
107
|
text_paint.setAntiAlias(true);
|
|
105
|
-
text_paint.setColor(
|
|
108
|
+
text_paint.setColor(chart.theme.colors[VROOM_COLOR_BADGE_TEXT]);
|
|
109
|
+
text_paint.setAlphaf(text_paint.getAlphaf() * lay.y_axis_opacity);
|
|
106
110
|
const float text_x = axis_center_x - text_w * 0.5f;
|
|
107
111
|
const float baseline_y = y - (tb.fTop + tb.fBottom) * 0.5f;
|
|
108
112
|
canvas->drawString(buf, text_x, baseline_y, font, text_paint);
|
|
@@ -215,8 +215,10 @@ void draw_axis_badge(SkCanvas* canvas,
|
|
|
215
215
|
double price,
|
|
216
216
|
float y,
|
|
217
217
|
SkColor fill,
|
|
218
|
+
SkColor text_color,
|
|
218
219
|
const PriceFormat& fmt) {
|
|
219
220
|
if (lay.y_axis_width_px <= 0.f) return;
|
|
221
|
+
if (lay.y_axis_opacity <= 0.f) return;
|
|
220
222
|
|
|
221
223
|
char buf[48];
|
|
222
224
|
vroom::format_price(buf, sizeof(buf), price, fmt);
|
|
@@ -233,11 +235,13 @@ void draw_axis_badge(SkCanvas* canvas,
|
|
|
233
235
|
SkPaint box;
|
|
234
236
|
box.setAntiAlias(true);
|
|
235
237
|
box.setColor(fill);
|
|
238
|
+
box.setAlphaf(box.getAlphaf() * lay.y_axis_opacity);
|
|
236
239
|
canvas->drawRRect(SkRRect::MakeRectXY(rect, kCorner, kCorner), box);
|
|
237
240
|
|
|
238
241
|
SkPaint text;
|
|
239
242
|
text.setAntiAlias(true);
|
|
240
|
-
text.setColor(
|
|
243
|
+
text.setColor(text_color);
|
|
244
|
+
text.setAlphaf(text.getAlphaf() * lay.y_axis_opacity);
|
|
241
245
|
canvas->drawString(buf, cx - text_w * 0.5f,
|
|
242
246
|
y - (tb.fTop + tb.fBottom) * 0.5f, font, text);
|
|
243
247
|
}
|
|
@@ -254,6 +258,7 @@ void draw(SkCanvas* canvas,
|
|
|
254
258
|
if (chart.price_lines.empty()) return;
|
|
255
259
|
|
|
256
260
|
const VroomPriceLineStyle& style = chart.price_line_style;
|
|
261
|
+
const SkColor badge_text = chart.theme.colors[VROOM_COLOR_BADGE_TEXT];
|
|
257
262
|
SkFont font;
|
|
258
263
|
const bool has_font = label_font(chart, &font);
|
|
259
264
|
const vroom::PriceFormat fmt = vroom::with_tick_guard(
|
|
@@ -336,12 +341,12 @@ void draw(SkCanvas* canvas,
|
|
|
336
341
|
line_color);
|
|
337
342
|
}
|
|
338
343
|
if (!group.quantity.empty()) {
|
|
339
|
-
// Solid fill +
|
|
344
|
+
// Solid fill + badge text, so size reads at a glance against the
|
|
340
345
|
// translucent body.
|
|
341
346
|
draw_pill(canvas, group.quantity, first == &group.quantity,
|
|
342
347
|
last == &group.quantity, line_color, SK_ColorTRANSPARENT);
|
|
343
348
|
draw_centered_text(canvas, font, pl.quantity, group.quantity, y,
|
|
344
|
-
|
|
349
|
+
badge_text);
|
|
345
350
|
}
|
|
346
351
|
if (!group.close.empty()) {
|
|
347
352
|
const SkColor close_color =
|
|
@@ -357,7 +362,7 @@ void draw(SkCanvas* canvas,
|
|
|
357
362
|
|
|
358
363
|
if (has_font && (pl.flags & VROOM_PRICE_LINE_AXIS_LABEL) != 0) {
|
|
359
364
|
draw_axis_badge(canvas, font, lay, render_price(chart, i), y,
|
|
360
|
-
line_color, fmt);
|
|
365
|
+
line_color, badge_text, fmt);
|
|
361
366
|
}
|
|
362
367
|
}
|
|
363
368
|
}
|
|
@@ -158,13 +158,15 @@ void draw(SkCanvas* canvas,
|
|
|
158
158
|
// 70 / 30 labels in the y-axis strip (drawn after the clip; the strip's
|
|
159
159
|
// background was already masked by draw_chart before this call). They label
|
|
160
160
|
// the band rules, so they go with them.
|
|
161
|
-
|
|
161
|
+
// The band labels live in the y-axis strip, so they go with it when it hides.
|
|
162
|
+
if (tf && cfg.bands_visible && lay.y_axis_opacity > 0.f) {
|
|
162
163
|
SkFont font(tf, chart.theme.floats[VROOM_FLOAT_AXIS_FONT_SIZE_PX]);
|
|
163
164
|
font.setSubpixel(true);
|
|
164
165
|
font.setEdging(SkFont::Edging::kSubpixelAntiAlias);
|
|
165
166
|
SkPaint text_paint;
|
|
166
167
|
text_paint.setAntiAlias(true);
|
|
167
168
|
text_paint.setColor(chart.theme.colors[VROOM_COLOR_AXIS_TEXT]);
|
|
169
|
+
text_paint.setAlphaf(text_paint.getAlphaf() * lay.y_axis_opacity);
|
|
168
170
|
|
|
169
171
|
const float axis_center_x = lay.width_px - lay.y_axis_width_px * 0.5f;
|
|
170
172
|
auto label = [&](const char* s, float y) {
|
package/cpp/_core_src/theme.cpp
CHANGED
|
@@ -13,8 +13,8 @@ constexpr uint32_t kDefaultColors[VROOM_COLOR_COUNT_] = {
|
|
|
13
13
|
0xff1a1e24, // GRID — ~20% darker than the crosshair so it recedes
|
|
14
14
|
0xffc9d1d9, // AXIS_TEXT
|
|
15
15
|
0xff303741, // CROSSHAIR — grid tone lightened ~44%; reads above GRID
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
0x00000000, // RESERVED_7_ — retired slot, never read
|
|
17
|
+
0xffffffff, // BADGE_TEXT — text on filled badges/pills
|
|
18
18
|
0xff3e4855, // CROSSHAIR_TARGET — CROSSHAIR lightened 30% (prior derived ring)
|
|
19
19
|
0x00000000, // BORDER_BULL — transparent sentinel: inherit BULL fill
|
|
20
20
|
0x00000000, // BORDER_BEAR — transparent sentinel: inherit BEAR fill
|
package/cpp/_core_src/viewport.h
CHANGED
|
@@ -20,8 +20,40 @@ struct Layout {
|
|
|
20
20
|
float top_padding_frac; // fraction of candle area; keeps prices off edges
|
|
21
21
|
float bottom_padding_frac;
|
|
22
22
|
float indicator_area_h; // height reserved for below-chart indicator panes
|
|
23
|
+
// Opacity of everything drawn *inside* an axis strip, 1 = shown, 0 = hidden.
|
|
24
|
+
// Separate from the strip's extent because the two ramp at different rates
|
|
25
|
+
// while an axis is being hidden or revealed (see axis_opacity).
|
|
26
|
+
float y_axis_opacity;
|
|
27
|
+
float x_axis_opacity;
|
|
23
28
|
};
|
|
24
29
|
|
|
30
|
+
// --- Axis show/hide ---------------------------------------------------------
|
|
31
|
+
// An axis is hidden by collapsing its strip to nothing rather than by zeroing
|
|
32
|
+
// the underlying dimension: the y-axis width is *measured* from the widest price
|
|
33
|
+
// label (labels::recompute_axis_width) and gets recomputed constantly, so a zero
|
|
34
|
+
// written there would not survive. `t` is the collapse progress the host eases,
|
|
35
|
+
// 0 = fully shown, 1 = fully hidden.
|
|
36
|
+
|
|
37
|
+
inline float clamp_collapse(float t) {
|
|
38
|
+
return t < 0.f ? 0.f : (t > 1.f ? 1.f : t);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// The strip's on-screen extent. Everything downstream (candle_area_width,
|
|
42
|
+
// price_pane_bottom) is derived from this, so the plot reclaims the space as
|
|
43
|
+
// the strip shrinks.
|
|
44
|
+
inline float axis_extent(float base, float t) {
|
|
45
|
+
return base * (1.f - clamp_collapse(t));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Opacity of the strip's contents. Deliberately front-loaded: the labels are
|
|
49
|
+
// fully gone by the time the strip is halfway collapsed, so text never sits in
|
|
50
|
+
// a strip too narrow to hold it.
|
|
51
|
+
inline float axis_opacity(float t) {
|
|
52
|
+
constexpr float kFadeBy = 0.5f; // fraction of the collapse spent fading
|
|
53
|
+
const float p = clamp_collapse(t) / kFadeBy;
|
|
54
|
+
return p >= 1.f ? 0.f : 1.f - p;
|
|
55
|
+
}
|
|
56
|
+
|
|
25
57
|
// Bottom of the price (candle) pane = top of the indicator band. Shrinks when
|
|
26
58
|
// an indicator pane is present so candles, volume, and price labels reflow.
|
|
27
59
|
inline float price_pane_bottom(const Layout& l) {
|
package/lib/index.d.mts
CHANGED
|
@@ -93,6 +93,13 @@ type VroomTheme = {
|
|
|
93
93
|
grid?: VroomColor;
|
|
94
94
|
/** Axis label text (price + time). */
|
|
95
95
|
axisText?: VroomColor;
|
|
96
|
+
/**
|
|
97
|
+
* Text drawn on a filled badge — the current-price indicator, the crosshair's
|
|
98
|
+
* price and time badges, and price-line pills. Defaults to white, which reads
|
|
99
|
+
* against the saturated fills those badges use on a dark theme; light themes
|
|
100
|
+
* generally want a dark value here.
|
|
101
|
+
*/
|
|
102
|
+
badgeText?: VroomColor;
|
|
96
103
|
/** Crosshair dashed lines. */
|
|
97
104
|
crosshair?: VroomColor;
|
|
98
105
|
/** Crosshair target — the hollow ring/dot at the intersection. */
|
|
@@ -134,6 +141,26 @@ type VroomTheme = {
|
|
|
134
141
|
* continuously — leave it off for charts that should be able to go idle.
|
|
135
142
|
*/
|
|
136
143
|
lineTipPulse?: boolean;
|
|
144
|
+
/**
|
|
145
|
+
* Show the price (y) axis strip down the right edge. Defaults to `true`.
|
|
146
|
+
*
|
|
147
|
+
* Hiding it collapses the strip and hands the reclaimed width to the plot, so
|
|
148
|
+
* the candles widen to fill it. Its contents — price labels, the current-price
|
|
149
|
+
* badge, the crosshair price badge, price-line axis badges — fade out ahead of
|
|
150
|
+
* the collapse; gridlines and the price indicator's dotted line stay. Animated
|
|
151
|
+
* over `transitionMs` unless the OS asks for reduced motion.
|
|
152
|
+
*
|
|
153
|
+
* The axis drag-to-scale region goes with the strip, so a hidden axis can't be
|
|
154
|
+
* scaled by dragging it.
|
|
155
|
+
*/
|
|
156
|
+
showYAxis?: boolean;
|
|
157
|
+
/**
|
|
158
|
+
* Show the time (x) axis strip along the bottom. Defaults to `true`.
|
|
159
|
+
*
|
|
160
|
+
* Behaves like {@link VroomTheme.showYAxis}: the strip collapses, the plot
|
|
161
|
+
* grows into it, and the time labels and crosshair time badge fade first.
|
|
162
|
+
*/
|
|
163
|
+
showXAxis?: boolean;
|
|
137
164
|
};
|
|
138
165
|
/** A time window over the candle data, as Unix epoch milliseconds. */
|
|
139
166
|
type VisibleRange = {
|
package/lib/index.d.ts
CHANGED
|
@@ -93,6 +93,13 @@ type VroomTheme = {
|
|
|
93
93
|
grid?: VroomColor;
|
|
94
94
|
/** Axis label text (price + time). */
|
|
95
95
|
axisText?: VroomColor;
|
|
96
|
+
/**
|
|
97
|
+
* Text drawn on a filled badge — the current-price indicator, the crosshair's
|
|
98
|
+
* price and time badges, and price-line pills. Defaults to white, which reads
|
|
99
|
+
* against the saturated fills those badges use on a dark theme; light themes
|
|
100
|
+
* generally want a dark value here.
|
|
101
|
+
*/
|
|
102
|
+
badgeText?: VroomColor;
|
|
96
103
|
/** Crosshair dashed lines. */
|
|
97
104
|
crosshair?: VroomColor;
|
|
98
105
|
/** Crosshair target — the hollow ring/dot at the intersection. */
|
|
@@ -134,6 +141,26 @@ type VroomTheme = {
|
|
|
134
141
|
* continuously — leave it off for charts that should be able to go idle.
|
|
135
142
|
*/
|
|
136
143
|
lineTipPulse?: boolean;
|
|
144
|
+
/**
|
|
145
|
+
* Show the price (y) axis strip down the right edge. Defaults to `true`.
|
|
146
|
+
*
|
|
147
|
+
* Hiding it collapses the strip and hands the reclaimed width to the plot, so
|
|
148
|
+
* the candles widen to fill it. Its contents — price labels, the current-price
|
|
149
|
+
* badge, the crosshair price badge, price-line axis badges — fade out ahead of
|
|
150
|
+
* the collapse; gridlines and the price indicator's dotted line stay. Animated
|
|
151
|
+
* over `transitionMs` unless the OS asks for reduced motion.
|
|
152
|
+
*
|
|
153
|
+
* The axis drag-to-scale region goes with the strip, so a hidden axis can't be
|
|
154
|
+
* scaled by dragging it.
|
|
155
|
+
*/
|
|
156
|
+
showYAxis?: boolean;
|
|
157
|
+
/**
|
|
158
|
+
* Show the time (x) axis strip along the bottom. Defaults to `true`.
|
|
159
|
+
*
|
|
160
|
+
* Behaves like {@link VroomTheme.showYAxis}: the strip collapses, the plot
|
|
161
|
+
* grows into it, and the time labels and crosshair time badge fade first.
|
|
162
|
+
*/
|
|
163
|
+
showXAxis?: boolean;
|
|
137
164
|
};
|
|
138
165
|
/** A time window over the candle data, as Unix epoch milliseconds. */
|
|
139
166
|
type VisibleRange = {
|
package/lib/index.js
CHANGED
|
@@ -163,6 +163,8 @@ var COLOR_KEYS = {
|
|
|
163
163
|
// VROOM_COLOR_AXIS_TEXT
|
|
164
164
|
crosshair: 6,
|
|
165
165
|
// VROOM_COLOR_CROSSHAIR
|
|
166
|
+
badgeText: 8,
|
|
167
|
+
// VROOM_COLOR_BADGE_TEXT (7 is a retired slot)
|
|
166
168
|
crosshairTarget: 9,
|
|
167
169
|
// VROOM_COLOR_CROSSHAIR_TARGET
|
|
168
170
|
borderBull: 10,
|
|
@@ -638,9 +640,13 @@ function VroomChart(props) {
|
|
|
638
640
|
handle.setChartType(target);
|
|
639
641
|
const p = handle.render();
|
|
640
642
|
if (p) pictureSV.value = p;
|
|
643
|
+
maybeStartAnim();
|
|
644
|
+
return void 0;
|
|
645
|
+
}
|
|
646
|
+
if (morphFade.current === target) {
|
|
647
|
+
maybeStartAnim();
|
|
641
648
|
return void 0;
|
|
642
649
|
}
|
|
643
|
-
if (morphFade.current === target) return void 0;
|
|
644
650
|
if (morphRaf.current != null) {
|
|
645
651
|
cancelAnimationFrame(morphRaf.current);
|
|
646
652
|
morphRaf.current = null;
|
|
@@ -651,6 +657,7 @@ function VroomChart(props) {
|
|
|
651
657
|
handle.setChartType(target);
|
|
652
658
|
const p = handle.render();
|
|
653
659
|
if (p) pictureSV.value = p;
|
|
660
|
+
maybeStartAnim();
|
|
654
661
|
return void 0;
|
|
655
662
|
}
|
|
656
663
|
const from = morphFade.current;
|
|
@@ -671,6 +678,7 @@ function VroomChart(props) {
|
|
|
671
678
|
handle.setChartType(target);
|
|
672
679
|
const q = handle.render();
|
|
673
680
|
if (q) pictureSV.value = q;
|
|
681
|
+
maybeStartAnim();
|
|
674
682
|
}
|
|
675
683
|
};
|
|
676
684
|
morphRaf.current = requestAnimationFrame(step);
|
|
@@ -680,7 +688,7 @@ function VroomChart(props) {
|
|
|
680
688
|
morphRaf.current = null;
|
|
681
689
|
}
|
|
682
690
|
};
|
|
683
|
-
}, [handle, chartType, transitionMs, reduceMotion, pictureSV]);
|
|
691
|
+
}, [handle, chartType, transitionMs, reduceMotion, pictureSV, maybeStartAnim]);
|
|
684
692
|
const volumeRaf = (0, import_react2.useRef)(null);
|
|
685
693
|
const volumeHandle = (0, import_react2.useRef)(null);
|
|
686
694
|
(0, import_react2.useEffect)(() => {
|
|
@@ -690,9 +698,13 @@ function VroomChart(props) {
|
|
|
690
698
|
if (volumeHandle.current !== handle || volumeCollapseRef.current == null) {
|
|
691
699
|
volumeHandle.current = handle;
|
|
692
700
|
volumeCollapseRef.current = { t: target, easing };
|
|
701
|
+
maybeStartAnim();
|
|
702
|
+
return void 0;
|
|
703
|
+
}
|
|
704
|
+
if (volumeCollapseRef.current.t === target) {
|
|
705
|
+
maybeStartAnim();
|
|
693
706
|
return void 0;
|
|
694
707
|
}
|
|
695
|
-
if (volumeCollapseRef.current.t === target) return void 0;
|
|
696
708
|
if (volumeRaf.current != null) {
|
|
697
709
|
cancelAnimationFrame(volumeRaf.current);
|
|
698
710
|
volumeRaf.current = null;
|
|
@@ -703,6 +715,7 @@ function VroomChart(props) {
|
|
|
703
715
|
handle.setVolumeCollapse(target, easing);
|
|
704
716
|
const p = handle.render();
|
|
705
717
|
if (p) pictureSV.value = p;
|
|
718
|
+
maybeStartAnim();
|
|
706
719
|
return void 0;
|
|
707
720
|
}
|
|
708
721
|
const from = volumeCollapseRef.current.t;
|
|
@@ -716,7 +729,12 @@ function VroomChart(props) {
|
|
|
716
729
|
handle.setVolumeCollapse(t, kind);
|
|
717
730
|
const p = handle.render();
|
|
718
731
|
if (p) pictureSV.value = p;
|
|
719
|
-
|
|
732
|
+
if (prog < 1) {
|
|
733
|
+
volumeRaf.current = requestAnimationFrame(step);
|
|
734
|
+
} else {
|
|
735
|
+
volumeRaf.current = null;
|
|
736
|
+
maybeStartAnim();
|
|
737
|
+
}
|
|
720
738
|
};
|
|
721
739
|
volumeRaf.current = requestAnimationFrame(step);
|
|
722
740
|
return () => {
|
|
@@ -725,7 +743,86 @@ function VroomChart(props) {
|
|
|
725
743
|
volumeRaf.current = null;
|
|
726
744
|
}
|
|
727
745
|
};
|
|
728
|
-
}, [
|
|
746
|
+
}, [
|
|
747
|
+
handle,
|
|
748
|
+
volume?.enabled,
|
|
749
|
+
transitionMs,
|
|
750
|
+
reduceMotion,
|
|
751
|
+
pictureSV,
|
|
752
|
+
volumeCollapseRef,
|
|
753
|
+
maybeStartAnim
|
|
754
|
+
]);
|
|
755
|
+
const axisRaf = (0, import_react2.useRef)(null);
|
|
756
|
+
const axisHandle = (0, import_react2.useRef)(null);
|
|
757
|
+
const axisCollapse = (0, import_react2.useRef)(null);
|
|
758
|
+
const showYAxis = theme?.showYAxis ?? true;
|
|
759
|
+
const showXAxis = theme?.showXAxis ?? true;
|
|
760
|
+
(0, import_react2.useEffect)(() => {
|
|
761
|
+
if (!handle) return void 0;
|
|
762
|
+
const targetY = showYAxis ? 0 : 1;
|
|
763
|
+
const targetX = showXAxis ? 0 : 1;
|
|
764
|
+
if (axisHandle.current !== handle || axisCollapse.current == null) {
|
|
765
|
+
axisHandle.current = handle;
|
|
766
|
+
axisCollapse.current = { y: targetY, x: targetX };
|
|
767
|
+
handle.setAxisCollapse(targetY, targetX);
|
|
768
|
+
const p = handle.render();
|
|
769
|
+
if (p) pictureSV.value = p;
|
|
770
|
+
maybeStartAnim();
|
|
771
|
+
return void 0;
|
|
772
|
+
}
|
|
773
|
+
const fromY = axisCollapse.current.y;
|
|
774
|
+
const fromX = axisCollapse.current.x;
|
|
775
|
+
if (fromY === targetY && fromX === targetX) {
|
|
776
|
+
maybeStartAnim();
|
|
777
|
+
return void 0;
|
|
778
|
+
}
|
|
779
|
+
if (axisRaf.current != null) {
|
|
780
|
+
cancelAnimationFrame(axisRaf.current);
|
|
781
|
+
axisRaf.current = null;
|
|
782
|
+
}
|
|
783
|
+
const dur = Math.max(0, transitionMs ?? 300);
|
|
784
|
+
if (dur === 0 || reduceMotion) {
|
|
785
|
+
axisCollapse.current = { y: targetY, x: targetX };
|
|
786
|
+
handle.setAxisCollapse(targetY, targetX);
|
|
787
|
+
const p = handle.render();
|
|
788
|
+
if (p) pictureSV.value = p;
|
|
789
|
+
maybeStartAnim();
|
|
790
|
+
return void 0;
|
|
791
|
+
}
|
|
792
|
+
let startTs = null;
|
|
793
|
+
const step = (now) => {
|
|
794
|
+
if (startTs == null) startTs = now;
|
|
795
|
+
const prog = Math.min(1, (now - startTs) / dur);
|
|
796
|
+
const e = ease(easingRef.current, prog);
|
|
797
|
+
const y = prog < 1 ? fromY + (targetY - fromY) * e : targetY;
|
|
798
|
+
const x = prog < 1 ? fromX + (targetX - fromX) * e : targetX;
|
|
799
|
+
axisCollapse.current = { y, x };
|
|
800
|
+
handle.setAxisCollapse(y, x);
|
|
801
|
+
const p = handle.render();
|
|
802
|
+
if (p) pictureSV.value = p;
|
|
803
|
+
if (prog < 1) {
|
|
804
|
+
axisRaf.current = requestAnimationFrame(step);
|
|
805
|
+
} else {
|
|
806
|
+
axisRaf.current = null;
|
|
807
|
+
maybeStartAnim();
|
|
808
|
+
}
|
|
809
|
+
};
|
|
810
|
+
axisRaf.current = requestAnimationFrame(step);
|
|
811
|
+
return () => {
|
|
812
|
+
if (axisRaf.current != null) {
|
|
813
|
+
cancelAnimationFrame(axisRaf.current);
|
|
814
|
+
axisRaf.current = null;
|
|
815
|
+
}
|
|
816
|
+
};
|
|
817
|
+
}, [
|
|
818
|
+
handle,
|
|
819
|
+
showYAxis,
|
|
820
|
+
showXAxis,
|
|
821
|
+
transitionMs,
|
|
822
|
+
reduceMotion,
|
|
823
|
+
pictureSV,
|
|
824
|
+
maybeStartAnim
|
|
825
|
+
]);
|
|
729
826
|
const hitAxis = (0, import_react2.useCallback)(
|
|
730
827
|
(x, y) => {
|
|
731
828
|
if (!handle) return "chart";
|