react-native-vroom-chart 0.5.0 → 0.7.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/android/CMakeLists.txt +143 -0
- package/android/README.md +60 -5
- package/android/build.gradle +115 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/cpp/VroomChartJni.cpp +24 -0
- package/android/src/main/java/com/vroom/chart/VroomChartModule.kt +31 -0
- package/android/src/main/java/com/vroom/chart/VroomChartPackage.kt +31 -0
- package/cpp/VroomChartHostObject.cpp +368 -33
- package/cpp/VroomJsiInstaller.cpp +11 -1
- package/cpp/VroomSkiaContext.cpp +15 -7
- package/cpp/_core_include/vroom/vroom_chart.h +250 -22
- package/cpp/_core_src/bollinger.h +1 -1
- package/cpp/_core_src/candles.cpp +138 -41
- package/cpp/_core_src/candles.h +11 -1
- package/cpp/_core_src/chart.cpp +98 -40
- package/cpp/_core_src/chart.h +82 -20
- package/cpp/_core_src/chart_facade.cpp +297 -66
- package/cpp/_core_src/gradient.cpp +46 -0
- package/cpp/_core_src/gradient.h +31 -0
- package/cpp/_core_src/labels.cpp +49 -16
- package/cpp/_core_src/labels.h +33 -4
- package/cpp/_core_src/liquidity.cpp +3 -37
- package/cpp/_core_src/ma_overlay.cpp +174 -12
- package/cpp/_core_src/ma_overlay.h +55 -3
- package/cpp/_core_src/macd.cpp +13 -42
- package/cpp/_core_src/macd.h +11 -8
- package/cpp/_core_src/macd_pane.cpp +57 -27
- package/cpp/_core_src/price_line_layout.cpp +87 -0
- package/cpp/_core_src/price_line_layout.h +80 -0
- package/cpp/_core_src/price_lines.cpp +428 -0
- package/cpp/_core_src/price_lines.h +56 -0
- package/cpp/_core_src/rsi.cpp +4 -18
- package/cpp/_core_src/rsi.h +6 -6
- package/cpp/_core_src/rsi_pane.cpp +34 -19
- package/cpp/_core_src/series_ma.cpp +64 -0
- package/cpp/_core_src/series_ma.h +30 -0
- package/cpp/_core_src/style_inherit.h +35 -0
- package/cpp/_core_src/theme.cpp +2 -1
- package/cpp/_core_src/viewport.cpp +36 -12
- package/cpp/_core_src/viewport.h +50 -0
- package/cpp/_core_src/volume.cpp +33 -8
- package/cpp/_core_src/volume.h +12 -1
- package/cpp/_core_src/volume_anim.cpp +32 -0
- package/cpp/_core_src/volume_anim.h +41 -0
- package/lib/index.d.mts +261 -25
- package/lib/index.d.ts +261 -25
- package/lib/index.js +252 -36
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +252 -36
- package/lib/index.mjs.map +1 -1
- package/package.json +5 -4
- package/src/VroomChart.tsx +162 -11
- package/src/easing.ts +40 -0
- package/src/index.ts +5 -0
- package/src/jsi.d.ts +124 -20
- package/src/theme.ts +1 -0
- package/src/types.ts +5 -0
- package/src/useChartCore.ts +166 -28
package/src/jsi.d.ts
CHANGED
|
@@ -102,22 +102,53 @@ export interface ChartHandle {
|
|
|
102
102
|
} | null;
|
|
103
103
|
} | null;
|
|
104
104
|
/**
|
|
105
|
-
* Configures the RSI pane
|
|
106
|
-
*
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
maPeriod: number
|
|
115
|
-
|
|
105
|
+
* Configures the RSI pane. maKind mirrors setOverlays' kind encoding; colors
|
|
106
|
+
* are packed 0xAARRGGBB where 0 means inherit, and a non-positive width
|
|
107
|
+
* inherits the default stroke.
|
|
108
|
+
*/
|
|
109
|
+
setRSI(spec: {
|
|
110
|
+
enabled: boolean;
|
|
111
|
+
period: number;
|
|
112
|
+
upperBand: number;
|
|
113
|
+
lowerBand: number;
|
|
114
|
+
maPeriod: number;
|
|
115
|
+
maKind: number;
|
|
116
|
+
maVisible: boolean;
|
|
117
|
+
lineColor: number;
|
|
118
|
+
lineWidth: number;
|
|
119
|
+
lineVisible: boolean;
|
|
120
|
+
maColor: number;
|
|
121
|
+
maWidth: number;
|
|
122
|
+
bandColor: number;
|
|
123
|
+
bandsVisible: boolean;
|
|
124
|
+
}): void;
|
|
116
125
|
/**
|
|
117
|
-
* Configures the MACD pane
|
|
118
|
-
*
|
|
126
|
+
* Configures the MACD pane. source/maKind mirror setOverlays' encodings;
|
|
127
|
+
* colors are packed 0xAARRGGBB where 0 means inherit, and a non-positive
|
|
128
|
+
* width inherits the default stroke.
|
|
119
129
|
*/
|
|
120
|
-
setMACD(
|
|
130
|
+
setMACD(spec: {
|
|
131
|
+
enabled: boolean;
|
|
132
|
+
fast: number;
|
|
133
|
+
slow: number;
|
|
134
|
+
signal: number;
|
|
135
|
+
source: number;
|
|
136
|
+
maKind: number;
|
|
137
|
+
signalMaKind: number;
|
|
138
|
+
lineColor: number;
|
|
139
|
+
lineWidth: number;
|
|
140
|
+
lineVisible: boolean;
|
|
141
|
+
signalColor: number;
|
|
142
|
+
signalWidth: number;
|
|
143
|
+
signalVisible: boolean;
|
|
144
|
+
histVisible: boolean;
|
|
145
|
+
histUpColor: number;
|
|
146
|
+
histUpFadingColor: number;
|
|
147
|
+
histDownColor: number;
|
|
148
|
+
histDownFadingColor: number;
|
|
149
|
+
zeroColor: number;
|
|
150
|
+
zeroVisible: boolean;
|
|
151
|
+
}): void;
|
|
121
152
|
/**
|
|
122
153
|
* Replaces the full set of MA/EMA overlay lines drawn on the price pane.
|
|
123
154
|
* kind: 0=SMA, 1=EMA; source: 0=close,1=open,2=high,3=low,4=hl2,5=hlc3,6=ohlc4;
|
|
@@ -136,12 +167,12 @@ export interface ChartHandle {
|
|
|
136
167
|
* Configures the session VWAP overlay. `resetOffsetMin` shifts the session
|
|
137
168
|
* boundary from UTC midnight (minutes); `color` is packed 0xAARRGGBB.
|
|
138
169
|
*/
|
|
139
|
-
setVWAP(
|
|
140
|
-
enabled: boolean
|
|
141
|
-
resetOffsetMin: number
|
|
142
|
-
color: number
|
|
143
|
-
width: number
|
|
144
|
-
): void;
|
|
170
|
+
setVWAP(spec: {
|
|
171
|
+
enabled: boolean;
|
|
172
|
+
resetOffsetMin: number;
|
|
173
|
+
color: number;
|
|
174
|
+
width: number;
|
|
175
|
+
}): void;
|
|
145
176
|
/**
|
|
146
177
|
* Configures the Bollinger Bands overlay (three price-pane lines + optional
|
|
147
178
|
* fill between the bands). source/basisKind mirror setOverlays' encodings;
|
|
@@ -162,6 +193,79 @@ export interface ChartHandle {
|
|
|
162
193
|
fillEnabled: boolean;
|
|
163
194
|
fillOpacity: number;
|
|
164
195
|
}): void;
|
|
196
|
+
/**
|
|
197
|
+
* Configures the volume bars under the candles. `heightFrac` is the tallest
|
|
198
|
+
* bar as a fraction of the price pane. The style fields carry an inherit
|
|
199
|
+
* sentinel: a negative number or a transparent color falls back to the
|
|
200
|
+
* matching theme key.
|
|
201
|
+
*/
|
|
202
|
+
setVolume(spec: {
|
|
203
|
+
enabled: boolean;
|
|
204
|
+
heightFrac: number;
|
|
205
|
+
opacity: number;
|
|
206
|
+
radiusPx: number;
|
|
207
|
+
upColor: number;
|
|
208
|
+
downColor: number;
|
|
209
|
+
}): void;
|
|
210
|
+
/**
|
|
211
|
+
* Staggered volume-bar collapse: 0 = full height, 1 = all bars flat. Bars fall
|
|
212
|
+
* tallest-first and land together; drive 1 → 0 to reveal them, which plays the
|
|
213
|
+
* cascade in reverse (shortest bar home first).
|
|
214
|
+
*
|
|
215
|
+
* Unlike setMorph, `t` must be **linear** progress — the core eases each bar
|
|
216
|
+
* over its own slice of the timeline, so the curve is applied there. `easing`
|
|
217
|
+
* indexes `linear | ease-in | ease-out | ease-in-out`. setVolume snaps this to
|
|
218
|
+
* match its `enabled`, so it's only needed while animating.
|
|
219
|
+
*/
|
|
220
|
+
setVolumeCollapse(t: number, easing: number): void;
|
|
221
|
+
/**
|
|
222
|
+
* The continuous data coordinate at pixel (x, y) — not snapped to a candle
|
|
223
|
+
* slot. Null when there are no candles or the viewport is degenerate. Cheap to
|
|
224
|
+
* call at gesture rate (no rendering).
|
|
225
|
+
*/
|
|
226
|
+
coordAt(x: number, y: number): { timeMs: number; price: number } | null;
|
|
227
|
+
/**
|
|
228
|
+
* Replaces the full set of price status lines (plus their shared style).
|
|
229
|
+
* lineStyle: 0=solid,1=dotted,2=dashed; color/bodyBg are packed 0xAARRGGBB;
|
|
230
|
+
* flags is a bitmask (1=draggable, 2=closable, 4=axis label, 8=extend left);
|
|
231
|
+
* align: 0=left,1=center,2=right. Pass an empty `lines` array to clear.
|
|
232
|
+
*/
|
|
233
|
+
setPriceLines(spec: {
|
|
234
|
+
lines: {
|
|
235
|
+
price: number;
|
|
236
|
+
color: number;
|
|
237
|
+
width: number;
|
|
238
|
+
lineStyle: number;
|
|
239
|
+
text: string;
|
|
240
|
+
quantity: string;
|
|
241
|
+
flags: number;
|
|
242
|
+
}[];
|
|
243
|
+
bodyBg: number;
|
|
244
|
+
fontSizePx: number;
|
|
245
|
+
lineLengthFrac: number;
|
|
246
|
+
align: number;
|
|
247
|
+
hoverBoost: number;
|
|
248
|
+
}): void;
|
|
249
|
+
/**
|
|
250
|
+
* Hit-tests pixel (x, y) against the price lines. `part` is 0 for the line or
|
|
251
|
+
* its label body (the drag target) and 1 for the close button; close buttons
|
|
252
|
+
* win over lines and the nearest in y wins among several candidates. Only
|
|
253
|
+
* draggable lines report part 0 and only closable lines report part 1. Cheap
|
|
254
|
+
* to call at gesture rate (no rendering).
|
|
255
|
+
*/
|
|
256
|
+
hitTestPriceLine(x: number, y: number): { index: number; part: number } | null;
|
|
257
|
+
/**
|
|
258
|
+
* Marks a price line's segment as hovered so it renders highlighted; -1 clears.
|
|
259
|
+
* `part` matches hitTestPriceLine. Touch has no hover state, so this is here
|
|
260
|
+
* for pointer devices and web parity.
|
|
261
|
+
*/
|
|
262
|
+
setPriceLineHover(index: number, part: number): void;
|
|
263
|
+
/**
|
|
264
|
+
* Drives the live drag preview: the line, its label and its badge render at
|
|
265
|
+
* `price` with a ghost at the committed one. Pass -1 to end the preview. The
|
|
266
|
+
* committed price is untouched — restate setPriceLines to apply the move.
|
|
267
|
+
*/
|
|
268
|
+
setPriceLineDrag(index: number, price: number): void;
|
|
165
269
|
/** True while any axis-label fade is still in progress. Drives a RAF loop. */
|
|
166
270
|
isAnimating(): boolean;
|
|
167
271
|
render(): SkPicture | null;
|
package/src/theme.ts
CHANGED
|
@@ -27,6 +27,7 @@ export const FLOAT_KEYS: Partial<Record<keyof VroomTheme, number>> = {
|
|
|
27
27
|
candleRadius: 8, // VROOM_FLOAT_CANDLE_RADIUS_PX
|
|
28
28
|
volumeRadius: 10, // VROOM_FLOAT_VOLUME_RADIUS_PX
|
|
29
29
|
lineWidth: 11, // VROOM_FLOAT_LINE_WIDTH_PX
|
|
30
|
+
lineGradientOpacity: 12, // VROOM_FLOAT_LINE_GRADIENT_OPACITY
|
|
30
31
|
};
|
|
31
32
|
|
|
32
33
|
// Maps each boolean VroomTheme field to its VroomFloatKey index (pushed as 0/1).
|
package/src/types.ts
CHANGED
|
@@ -12,11 +12,16 @@ export type {
|
|
|
12
12
|
VisibleRange,
|
|
13
13
|
RSIConfig,
|
|
14
14
|
MASource,
|
|
15
|
+
MAKind,
|
|
15
16
|
MovingAverageOverlay,
|
|
16
17
|
VWAPConfig,
|
|
17
18
|
BollingerBandsConfig,
|
|
19
|
+
VolumeConfig,
|
|
18
20
|
MACDConfig,
|
|
19
21
|
ChartType,
|
|
22
|
+
TransitionEasing,
|
|
23
|
+
PriceLine,
|
|
24
|
+
PriceLinesStyle,
|
|
20
25
|
} from '@vroomchart/types';
|
|
21
26
|
|
|
22
27
|
/**
|
package/src/useChartCore.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import type { MutableRefObject } from 'react';
|
|
2
3
|
import type { SkPicture } from '@shopify/react-native-skia';
|
|
3
4
|
|
|
4
5
|
import NativeVroomChart from './NativeVroomChart';
|
|
@@ -11,8 +12,11 @@ import type {
|
|
|
11
12
|
ChartType,
|
|
12
13
|
MACDConfig,
|
|
13
14
|
MovingAverageOverlay,
|
|
15
|
+
PriceLine,
|
|
16
|
+
PriceLinesStyle,
|
|
14
17
|
RSIConfig,
|
|
15
18
|
VisibleRange,
|
|
19
|
+
VolumeConfig,
|
|
16
20
|
VroomTheme,
|
|
17
21
|
VWAPConfig,
|
|
18
22
|
} from './types';
|
|
@@ -28,17 +32,49 @@ const MA_SOURCES = [
|
|
|
28
32
|
'ohlc4',
|
|
29
33
|
] as const;
|
|
30
34
|
|
|
35
|
+
// An unset style color marshals as the core's transparent inherit sentinel.
|
|
36
|
+
const inheritColor = (v: string | number | undefined): number =>
|
|
37
|
+
(v != null ? parseColor(v) : null) ?? 0;
|
|
38
|
+
|
|
31
39
|
function overlayToNumeric(o: MovingAverageOverlay) {
|
|
32
40
|
const srcIdx = o.source ? MA_SOURCES.indexOf(o.source) : 0;
|
|
33
41
|
return {
|
|
34
|
-
kind: o.
|
|
35
|
-
period: o.
|
|
42
|
+
kind: o.maType === 'ema' ? 1 : 0,
|
|
43
|
+
period: o.period,
|
|
36
44
|
source: srcIdx < 0 ? 0 : srcIdx,
|
|
37
45
|
color: (o.color != null ? parseColor(o.color) : null) ?? 0xff2962ff,
|
|
38
46
|
width: o.width ?? 1.5,
|
|
39
47
|
};
|
|
40
48
|
}
|
|
41
49
|
|
|
50
|
+
function rsiToSpec(cfg: RSIConfig | undefined) {
|
|
51
|
+
return {
|
|
52
|
+
enabled: cfg?.enabled ?? false,
|
|
53
|
+
period: cfg?.period ?? 14,
|
|
54
|
+
upperBand: cfg?.upperBand ?? 70,
|
|
55
|
+
lowerBand: cfg?.lowerBand ?? 30,
|
|
56
|
+
maPeriod: cfg?.maPeriod ?? 14,
|
|
57
|
+
maKind: cfg?.maType === 'ema' ? 1 : 0,
|
|
58
|
+
maVisible: cfg?.maVisible ?? true,
|
|
59
|
+
lineColor: inheritColor(cfg?.lineColor),
|
|
60
|
+
lineWidth: cfg?.lineWidth ?? -1,
|
|
61
|
+
lineVisible: cfg?.lineVisible ?? true,
|
|
62
|
+
maColor: inheritColor(cfg?.maColor),
|
|
63
|
+
maWidth: cfg?.maWidth ?? -1,
|
|
64
|
+
bandColor: inheritColor(cfg?.bandColor),
|
|
65
|
+
bandsVisible: cfg?.bandsVisible ?? true,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function vwapToSpec(cfg: VWAPConfig | undefined) {
|
|
70
|
+
return {
|
|
71
|
+
enabled: cfg?.enabled ?? false,
|
|
72
|
+
resetOffsetMin: cfg?.resetMinutes ?? 0,
|
|
73
|
+
color: inheritColor(cfg?.color),
|
|
74
|
+
width: cfg?.width ?? -1,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
42
78
|
// Bollinger defaults: blue bands / orange basis, matching the repo palette.
|
|
43
79
|
const DEFAULT_BB_BAND_COLOR = 0xff2962ff;
|
|
44
80
|
const DEFAULT_BB_BASIS_COLOR = 0xffff6d00;
|
|
@@ -50,7 +86,7 @@ function bollingerToSpec(cfg: BollingerBandsConfig | undefined) {
|
|
|
50
86
|
period: cfg?.period ?? 20,
|
|
51
87
|
mult: cfg?.stdDev ?? 2,
|
|
52
88
|
source: srcIdx < 0 ? 0 : srcIdx,
|
|
53
|
-
basisKind: cfg?.
|
|
89
|
+
basisKind: cfg?.maType === 'ema' ? 1 : 0,
|
|
54
90
|
upperColor:
|
|
55
91
|
(cfg?.upperColor != null ? parseColor(cfg.upperColor) : null) ??
|
|
56
92
|
DEFAULT_BB_BAND_COLOR,
|
|
@@ -63,11 +99,106 @@ function bollingerToSpec(cfg: BollingerBandsConfig | undefined) {
|
|
|
63
99
|
(cfg?.lowerColor != null ? parseColor(cfg.lowerColor) : null) ??
|
|
64
100
|
DEFAULT_BB_BAND_COLOR,
|
|
65
101
|
lowerWidth: cfg?.lowerWidth ?? 1,
|
|
66
|
-
fillEnabled: cfg?.
|
|
102
|
+
fillEnabled: cfg?.fillVisible ?? true,
|
|
67
103
|
fillOpacity: cfg?.fillOpacity ?? 0.1,
|
|
68
104
|
};
|
|
69
105
|
}
|
|
70
106
|
|
|
107
|
+
function macdToSpec(cfg: MACDConfig | undefined) {
|
|
108
|
+
const srcIdx = cfg?.source ? MA_SOURCES.indexOf(cfg.source) : 0;
|
|
109
|
+
return {
|
|
110
|
+
enabled: cfg?.enabled ?? false,
|
|
111
|
+
fast: cfg?.fast ?? 12,
|
|
112
|
+
slow: cfg?.slow ?? 26,
|
|
113
|
+
signal: cfg?.signal ?? 9,
|
|
114
|
+
source: srcIdx < 0 ? 0 : srcIdx,
|
|
115
|
+
maKind: cfg?.maType === 'sma' ? 0 : 1,
|
|
116
|
+
signalMaKind: cfg?.signalMaType === 'sma' ? 0 : 1,
|
|
117
|
+
lineColor: inheritColor(cfg?.lineColor),
|
|
118
|
+
lineWidth: cfg?.lineWidth ?? -1,
|
|
119
|
+
lineVisible: cfg?.lineVisible ?? true,
|
|
120
|
+
signalColor: inheritColor(cfg?.signalColor),
|
|
121
|
+
signalWidth: cfg?.signalWidth ?? -1,
|
|
122
|
+
signalVisible: cfg?.signalVisible ?? true,
|
|
123
|
+
histVisible: cfg?.histogramVisible ?? true,
|
|
124
|
+
histUpColor: inheritColor(cfg?.histogramUpColor),
|
|
125
|
+
histUpFadingColor: inheritColor(cfg?.histogramUpFadingColor),
|
|
126
|
+
histDownColor: inheritColor(cfg?.histogramDownColor),
|
|
127
|
+
histDownFadingColor: inheritColor(cfg?.histogramDownFadingColor),
|
|
128
|
+
zeroColor: inheritColor(cfg?.zeroLineColor),
|
|
129
|
+
zeroVisible: cfg?.zeroLineVisible ?? true,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Unset style fields go down as the core's inherit sentinels (negative float,
|
|
134
|
+
// transparent color) rather than as literal defaults, so the theme keys stay in
|
|
135
|
+
// charge of anything the consumer didn't set.
|
|
136
|
+
function volumeToSpec(cfg: VolumeConfig | undefined) {
|
|
137
|
+
return {
|
|
138
|
+
enabled: cfg?.enabled ?? true,
|
|
139
|
+
heightFrac: cfg?.height ?? -1,
|
|
140
|
+
opacity: cfg?.opacity ?? -1,
|
|
141
|
+
radiusPx: cfg?.radius ?? -1,
|
|
142
|
+
upColor: (cfg?.upColor != null ? parseColor(cfg.upColor) : null) ?? 0,
|
|
143
|
+
downColor: (cfg?.downColor != null ? parseColor(cfg.downColor) : null) ?? 0,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Price-line defaults: a soft red dotted rule with a dark translucent label,
|
|
148
|
+
// close in weight to the current-price indicator it sits beside.
|
|
149
|
+
const DEFAULT_PRICE_LINE_COLOR = 0xffef5350;
|
|
150
|
+
const DEFAULT_PRICE_LINE_BODY_BG = 0xd91c2128;
|
|
151
|
+
const DEFAULT_PRICE_LINE_HOVER_BOOST = 1.25;
|
|
152
|
+
|
|
153
|
+
const LINE_STYLES = { solid: 0, dotted: 1, dashed: 2 } as const;
|
|
154
|
+
|
|
155
|
+
// Mirrors VroomPriceLineFlags in packages/core/include/vroom/vroom_chart.h.
|
|
156
|
+
const PRICE_LINE_DRAGGABLE = 1 << 0;
|
|
157
|
+
const PRICE_LINE_CLOSABLE = 1 << 1;
|
|
158
|
+
const PRICE_LINE_AXIS_LABEL = 1 << 2;
|
|
159
|
+
const PRICE_LINE_EXTEND_LEFT = 1 << 3;
|
|
160
|
+
|
|
161
|
+
/** The price lines + their shared style, as the chart's props express them. */
|
|
162
|
+
export type PriceLinesProp = {
|
|
163
|
+
lines: PriceLine[];
|
|
164
|
+
style?: PriceLinesStyle;
|
|
165
|
+
/**
|
|
166
|
+
* Whether the host supplied a close handler. The close button is
|
|
167
|
+
* callback-gated, so with nothing for it to do it isn't drawn at all.
|
|
168
|
+
*/
|
|
169
|
+
hasCloseHandler: boolean;
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
function priceLinesToSpec(cfg: PriceLinesProp) {
|
|
173
|
+
return {
|
|
174
|
+
lines: cfg.lines.map((l) => ({
|
|
175
|
+
price: l.price,
|
|
176
|
+
color:
|
|
177
|
+
(l.color != null ? parseColor(l.color) : null) ?? DEFAULT_PRICE_LINE_COLOR,
|
|
178
|
+
width: l.width ?? 1,
|
|
179
|
+
lineStyle: LINE_STYLES[l.lineStyle ?? 'dotted'],
|
|
180
|
+
text: l.text ?? '',
|
|
181
|
+
quantity: l.quantity ?? '',
|
|
182
|
+
flags:
|
|
183
|
+
(l.draggable ? PRICE_LINE_DRAGGABLE : 0) |
|
|
184
|
+
(cfg.hasCloseHandler && l.closable !== false ? PRICE_LINE_CLOSABLE : 0) |
|
|
185
|
+
(l.axisLabel !== false ? PRICE_LINE_AXIS_LABEL : 0) |
|
|
186
|
+
(l.extendLeft !== false ? PRICE_LINE_EXTEND_LEFT : 0),
|
|
187
|
+
})),
|
|
188
|
+
bodyBg:
|
|
189
|
+
(cfg.style?.bodyBackground != null ? parseColor(cfg.style.bodyBackground) : null) ??
|
|
190
|
+
DEFAULT_PRICE_LINE_BODY_BG,
|
|
191
|
+
fontSizePx: cfg.style?.fontSize ?? 0,
|
|
192
|
+
lineLengthFrac: cfg.style?.inset ?? 0,
|
|
193
|
+
align: cfg.style?.align === 'left' ? 0 : cfg.style?.align === 'center' ? 1 : 2,
|
|
194
|
+
hoverBoost: cfg.style?.hoverBoost ?? DEFAULT_PRICE_LINE_HOVER_BOOST,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Cleared overlay: no lines (the style values are irrelevant, but the spec shape
|
|
199
|
+
// requires them).
|
|
200
|
+
const EMPTY_PRICE_LINES = priceLinesToSpec({ lines: [], hasCloseHandler: false });
|
|
201
|
+
|
|
71
202
|
let installed = false;
|
|
72
203
|
function ensureInstalled(): void {
|
|
73
204
|
if (installed) return;
|
|
@@ -79,10 +210,19 @@ function ensureInstalled(): void {
|
|
|
79
210
|
installed = true;
|
|
80
211
|
}
|
|
81
212
|
|
|
213
|
+
/** Progress + curve of the staggered volume-bar collapse. See setVolumeCollapse. */
|
|
214
|
+
export type VolumeCollapse = { t: number; easing: number };
|
|
215
|
+
|
|
82
216
|
export type ChartCoreState = {
|
|
83
217
|
handle: ChartHandle | null;
|
|
84
218
|
/** Picture freshly rendered after the latest data/size/range push. */
|
|
85
219
|
picture: SkPicture | null;
|
|
220
|
+
/**
|
|
221
|
+
* The last volume collapse handed to the core, or null before the first push.
|
|
222
|
+
* VroomChart's animation loop owns this — it lives here only so the data effect
|
|
223
|
+
* can restore it, since setVolume snaps the scalar (see below).
|
|
224
|
+
*/
|
|
225
|
+
volumeCollapseRef: MutableRefObject<VolumeCollapse | null>;
|
|
86
226
|
};
|
|
87
227
|
|
|
88
228
|
// Owns a ChartHandle and produces an "initial" picture whenever data, size,
|
|
@@ -101,12 +241,15 @@ export function useChartCore(
|
|
|
101
241
|
movingAverages?: MovingAverageOverlay[],
|
|
102
242
|
vwap?: VWAPConfig,
|
|
103
243
|
bollingerBands?: BollingerBandsConfig,
|
|
244
|
+
volume?: VolumeConfig,
|
|
245
|
+
priceLines?: PriceLinesProp,
|
|
104
246
|
): ChartCoreState {
|
|
105
247
|
const handleRef = useRef<ChartHandle | null>(null);
|
|
106
248
|
// Push setDefaultCandleWidth only once (first load): setCandles re-runs on
|
|
107
249
|
// every data change, and the core setter re-frames when candles are present,
|
|
108
250
|
// so re-pushing would snap the view away from the user's pan/zoom.
|
|
109
251
|
const defaultWidthAppliedRef = useRef(false);
|
|
252
|
+
const volumeCollapseRef = useRef<VolumeCollapse | null>(null);
|
|
110
253
|
const [picture, setPicture] = useState<SkPicture | null>(null);
|
|
111
254
|
|
|
112
255
|
if (!handleRef.current && size.width > 0 && size.height > 0) {
|
|
@@ -130,6 +273,8 @@ export function useChartCore(
|
|
|
130
273
|
const maKey = movingAverages ? JSON.stringify(movingAverages) : '';
|
|
131
274
|
const vwapKey = vwap ? JSON.stringify(vwap) : '';
|
|
132
275
|
const bollingerKey = bollingerBands ? JSON.stringify(bollingerBands) : '';
|
|
276
|
+
const volumeKey = volume ? JSON.stringify(volume) : '';
|
|
277
|
+
const priceLinesKey = priceLines ? JSON.stringify(priceLines) : '';
|
|
133
278
|
|
|
134
279
|
useEffect(() => {
|
|
135
280
|
const h = handleRef.current;
|
|
@@ -158,35 +303,28 @@ export function useChartCore(
|
|
|
158
303
|
if (theme) {
|
|
159
304
|
applyTheme(h, theme);
|
|
160
305
|
}
|
|
161
|
-
h.setRSI(
|
|
162
|
-
|
|
163
|
-
rsi?.period ?? 14,
|
|
164
|
-
rsi?.upperBand ?? 70,
|
|
165
|
-
rsi?.lowerBand ?? 30,
|
|
166
|
-
rsi?.maEnabled ?? true,
|
|
167
|
-
rsi?.maPeriod ?? 14,
|
|
168
|
-
);
|
|
169
|
-
h.setMACD(
|
|
170
|
-
macd?.enabled ?? false,
|
|
171
|
-
macd?.fast ?? 12,
|
|
172
|
-
macd?.slow ?? 26,
|
|
173
|
-
macd?.signal ?? 9,
|
|
174
|
-
);
|
|
306
|
+
h.setRSI(rsiToSpec(rsi));
|
|
307
|
+
h.setMACD(macdToSpec(macd));
|
|
175
308
|
h.setOverlays((movingAverages ?? []).map(overlayToNumeric));
|
|
176
|
-
h.setVWAP(
|
|
177
|
-
vwap?.enabled ?? false,
|
|
178
|
-
vwap?.resetMinutes ?? 0,
|
|
179
|
-
(vwap?.color != null ? parseColor(vwap.color) : null) ?? 0xff00bcd4,
|
|
180
|
-
vwap?.width ?? 1.5,
|
|
181
|
-
);
|
|
309
|
+
h.setVWAP(vwapToSpec(vwap));
|
|
182
310
|
h.setBollinger(bollingerToSpec(bollingerBands));
|
|
311
|
+
h.setVolume(volumeToSpec(volume));
|
|
312
|
+
// setVolume snaps the collapse scalar to its `enabled`, which would cut a
|
|
313
|
+
// toggle animation short whenever this effect re-runs (a streaming candle, a
|
|
314
|
+
// resize). Hand the in-flight value back; VroomChart's loop drives it from
|
|
315
|
+
// there.
|
|
316
|
+
const collapse = volumeCollapseRef.current;
|
|
317
|
+
if (collapse) h.setVolumeCollapse(collapse.t, collapse.easing);
|
|
318
|
+
h.setPriceLines(
|
|
319
|
+
priceLines?.lines.length ? priceLinesToSpec(priceLines) : EMPTY_PRICE_LINES,
|
|
320
|
+
);
|
|
183
321
|
// TODO(rn-parity): mirror the web `liquidity` overlay here (setLiquidity +
|
|
184
322
|
// the VroomBand structs in the JSI handle) — web-only for now.
|
|
185
323
|
setPicture(h.render());
|
|
186
|
-
// theme/rsi/macd/movingAverages/vwap/bollingerBands are
|
|
187
|
-
// their *Key deps.
|
|
324
|
+
// theme/rsi/macd/movingAverages/vwap/bollingerBands/volume/priceLines are
|
|
325
|
+
// represented by their *Key deps.
|
|
188
326
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
189
|
-
}, [candles, size.width, size.height, size.pxRatio, explicit, startMs, endMs, defaultCandleWidth, themeKey, rsiKey, macdKey, maKey, vwapKey, bollingerKey]);
|
|
327
|
+
}, [candles, size.width, size.height, size.pxRatio, explicit, startMs, endMs, defaultCandleWidth, themeKey, rsiKey, macdKey, maKey, vwapKey, bollingerKey, volumeKey, priceLinesKey]);
|
|
190
328
|
|
|
191
|
-
return { handle: handleRef.current, picture };
|
|
329
|
+
return { handle: handleRef.current, picture, volumeCollapseRef };
|
|
192
330
|
}
|