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.
@@ -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
- VROOM_COLOR_TOOLTIP_TEXT,
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
@@ -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
 
@@ -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 (neutral grey, white text) sit on top of the axis labels and
112
- // the current-price indicator since the crosshair is the last draw step.
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
- SK_ColorWHITE);
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
- SK_ColorWHITE);
163
+ badge_text, lay.y_axis_opacity);
159
164
  }
160
165
  }
161
166
 
@@ -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
- // White price text, horizontally centered on the same axis center, and
101
- // vertically centered: shift the baseline so the glyph bounds' midpoint
102
- // lands on `y` (= the box center and the dotted line).
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(SK_ColorWHITE);
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(SK_ColorWHITE);
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 + white text, so size reads at a glance against the
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
- SK_ColorWHITE);
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
- if (tf && cfg.bands_visible) {
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) {
@@ -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
- 0xff161b22, // TOOLTIP_BG
17
- 0xffc9d1d9, // TOOLTIP_TEXT
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
@@ -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
- volumeRaf.current = prog < 1 ? requestAnimationFrame(step) : null;
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
- }, [handle, volume?.enabled, transitionMs, reduceMotion, pictureSV, volumeCollapseRef]);
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";