react-native-vroom-chart 0.4.0 → 0.6.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 +265 -1
- package/cpp/VroomJsiInstaller.cpp +11 -1
- package/cpp/VroomSkiaContext.cpp +15 -7
- package/cpp/_core_include/vroom/vroom_chart.h +95 -0
- package/cpp/_core_src/bollinger.cpp +43 -0
- package/cpp/_core_src/bollinger.h +31 -0
- package/cpp/_core_src/chart.cpp +56 -0
- package/cpp/_core_src/chart.h +45 -0
- package/cpp/_core_src/chart_facade.cpp +117 -0
- package/cpp/_core_src/ma_overlay.cpp +67 -0
- package/cpp/_core_src/ma_overlay.h +21 -0
- 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/lib/index.d.mts +201 -1
- package/lib/index.d.ts +201 -1
- package/lib/index.js +122 -7
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +122 -7
- package/lib/index.mjs.map +1 -1
- package/package.json +20 -8
- package/react-native-vroom-chart.podspec +1 -1
- package/src/VroomChart.tsx +95 -8
- package/src/index.ts +3 -0
- package/src/jsi.d.ts +68 -0
- package/src/types.ts +3 -0
- package/src/useChartCore.ts +98 -2
package/src/VroomChart.tsx
CHANGED
|
@@ -48,9 +48,15 @@ export function VroomChart(props: VroomChartProps) {
|
|
|
48
48
|
macd,
|
|
49
49
|
movingAverages,
|
|
50
50
|
vwap,
|
|
51
|
+
bollingerBands,
|
|
51
52
|
crosshairOffset = 40,
|
|
52
53
|
onCrosshair,
|
|
53
54
|
onViewportChange,
|
|
55
|
+
priceLines,
|
|
56
|
+
priceLinesStyle,
|
|
57
|
+
onPriceLineDrag,
|
|
58
|
+
onPriceLineDragEnd,
|
|
59
|
+
onPriceLineClose,
|
|
54
60
|
} = props;
|
|
55
61
|
|
|
56
62
|
// Fill the parent by default: measure via onLayout. Explicit width/height
|
|
@@ -68,6 +74,20 @@ export function VroomChart(props: VroomChartProps) {
|
|
|
68
74
|
);
|
|
69
75
|
}, []);
|
|
70
76
|
|
|
77
|
+
// The close button is callback-gated, so whether a handler exists is part of
|
|
78
|
+
// what gets rendered.
|
|
79
|
+
const priceLinesProp = useMemo(
|
|
80
|
+
() =>
|
|
81
|
+
priceLines
|
|
82
|
+
? {
|
|
83
|
+
lines: priceLines,
|
|
84
|
+
style: priceLinesStyle,
|
|
85
|
+
hasCloseHandler: onPriceLineClose != null,
|
|
86
|
+
}
|
|
87
|
+
: undefined,
|
|
88
|
+
[priceLines, priceLinesStyle, onPriceLineClose],
|
|
89
|
+
);
|
|
90
|
+
|
|
71
91
|
const { handle, picture } = useChartCore(
|
|
72
92
|
candles,
|
|
73
93
|
{ width, height },
|
|
@@ -79,6 +99,8 @@ export function VroomChart(props: VroomChartProps) {
|
|
|
79
99
|
macd,
|
|
80
100
|
movingAverages,
|
|
81
101
|
vwap,
|
|
102
|
+
bollingerBands,
|
|
103
|
+
priceLinesProp,
|
|
82
104
|
);
|
|
83
105
|
|
|
84
106
|
// RN-Skia's recorder reads this SharedValue on the UI/render runtime, a beat
|
|
@@ -236,13 +258,31 @@ export function VroomChart(props: VroomChartProps) {
|
|
|
236
258
|
[handle, width, height],
|
|
237
259
|
);
|
|
238
260
|
|
|
261
|
+
// Hit-tests the price lines at a touch point, resolving the core's index back
|
|
262
|
+
// to the line it belongs to. Null when nothing was hit.
|
|
263
|
+
const hitPriceLine = useCallback(
|
|
264
|
+
(x: number, y: number) => {
|
|
265
|
+
if (!handle || !priceLines?.length) return null;
|
|
266
|
+
const hit = handle.hitTestPriceLine(x, y);
|
|
267
|
+
const line = hit ? priceLines[hit.index] : undefined;
|
|
268
|
+
return hit && line ? { index: hit.index, part: hit.part, line } : null;
|
|
269
|
+
},
|
|
270
|
+
[handle, priceLines],
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
// A price line being dragged vertically: its core index, its id, and the last
|
|
274
|
+
// previewed price (the payload for the drop).
|
|
275
|
+
const priceDrag = useRef<{ index: number; id: string; price: number } | null>(
|
|
276
|
+
null,
|
|
277
|
+
);
|
|
278
|
+
|
|
239
279
|
// Pan routes to different C++ mutators depending on where it started: the
|
|
240
280
|
// candle area (chart scroll / crosshair move), the y-axis strip (price
|
|
241
|
-
// scale), the x-axis strip (time scale),
|
|
242
|
-
// scroll only). We classify on onStart.
|
|
243
|
-
const panMode = useRef<
|
|
244
|
-
'chart'
|
|
245
|
-
);
|
|
281
|
+
// scale), the x-axis strip (time scale), the indicator pane (horizontal
|
|
282
|
+
// scroll only), or a draggable price line. We classify on onStart.
|
|
283
|
+
const panMode = useRef<
|
|
284
|
+
'chart' | 'price-axis' | 'time-axis' | 'indicator' | 'price-line'
|
|
285
|
+
>('chart');
|
|
246
286
|
|
|
247
287
|
const pan = Gesture.Pan()
|
|
248
288
|
.runOnJS(true)
|
|
@@ -252,6 +292,20 @@ export function VroomChart(props: VroomChartProps) {
|
|
|
252
292
|
// Always classify — an axis drag controls the axis even while the
|
|
253
293
|
// crosshair is up. Only a chart-area drag interacts with the crosshair.
|
|
254
294
|
panMode.current = hitAxis(e.x, e.y);
|
|
295
|
+
// A draggable price line takes the drag over from the chart. Seeding the
|
|
296
|
+
// preview at the committed price puts the label in drag styling before the
|
|
297
|
+
// first move, so the grab registers immediately.
|
|
298
|
+
priceDrag.current = null;
|
|
299
|
+
if (handle && panMode.current === 'chart' && !crosshairActive.current) {
|
|
300
|
+
const pl = hitPriceLine(e.x, e.y);
|
|
301
|
+
if (pl && pl.part === 0) {
|
|
302
|
+
panMode.current = 'price-line';
|
|
303
|
+
priceDrag.current = { index: pl.index, id: pl.line.id, price: pl.line.price };
|
|
304
|
+
handle.setPriceLineDrag(pl.index, pl.line.price);
|
|
305
|
+
const p = handle.render();
|
|
306
|
+
if (p) pictureSV.value = p;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
255
309
|
})
|
|
256
310
|
.onChange((e) => {
|
|
257
311
|
if (!handle) return;
|
|
@@ -264,6 +318,16 @@ export function VroomChart(props: VroomChartProps) {
|
|
|
264
318
|
// Drag in an indicator pane scrolls the candles horizontally only —
|
|
265
319
|
// no vertical price slide (the pane's scale is fixed).
|
|
266
320
|
next = handle.pan(e.changeX, 0);
|
|
321
|
+
} else if (panMode.current === 'price-line') {
|
|
322
|
+
// Preview the price under the finger; nothing is committed until the drop.
|
|
323
|
+
const g = priceDrag.current;
|
|
324
|
+
if (!g) return;
|
|
325
|
+
const c = handle.coordAt(e.x, e.y);
|
|
326
|
+
if (!c) return;
|
|
327
|
+
g.price = c.price;
|
|
328
|
+
handle.setPriceLineDrag(g.index, c.price);
|
|
329
|
+
onPriceLineDrag?.(g.id, c.price);
|
|
330
|
+
next = handle.render();
|
|
267
331
|
} else if (crosshairActive.current) {
|
|
268
332
|
// Chart area + crosshair up → the drag moves the crosshair instead of
|
|
269
333
|
// scrolling. Vertical line tracks the finger x; the dot/horizontal line
|
|
@@ -292,6 +356,18 @@ export function VroomChart(props: VroomChartProps) {
|
|
|
292
356
|
})
|
|
293
357
|
.onEnd((e) => {
|
|
294
358
|
if (!handle) return;
|
|
359
|
+
// Price-line drop. The preview always clears here: the line is a controlled
|
|
360
|
+
// prop, so it only really moves once the host restates it — which means a
|
|
361
|
+
// rejected (or ignored) move reverts on its own.
|
|
362
|
+
if (panMode.current === 'price-line') {
|
|
363
|
+
const g = priceDrag.current;
|
|
364
|
+
priceDrag.current = null;
|
|
365
|
+
handle.setPriceLineDrag(-1, 0);
|
|
366
|
+
const p = handle.render();
|
|
367
|
+
if (p) pictureSV.value = p;
|
|
368
|
+
if (g) onPriceLineDragEnd?.(g.id, g.price);
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
295
371
|
// A chart-area drag with the crosshair up just moved the crosshair —
|
|
296
372
|
// nothing about the viewport changed, and no momentum.
|
|
297
373
|
if (panMode.current === 'chart' && crosshairActive.current) return;
|
|
@@ -404,6 +480,9 @@ export function VroomChart(props: VroomChartProps) {
|
|
|
404
480
|
if (!handle) return;
|
|
405
481
|
// A long press on an axis strip controls the axis, never the crosshair.
|
|
406
482
|
if (hitAxis(e.x, e.y) !== 'chart') return;
|
|
483
|
+
// A press on a price line belongs to that line — dragging it or tapping its
|
|
484
|
+
// close button — so it must not raise the crosshair over the top.
|
|
485
|
+
if (hitPriceLine(e.x, e.y)) return;
|
|
407
486
|
cancelDecay();
|
|
408
487
|
crosshairActive.current = true;
|
|
409
488
|
const ch = handle.setCrosshair(e.x, e.y - crosshairOffset);
|
|
@@ -419,12 +498,20 @@ export function VroomChart(props: VroomChartProps) {
|
|
|
419
498
|
});
|
|
420
499
|
});
|
|
421
500
|
|
|
422
|
-
// A tap
|
|
423
|
-
//
|
|
501
|
+
// A tap activates a price line's close button, and otherwise dismisses the
|
|
502
|
+
// crosshair while it's up. Any other tap is a no-op, so it never interferes
|
|
503
|
+
// with normal pan/pinch.
|
|
424
504
|
const tap = Gesture.Tap()
|
|
425
505
|
.runOnJS(true)
|
|
426
506
|
.onStart((e) => {
|
|
427
|
-
if (!handle
|
|
507
|
+
if (!handle) return;
|
|
508
|
+
// The close button is a tap target whether or not the crosshair is up.
|
|
509
|
+
const pl = hitPriceLine(e.x, e.y);
|
|
510
|
+
if (pl && pl.part === 1) {
|
|
511
|
+
onPriceLineClose?.(pl.line.id);
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
if (!crosshairActive.current) return;
|
|
428
515
|
// A tap on an axis strip controls the axis, never dismisses the crosshair.
|
|
429
516
|
if (hitAxis(e.x, e.y) !== 'chart') return;
|
|
430
517
|
crosshairActive.current = false;
|
package/src/index.ts
CHANGED
package/src/jsi.d.ts
CHANGED
|
@@ -142,6 +142,74 @@ export interface ChartHandle {
|
|
|
142
142
|
color: number,
|
|
143
143
|
width: number,
|
|
144
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;
|
|
165
|
+
/**
|
|
166
|
+
* The continuous data coordinate at pixel (x, y) — not snapped to a candle
|
|
167
|
+
* slot. Null when there are no candles or the viewport is degenerate. Cheap to
|
|
168
|
+
* call at gesture rate (no rendering).
|
|
169
|
+
*/
|
|
170
|
+
coordAt(x: number, y: number): { timeMs: number; price: number } | null;
|
|
171
|
+
/**
|
|
172
|
+
* Replaces the full set of price status lines (plus their shared style).
|
|
173
|
+
* lineStyle: 0=solid,1=dotted,2=dashed; color/bodyBg are packed 0xAARRGGBB;
|
|
174
|
+
* flags is a bitmask (1=draggable, 2=closable, 4=axis label, 8=extend left);
|
|
175
|
+
* align: 0=left,1=center,2=right. Pass an empty `lines` array to clear.
|
|
176
|
+
*/
|
|
177
|
+
setPriceLines(spec: {
|
|
178
|
+
lines: {
|
|
179
|
+
price: number;
|
|
180
|
+
color: number;
|
|
181
|
+
width: number;
|
|
182
|
+
lineStyle: number;
|
|
183
|
+
text: string;
|
|
184
|
+
quantity: string;
|
|
185
|
+
flags: number;
|
|
186
|
+
}[];
|
|
187
|
+
bodyBg: number;
|
|
188
|
+
fontSizePx: number;
|
|
189
|
+
lineLengthFrac: number;
|
|
190
|
+
align: number;
|
|
191
|
+
hoverBoost: number;
|
|
192
|
+
}): void;
|
|
193
|
+
/**
|
|
194
|
+
* Hit-tests pixel (x, y) against the price lines. `part` is 0 for the line or
|
|
195
|
+
* its label body (the drag target) and 1 for the close button; close buttons
|
|
196
|
+
* win over lines and the nearest in y wins among several candidates. Only
|
|
197
|
+
* draggable lines report part 0 and only closable lines report part 1. Cheap
|
|
198
|
+
* to call at gesture rate (no rendering).
|
|
199
|
+
*/
|
|
200
|
+
hitTestPriceLine(x: number, y: number): { index: number; part: number } | null;
|
|
201
|
+
/**
|
|
202
|
+
* Marks a price line's segment as hovered so it renders highlighted; -1 clears.
|
|
203
|
+
* `part` matches hitTestPriceLine. Touch has no hover state, so this is here
|
|
204
|
+
* for pointer devices and web parity.
|
|
205
|
+
*/
|
|
206
|
+
setPriceLineHover(index: number, part: number): void;
|
|
207
|
+
/**
|
|
208
|
+
* Drives the live drag preview: the line, its label and its badge render at
|
|
209
|
+
* `price` with a ghost at the committed one. Pass -1 to end the preview. The
|
|
210
|
+
* committed price is untouched — restate setPriceLines to apply the move.
|
|
211
|
+
*/
|
|
212
|
+
setPriceLineDrag(index: number, price: number): void;
|
|
145
213
|
/** True while any axis-label fade is still in progress. Drives a RAF loop. */
|
|
146
214
|
isAnimating(): boolean;
|
|
147
215
|
render(): SkPicture | null;
|
package/src/types.ts
CHANGED
package/src/useChartCore.ts
CHANGED
|
@@ -6,10 +6,13 @@ 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,
|
|
10
11
|
ChartType,
|
|
11
12
|
MACDConfig,
|
|
12
13
|
MovingAverageOverlay,
|
|
14
|
+
PriceLine,
|
|
15
|
+
PriceLinesStyle,
|
|
13
16
|
RSIConfig,
|
|
14
17
|
VisibleRange,
|
|
15
18
|
VroomTheme,
|
|
@@ -38,6 +41,90 @@ function overlayToNumeric(o: MovingAverageOverlay) {
|
|
|
38
41
|
};
|
|
39
42
|
}
|
|
40
43
|
|
|
44
|
+
// Bollinger defaults: blue bands / orange basis, matching the repo palette.
|
|
45
|
+
const DEFAULT_BB_BAND_COLOR = 0xff2962ff;
|
|
46
|
+
const DEFAULT_BB_BASIS_COLOR = 0xffff6d00;
|
|
47
|
+
|
|
48
|
+
function bollingerToSpec(cfg: BollingerBandsConfig | undefined) {
|
|
49
|
+
const srcIdx = cfg?.source ? MA_SOURCES.indexOf(cfg.source) : 0;
|
|
50
|
+
return {
|
|
51
|
+
enabled: cfg?.enabled ?? false,
|
|
52
|
+
period: cfg?.period ?? 20,
|
|
53
|
+
mult: cfg?.stdDev ?? 2,
|
|
54
|
+
source: srcIdx < 0 ? 0 : srcIdx,
|
|
55
|
+
basisKind: cfg?.basis === 'ema' ? 1 : 0,
|
|
56
|
+
upperColor:
|
|
57
|
+
(cfg?.upperColor != null ? parseColor(cfg.upperColor) : null) ??
|
|
58
|
+
DEFAULT_BB_BAND_COLOR,
|
|
59
|
+
upperWidth: cfg?.upperWidth ?? 1,
|
|
60
|
+
middleColor:
|
|
61
|
+
(cfg?.middleColor != null ? parseColor(cfg.middleColor) : null) ??
|
|
62
|
+
DEFAULT_BB_BASIS_COLOR,
|
|
63
|
+
middleWidth: cfg?.middleWidth ?? 1,
|
|
64
|
+
lowerColor:
|
|
65
|
+
(cfg?.lowerColor != null ? parseColor(cfg.lowerColor) : null) ??
|
|
66
|
+
DEFAULT_BB_BAND_COLOR,
|
|
67
|
+
lowerWidth: cfg?.lowerWidth ?? 1,
|
|
68
|
+
fillEnabled: cfg?.fill ?? true,
|
|
69
|
+
fillOpacity: cfg?.fillOpacity ?? 0.1,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Price-line defaults: a soft red dotted rule with a dark translucent label,
|
|
74
|
+
// close in weight to the current-price indicator it sits beside.
|
|
75
|
+
const DEFAULT_PRICE_LINE_COLOR = 0xffef5350;
|
|
76
|
+
const DEFAULT_PRICE_LINE_BODY_BG = 0xd91c2128;
|
|
77
|
+
const DEFAULT_PRICE_LINE_HOVER_BOOST = 1.25;
|
|
78
|
+
|
|
79
|
+
const LINE_STYLES = { solid: 0, dotted: 1, dashed: 2 } as const;
|
|
80
|
+
|
|
81
|
+
// Mirrors VroomPriceLineFlags in packages/core/include/vroom/vroom_chart.h.
|
|
82
|
+
const PRICE_LINE_DRAGGABLE = 1 << 0;
|
|
83
|
+
const PRICE_LINE_CLOSABLE = 1 << 1;
|
|
84
|
+
const PRICE_LINE_AXIS_LABEL = 1 << 2;
|
|
85
|
+
const PRICE_LINE_EXTEND_LEFT = 1 << 3;
|
|
86
|
+
|
|
87
|
+
/** The price lines + their shared style, as the chart's props express them. */
|
|
88
|
+
export type PriceLinesProp = {
|
|
89
|
+
lines: PriceLine[];
|
|
90
|
+
style?: PriceLinesStyle;
|
|
91
|
+
/**
|
|
92
|
+
* Whether the host supplied a close handler. The close button is
|
|
93
|
+
* callback-gated, so with nothing for it to do it isn't drawn at all.
|
|
94
|
+
*/
|
|
95
|
+
hasCloseHandler: boolean;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
function priceLinesToSpec(cfg: PriceLinesProp) {
|
|
99
|
+
return {
|
|
100
|
+
lines: cfg.lines.map((l) => ({
|
|
101
|
+
price: l.price,
|
|
102
|
+
color:
|
|
103
|
+
(l.color != null ? parseColor(l.color) : null) ?? DEFAULT_PRICE_LINE_COLOR,
|
|
104
|
+
width: l.width ?? 1,
|
|
105
|
+
lineStyle: LINE_STYLES[l.lineStyle ?? 'dotted'],
|
|
106
|
+
text: l.text ?? '',
|
|
107
|
+
quantity: l.quantity ?? '',
|
|
108
|
+
flags:
|
|
109
|
+
(l.draggable ? PRICE_LINE_DRAGGABLE : 0) |
|
|
110
|
+
(cfg.hasCloseHandler && l.closable !== false ? PRICE_LINE_CLOSABLE : 0) |
|
|
111
|
+
(l.axisLabel !== false ? PRICE_LINE_AXIS_LABEL : 0) |
|
|
112
|
+
(l.extendLeft !== false ? PRICE_LINE_EXTEND_LEFT : 0),
|
|
113
|
+
})),
|
|
114
|
+
bodyBg:
|
|
115
|
+
(cfg.style?.bodyBackground != null ? parseColor(cfg.style.bodyBackground) : null) ??
|
|
116
|
+
DEFAULT_PRICE_LINE_BODY_BG,
|
|
117
|
+
fontSizePx: cfg.style?.fontSize ?? 0,
|
|
118
|
+
lineLengthFrac: cfg.style?.inset ?? 0,
|
|
119
|
+
align: cfg.style?.align === 'left' ? 0 : cfg.style?.align === 'center' ? 1 : 2,
|
|
120
|
+
hoverBoost: cfg.style?.hoverBoost ?? DEFAULT_PRICE_LINE_HOVER_BOOST,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Cleared overlay: no lines (the style values are irrelevant, but the spec shape
|
|
125
|
+
// requires them).
|
|
126
|
+
const EMPTY_PRICE_LINES = priceLinesToSpec({ lines: [], hasCloseHandler: false });
|
|
127
|
+
|
|
41
128
|
let installed = false;
|
|
42
129
|
function ensureInstalled(): void {
|
|
43
130
|
if (installed) return;
|
|
@@ -70,6 +157,8 @@ export function useChartCore(
|
|
|
70
157
|
macd?: MACDConfig,
|
|
71
158
|
movingAverages?: MovingAverageOverlay[],
|
|
72
159
|
vwap?: VWAPConfig,
|
|
160
|
+
bollingerBands?: BollingerBandsConfig,
|
|
161
|
+
priceLines?: PriceLinesProp,
|
|
73
162
|
): ChartCoreState {
|
|
74
163
|
const handleRef = useRef<ChartHandle | null>(null);
|
|
75
164
|
// Push setDefaultCandleWidth only once (first load): setCandles re-runs on
|
|
@@ -98,6 +187,8 @@ export function useChartCore(
|
|
|
98
187
|
const macdKey = macd ? JSON.stringify(macd) : '';
|
|
99
188
|
const maKey = movingAverages ? JSON.stringify(movingAverages) : '';
|
|
100
189
|
const vwapKey = vwap ? JSON.stringify(vwap) : '';
|
|
190
|
+
const bollingerKey = bollingerBands ? JSON.stringify(bollingerBands) : '';
|
|
191
|
+
const priceLinesKey = priceLines ? JSON.stringify(priceLines) : '';
|
|
101
192
|
|
|
102
193
|
useEffect(() => {
|
|
103
194
|
const h = handleRef.current;
|
|
@@ -147,12 +238,17 @@ export function useChartCore(
|
|
|
147
238
|
(vwap?.color != null ? parseColor(vwap.color) : null) ?? 0xff00bcd4,
|
|
148
239
|
vwap?.width ?? 1.5,
|
|
149
240
|
);
|
|
241
|
+
h.setBollinger(bollingerToSpec(bollingerBands));
|
|
242
|
+
h.setPriceLines(
|
|
243
|
+
priceLines?.lines.length ? priceLinesToSpec(priceLines) : EMPTY_PRICE_LINES,
|
|
244
|
+
);
|
|
150
245
|
// TODO(rn-parity): mirror the web `liquidity` overlay here (setLiquidity +
|
|
151
246
|
// the VroomBand structs in the JSI handle) — web-only for now.
|
|
152
247
|
setPicture(h.render());
|
|
153
|
-
// theme/rsi/macd/movingAverages/vwap are
|
|
248
|
+
// theme/rsi/macd/movingAverages/vwap/bollingerBands/priceLines are
|
|
249
|
+
// represented by their *Key deps.
|
|
154
250
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
155
|
-
}, [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, priceLinesKey]);
|
|
156
252
|
|
|
157
253
|
return { handle: handleRef.current, picture };
|
|
158
254
|
}
|