react-native-vroom-chart 0.2.0 → 0.5.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 +85 -1
- package/cpp/_core_include/vroom/vroom_chart.h +111 -11
- package/cpp/_core_src/bollinger.cpp +43 -0
- package/cpp/_core_src/bollinger.h +31 -0
- package/cpp/_core_src/candles.cpp +32 -5
- package/cpp/_core_src/candles.h +8 -1
- package/cpp/_core_src/chart.cpp +70 -3
- package/cpp/_core_src/chart.h +53 -4
- package/cpp/_core_src/chart_facade.cpp +183 -2
- package/cpp/_core_src/drawings.cpp +313 -15
- package/cpp/_core_src/drawings.h +23 -0
- package/cpp/_core_src/ma_overlay.cpp +76 -1
- package/cpp/_core_src/ma_overlay.h +23 -1
- package/cpp/_core_src/theme.cpp +2 -0
- package/lib/index.d.mts +211 -13
- package/lib/index.d.ts +211 -13
- package/lib/index.js +92 -5
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +92 -5
- package/lib/index.mjs.map +1 -1
- package/package.json +18 -7
- package/react-native-vroom-chart.podspec +1 -1
- package/src/VroomChart.tsx +71 -0
- package/src/index.ts +2 -0
- package/src/jsi.d.ts +24 -0
- package/src/theme.ts +2 -0
- package/src/types.ts +2 -0
- package/src/useChartCore.ts +40 -2
package/lib/index.d.ts
CHANGED
|
@@ -92,6 +92,10 @@ type VroomTheme = {
|
|
|
92
92
|
crosshair?: VroomColor;
|
|
93
93
|
/** Crosshair target — the hollow ring/dot at the intersection. */
|
|
94
94
|
crosshairTarget?: VroomColor;
|
|
95
|
+
/** Line-chart-mode close polyline color. Defaults to a neutral foreground. */
|
|
96
|
+
lineColor?: VroomColor;
|
|
97
|
+
/** Line-chart-mode polyline stroke width in px. Defaults to 1.5. */
|
|
98
|
+
lineWidth?: number;
|
|
95
99
|
};
|
|
96
100
|
/** A time window over the candle data, as Unix epoch milliseconds. */
|
|
97
101
|
type VisibleRange = {
|
|
@@ -106,8 +110,15 @@ type VisibleRange = {
|
|
|
106
110
|
* 'draw' — left-clicks place drawing points; panning/zooming are suppressed.
|
|
107
111
|
*/
|
|
108
112
|
type ChartMode = 'pan' | 'draw';
|
|
113
|
+
/**
|
|
114
|
+
* How the price series is drawn.
|
|
115
|
+
* 'candles' — default: candlestick bodies + wicks.
|
|
116
|
+
* 'line' — a single polyline through each candle's close. Volume, indicators,
|
|
117
|
+
* overlays, crosshair, and drawings still render.
|
|
118
|
+
*/
|
|
119
|
+
type ChartType = 'candles' | 'line';
|
|
109
120
|
/** Active drawing tool while in `draw` mode. `null` draws nothing. */
|
|
110
|
-
type DrawTool = null | 'line';
|
|
121
|
+
type DrawTool = null | 'line' | 'box' | 'pencil';
|
|
111
122
|
/** A drawing anchor in data space, so it stays glued to the candles on pan/zoom. */
|
|
112
123
|
type DrawPoint = {
|
|
113
124
|
/** Anchor time as Unix epoch milliseconds (not snapped to a candle slot). */
|
|
@@ -115,23 +126,106 @@ type DrawPoint = {
|
|
|
115
126
|
/** Anchor price. */
|
|
116
127
|
price: number;
|
|
117
128
|
};
|
|
118
|
-
/**
|
|
119
|
-
|
|
120
|
-
* persisted annotations; the chart appends a new one (via `onDrawingComplete`)
|
|
121
|
-
* each time the user finishes drawing. For now only the `'line'` (two-point
|
|
122
|
-
* trendline) type exists.
|
|
123
|
-
*/
|
|
124
|
-
type Drawing = {
|
|
129
|
+
/** Fields shared by every drawing type. */
|
|
130
|
+
type DrawingBase = {
|
|
125
131
|
/** Stable unique id (the chart generates one for drawings it creates). */
|
|
126
132
|
id: string;
|
|
127
|
-
|
|
128
|
-
/** The two endpoints, in data space. */
|
|
129
|
-
points: [DrawPoint, DrawPoint];
|
|
130
|
-
/** Line color (hex string or packed ARGB number). Default solid blue. */
|
|
133
|
+
/** Stroke color (hex string or packed ARGB number). Default solid blue. */
|
|
131
134
|
color?: VroomColor;
|
|
132
135
|
/** Stroke width in px. Default 2. */
|
|
133
136
|
width?: number;
|
|
134
137
|
};
|
|
138
|
+
/** A two-point trendline from `points[0]` to `points[1]`. */
|
|
139
|
+
type LineDrawing = DrawingBase & {
|
|
140
|
+
type: 'line';
|
|
141
|
+
/** The two endpoints, in data space. */
|
|
142
|
+
points: [DrawPoint, DrawPoint];
|
|
143
|
+
};
|
|
144
|
+
/**
|
|
145
|
+
* An axis-aligned rectangle whose two opposite corners are `points[0]` and
|
|
146
|
+
* `points[1]` (the other two corners are derived).
|
|
147
|
+
*/
|
|
148
|
+
type BoxDrawing = DrawingBase & {
|
|
149
|
+
type: 'box';
|
|
150
|
+
/** Two opposite corners, in data space. */
|
|
151
|
+
points: [DrawPoint, DrawPoint];
|
|
152
|
+
};
|
|
153
|
+
/**
|
|
154
|
+
* A freehand pencil stroke: an open path through `points`, in order. Unlike the
|
|
155
|
+
* other tools a stroke has a variable number of points, and once committed it
|
|
156
|
+
* can only be translated — never reshaped.
|
|
157
|
+
*/
|
|
158
|
+
type PencilDrawing = DrawingBase & {
|
|
159
|
+
type: 'pencil';
|
|
160
|
+
/** The path's points in draw order (at least 2), in data space. */
|
|
161
|
+
points: DrawPoint[];
|
|
162
|
+
};
|
|
163
|
+
/**
|
|
164
|
+
* A committed drawing. Pass an array of these via the `drawings` prop to render
|
|
165
|
+
* persisted annotations; the chart appends a new one (via `onDrawingComplete`)
|
|
166
|
+
* each time the user finishes drawing.
|
|
167
|
+
*
|
|
168
|
+
* This is a discriminated union on `type` — narrow on it before reading
|
|
169
|
+
* `points[1]`, since a `'pencil'` stroke has a variable-length array while
|
|
170
|
+
* `'line'` and `'box'` are always exactly two points.
|
|
171
|
+
*/
|
|
172
|
+
type Drawing = LineDrawing | BoxDrawing | PencilDrawing;
|
|
173
|
+
/**
|
|
174
|
+
* Storage adapter for **managed** drawing persistence. Provide it via the
|
|
175
|
+
* `drawingStore` prop and the chart owns the drawings array itself — loading and
|
|
176
|
+
* saving through this adapter instead of you wiring the controlled `drawings`
|
|
177
|
+
* prop + `onDrawing*` callbacks.
|
|
178
|
+
*
|
|
179
|
+
* The adapter is an **opaque string key-value store** — the chart serializes
|
|
180
|
+
* drawings into a **versioned envelope** (`{ v, drawings }`) and hands you the
|
|
181
|
+
* string; you just persist bytes. Because the library owns the schema and
|
|
182
|
+
* migrates old payloads on load, adding drawing tools or persisted fields later
|
|
183
|
+
* never changes this interface — your adapter is written once.
|
|
184
|
+
*
|
|
185
|
+
* `marketId` is the chart's `seriesKey`, so drawings are bucketed per market:
|
|
186
|
+
* they persist across timeframe changes (same key) but not across markets. Both
|
|
187
|
+
* methods may be async (localStorage is sync; AsyncStorage / MMKV / a REST
|
|
188
|
+
* backend are async). The chart debounces `save`. Consumers that only handle a
|
|
189
|
+
* single market can ignore `marketId`.
|
|
190
|
+
*/
|
|
191
|
+
type DrawingStore = {
|
|
192
|
+
/**
|
|
193
|
+
* Return the raw string previously handed to `save` for `marketId`, or
|
|
194
|
+
* `null`/`undefined`/`''` if nothing is stored. Sync or async.
|
|
195
|
+
*/
|
|
196
|
+
load: (marketId: string) => string | null | undefined | Promise<string | null | undefined>;
|
|
197
|
+
/**
|
|
198
|
+
* Persist the opaque `data` string for `marketId`. Sync or async; the chart
|
|
199
|
+
* debounces calls. The string is a versioned envelope owned by the library —
|
|
200
|
+
* store it verbatim, don't parse or reshape it.
|
|
201
|
+
*/
|
|
202
|
+
save: (marketId: string, data: string) => void | Promise<void>;
|
|
203
|
+
};
|
|
204
|
+
/**
|
|
205
|
+
* Whether undo/redo are currently available for the chart's drawings — the
|
|
206
|
+
* payload of `onHistoryChange`, for binding toolbar button enabled-states.
|
|
207
|
+
*/
|
|
208
|
+
type UndoRedoState = {
|
|
209
|
+
canUndo: boolean;
|
|
210
|
+
canRedo: boolean;
|
|
211
|
+
};
|
|
212
|
+
/**
|
|
213
|
+
* Programmatic undo/redo controls, published through the `historyRef` prop in
|
|
214
|
+
* managed mode — for toolbar buttons and other UI outside the chart. The
|
|
215
|
+
* keyboard shortcuts (⌘Z / ⇧⌘Z / Ctrl+Y) work without this.
|
|
216
|
+
*/
|
|
217
|
+
type UndoRedoControls = {
|
|
218
|
+
/** Roll back the most recent committed drawing action. No-op when empty. */
|
|
219
|
+
undo: () => void;
|
|
220
|
+
/** Re-apply the most recently undone action. No-op when empty. */
|
|
221
|
+
redo: () => void;
|
|
222
|
+
/**
|
|
223
|
+
* Drop the undo/redo stacks without touching the drawings. Rarely needed —
|
|
224
|
+
* users find a cleared history surprising — but useful when reusing a mounted
|
|
225
|
+
* chart for what the user perceives as a brand-new context.
|
|
226
|
+
*/
|
|
227
|
+
clearHistory: () => void;
|
|
228
|
+
};
|
|
135
229
|
/** RSI indicator config. Rendered in a pane below the candles when enabled. */
|
|
136
230
|
type RSIConfig = {
|
|
137
231
|
enabled?: boolean;
|
|
@@ -177,6 +271,42 @@ type VWAPConfig = {
|
|
|
177
271
|
/** Stroke width in px. Default 1.5. */
|
|
178
272
|
width?: number;
|
|
179
273
|
};
|
|
274
|
+
/**
|
|
275
|
+
* Bollinger Bands overlay config. A basis moving average of `source` over
|
|
276
|
+
* `period`, banded at ± `stdDev` × population standard deviation of the same
|
|
277
|
+
* window, drawn as three lines on the price pane with an optional translucent
|
|
278
|
+
* fill between the bands. No pane is reserved.
|
|
279
|
+
*/
|
|
280
|
+
type BollingerBandsConfig = {
|
|
281
|
+
enabled?: boolean;
|
|
282
|
+
/** Lookback in candles. Default 20, clamped to >= 1. */
|
|
283
|
+
period?: number;
|
|
284
|
+
/** Standard-deviation multiplier. Default 2. */
|
|
285
|
+
stdDev?: number;
|
|
286
|
+
/** Price source. Default 'close'. */
|
|
287
|
+
source?: MASource;
|
|
288
|
+
/**
|
|
289
|
+
* Basis (middle) line type. Default 'sma'. The stdev always uses the
|
|
290
|
+
* window's arithmetic mean, even with an EMA basis (TradingView semantics).
|
|
291
|
+
*/
|
|
292
|
+
basis?: 'sma' | 'ema';
|
|
293
|
+
/** Upper band color (hex string or packed ARGB number). Default blue. */
|
|
294
|
+
upperColor?: string | number;
|
|
295
|
+
/** Upper band stroke width in px. Default 1. */
|
|
296
|
+
upperWidth?: number;
|
|
297
|
+
/** Basis (middle) line color. Default orange. */
|
|
298
|
+
middleColor?: string | number;
|
|
299
|
+
/** Basis line stroke width in px. Default 1. */
|
|
300
|
+
middleWidth?: number;
|
|
301
|
+
/** Lower band color. Default blue. */
|
|
302
|
+
lowerColor?: string | number;
|
|
303
|
+
/** Lower band stroke width in px. Default 1. */
|
|
304
|
+
lowerWidth?: number;
|
|
305
|
+
/** Translucent fill between the bands. Default true. */
|
|
306
|
+
fill?: boolean;
|
|
307
|
+
/** Fill opacity 0..1, applied to the upper band color. Default 0.1. */
|
|
308
|
+
fillOpacity?: number;
|
|
309
|
+
};
|
|
180
310
|
/**
|
|
181
311
|
* A single resting-liquidity band: a price interval carrying a total order size
|
|
182
312
|
* on one side of the book. Consolidate raw L2 levels into these buckets before
|
|
@@ -265,6 +395,18 @@ type VroomChartCoreProps = {
|
|
|
265
395
|
* devices of different widths.
|
|
266
396
|
*/
|
|
267
397
|
defaultCandleWidth?: number;
|
|
398
|
+
/**
|
|
399
|
+
* Price-series render style. `'candles'` (default) draws candlesticks;
|
|
400
|
+
* `'line'` draws a polyline through each candle's close (style it with
|
|
401
|
+
* `theme.lineColor` / `theme.lineWidth`). All other layers are unaffected.
|
|
402
|
+
*/
|
|
403
|
+
chartType?: ChartType;
|
|
404
|
+
/**
|
|
405
|
+
* Duration (ms) of the animated candle↔line transition when `chartType`
|
|
406
|
+
* changes. Default ~300. `0` snaps instantly. Ignored (snaps) when the OS
|
|
407
|
+
* requests reduced motion, which instead uses a plain cross-fade.
|
|
408
|
+
*/
|
|
409
|
+
transitionMs?: number;
|
|
268
410
|
theme?: VroomTheme;
|
|
269
411
|
/** RSI indicator (pane below the candles). Omit/disable to hide it. */
|
|
270
412
|
rsi?: RSIConfig;
|
|
@@ -274,6 +416,8 @@ type VroomChartCoreProps = {
|
|
|
274
416
|
movingAverages?: MovingAverageOverlay[];
|
|
275
417
|
/** VWAP overlay (session anchor, configurable reset). */
|
|
276
418
|
vwap?: VWAPConfig;
|
|
419
|
+
/** Bollinger Bands overlay (three lines + fill on the price pane). */
|
|
420
|
+
bollingerBands?: BollingerBandsConfig;
|
|
277
421
|
/** Resting-order / order-book liquidity bands drawn behind the candles. */
|
|
278
422
|
liquidity?: LiquidityConfig;
|
|
279
423
|
/**
|
|
@@ -306,16 +450,70 @@ type VroomChartCoreProps = {
|
|
|
306
450
|
* Committed drawings to render, anchored to data so they track the candles on
|
|
307
451
|
* pan/zoom. This is a controlled prop: append the value the chart hands you in
|
|
308
452
|
* `onDrawingComplete` to persist it.
|
|
453
|
+
*
|
|
454
|
+
* Ignored when `drawingStore` is set (the chart then owns the array itself).
|
|
309
455
|
*/
|
|
310
456
|
drawings?: Drawing[];
|
|
457
|
+
/**
|
|
458
|
+
* Opt into **managed** drawing persistence: the chart owns the drawings array
|
|
459
|
+
* internally and loads/saves it through this adapter, keyed by `seriesKey`.
|
|
460
|
+
* When set, `drawings` and the `onDrawing*` callbacks are ignored. Web only.
|
|
461
|
+
* Requires `seriesKey` — without one, drawings work in-session but aren't saved.
|
|
462
|
+
*/
|
|
463
|
+
drawingStore?: DrawingStore;
|
|
311
464
|
/** Fired with the finished drawing when the user completes one. */
|
|
312
465
|
onDrawingComplete?: (drawing: Drawing) => void;
|
|
466
|
+
/**
|
|
467
|
+
* Fired after the user drags a selected line's endpoint handle. The payload is
|
|
468
|
+
* the same drawing (same `id`) with updated `points`; apply it to your
|
|
469
|
+
* controlled `drawings` state (replace by id). Web only.
|
|
470
|
+
*/
|
|
471
|
+
onDrawingChange?: (drawing: Drawing) => void;
|
|
472
|
+
/**
|
|
473
|
+
* Fired when the user deletes the selected line (Backspace/Delete). Remove the
|
|
474
|
+
* drawing with this `id` from your controlled `drawings` state. Web only.
|
|
475
|
+
*/
|
|
476
|
+
onDrawingDelete?: (id: string) => void;
|
|
313
477
|
/**
|
|
314
478
|
* Fired when the chart wants the mode changed — e.g. it requests `'pan'` after
|
|
315
479
|
* the user clicks away from a just-drawn line. Since `mode` is controlled, the
|
|
316
480
|
* host should apply the requested mode.
|
|
317
481
|
*/
|
|
318
482
|
onModeChange?: (mode: ChartMode) => void;
|
|
483
|
+
/**
|
|
484
|
+
* Max drawing undo depth in managed mode (one step = one committed drawing
|
|
485
|
+
* action). Oldest steps are evicted beyond this. Default 100. History is
|
|
486
|
+
* in-memory and per-`seriesKey`: it resets on market switch and is never
|
|
487
|
+
* persisted — only the drawings themselves are saved. Web only.
|
|
488
|
+
*/
|
|
489
|
+
historyLimit?: number;
|
|
490
|
+
/**
|
|
491
|
+
* Fired when drawing undo/redo availability changes in managed mode — bind
|
|
492
|
+
* toolbar undo/redo buttons' enabled-state to it. (In controlled mode you own
|
|
493
|
+
* the history, so track availability yourself.) Web only.
|
|
494
|
+
*/
|
|
495
|
+
onHistoryChange?: (state: UndoRedoState) => void;
|
|
496
|
+
/**
|
|
497
|
+
* Receives programmatic `undo`/`redo`/`clearHistory` controls in managed mode
|
|
498
|
+
* (e.g. `useRef<UndoRedoControls | null>(null)` passed here, then
|
|
499
|
+
* `historyRef.current?.undo()` from a toolbar button). Set to `null` while
|
|
500
|
+
* unmounted or when no `drawingStore` is present. Web only.
|
|
501
|
+
*/
|
|
502
|
+
historyRef?: {
|
|
503
|
+
current: UndoRedoControls | null;
|
|
504
|
+
};
|
|
505
|
+
/**
|
|
506
|
+
* Fired when the user presses the undo shortcut (⌘Z / Ctrl+Z) in controlled
|
|
507
|
+
* mode — apply the undo to your own drawings state. Ignored when
|
|
508
|
+
* `drawingStore` is set (managed mode undoes internally). Web only.
|
|
509
|
+
*/
|
|
510
|
+
onUndo?: () => void;
|
|
511
|
+
/**
|
|
512
|
+
* Fired when the user presses the redo shortcut (⇧⌘Z / Ctrl+Shift+Z /
|
|
513
|
+
* Ctrl+Y) in controlled mode — apply the redo to your own drawings state.
|
|
514
|
+
* Ignored when `drawingStore` is set. Web only.
|
|
515
|
+
*/
|
|
516
|
+
onRedo?: () => void;
|
|
319
517
|
onCrosshair?: (e: CrosshairEvent) => void;
|
|
320
518
|
onViewportChange?: (startMs: number, endMs: number) => void;
|
|
321
519
|
};
|
|
@@ -349,4 +547,4 @@ declare global {
|
|
|
349
547
|
*/
|
|
350
548
|
declare function VroomChart(props: VroomChartProps): React.JSX.Element;
|
|
351
549
|
|
|
352
|
-
export { type Candle, type CrosshairEvent, type MACDConfig, type MASource, type MovingAverageOverlay, type RSIConfig, type VWAPConfig, type VisibleRange, VroomChart, type VroomChartProps, type VroomColor, type VroomTheme };
|
|
550
|
+
export { type BollingerBandsConfig, type Candle, type ChartType, type CrosshairEvent, type MACDConfig, type MASource, type MovingAverageOverlay, type RSIConfig, type VWAPConfig, type VisibleRange, VroomChart, type VroomChartProps, type VroomColor, type VroomTheme };
|
package/lib/index.js
CHANGED
|
@@ -92,16 +92,20 @@ var COLOR_KEYS = {
|
|
|
92
92
|
// VROOM_COLOR_WICK_BEAR
|
|
93
93
|
accentBull: 14,
|
|
94
94
|
// VROOM_COLOR_ACCENT_BULL
|
|
95
|
-
accentBear: 15
|
|
95
|
+
accentBear: 15,
|
|
96
96
|
// VROOM_COLOR_ACCENT_BEAR
|
|
97
|
+
lineColor: 16
|
|
98
|
+
// VROOM_COLOR_LINE
|
|
97
99
|
};
|
|
98
100
|
var FLOAT_KEYS = {
|
|
99
101
|
wickWidth: 1,
|
|
100
102
|
// VROOM_FLOAT_WICK_WIDTH_PX
|
|
101
103
|
candleRadius: 8,
|
|
102
104
|
// VROOM_FLOAT_CANDLE_RADIUS_PX
|
|
103
|
-
volumeRadius: 10
|
|
105
|
+
volumeRadius: 10,
|
|
104
106
|
// VROOM_FLOAT_VOLUME_RADIUS_PX
|
|
107
|
+
lineWidth: 11
|
|
108
|
+
// VROOM_FLOAT_LINE_WIDTH_PX
|
|
105
109
|
};
|
|
106
110
|
var BOOL_KEYS = {
|
|
107
111
|
wickRoundCap: 9
|
|
@@ -157,6 +161,26 @@ function overlayToNumeric(o) {
|
|
|
157
161
|
width: o.width ?? 1.5
|
|
158
162
|
};
|
|
159
163
|
}
|
|
164
|
+
var DEFAULT_BB_BAND_COLOR = 4280902399;
|
|
165
|
+
var DEFAULT_BB_BASIS_COLOR = 4294929664;
|
|
166
|
+
function bollingerToSpec(cfg) {
|
|
167
|
+
const srcIdx = cfg?.source ? MA_SOURCES.indexOf(cfg.source) : 0;
|
|
168
|
+
return {
|
|
169
|
+
enabled: cfg?.enabled ?? false,
|
|
170
|
+
period: cfg?.period ?? 20,
|
|
171
|
+
mult: cfg?.stdDev ?? 2,
|
|
172
|
+
source: srcIdx < 0 ? 0 : srcIdx,
|
|
173
|
+
basisKind: cfg?.basis === "ema" ? 1 : 0,
|
|
174
|
+
upperColor: (cfg?.upperColor != null ? parseColor(cfg.upperColor) : null) ?? DEFAULT_BB_BAND_COLOR,
|
|
175
|
+
upperWidth: cfg?.upperWidth ?? 1,
|
|
176
|
+
middleColor: (cfg?.middleColor != null ? parseColor(cfg.middleColor) : null) ?? DEFAULT_BB_BASIS_COLOR,
|
|
177
|
+
middleWidth: cfg?.middleWidth ?? 1,
|
|
178
|
+
lowerColor: (cfg?.lowerColor != null ? parseColor(cfg.lowerColor) : null) ?? DEFAULT_BB_BAND_COLOR,
|
|
179
|
+
lowerWidth: cfg?.lowerWidth ?? 1,
|
|
180
|
+
fillEnabled: cfg?.fill ?? true,
|
|
181
|
+
fillOpacity: cfg?.fillOpacity ?? 0.1
|
|
182
|
+
};
|
|
183
|
+
}
|
|
160
184
|
var installed = false;
|
|
161
185
|
function ensureInstalled() {
|
|
162
186
|
if (installed) return;
|
|
@@ -167,7 +191,7 @@ function ensureInstalled() {
|
|
|
167
191
|
}
|
|
168
192
|
installed = true;
|
|
169
193
|
}
|
|
170
|
-
function useChartCore(candles, size, visibleRange, defaultCandleWidth, theme, rsi, macd, movingAverages, vwap) {
|
|
194
|
+
function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType, theme, rsi, macd, movingAverages, vwap, bollingerBands) {
|
|
171
195
|
const handleRef = (0, import_react.useRef)(null);
|
|
172
196
|
const defaultWidthAppliedRef = (0, import_react.useRef)(false);
|
|
173
197
|
const [picture, setPicture] = (0, import_react.useState)(null);
|
|
@@ -183,6 +207,7 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, theme, rs
|
|
|
183
207
|
const macdKey = macd ? JSON.stringify(macd) : "";
|
|
184
208
|
const maKey = movingAverages ? JSON.stringify(movingAverages) : "";
|
|
185
209
|
const vwapKey = vwap ? JSON.stringify(vwap) : "";
|
|
210
|
+
const bollingerKey = bollingerBands ? JSON.stringify(bollingerBands) : "";
|
|
186
211
|
(0, import_react.useEffect)(() => {
|
|
187
212
|
const h = handleRef.current;
|
|
188
213
|
if (!h) return;
|
|
@@ -221,8 +246,9 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, theme, rs
|
|
|
221
246
|
(vwap?.color != null ? parseColor(vwap.color) : null) ?? 4278238420,
|
|
222
247
|
vwap?.width ?? 1.5
|
|
223
248
|
);
|
|
249
|
+
h.setBollinger(bollingerToSpec(bollingerBands));
|
|
224
250
|
setPicture(h.render());
|
|
225
|
-
}, [candles, size.width, size.height, size.pxRatio, explicit, startMs, endMs, defaultCandleWidth, themeKey, rsiKey, macdKey, maKey, vwapKey]);
|
|
251
|
+
}, [candles, size.width, size.height, size.pxRatio, explicit, startMs, endMs, defaultCandleWidth, themeKey, rsiKey, macdKey, maKey, vwapKey, bollingerKey]);
|
|
226
252
|
return { handle: handleRef.current, picture };
|
|
227
253
|
}
|
|
228
254
|
|
|
@@ -235,11 +261,14 @@ function VroomChart(props) {
|
|
|
235
261
|
style,
|
|
236
262
|
visibleRange,
|
|
237
263
|
defaultCandleWidth,
|
|
264
|
+
chartType,
|
|
265
|
+
transitionMs,
|
|
238
266
|
theme,
|
|
239
267
|
rsi,
|
|
240
268
|
macd,
|
|
241
269
|
movingAverages,
|
|
242
270
|
vwap,
|
|
271
|
+
bollingerBands,
|
|
243
272
|
crosshairOffset = 40,
|
|
244
273
|
onCrosshair,
|
|
245
274
|
onViewportChange
|
|
@@ -259,11 +288,13 @@ function VroomChart(props) {
|
|
|
259
288
|
{ width, height },
|
|
260
289
|
visibleRange,
|
|
261
290
|
defaultCandleWidth,
|
|
291
|
+
chartType,
|
|
262
292
|
theme,
|
|
263
293
|
rsi,
|
|
264
294
|
macd,
|
|
265
295
|
movingAverages,
|
|
266
|
-
vwap
|
|
296
|
+
vwap,
|
|
297
|
+
bollingerBands
|
|
267
298
|
);
|
|
268
299
|
const emptyPicture = (0, import_react2.useMemo)(() => {
|
|
269
300
|
const rec = import_react_native_skia.Skia.PictureRecorder();
|
|
@@ -307,6 +338,62 @@ function VroomChart(props) {
|
|
|
307
338
|
}
|
|
308
339
|
};
|
|
309
340
|
}, []);
|
|
341
|
+
const morphRaf = (0, import_react2.useRef)(null);
|
|
342
|
+
const morphFade = (0, import_react2.useRef)(null);
|
|
343
|
+
const morphHandle = (0, import_react2.useRef)(null);
|
|
344
|
+
(0, import_react2.useEffect)(() => {
|
|
345
|
+
if (!handle) return void 0;
|
|
346
|
+
const target = chartType === "line" ? 1 : 0;
|
|
347
|
+
if (morphHandle.current !== handle || morphFade.current == null) {
|
|
348
|
+
morphHandle.current = handle;
|
|
349
|
+
morphFade.current = target;
|
|
350
|
+
handle.setChartType(target);
|
|
351
|
+
const p = handle.render();
|
|
352
|
+
if (p) pictureSV.value = p;
|
|
353
|
+
return void 0;
|
|
354
|
+
}
|
|
355
|
+
if (morphFade.current === target) return void 0;
|
|
356
|
+
if (morphRaf.current != null) {
|
|
357
|
+
cancelAnimationFrame(morphRaf.current);
|
|
358
|
+
morphRaf.current = null;
|
|
359
|
+
}
|
|
360
|
+
const dur = Math.max(0, transitionMs ?? 300);
|
|
361
|
+
if (dur === 0) {
|
|
362
|
+
morphFade.current = target;
|
|
363
|
+
handle.setChartType(target);
|
|
364
|
+
const p = handle.render();
|
|
365
|
+
if (p) pictureSV.value = p;
|
|
366
|
+
return void 0;
|
|
367
|
+
}
|
|
368
|
+
const from = morphFade.current;
|
|
369
|
+
let startTs = null;
|
|
370
|
+
const step = (now) => {
|
|
371
|
+
if (startTs == null) startTs = now;
|
|
372
|
+
const prog = Math.min(1, (now - startTs) / dur);
|
|
373
|
+
const e = prog * prog * (3 - 2 * prog);
|
|
374
|
+
const fade = from + (target - from) * e;
|
|
375
|
+
morphFade.current = fade;
|
|
376
|
+
handle.setMorph(fade, fade);
|
|
377
|
+
const p = handle.render();
|
|
378
|
+
if (p) pictureSV.value = p;
|
|
379
|
+
if (prog < 1) {
|
|
380
|
+
morphRaf.current = requestAnimationFrame(step);
|
|
381
|
+
} else {
|
|
382
|
+
morphRaf.current = null;
|
|
383
|
+
morphFade.current = target;
|
|
384
|
+
handle.setChartType(target);
|
|
385
|
+
const q = handle.render();
|
|
386
|
+
if (q) pictureSV.value = q;
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
morphRaf.current = requestAnimationFrame(step);
|
|
390
|
+
return () => {
|
|
391
|
+
if (morphRaf.current != null) {
|
|
392
|
+
cancelAnimationFrame(morphRaf.current);
|
|
393
|
+
morphRaf.current = null;
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
}, [handle, chartType, transitionMs, pictureSV]);
|
|
310
397
|
const hitAxis = (0, import_react2.useCallback)(
|
|
311
398
|
(x, y) => {
|
|
312
399
|
if (!handle) return "chart";
|