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
|
@@ -15,7 +15,7 @@ Pod::Spec.new do |s|
|
|
|
15
15
|
s.name = "react-native-vroom-chart"
|
|
16
16
|
s.version = package["version"]
|
|
17
17
|
s.summary = package["description"]
|
|
18
|
-
s.homepage = "https://github.com/
|
|
18
|
+
s.homepage = "https://github.com/keepitreal/vroom"
|
|
19
19
|
s.license = { :type => "MIT" }
|
|
20
20
|
s.authors = { "vroom" => "noreply@example.com" }
|
|
21
21
|
s.platforms = { :ios => "14.0" }
|
package/src/VroomChart.tsx
CHANGED
|
@@ -41,11 +41,14 @@ export function VroomChart(props: VroomChartProps) {
|
|
|
41
41
|
style,
|
|
42
42
|
visibleRange,
|
|
43
43
|
defaultCandleWidth,
|
|
44
|
+
chartType,
|
|
45
|
+
transitionMs,
|
|
44
46
|
theme,
|
|
45
47
|
rsi,
|
|
46
48
|
macd,
|
|
47
49
|
movingAverages,
|
|
48
50
|
vwap,
|
|
51
|
+
bollingerBands,
|
|
49
52
|
crosshairOffset = 40,
|
|
50
53
|
onCrosshair,
|
|
51
54
|
onViewportChange,
|
|
@@ -71,11 +74,13 @@ export function VroomChart(props: VroomChartProps) {
|
|
|
71
74
|
{ width, height },
|
|
72
75
|
visibleRange,
|
|
73
76
|
defaultCandleWidth,
|
|
77
|
+
chartType,
|
|
74
78
|
theme,
|
|
75
79
|
rsi,
|
|
76
80
|
macd,
|
|
77
81
|
movingAverages,
|
|
78
82
|
vwap,
|
|
83
|
+
bollingerBands,
|
|
79
84
|
);
|
|
80
85
|
|
|
81
86
|
// RN-Skia's recorder reads this SharedValue on the UI/render runtime, a beat
|
|
@@ -147,6 +152,72 @@ export function VroomChart(props: VroomChartProps) {
|
|
|
147
152
|
};
|
|
148
153
|
}, []);
|
|
149
154
|
|
|
155
|
+
// Candle↔line morph. When `chartType` changes we drive the core per-frame with
|
|
156
|
+
// a (collapse, fade) blend and push a fresh picture into the SV each frame — the
|
|
157
|
+
// JS side owns the eased clock (see plan). A fresh handle snaps to the target;
|
|
158
|
+
// transitionMs=0 snaps. Mirrors the web driver in react/src/useChartCore.ts.
|
|
159
|
+
const morphRaf = useRef<number | null>(null);
|
|
160
|
+
const morphFade = useRef<number | null>(null);
|
|
161
|
+
const morphHandle = useRef<typeof handle>(null);
|
|
162
|
+
useEffect(() => {
|
|
163
|
+
if (!handle) return undefined;
|
|
164
|
+
const target = chartType === 'line' ? 1 : 0;
|
|
165
|
+
|
|
166
|
+
// Fresh handle (first load / recreate): snap, don't animate.
|
|
167
|
+
if (morphHandle.current !== handle || morphFade.current == null) {
|
|
168
|
+
morphHandle.current = handle;
|
|
169
|
+
morphFade.current = target;
|
|
170
|
+
handle.setChartType(target);
|
|
171
|
+
const p = handle.render();
|
|
172
|
+
if (p) pictureSV.value = p;
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
if (morphFade.current === target) return undefined;
|
|
176
|
+
|
|
177
|
+
if (morphRaf.current != null) {
|
|
178
|
+
cancelAnimationFrame(morphRaf.current);
|
|
179
|
+
morphRaf.current = null;
|
|
180
|
+
}
|
|
181
|
+
const dur = Math.max(0, transitionMs ?? 300);
|
|
182
|
+
if (dur === 0) {
|
|
183
|
+
morphFade.current = target;
|
|
184
|
+
handle.setChartType(target);
|
|
185
|
+
const p = handle.render();
|
|
186
|
+
if (p) pictureSV.value = p;
|
|
187
|
+
return undefined;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const from = morphFade.current;
|
|
191
|
+
let startTs: number | null = null;
|
|
192
|
+
const step = (now: number) => {
|
|
193
|
+
if (startTs == null) startTs = now;
|
|
194
|
+
const prog = Math.min(1, (now - startTs) / dur);
|
|
195
|
+
const e = prog * prog * (3 - 2 * prog); // smoothstep ease-in-out
|
|
196
|
+
const fade = from + (target - from) * e;
|
|
197
|
+
morphFade.current = fade;
|
|
198
|
+
handle.setMorph(fade, fade);
|
|
199
|
+
const p = handle.render();
|
|
200
|
+
if (p) pictureSV.value = p;
|
|
201
|
+
if (prog < 1) {
|
|
202
|
+
morphRaf.current = requestAnimationFrame(step);
|
|
203
|
+
} else {
|
|
204
|
+
morphRaf.current = null;
|
|
205
|
+
morphFade.current = target;
|
|
206
|
+
handle.setChartType(target); // lock the exact endpoint
|
|
207
|
+
const q = handle.render();
|
|
208
|
+
if (q) pictureSV.value = q;
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
morphRaf.current = requestAnimationFrame(step);
|
|
212
|
+
|
|
213
|
+
return () => {
|
|
214
|
+
if (morphRaf.current != null) {
|
|
215
|
+
cancelAnimationFrame(morphRaf.current);
|
|
216
|
+
morphRaf.current = null;
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
}, [handle, chartType, transitionMs, pictureSV]);
|
|
220
|
+
|
|
150
221
|
// Classifies a touch point into the candle area vs. an axis strip. Axis
|
|
151
222
|
// strips always own their gesture (scale price/time) and take priority over
|
|
152
223
|
// the crosshair: an axis touch never opens, moves, or dismisses it.
|
package/src/index.ts
CHANGED
package/src/jsi.d.ts
CHANGED
|
@@ -23,6 +23,10 @@ export interface ChartHandle {
|
|
|
23
23
|
* Affects initial framing only; an explicit setVisibleRange takes precedence.
|
|
24
24
|
*/
|
|
25
25
|
setDefaultCandleWidth(px: number): void;
|
|
26
|
+
/** Render mode: 0 = candlesticks (default), 1 = line chart (close polyline). */
|
|
27
|
+
setChartType(mode: number): void;
|
|
28
|
+
/** Candle↔line morph blend: collapse folds candles to close, fade crossfades. */
|
|
29
|
+
setMorph(collapse: number, fade: number): void;
|
|
26
30
|
/** Shifts the visible range by `dx`/`dy` pixels and returns a fresh picture. */
|
|
27
31
|
pan(dx: number, dy: number): SkPicture | null;
|
|
28
32
|
/**
|
|
@@ -138,6 +142,26 @@ export interface ChartHandle {
|
|
|
138
142
|
color: number,
|
|
139
143
|
width: number,
|
|
140
144
|
): void;
|
|
145
|
+
/**
|
|
146
|
+
* Configures the Bollinger Bands overlay (three price-pane lines + optional
|
|
147
|
+
* fill between the bands). source/basisKind mirror setOverlays' encodings;
|
|
148
|
+
* colors are packed 0xAARRGGBB; fillOpacity is 0..1.
|
|
149
|
+
*/
|
|
150
|
+
setBollinger(spec: {
|
|
151
|
+
enabled: boolean;
|
|
152
|
+
period: number;
|
|
153
|
+
mult: number;
|
|
154
|
+
source: number;
|
|
155
|
+
basisKind: number;
|
|
156
|
+
upperColor: number;
|
|
157
|
+
upperWidth: number;
|
|
158
|
+
middleColor: number;
|
|
159
|
+
middleWidth: number;
|
|
160
|
+
lowerColor: number;
|
|
161
|
+
lowerWidth: number;
|
|
162
|
+
fillEnabled: boolean;
|
|
163
|
+
fillOpacity: number;
|
|
164
|
+
}): void;
|
|
141
165
|
/** True while any axis-label fade is still in progress. Drives a RAF loop. */
|
|
142
166
|
isAnimating(): boolean;
|
|
143
167
|
render(): SkPicture | null;
|
package/src/theme.ts
CHANGED
|
@@ -18,6 +18,7 @@ export const COLOR_KEYS: Partial<Record<keyof VroomTheme, number>> = {
|
|
|
18
18
|
wickBear: 13, // VROOM_COLOR_WICK_BEAR
|
|
19
19
|
accentBull: 14, // VROOM_COLOR_ACCENT_BULL
|
|
20
20
|
accentBear: 15, // VROOM_COLOR_ACCENT_BEAR
|
|
21
|
+
lineColor: 16, // VROOM_COLOR_LINE
|
|
21
22
|
};
|
|
22
23
|
|
|
23
24
|
// Maps each numeric VroomTheme field to its VroomFloatKey index.
|
|
@@ -25,6 +26,7 @@ export const FLOAT_KEYS: Partial<Record<keyof VroomTheme, number>> = {
|
|
|
25
26
|
wickWidth: 1, // VROOM_FLOAT_WICK_WIDTH_PX
|
|
26
27
|
candleRadius: 8, // VROOM_FLOAT_CANDLE_RADIUS_PX
|
|
27
28
|
volumeRadius: 10, // VROOM_FLOAT_VOLUME_RADIUS_PX
|
|
29
|
+
lineWidth: 11, // VROOM_FLOAT_LINE_WIDTH_PX
|
|
28
30
|
};
|
|
29
31
|
|
|
30
32
|
// Maps each boolean VroomTheme field to its VroomFloatKey index (pushed as 0/1).
|
package/src/types.ts
CHANGED
package/src/useChartCore.ts
CHANGED
|
@@ -6,7 +6,9 @@ import type { ChartHandle } from './jsi.d';
|
|
|
6
6
|
import { packCandles } from './packCandles';
|
|
7
7
|
import { applyTheme, parseColor } from './theme';
|
|
8
8
|
import type {
|
|
9
|
+
BollingerBandsConfig,
|
|
9
10
|
Candle,
|
|
11
|
+
ChartType,
|
|
10
12
|
MACDConfig,
|
|
11
13
|
MovingAverageOverlay,
|
|
12
14
|
RSIConfig,
|
|
@@ -37,6 +39,35 @@ function overlayToNumeric(o: MovingAverageOverlay) {
|
|
|
37
39
|
};
|
|
38
40
|
}
|
|
39
41
|
|
|
42
|
+
// Bollinger defaults: blue bands / orange basis, matching the repo palette.
|
|
43
|
+
const DEFAULT_BB_BAND_COLOR = 0xff2962ff;
|
|
44
|
+
const DEFAULT_BB_BASIS_COLOR = 0xffff6d00;
|
|
45
|
+
|
|
46
|
+
function bollingerToSpec(cfg: BollingerBandsConfig | undefined) {
|
|
47
|
+
const srcIdx = cfg?.source ? MA_SOURCES.indexOf(cfg.source) : 0;
|
|
48
|
+
return {
|
|
49
|
+
enabled: cfg?.enabled ?? false,
|
|
50
|
+
period: cfg?.period ?? 20,
|
|
51
|
+
mult: cfg?.stdDev ?? 2,
|
|
52
|
+
source: srcIdx < 0 ? 0 : srcIdx,
|
|
53
|
+
basisKind: cfg?.basis === 'ema' ? 1 : 0,
|
|
54
|
+
upperColor:
|
|
55
|
+
(cfg?.upperColor != null ? parseColor(cfg.upperColor) : null) ??
|
|
56
|
+
DEFAULT_BB_BAND_COLOR,
|
|
57
|
+
upperWidth: cfg?.upperWidth ?? 1,
|
|
58
|
+
middleColor:
|
|
59
|
+
(cfg?.middleColor != null ? parseColor(cfg.middleColor) : null) ??
|
|
60
|
+
DEFAULT_BB_BASIS_COLOR,
|
|
61
|
+
middleWidth: cfg?.middleWidth ?? 1,
|
|
62
|
+
lowerColor:
|
|
63
|
+
(cfg?.lowerColor != null ? parseColor(cfg.lowerColor) : null) ??
|
|
64
|
+
DEFAULT_BB_BAND_COLOR,
|
|
65
|
+
lowerWidth: cfg?.lowerWidth ?? 1,
|
|
66
|
+
fillEnabled: cfg?.fill ?? true,
|
|
67
|
+
fillOpacity: cfg?.fillOpacity ?? 0.1,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
40
71
|
let installed = false;
|
|
41
72
|
function ensureInstalled(): void {
|
|
42
73
|
if (installed) return;
|
|
@@ -63,11 +94,13 @@ export function useChartCore(
|
|
|
63
94
|
size: { width: number; height: number; pxRatio?: number },
|
|
64
95
|
visibleRange?: VisibleRange,
|
|
65
96
|
defaultCandleWidth?: number,
|
|
97
|
+
chartType?: ChartType,
|
|
66
98
|
theme?: VroomTheme,
|
|
67
99
|
rsi?: RSIConfig,
|
|
68
100
|
macd?: MACDConfig,
|
|
69
101
|
movingAverages?: MovingAverageOverlay[],
|
|
70
102
|
vwap?: VWAPConfig,
|
|
103
|
+
bollingerBands?: BollingerBandsConfig,
|
|
71
104
|
): ChartCoreState {
|
|
72
105
|
const handleRef = useRef<ChartHandle | null>(null);
|
|
73
106
|
// Push setDefaultCandleWidth only once (first load): setCandles re-runs on
|
|
@@ -96,6 +129,7 @@ export function useChartCore(
|
|
|
96
129
|
const macdKey = macd ? JSON.stringify(macd) : '';
|
|
97
130
|
const maKey = movingAverages ? JSON.stringify(movingAverages) : '';
|
|
98
131
|
const vwapKey = vwap ? JSON.stringify(vwap) : '';
|
|
132
|
+
const bollingerKey = bollingerBands ? JSON.stringify(bollingerBands) : '';
|
|
99
133
|
|
|
100
134
|
useEffect(() => {
|
|
101
135
|
const h = handleRef.current;
|
|
@@ -119,6 +153,8 @@ export function useChartCore(
|
|
|
119
153
|
if (explicit) {
|
|
120
154
|
h.setVisibleRange(startMs, endMs);
|
|
121
155
|
}
|
|
156
|
+
// chartType / the candle↔line morph is driven separately (VroomChart owns the
|
|
157
|
+
// per-frame animation loop so it can update the picture SharedValue directly).
|
|
122
158
|
if (theme) {
|
|
123
159
|
applyTheme(h, theme);
|
|
124
160
|
}
|
|
@@ -143,12 +179,14 @@ export function useChartCore(
|
|
|
143
179
|
(vwap?.color != null ? parseColor(vwap.color) : null) ?? 0xff00bcd4,
|
|
144
180
|
vwap?.width ?? 1.5,
|
|
145
181
|
);
|
|
182
|
+
h.setBollinger(bollingerToSpec(bollingerBands));
|
|
146
183
|
// TODO(rn-parity): mirror the web `liquidity` overlay here (setLiquidity +
|
|
147
184
|
// the VroomBand structs in the JSI handle) — web-only for now.
|
|
148
185
|
setPicture(h.render());
|
|
149
|
-
// theme/rsi/macd/movingAverages/vwap are represented by
|
|
186
|
+
// theme/rsi/macd/movingAverages/vwap/bollingerBands are represented by
|
|
187
|
+
// their *Key deps.
|
|
150
188
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
151
|
-
}, [candles, size.width, size.height, size.pxRatio, explicit, startMs, endMs, defaultCandleWidth, themeKey, rsiKey, macdKey, maKey, vwapKey]);
|
|
189
|
+
}, [candles, size.width, size.height, size.pxRatio, explicit, startMs, endMs, defaultCandleWidth, themeKey, rsiKey, macdKey, maKey, vwapKey, bollingerKey]);
|
|
152
190
|
|
|
153
191
|
return { handle: handleRef.current, picture };
|
|
154
192
|
}
|