react-native-vroom-chart 0.1.1 → 0.1.3

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.
@@ -0,0 +1,108 @@
1
+ #include "drawings.h"
2
+
3
+ #pragma clang diagnostic push
4
+ #pragma clang diagnostic ignored "-Wdocumentation"
5
+ #include "include/core/SkCanvas.h"
6
+ #include "include/core/SkColor.h"
7
+ #include "include/core/SkPaint.h"
8
+ #include "include/core/SkPoint.h"
9
+ #include "include/core/SkRect.h"
10
+ #pragma clang diagnostic pop
11
+
12
+ #include "chart.h"
13
+
14
+ namespace vroom::drawings {
15
+
16
+ namespace {
17
+ // Node-dot styling (fixed for v1, not themed): a 4px black dot with a 2px blue
18
+ // ring around it. kNodeFillRadius is the black core; the blue stroke is centered
19
+ // at kNodeRingRadius so it sits just outside the core.
20
+ constexpr SkColor kNodeFill = 0xff000000; // black core
21
+ constexpr SkColor kNodeBorder = 0xff2962ff; // blue ring
22
+ constexpr float kNodeFillRadius = 2.f; // 4px diameter
23
+ constexpr float kNodeBorderWidth = 2.f;
24
+ constexpr float kNodeRingRadius = 3.f; // ring centered outside the core
25
+
26
+ // Maps a data-space point to pixels in the price pane.
27
+ SkPoint to_px(const VroomChart& chart, const Layout& lay, const PriceBounds& bounds,
28
+ int64_t window_ms, const VroomDrawPoint& p) {
29
+ const float x = vroom::x_at_time(lay, chart.visible_start_ms, window_ms, p.time_ms);
30
+ const float y = vroom::price_to_y(lay, bounds, p.price);
31
+ return SkPoint{x, y};
32
+ }
33
+
34
+ void draw_node(SkCanvas* canvas, SkPoint pt) {
35
+ SkPaint fill;
36
+ fill.setAntiAlias(true);
37
+ fill.setColor(kNodeFill);
38
+ canvas->drawCircle(pt.fX, pt.fY, kNodeFillRadius, fill);
39
+
40
+ SkPaint border;
41
+ border.setAntiAlias(true);
42
+ border.setColor(kNodeBorder);
43
+ border.setStyle(SkPaint::kStroke_Style);
44
+ border.setStrokeWidth(kNodeBorderWidth);
45
+ canvas->drawCircle(pt.fX, pt.fY, kNodeRingRadius, border);
46
+ }
47
+ } // namespace
48
+
49
+ void draw(SkCanvas* canvas,
50
+ const VroomChart& chart,
51
+ const Layout& lay,
52
+ const PriceBounds& bounds,
53
+ float candle_right,
54
+ float candle_area_h) {
55
+ if (!canvas || candle_right <= 0.f || candle_area_h <= 0.f) return;
56
+ const int64_t window_ms = chart.visible_end_ms - chart.visible_start_ms;
57
+ if (window_ms <= 0) return;
58
+
59
+ const SkRect clip = SkRect::MakeLTRB(0.f, 0.f, candle_right, candle_area_h);
60
+
61
+ // 1. Committed line segments, clipped to the candle area.
62
+ if (!chart.drawings.empty()) {
63
+ canvas->save();
64
+ canvas->clipRect(clip);
65
+ for (const VroomDrawing& d : chart.drawings) {
66
+ const SkPoint a = to_px(chart, lay, bounds, window_ms, d.a);
67
+ const SkPoint b = to_px(chart, lay, bounds, window_ms, d.b);
68
+ SkPaint line;
69
+ line.setAntiAlias(true);
70
+ line.setColor(static_cast<SkColor>(d.color));
71
+ line.setStyle(SkPaint::kStroke_Style);
72
+ line.setStrokeWidth(d.width > 0.f ? d.width : 2.f);
73
+ canvas->drawLine(a, b, line);
74
+ }
75
+ canvas->restore();
76
+ }
77
+
78
+ if (!chart.draft_active) return;
79
+
80
+ const SkPoint a = to_px(chart, lay, bounds, window_ms, chart.draft_a);
81
+ const bool has_b = chart.draft_has_b;
82
+ const SkPoint b =
83
+ has_b ? to_px(chart, lay, bounds, window_ms, chart.draft_b) : a;
84
+
85
+ // 2. Guideline preview (A->B), clipped to the candle area. Only while the
86
+ // second point is still being placed (draft_guide); once committed, the
87
+ // solid segment comes from chart.drawings instead.
88
+ if (chart.draft_guide && has_b) {
89
+ canvas->save();
90
+ canvas->clipRect(clip);
91
+ SkPaint guide;
92
+ guide.setAntiAlias(true);
93
+ guide.setColor(static_cast<SkColor>(chart.draft_color));
94
+ guide.setStyle(SkPaint::kStroke_Style);
95
+ guide.setStrokeWidth(chart.draft_width > 0.f ? chart.draft_width : 2.f);
96
+ canvas->drawLine(a, b, guide);
97
+ canvas->restore();
98
+ }
99
+
100
+ // 3. Node dots on top (not clipped, so an edge dot still renders fully).
101
+ // While guiding (placing the second point) only the anchor dot shows; the
102
+ // moving end is conveyed by the guideline. Once selected (committed) both
103
+ // endpoints show dots.
104
+ draw_node(canvas, a);
105
+ if (has_b && !chart.draft_guide) draw_node(canvas, b);
106
+ }
107
+
108
+ } // namespace vroom::drawings
@@ -0,0 +1,34 @@
1
+ // Drawing annotations — user-placed line tools rendered on the price pane.
2
+ //
3
+ // Two kinds of geometry are drawn here:
4
+ // * Committed lines (chart.drawings) — two-point trendlines anchored in data
5
+ // space, so they stay glued to the candles as the user pans/zooms.
6
+ // * The transient "draft" (chart.draft_*) — the in-progress node dots and the
7
+ // live guideline shown while the user is placing a line.
8
+ //
9
+ // Like ma_overlay / crosshair this is its own module so the chart orchestrator
10
+ // stays thin. Drawn after the overlays and before the axis backgrounds.
11
+
12
+ #pragma once
13
+
14
+ #include "viewport.h"
15
+ #include "vroom/vroom_chart.h"
16
+
17
+ class SkCanvas;
18
+ struct VroomChart;
19
+
20
+ namespace vroom::drawings {
21
+
22
+ // Draws the committed line drawings and the in-progress draft. `candle_right` is
23
+ // the x of the y-axis strip and `candle_area_h` the price-pane bottom; geometry
24
+ // is clipped to that rectangle so lines never bleed into the axis strips or the
25
+ // indicator panes below. Node dots are exempt from the clip so a dot placed at
26
+ // the very edge still renders fully.
27
+ void draw(SkCanvas* canvas,
28
+ const VroomChart& chart,
29
+ const vroom::Layout& lay,
30
+ const vroom::PriceBounds& bounds,
31
+ float candle_right,
32
+ float candle_area_h);
33
+
34
+ } // namespace vroom::drawings
@@ -318,7 +318,7 @@ void recompute_axis_width(VroomChart& chart) {
318
318
  return;
319
319
  }
