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/lib/index.mjs
CHANGED
|
@@ -72,8 +72,10 @@ var FLOAT_KEYS = {
|
|
|
72
72
|
// VROOM_FLOAT_CANDLE_RADIUS_PX
|
|
73
73
|
volumeRadius: 10,
|
|
74
74
|
// VROOM_FLOAT_VOLUME_RADIUS_PX
|
|
75
|
-
lineWidth: 11
|
|
75
|
+
lineWidth: 11,
|
|
76
76
|
// VROOM_FLOAT_LINE_WIDTH_PX
|
|
77
|
+
lineGradientOpacity: 12
|
|
78
|
+
// VROOM_FLOAT_LINE_GRADIENT_OPACITY
|
|
77
79
|
};
|
|
78
80
|
var BOOL_KEYS = {
|
|
79
81
|
wickRoundCap: 9
|
|
@@ -119,16 +121,43 @@ var MA_SOURCES = [
|
|
|
119
121
|
"hlc3",
|
|
120
122
|
"ohlc4"
|
|
121
123
|
];
|
|
124
|
+
var inheritColor = (v) => (v != null ? parseColor(v) : null) ?? 0;
|
|
122
125
|
function overlayToNumeric(o) {
|
|
123
126
|
const srcIdx = o.source ? MA_SOURCES.indexOf(o.source) : 0;
|
|
124
127
|
return {
|
|
125
|
-
kind: o.
|
|
126
|
-
period: o.
|
|
128
|
+
kind: o.maType === "ema" ? 1 : 0,
|
|
129
|
+
period: o.period,
|
|
127
130
|
source: srcIdx < 0 ? 0 : srcIdx,
|
|
128
131
|
color: (o.color != null ? parseColor(o.color) : null) ?? 4280902399,
|
|
129
132
|
width: o.width ?? 1.5
|
|
130
133
|
};
|
|
131
134
|
}
|
|
135
|
+
function rsiToSpec(cfg) {
|
|
136
|
+
return {
|
|
137
|
+
enabled: cfg?.enabled ?? false,
|
|
138
|
+
period: cfg?.period ?? 14,
|
|
139
|
+
upperBand: cfg?.upperBand ?? 70,
|
|
140
|
+
lowerBand: cfg?.lowerBand ?? 30,
|
|
141
|
+
maPeriod: cfg?.maPeriod ?? 14,
|
|
142
|
+
maKind: cfg?.maType === "ema" ? 1 : 0,
|
|
143
|
+
maVisible: cfg?.maVisible ?? true,
|
|
144
|
+
lineColor: inheritColor(cfg?.lineColor),
|
|
145
|
+
lineWidth: cfg?.lineWidth ?? -1,
|
|
146
|
+
lineVisible: cfg?.lineVisible ?? true,
|
|
147
|
+
maColor: inheritColor(cfg?.maColor),
|
|
148
|
+
maWidth: cfg?.maWidth ?? -1,
|
|
149
|
+
bandColor: inheritColor(cfg?.bandColor),
|
|
150
|
+
bandsVisible: cfg?.bandsVisible ?? true
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
function vwapToSpec(cfg) {
|
|
154
|
+
return {
|
|
155
|
+
enabled: cfg?.enabled ?? false,
|
|
156
|
+
resetOffsetMin: cfg?.resetMinutes ?? 0,
|
|
157
|
+
color: inheritColor(cfg?.color),
|
|
158
|
+
width: cfg?.width ?? -1
|
|
159
|
+
};
|
|
160
|
+
}
|
|
132
161
|
var DEFAULT_BB_BAND_COLOR = 4280902399;
|
|
133
162
|
var DEFAULT_BB_BASIS_COLOR = 4294929664;
|
|
134
163
|
function bollingerToSpec(cfg) {
|
|
@@ -138,17 +167,79 @@ function bollingerToSpec(cfg) {
|
|
|
138
167
|
period: cfg?.period ?? 20,
|
|
139
168
|
mult: cfg?.stdDev ?? 2,
|
|
140
169
|
source: srcIdx < 0 ? 0 : srcIdx,
|
|
141
|
-
basisKind: cfg?.
|
|
170
|
+
basisKind: cfg?.maType === "ema" ? 1 : 0,
|
|
142
171
|
upperColor: (cfg?.upperColor != null ? parseColor(cfg.upperColor) : null) ?? DEFAULT_BB_BAND_COLOR,
|
|
143
172
|
upperWidth: cfg?.upperWidth ?? 1,
|
|
144
173
|
middleColor: (cfg?.middleColor != null ? parseColor(cfg.middleColor) : null) ?? DEFAULT_BB_BASIS_COLOR,
|
|
145
174
|
middleWidth: cfg?.middleWidth ?? 1,
|
|
146
175
|
lowerColor: (cfg?.lowerColor != null ? parseColor(cfg.lowerColor) : null) ?? DEFAULT_BB_BAND_COLOR,
|
|
147
176
|
lowerWidth: cfg?.lowerWidth ?? 1,
|
|
148
|
-
fillEnabled: cfg?.
|
|
177
|
+
fillEnabled: cfg?.fillVisible ?? true,
|
|
149
178
|
fillOpacity: cfg?.fillOpacity ?? 0.1
|
|
150
179
|
};
|
|
151
180
|
}
|
|
181
|
+
function macdToSpec(cfg) {
|
|
182
|
+
const srcIdx = cfg?.source ? MA_SOURCES.indexOf(cfg.source) : 0;
|
|
183
|
+
return {
|
|
184
|
+
enabled: cfg?.enabled ?? false,
|
|
185
|
+
fast: cfg?.fast ?? 12,
|
|
186
|
+
slow: cfg?.slow ?? 26,
|
|
187
|
+
signal: cfg?.signal ?? 9,
|
|
188
|
+
source: srcIdx < 0 ? 0 : srcIdx,
|
|
189
|
+
maKind: cfg?.maType === "sma" ? 0 : 1,
|
|
190
|
+
signalMaKind: cfg?.signalMaType === "sma" ? 0 : 1,
|
|
191
|
+
lineColor: inheritColor(cfg?.lineColor),
|
|
192
|
+
lineWidth: cfg?.lineWidth ?? -1,
|
|
193
|
+
lineVisible: cfg?.lineVisible ?? true,
|
|
194
|
+
signalColor: inheritColor(cfg?.signalColor),
|
|
195
|
+
signalWidth: cfg?.signalWidth ?? -1,
|
|
196
|
+
signalVisible: cfg?.signalVisible ?? true,
|
|
197
|
+
histVisible: cfg?.histogramVisible ?? true,
|
|
198
|
+
histUpColor: inheritColor(cfg?.histogramUpColor),
|
|
199
|
+
histUpFadingColor: inheritColor(cfg?.histogramUpFadingColor),
|
|
200
|
+
histDownColor: inheritColor(cfg?.histogramDownColor),
|
|
201
|
+
histDownFadingColor: inheritColor(cfg?.histogramDownFadingColor),
|
|
202
|
+
zeroColor: inheritColor(cfg?.zeroLineColor),
|
|
203
|
+
zeroVisible: cfg?.zeroLineVisible ?? true
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
function volumeToSpec(cfg) {
|
|
207
|
+
return {
|
|
208
|
+
enabled: cfg?.enabled ?? true,
|
|
209
|
+
heightFrac: cfg?.height ?? -1,
|
|
210
|
+
opacity: cfg?.opacity ?? -1,
|
|
211
|
+
radiusPx: cfg?.radius ?? -1,
|
|
212
|
+
upColor: (cfg?.upColor != null ? parseColor(cfg.upColor) : null) ?? 0,
|
|
213
|
+
downColor: (cfg?.downColor != null ? parseColor(cfg.downColor) : null) ?? 0
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
var DEFAULT_PRICE_LINE_COLOR = 4293874512;
|
|
217
|
+
var DEFAULT_PRICE_LINE_BODY_BG = 3642499368;
|
|
218
|
+
var DEFAULT_PRICE_LINE_HOVER_BOOST = 1.25;
|
|
219
|
+
var LINE_STYLES = { solid: 0, dotted: 1, dashed: 2 };
|
|
220
|
+
var PRICE_LINE_DRAGGABLE = 1 << 0;
|
|
221
|
+
var PRICE_LINE_CLOSABLE = 1 << 1;
|
|
222
|
+
var PRICE_LINE_AXIS_LABEL = 1 << 2;
|
|
223
|
+
var PRICE_LINE_EXTEND_LEFT = 1 << 3;
|
|
224
|
+
function priceLinesToSpec(cfg) {
|
|
225
|
+
return {
|
|
226
|
+
lines: cfg.lines.map((l) => ({
|
|
227
|
+
price: l.price,
|
|
228
|
+
color: (l.color != null ? parseColor(l.color) : null) ?? DEFAULT_PRICE_LINE_COLOR,
|
|
229
|
+
width: l.width ?? 1,
|
|
230
|
+
lineStyle: LINE_STYLES[l.lineStyle ?? "dotted"],
|
|
231
|
+
text: l.text ?? "",
|
|
232
|
+
quantity: l.quantity ?? "",
|
|
233
|
+
flags: (l.draggable ? PRICE_LINE_DRAGGABLE : 0) | (cfg.hasCloseHandler && l.closable !== false ? PRICE_LINE_CLOSABLE : 0) | (l.axisLabel !== false ? PRICE_LINE_AXIS_LABEL : 0) | (l.extendLeft !== false ? PRICE_LINE_EXTEND_LEFT : 0)
|
|
234
|
+
})),
|
|
235
|
+
bodyBg: (cfg.style?.bodyBackground != null ? parseColor(cfg.style.bodyBackground) : null) ?? DEFAULT_PRICE_LINE_BODY_BG,
|
|
236
|
+
fontSizePx: cfg.style?.fontSize ?? 0,
|
|
237
|
+
lineLengthFrac: cfg.style?.inset ?? 0,
|
|
238
|
+
align: cfg.style?.align === "left" ? 0 : cfg.style?.align === "center" ? 1 : 2,
|
|
239
|
+
hoverBoost: cfg.style?.hoverBoost ?? DEFAULT_PRICE_LINE_HOVER_BOOST
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
var EMPTY_PRICE_LINES = priceLinesToSpec({ lines: [], hasCloseHandler: false });
|
|
152
243
|
var installed = false;
|
|
153
244
|
function ensureInstalled() {
|
|
154
245
|
if (installed) return;
|
|
@@ -159,9 +250,10 @@ function ensureInstalled() {
|
|
|
159
250
|
}
|
|
160
251
|
installed = true;
|
|
161
252
|
}
|
|
162
|
-
function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType, theme, rsi, macd, movingAverages, vwap, bollingerBands) {
|
|
253
|
+
function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType, theme, rsi, macd, movingAverages, vwap, bollingerBands, volume, priceLines) {
|
|
163
254
|
const handleRef = useRef(null);
|
|
164
255
|
const defaultWidthAppliedRef = useRef(false);
|
|
256
|
+
const volumeCollapseRef = useRef(null);
|
|
165
257
|
const [picture, setPicture] = useState(null);
|
|
166
258
|
if (!handleRef.current && size.width > 0 && size.height > 0) {
|
|
167
259
|
ensureInstalled();
|
|
@@ -176,6 +268,8 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
|
|
|
176
268
|
const maKey = movingAverages ? JSON.stringify(movingAverages) : "";
|
|
177
269
|
const vwapKey = vwap ? JSON.stringify(vwap) : "";
|
|
178
270
|
const bollingerKey = bollingerBands ? JSON.stringify(bollingerBands) : "";
|
|
271
|
+
const volumeKey = volume ? JSON.stringify(volume) : "";
|
|
272
|
+
const priceLinesKey = priceLines ? JSON.stringify(priceLines) : "";
|
|
179
273
|
useEffect(() => {
|
|
180
274
|
const h = handleRef.current;
|
|
181
275
|
if (!h) return;
|
|
@@ -193,31 +287,44 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
|
|
|
193
287
|
if (theme) {
|
|
194
288
|
applyTheme(h, theme);
|
|
195
289
|
}
|
|
196
|
-
h.setRSI(
|
|
197
|
-
|
|
198
|
-
rsi?.period ?? 14,
|
|
199
|
-
rsi?.upperBand ?? 70,
|
|
200
|
-
rsi?.lowerBand ?? 30,
|
|
201
|
-
rsi?.maEnabled ?? true,
|
|
202
|
-
rsi?.maPeriod ?? 14
|
|
203
|
-
);
|
|
204
|
-
h.setMACD(
|
|
205
|
-
macd?.enabled ?? false,
|
|
206
|
-
macd?.fast ?? 12,
|
|
207
|
-
macd?.slow ?? 26,
|
|
208
|
-
macd?.signal ?? 9
|
|
209
|
-
);
|
|
290
|
+
h.setRSI(rsiToSpec(rsi));
|
|
291
|
+
h.setMACD(macdToSpec(macd));
|
|
210
292
|
h.setOverlays((movingAverages ?? []).map(overlayToNumeric));
|
|
211
|
-
h.setVWAP(
|
|
212
|
-
vwap?.enabled ?? false,
|
|
213
|
-
vwap?.resetMinutes ?? 0,
|
|
214
|
-
(vwap?.color != null ? parseColor(vwap.color) : null) ?? 4278238420,
|
|
215
|
-
vwap?.width ?? 1.5
|
|
216
|
-
);
|
|
293
|
+
h.setVWAP(vwapToSpec(vwap));
|
|
217
294
|
h.setBollinger(bollingerToSpec(bollingerBands));
|
|
295
|
+
h.setVolume(volumeToSpec(volume));
|
|
296
|
+
const collapse = volumeCollapseRef.current;
|
|
297
|
+
if (collapse) h.setVolumeCollapse(collapse.t, collapse.easing);
|
|
298
|
+
h.setPriceLines(
|
|
299
|
+
priceLines?.lines.length ? priceLinesToSpec(priceLines) : EMPTY_PRICE_LINES
|
|
300
|
+
);
|
|
218
301
|
setPicture(h.render());
|
|
219
|
-
}, [candles, size.width, size.height, size.pxRatio, explicit, startMs, endMs, defaultCandleWidth, themeKey, rsiKey, macdKey, maKey, vwapKey, bollingerKey]);
|
|
220
|
-
return { handle: handleRef.current, picture };
|
|
302
|
+
}, [candles, size.width, size.height, size.pxRatio, explicit, startMs, endMs, defaultCandleWidth, themeKey, rsiKey, macdKey, maKey, vwapKey, bollingerKey, volumeKey, priceLinesKey]);
|
|
303
|
+
return { handle: handleRef.current, picture, volumeCollapseRef };
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// src/easing.ts
|
|
307
|
+
function ease(kind, p) {
|
|
308
|
+
switch (kind) {
|
|
309
|
+
case "linear":
|
|
310
|
+
return p;
|
|
311
|
+
case "ease-in":
|
|
312
|
+
return p * p;
|
|
313
|
+
case "ease-out":
|
|
314
|
+
return p * (2 - p);
|
|
315
|
+
default:
|
|
316
|
+
return p * p * (3 - 2 * p);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
var EASINGS = [
|
|
320
|
+
"linear",
|
|
321
|
+
"ease-in",
|
|
322
|
+
"ease-out",
|
|
323
|
+
"ease-in-out"
|
|
324
|
+
];
|
|
325
|
+
function easingIndex(kind) {
|
|
326
|
+
const i = kind ? EASINGS.indexOf(kind) : -1;
|
|
327
|
+
return i < 0 ? EASINGS.indexOf("ease-in-out") : i;
|
|
221
328
|
}
|
|
222
329
|
|
|
223
330
|
// src/VroomChart.tsx
|
|
@@ -231,15 +338,22 @@ function VroomChart(props) {
|
|
|
231
338
|
defaultCandleWidth,
|
|
232
339
|
chartType,
|
|
233
340
|
transitionMs,
|
|
341
|
+
transitionEasing,
|
|
234
342
|
theme,
|
|
235
343
|
rsi,
|
|
236
344
|
macd,
|
|
237
345
|
movingAverages,
|
|
238
346
|
vwap,
|
|
239
347
|
bollingerBands,
|
|
348
|
+
volume,
|
|
240
349
|
crosshairOffset = 40,
|
|
241
350
|
onCrosshair,
|
|
242
|
-
onViewportChange
|
|
351
|
+
onViewportChange,
|
|
352
|
+
priceLines,
|
|
353
|
+
priceLinesStyle,
|
|
354
|
+
onPriceLineDrag,
|
|
355
|
+
onPriceLineDragEnd,
|
|
356
|
+
onPriceLineClose
|
|
243
357
|
} = props;
|
|
244
358
|
const [measured, setMeasured] = useState2({ width: 0, height: 0 });
|
|
245
359
|
const width = widthProp ?? measured.width;
|
|
@@ -251,7 +365,15 @@ function VroomChart(props) {
|
|
|
251
365
|
(prev) => prev.width === w && prev.height === h ? prev : { width: w, height: h }
|
|
252
366
|
);
|
|
253
367
|
}, []);
|
|
254
|
-
const
|
|
368
|
+
const priceLinesProp = useMemo(
|
|
369
|
+
() => priceLines ? {
|
|
370
|
+
lines: priceLines,
|
|
371
|
+
style: priceLinesStyle,
|
|
372
|
+
hasCloseHandler: onPriceLineClose != null
|
|
373
|
+
} : void 0,
|
|
374
|
+
[priceLines, priceLinesStyle, onPriceLineClose]
|
|
375
|
+
);
|
|
376
|
+
const { handle, picture, volumeCollapseRef } = useChartCore(
|
|
255
377
|
candles,
|
|
256
378
|
{ width, height },
|
|
257
379
|
visibleRange,
|
|
@@ -262,7 +384,9 @@ function VroomChart(props) {
|
|
|
262
384
|
macd,
|
|
263
385
|
movingAverages,
|
|
264
386
|
vwap,
|
|
265
|
-
bollingerBands
|
|
387
|
+
bollingerBands,
|
|
388
|
+
volume,
|
|
389
|
+
priceLinesProp
|
|
266
390
|
);
|
|
267
391
|
const emptyPicture = useMemo(() => {
|
|
268
392
|
const rec = Skia.PictureRecorder();
|
|
@@ -309,6 +433,8 @@ function VroomChart(props) {
|
|
|
309
433
|
const morphRaf = useRef2(null);
|
|
310
434
|
const morphFade = useRef2(null);
|
|
311
435
|
const morphHandle = useRef2(null);
|
|
436
|
+
const easingRef = useRef2(transitionEasing);
|
|
437
|
+
easingRef.current = transitionEasing;
|
|
312
438
|
useEffect2(() => {
|
|
313
439
|
if (!handle) return void 0;
|
|
314
440
|
const target = chartType === "line" ? 1 : 0;
|
|
@@ -338,8 +464,7 @@ function VroomChart(props) {
|
|
|
338
464
|
const step = (now) => {
|
|
339
465
|
if (startTs == null) startTs = now;
|
|
340
466
|
const prog = Math.min(1, (now - startTs) / dur);
|
|
341
|
-
const
|
|
342
|
-
const fade = from + (target - from) * e;
|
|
467
|
+
const fade = from + (target - from) * ease(easingRef.current, prog);
|
|
343
468
|
morphFade.current = fade;
|
|
344
469
|
handle.setMorph(fade, fade);
|
|
345
470
|
const p = handle.render();
|
|
@@ -362,6 +487,51 @@ function VroomChart(props) {
|
|
|
362
487
|
}
|
|
363
488
|
};
|
|
364
489
|
}, [handle, chartType, transitionMs, pictureSV]);
|
|
490
|
+
const volumeRaf = useRef2(null);
|
|
491
|
+
const volumeHandle = useRef2(null);
|
|
492
|
+
useEffect2(() => {
|
|
493
|
+
if (!handle) return void 0;
|
|
494
|
+
const target = volume?.enabled ?? true ? 0 : 1;
|
|
495
|
+
const easing = easingIndex(easingRef.current);
|
|
496
|
+
if (volumeHandle.current !== handle || volumeCollapseRef.current == null) {
|
|
497
|
+
volumeHandle.current = handle;
|
|
498
|
+
volumeCollapseRef.current = { t: target, easing };
|
|
499
|
+
return void 0;
|
|
500
|
+
}
|
|
501
|
+
if (volumeCollapseRef.current.t === target) return void 0;
|
|
502
|
+
if (volumeRaf.current != null) {
|
|
503
|
+
cancelAnimationFrame(volumeRaf.current);
|
|
504
|
+
volumeRaf.current = null;
|
|
505
|
+
}
|
|
506
|
+
const dur = Math.max(0, transitionMs ?? 300);
|
|
507
|
+
if (dur === 0) {
|
|
508
|
+
volumeCollapseRef.current = { t: target, easing };
|
|
509
|
+
handle.setVolumeCollapse(target, easing);
|
|
510
|
+
const p = handle.render();
|
|
511
|
+
if (p) pictureSV.value = p;
|
|
512
|
+
return void 0;
|
|
513
|
+
}
|
|
514
|
+
const from = volumeCollapseRef.current.t;
|
|
515
|
+
let startTs = null;
|
|
516
|
+
const step = (now) => {
|
|
517
|
+
if (startTs == null) startTs = now;
|
|
518
|
+
const prog = Math.min(1, (now - startTs) / dur);
|
|
519
|
+
const t = prog < 1 ? from + (target - from) * prog : target;
|
|
520
|
+
const kind = easingIndex(easingRef.current);
|
|
521
|
+
volumeCollapseRef.current = { t, easing: kind };
|
|
522
|
+
handle.setVolumeCollapse(t, kind);
|
|
523
|
+
const p = handle.render();
|
|
524
|
+
if (p) pictureSV.value = p;
|
|
525
|
+
volumeRaf.current = prog < 1 ? requestAnimationFrame(step) : null;
|
|
526
|
+
};
|
|
527
|
+
volumeRaf.current = requestAnimationFrame(step);
|
|
528
|
+
return () => {
|
|
529
|
+
if (volumeRaf.current != null) {
|
|
530
|
+
cancelAnimationFrame(volumeRaf.current);
|
|
531
|
+
volumeRaf.current = null;
|
|
532
|
+
}
|
|
533
|
+
};
|
|
534
|
+
}, [handle, volume?.enabled, transitionMs, pictureSV, volumeCollapseRef]);
|
|
365
535
|
const hitAxis = useCallback(
|
|
366
536
|
(x, y) => {
|
|
367
537
|
if (!handle) return "chart";
|
|
@@ -375,12 +545,33 @@ function VroomChart(props) {
|
|
|
375
545
|
},
|
|
376
546
|
[handle, width, height]
|
|
377
547
|
);
|
|
378
|
-
const
|
|
379
|
-
|
|
548
|
+
const hitPriceLine = useCallback(
|
|
549
|
+
(x, y) => {
|
|
550
|
+
if (!handle || !priceLines?.length) return null;
|
|
551
|
+
const hit = handle.hitTestPriceLine(x, y);
|
|
552
|
+
const line = hit ? priceLines[hit.index] : void 0;
|
|
553
|
+
return hit && line ? { index: hit.index, part: hit.part, line } : null;
|
|
554
|
+
},
|
|
555
|
+
[handle, priceLines]
|
|
380
556
|
);
|
|
557
|
+
const priceDrag = useRef2(
|
|
558
|
+
null
|
|
559
|
+
);
|
|
560
|
+
const panMode = useRef2("chart");
|
|
381
561
|
const pan = Gesture.Pan().runOnJS(true).maxPointers(1).onStart((e) => {
|
|
382
562
|
cancelDecay();
|
|
383
563
|
panMode.current = hitAxis(e.x, e.y);
|
|
564
|
+
priceDrag.current = null;
|
|
565
|
+
if (handle && panMode.current === "chart" && !crosshairActive.current) {
|
|
566
|
+
const pl = hitPriceLine(e.x, e.y);
|
|
567
|
+
if (pl && pl.part === 0) {
|
|
568
|
+
panMode.current = "price-line";
|
|
569
|
+
priceDrag.current = { index: pl.index, id: pl.line.id, price: pl.line.price };
|
|
570
|
+
handle.setPriceLineDrag(pl.index, pl.line.price);
|
|
571
|
+
const p = handle.render();
|
|
572
|
+
if (p) pictureSV.value = p;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
384
575
|
}).onChange((e) => {
|
|
385
576
|
if (!handle) return;
|
|
386
577
|
let next = null;
|
|
@@ -390,6 +581,15 @@ function VroomChart(props) {
|
|
|
390
581
|
next = handle.scaleTimeAxis(e.changeX);
|
|
391
582
|
} else if (panMode.current === "indicator") {
|
|
392
583
|
next = handle.pan(e.changeX, 0);
|
|
584
|
+
} else if (panMode.current === "price-line") {
|
|
585
|
+
const g = priceDrag.current;
|
|
586
|
+
if (!g) return;
|
|
587
|
+
const c = handle.coordAt(e.x, e.y);
|
|
588
|
+
if (!c) return;
|
|
589
|
+
g.price = c.price;
|
|
590
|
+
handle.setPriceLineDrag(g.index, c.price);
|
|
591
|
+
onPriceLineDrag?.(g.id, c.price);
|
|
592
|
+
next = handle.render();
|
|
393
593
|
} else if (crosshairActive.current) {
|
|
394
594
|
const ch = handle.setCrosshair(e.x, e.y - crosshairOffset);
|
|
395
595
|
if (ch) pictureSV.value = ch;
|
|
@@ -407,6 +607,15 @@ function VroomChart(props) {
|
|
|
407
607
|
maybeStartAnim();
|
|
408
608
|
}).onEnd((e) => {
|
|
409
609
|
if (!handle) return;
|
|
610
|
+
if (panMode.current === "price-line") {
|
|
611
|
+
const g = priceDrag.current;
|
|
612
|
+
priceDrag.current = null;
|
|
613
|
+
handle.setPriceLineDrag(-1, 0);
|
|
614
|
+
const p = handle.render();
|
|
615
|
+
if (p) pictureSV.value = p;
|
|
616
|
+
if (g) onPriceLineDragEnd?.(g.id, g.price);
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
410
619
|
if (panMode.current === "chart" && crosshairActive.current) return;
|
|
411
620
|
onViewportChange?.(0, 0);
|
|
412
621
|
if (panMode.current !== "chart" && panMode.current !== "indicator") return;
|
|
@@ -483,6 +692,7 @@ function VroomChart(props) {
|
|
|
483
692
|
const longPress = Gesture.LongPress().runOnJS(true).onStart((e) => {
|
|
484
693
|
if (!handle) return;
|
|
485
694
|
if (hitAxis(e.x, e.y) !== "chart") return;
|
|
695
|
+
if (hitPriceLine(e.x, e.y)) return;
|
|
486
696
|
cancelDecay();
|
|
487
697
|
crosshairActive.current = true;
|
|
488
698
|
const ch = handle.setCrosshair(e.x, e.y - crosshairOffset);
|
|
@@ -499,7 +709,13 @@ function VroomChart(props) {
|
|
|
499
709
|
});
|
|
500
710
|
});
|
|
501
711
|
const tap = Gesture.Tap().runOnJS(true).onStart((e) => {
|
|
502
|
-
if (!handle
|
|
712
|
+
if (!handle) return;
|
|
713
|
+
const pl = hitPriceLine(e.x, e.y);
|
|
714
|
+
if (pl && pl.part === 1) {
|
|
715
|
+
onPriceLineClose?.(pl.line.id);
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
if (!crosshairActive.current) return;
|
|
503
719
|
if (hitAxis(e.x, e.y) !== "chart") return;
|
|
504
720
|
crosshairActive.current = false;
|
|
505
721
|
const ch = handle.clearCrosshair();
|