320
320
  double hi, lo;
321
- if (chart.price_bounds_initialized) {
321
+ if (chart.price_bounds_manual) {
322
322
  hi = chart.price_bounds.max;
323
323
  lo = chart.price_bounds.min;
324
324
  } else if (!chart.candles.empty()) {
@@ -52,7 +52,10 @@ void draw(SkCanvas* canvas,
52
52
  if (band_h <= 0.f) return;
53
53
 
54
54
  const float mid = (pane_top + pane_bottom) * 0.5f;
55
- const float half = band_h * 0.5f * kPadFrac;
55
+ // User y-zoom scales the amplitude about the zero line (mid). 1.0 = the
56
+ // default auto-fit; >1 zooms in, <1 zooms out.
57
+ const float half =
58
+ band_h * 0.5f * kPadFrac * static_cast<float>(chart.macd_y_scale);
56
59
 
57
60
  // Auto-scale symmetric about zero across all visible finite values.
58
61
  double scale = 0.0;
@@ -100,8 +103,8 @@ void draw(SkCanvas* canvas,
100
103
  vroom::candle_body_width(lay, window_ms, candle_duration_ms);
101
104
  const float half_body = body_w * 0.5f;
102
105
  const float zero_y = y_for(0.0);
103
- const SkColor bull = chart.theme.colors[VROOM_COLOR_BULL];
104
- const SkColor bear = chart.theme.colors[VROOM_COLOR_BEAR];
106
+ const SkColor bull = chart.theme.colors[VROOM_COLOR_ACCENT_BULL];
107
+ const SkColor bear = chart.theme.colors[VROOM_COLOR_ACCENT_BEAR];
105
108
  for (std::size_t i = 0; i < n; ++i) {
106
109
  const double h = hist_visible[i];
107
110
  if (!std::isfinite(h)) continue;
@@ -43,7 +43,7 @@ void draw(SkCanvas* canvas,
43
43
  const ::VroomCandle& last = chart.candles.back();
44
44
  const bool bull = last.close >= last.open;
45
45
  const SkColor color =
46
- chart.theme.colors[bull ? VROOM_COLOR_BULL : VROOM_COLOR_BEAR];
46
+ chart.theme.colors[bull ? VROOM_COLOR_ACCENT_BULL : VROOM_COLOR_ACCENT_BEAR];
47
47
 
48
48
  const float y = vroom::price_to_y(lay, bounds, last.close);
49
49
  if (y < 0.f || y > candle_area_h) return; // price scrolled off-range
@@ -50,8 +50,13 @@ void draw(SkCanvas* canvas,
50
50
  const float band_h = pane_bottom - pane_top;
51
51
  if (band_h <= 0.f) return;
52
52
 
53
+ // Map 0..100 about the pane center so the user y-zoom scales symmetrically
54
+ // around 50. z == 1 reproduces the default fit (0 -> pane_bottom, 100 ->
55
+ // pane_top); z > 1 zooms in (taller), z < 1 zooms out (flatter).
56
+ const float center_y = (pane_top + pane_bottom) * 0.5f;
57
+ const float z = static_cast<float>(chart.rsi_y_scale);
53
58
  auto y_for = [&](double v) -> float {
54
- return pane_bottom - static_cast<float>(v / 100.0) * band_h;
59
+ return center_y - static_cast<float>((v - 50.0) / 100.0) * band_h * z;
55
60
  };
56
61
 
57
62
  // Mask the band with the background so candles/volume that overflow below
@@ -16,6 +16,12 @@ constexpr uint32_t kDefaultColors[VROOM_COLOR_COUNT_] = {
16
16
  0xff161b22, // TOOLTIP_BG
17
17
  0xffc9d1d9, // TOOLTIP_TEXT
18
18
  0xff3e4855, // CROSSHAIR_TARGET — CROSSHAIR lightened 30% (prior derived ring)
19
+ 0x00000000, // BORDER_BULL — transparent sentinel: inherit BULL fill
20
+ 0x00000000, // BORDER_BEAR — transparent sentinel: inherit BEAR fill
21
+ 0x00000000, // WICK_BULL — transparent sentinel: inherit BULL fill
22
+ 0x00000000, // WICK_BEAR — transparent sentinel: inherit BEAR fill
23
+ 0xff26a69a, // ACCENT_BULL — classic teal-green (price indicator, volume, MACD)
24
+ 0xffef5350, // ACCENT_BEAR — classic red
19
25
  };
20
26
 
21
27
  constexpr float kDefaultFloats[VROOM_FLOAT_COUNT_] = {
@@ -35,6 +35,31 @@ float candle_center_x(const Layout& layout,
35
35
  return static_cast<float>(static_cast<double>(usable) * frac);
36
36
  }
37
37
 
38
+ int64_t time_at_x(const Layout& layout,
39
+ int64_t visible_start_ms,
40
+ int64_t window_ms,
41
+ float x_px) {
42
+ const float usable =
43
+ layout.width_px - layout.y_axis_width_px - layout.right_padding_px;
44
+ if (usable <= 0.f || window_ms <= 0) return visible_start_ms;
45
+ const double frac = static_cast<double>(x_px) / static_cast<double>(usable);
46
+ return visible_start_ms +
47
+ static_cast<int64_t>(std::llround(frac * static_cast<double>(window_ms)));
48
+ }
49
+
50
+ float x_at_time(const Layout& layout,
51
+ int64_t visible_start_ms,
52
+ int64_t window_ms,
53
+ int64_t time_ms) {
54
+ if (window_ms <= 0) return 0.f;
55
+ const float usable =
56
+ layout.width_px - layout.y_axis_width_px - layout.right_padding_px;
57
+ const double frac =
58
+ static_cast<double>(time_ms - visible_start_ms) /
59
+ static_cast<double>(window_ms);
60
+ return static_cast<float>(static_cast<double>(usable) * frac);
61
+ }
62
+
38
63
  size_t snap_index_to_candle(const Layout& layout,
39
64
  const ::VroomCandle* candles,
40
65
  size_t count,
@@ -180,6 +205,14 @@ PriceBounds price_bounds(const ::VroomCandle* candles, size_t count) {
180
205
  return b;
181
206
  }
182
207
 
208
+ PriceBounds auto_price_bounds(const ::VroomCandle* candles, size_t count) {
209
+ PriceBounds b = price_bounds(candles, count);
210
+ if (count == 0) return b;
211
+ const double mid = (b.min + b.max) * 0.5;
212
+ const double half = (b.max - b.min) * 0.5 * kAutoYZoom;
213
+ return {mid - half, mid + half};
214
+ }
215
+
183
216
  float price_to_y(const Layout& layout,
184
217
  const PriceBounds& bounds,
185
218
  double price) {
@@ -113,9 +113,33 @@ float snap_x_to_candle(const Layout& layout,
113
113
  int64_t window_ms,
114
114
  float x_px);
115
115
 
116
+ // Free (non-snapped, non-candle-centered) mapping between time and pixel x,
117
+ // used for drawing-tool endpoints that can sit anywhere — not just on a candle
118
+ // slot. `x_at_time` is the exact inverse of `time_at_x`:
119
+ // frac = (time - start) / window; x = usable * frac
120
+ // where `usable` is the candle area width (width - y-axis - right padding).
121
+ // Returns 0 / visible_start_ms respectively for a degenerate window/usable.
122
+ int64_t time_at_x(const Layout& layout,
123
+ int64_t visible_start_ms,
124
+ int64_t window_ms,
125
+ float x_px);
126
+ float x_at_time(const Layout& layout,
127
+ int64_t visible_start_ms,
128
+ int64_t window_ms,
129
+ int64_t time_ms);
130
+
116
131
  // Min/max of (low..high) across the given range.
117
132
  PriceBounds price_bounds(const ::VroomCandle* candles, size_t count);
118
133
 
134
+ // Widening applied to auto-fit price bounds so candles keep some headroom
135
+ // instead of touching the pane edges. 1.0 = snug; larger = wider.
136
+ constexpr double kAutoYZoom = 1.5;
137
+
138
+ // price_bounds() widened about its midpoint by kAutoYZoom. This is the y-range
139
+ // used whenever the price scale is in auto (follow-the-data) mode. Returns the
140
+ // {0, 1} sentinel when count == 0 (callers keep their previous bounds).
141
+ PriceBounds auto_price_bounds(const ::VroomCandle* candles, size_t count);
142
+
119
143
  // Map a price to y in pixels. y=0 is top of the chart.
120
144
  float price_to_y(const Layout& layout, const PriceBounds& bounds, double price);
121
145
 
@@ -43,12 +43,12 @@ void draw(SkCanvas* canvas,
43
43
  const float opacity = theme.floats[VROOM_FLOAT_VOLUME_OPACITY];
44
44
  SkPaint bull_paint;
45
45
  bull_paint.setAntiAlias(true);
46
- bull_paint.setColor(theme.colors[VROOM_COLOR_BULL]);
46
+ bull_paint.setColor(theme.colors[VROOM_COLOR_ACCENT_BULL]);
47
47
  bull_paint.setAlphaf(opacity);
48
48
 
49
49
  SkPaint bear_paint;
50
50
  bear_paint.setAntiAlias(true);
51
- bear_paint.setColor(theme.colors[VROOM_COLOR_BEAR]);
51
+ bear_paint.setColor(theme.colors[VROOM_COLOR_ACCENT_BEAR]);
52
52
  bear_paint.setAlphaf(opacity);
53
53
 
54
54
  for (std::size_t i = 0; i < n; ++i) {
package/lib/index.d.mts CHANGED
@@ -52,10 +52,22 @@ type VroomColor = string | number;
52
52
  type VroomTheme = {
53
53
  /** Chart + axis-strip background. */
54
54
  background?: VroomColor;
55
- /** Up candles (also bull wicks, bull volume bars, rising price indicator). */
55
+ /** Up candle body fill. Wick and border default to this unless overridden. */
56
56
  bull?: VroomColor;
57
- /** Down candles (also bear wicks, bear volume bars, falling price indicator). */
57
+ /** Down candle body fill. Wick and border default to this unless overridden. */
58
58
  bear?: VroomColor;
59
+ /** Generic up color for the price indicator, volume bars, and MACD histogram. Defaults to teal-green; independent of `bull`. */
60
+ accentBull?: VroomColor;
61
+ /** Generic down color for the price indicator, volume bars, and MACD histogram. Defaults to red; independent of `bear`. */
62
+ accentBear?: VroomColor;
63
+ /** Up candle body 1px border. Defaults to the bull fill color. */
64
+ borderBull?: VroomColor;
65
+ /** Down candle body 1px border. Defaults to the bear fill color. */
66
+ borderBear?: VroomColor;
67
+ /** Up candle wick color. Defaults to the bull fill color. */
68
+ wickBull?: VroomColor;
69
+ /** Down candle wick color. Defaults to the bear fill color. */
70
+ wickBear?: VroomColor;
59
71
  /** Gridlines. */
60
72
  grid?: VroomColor;
61
73
  /** Axis label text (price + time). */
@@ -72,6 +84,38 @@ type VisibleRange = {
72
84
  /** Window end (inclusive), Unix epoch milliseconds. */
73
85
  endMs: number;
74
86
  };
87
+ /**
88
+ * Chart interaction mode.
89
+ * 'pan' — default: drag to pan, pinch/wheel to zoom, hover/long-press crosshair.
90
+ * 'draw' — left-clicks place drawing points; panning/zooming are suppressed.
91
+ */
92
+ type ChartMode = 'pan' | 'draw';
93
+ /** Active drawing tool while in `draw` mode. `null` draws nothing. */
94
+ type DrawTool = null | 'line';
95
+ /** A drawing anchor in data space, so it stays glued to the candles on pan/zoom. */
96
+ type DrawPoint = {
97
+ /** Anchor time as Unix epoch milliseconds (not snapped to a candle slot). */
98
+ timeMs: number;
99
+ /** Anchor price. */
100
+ price: number;
101
+ };
102
+ /**
103
+ * A committed drawing. Pass an array of these via the `drawings` prop to render
104
+ * persisted annotations; the chart appends a new one (via `onDrawingComplete`)
105
+ * each time the user finishes drawing. For now only the `'line'` (two-point
106
+ * trendline) type exists.
107
+ */
108
+ type Drawing = {
109
+ /** Stable unique id (the chart generates one for drawings it creates). */
110
+ id: string;
111
+ type: 'line';
112
+ /** The two endpoints, in data space. */
113
+ points: [DrawPoint, DrawPoint];
114
+ /** Line color (hex string or packed ARGB number). Default solid blue. */
115
+ color?: VroomColor;
116
+ /** Stroke width in px. Default 2. */
117
+ width?: number;
118
+ };
75
119
  /** RSI indicator config. Rendered in a pane below the candles when enabled. */
76
120
  type RSIConfig = {
77
121
  enabled?: boolean;
@@ -135,6 +179,14 @@ type MACDConfig = {
135
179
  type VroomChartCoreProps = {
136
180
  /** OHLCV bars to render. The only required prop. */
137
181
  candles: Candle[];
182
+ /**
183
+ * Identity of the data series (e.g. "BTC-USD"). When it changes between
184
+ * renders the chart resets to the default view (most recent candles + price
185
+ * auto-fit), regardless of what the data heuristics conclude — the escape
186
+ * hatch for ambiguous switches like two assets trading at similar prices.
187
+ * Omit to rely on automatic detection from the candle data alone.
188
+ */
189
+ seriesKey?: string;
138
190
  /**
139
191
  * Explicit size overrides in logical px. When omitted, the chart fills its
140
192
  * parent (measured at runtime). Prefer layout-driven sizing via the
@@ -159,6 +211,27 @@ type VroomChartCoreProps = {
159
211
  * touch x. Default 40.
160
212
  */
161
213
  crosshairOffset?: number;
214
+ /**
215
+ * Interaction mode. Default `'pan'`. Set to `'draw'` (with a `tool`) to let
216
+ * the user place drawing points; panning/zooming are suppressed while drawing.
217
+ */
218
+ mode?: ChartMode;
219
+ /** Active drawing tool while in `draw` mode. Default `null` (draws nothing). */
220
+ tool?: DrawTool;
221
+ /**
222
+ * Committed drawings to render, anchored to data so they track the candles on
223
+ * pan/zoom. This is a controlled prop: append the value the chart hands you in
224
+ * `onDrawingComplete` to persist it.
225
+ */
226
+ drawings?: Drawing[];
227
+ /** Fired with the finished drawing when the user completes one. */
228
+ onDrawingComplete?: (drawing: Drawing) => void;
229
+ /**
230
+ * Fired when the chart wants the mode changed — e.g. it requests `'pan'` after
231
+ * the user clicks away from a just-drawn line. Since `mode` is controlled, the
232
+ * host should apply the requested mode.
233
+ */
234
+ onModeChange?: (mode: ChartMode) => void;
162
235
  onCrosshair?: (e: CrosshairEvent) => void;
163
236
  onViewportChange?: (startMs: number, endMs: number) => void;
164
237
  };
package/lib/index.d.ts CHANGED
@@ -52,10 +52,22 @@ type VroomColor = string | number;
52
52
  type VroomTheme = {
53
53
  /** Chart + axis-strip background. */
54
54
  background?: VroomColor;
55
- /** Up candles (also bull wicks, bull volume bars, rising price indicator). */
55
+ /** Up candle body fill. Wick and border default to this unless overridden. */
56
56
  bull?: VroomColor;
57
- /** Down candles (also bear wicks, bear volume bars, falling price indicator). */
57
+ /** Down candle body fill. Wick and border default to this unless overridden. */
58
58
  bear?: VroomColor;
59
+ /** Generic up color for the price indicator, volume bars, and MACD histogram. Defaults to teal-green; independent of `bull`. */
60
+ accentBull?: VroomColor;
61
+ /** Generic down color for the price indicator, volume bars, and MACD histogram. Defaults to red; independent of `bear`. */
62
+ accentBear?: VroomColor;
63
+ /** Up candle body 1px border. Defaults to the bull fill color. */
64
+ borderBull?: VroomColor;
65
+ /** Down candle body 1px border. Defaults to the bear fill color. */
66
+ borderBear?: VroomColor;
67
+ /** Up candle wick color. Defaults to the bull fill color. */
68
+ wickBull?: VroomColor;
69
+ /** Down candle wick color. Defaults to the bear fill color. */
70
+ wickBear?: VroomColor;
59
71
  /** Gridlines. */
60
72
  grid?: VroomColor;
61
73
  /** Axis label text (price + time). */
@@ -72,6 +84,38 @@ type VisibleRange = {
72
84
  /** Window end (inclusive), Unix epoch milliseconds. */
73
85
  endMs: number;
74
86
  };
87
+ /**
88
+ * Chart interaction mode.
89
+ * 'pan' — default: drag to pan, pinch/wheel to zoom, hover/long-press crosshair.
90
+ * 'draw' — left-clicks place drawing points; panning/zooming are suppressed.
91
+ */
92
+ type ChartMode = 'pan' | 'draw';
93
+ /** Active drawing tool while in `draw` mode. `null` draws nothing. */
94
+ type DrawTool = null | 'line';
95
+ /** A drawing anchor in data space, so it stays glued to the candles on pan/zoom. */
96
+ type DrawPoint = {
97
+ /** Anchor time as Unix epoch milliseconds (not snapped to a candle slot). */
98
+ timeMs: number;
99
+ /** Anchor price. */
100
+ price: number;
101
+ };
102
+ /**
103
+ * A committed drawing. Pass an array of these via the `drawings` prop to render
104
+ * persisted annotations; the chart appends a new one (via `onDrawingComplete`)
105
+ * each time the user finishes drawing. For now only the `'line'` (two-point
106
+ * trendline) type exists.
107
+ */
108
+ type Drawing = {
109
+ /** Stable unique id (the chart generates one for drawings it creates). */
110
+ id: string;
111
+ type: 'line';
112
+ /** The two endpoints, in data space. */
113
+ points: [DrawPoint, DrawPoint];
114
+ /** Line color (hex string or packed ARGB number). Default solid blue. */
115
+ color?: VroomColor;
116
+ /** Stroke width in px. Default 2. */
117
+ width?: number;
118
+ };
75
119
  /** RSI indicator config. Rendered in a pane below the candles when enabled. */
76
120
  type RSIConfig = {
77
121
  enabled?: boolean;
@@ -135,6 +179,14 @@ type MACDConfig = {
135
179
  type VroomChartCoreProps = {
136
180
  /** OHLCV bars to render. The only required prop. */
137
181
  candles: Candle[];
182
+ /**
183
+ * Identity of the data series (e.g. "BTC-USD"). When it changes between
184
+ * renders the chart resets to the default view (most recent candles + price
185
+ * auto-fit), regardless of what the data heuristics conclude — the escape
186
+ * hatch for ambiguous switches like two assets trading at similar prices.
187
+ * Omit to rely on automatic detection from the candle data alone.
188
+ */
189
+ seriesKey?: string;
138
190
  /**
139
191
  * Explicit size overrides in logical px. When omitted, the chart fills its
140
192
  * parent (measured at runtime). Prefer layout-driven sizing via the
@@ -159,6 +211,27 @@ type VroomChartCoreProps = {
159
211
  * touch x. Default 40.
160
212
  */
161
213
  crosshairOffset?: number;
214
+ /**
215
+ * Interaction mode. Default `'pan'`. Set to `'draw'` (with a `tool`) to let
216
+ * the user place drawing points; panning/zooming are suppressed while drawing.
217
+ */
218
+ mode?: ChartMode;
219
+ /** Active drawing tool while in `draw` mode. Default `null` (draws nothing). */
220
+ tool?: DrawTool;
221
+ /**
222
+ * Committed drawings to render, anchored to data so they track the candles on
223
+ * pan/zoom. This is a controlled prop: append the value the chart hands you in
224
+ * `onDrawingComplete` to persist it.
225
+ */
226
+ drawings?: Drawing[];
227
+ /** Fired with the finished drawing when the user completes one. */
228
+ onDrawingComplete?: (drawing: Drawing) => void;
229
+ /**
230
+ * Fired when the chart wants the mode changed — e.g. it requests `'pan'` after
231
+ * the user clicks away from a just-drawn line. Since `mode` is controlled, the
232
+ * host should apply the requested mode.
233
+ */
234
+ onModeChange?: (mode: ChartMode) => void;
162
235
  onCrosshair?: (e: CrosshairEvent) => void;
163
236
  onViewportChange?: (startMs: number, endMs: number) => void;
164
237
  };
package/lib/index.js CHANGED
@@ -80,8 +80,20 @@ var COLOR_KEYS = {
80
80
  // VROOM_COLOR_AXIS_TEXT
81
81
  crosshair: 6,
82
82
  // VROOM_COLOR_CROSSHAIR
83
- crosshairTarget: 9
83
+ crosshairTarget: 9,
84
84
  // VROOM_COLOR_CROSSHAIR_TARGET
85
+ borderBull: 10,
86
+ // VROOM_COLOR_BORDER_BULL
87
+ borderBear: 11,
88
+ // VROOM_COLOR_BORDER_BEAR
89
+ wickBull: 12,
90
+ // VROOM_COLOR_WICK_BULL
91
+ wickBear: 13,
92
+ // VROOM_COLOR_WICK_BEAR
93
+ accentBull: 14,
94
+ // VROOM_COLOR_ACCENT_BULL
95
+ accentBear: 15
96
+ // VROOM_COLOR_ACCENT_BEAR
85
97
  };
86
98
  function parseColor(value) {
87
99
  if (typeof value === "number") {