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.js
CHANGED
|
@@ -104,8 +104,10 @@ var FLOAT_KEYS = {
|
|
|
104
104
|
// VROOM_FLOAT_CANDLE_RADIUS_PX
|
|
105
105
|
volumeRadius: 10,
|
|
106
106
|
// VROOM_FLOAT_VOLUME_RADIUS_PX
|
|
107
|
-
lineWidth: 11
|
|
107
|
+
lineWidth: 11,
|
|
108
108
|
// VROOM_FLOAT_LINE_WIDTH_PX
|
|
109
|
+
lineGradientOpacity: 12
|
|
110
|
+
// VROOM_FLOAT_LINE_GRADIENT_OPACITY
|
|
109
111
|
};
|
|
110
112
|
var BOOL_KEYS = {
|
|
111
113
|
wickRoundCap: 9
|
|
@@ -151,16 +153,43 @@ var MA_SOURCES = [
|
|
|
151
153
|
"hlc3",
|
|
152
154
|
"ohlc4"
|
|
153
155
|
];
|
|
156
|
+
var inheritColor = (v) => (v != null ? parseColor(v) : null) ?? 0;
|
|
154
157
|
function overlayToNumeric(o) {
|
|
155
158
|
const srcIdx = o.source ? MA_SOURCES.indexOf(o.source) : 0;
|
|
156
159
|
return {
|
|
157
|
-
kind: o.
|
|
158
|
-
period: o.
|
|
160
|
+
kind: o.maType === "ema" ? 1 : 0,
|
|
161
|
+
period: o.period,
|
|
159
162
|
source: srcIdx < 0 ? 0 : srcIdx,
|
|
160
163
|
color: (o.color != null ? parseColor(o.color) : null) ?? 4280902399,
|
|
161
164
|
width: o.width ?? 1.5
|
|
162
165
|
};
|
|
163
166
|
}
|
|
167
|
+
function rsiToSpec(cfg) {
|
|
168
|
+
return {
|
|
169
|
+
enabled: cfg?.enabled ?? false,
|
|
170
|
+
period: cfg?.period ?? 14,
|
|
171
|
+
upperBand: cfg?.upperBand ?? 70,
|
|
172
|
+
lowerBand: cfg?.lowerBand ?? 30,
|
|
173
|
+
maPeriod: cfg?.maPeriod ?? 14,
|
|
174
|
+
maKind: cfg?.maType === "ema" ? 1 : 0,
|
|
175
|
+
maVisible: cfg?.maVisible ?? true,
|
|
176
|
+
lineColor: inheritColor(cfg?.lineColor),
|
|
177
|
+
lineWidth: cfg?.lineWidth ?? -1,
|
|
178
|
+
lineVisible: cfg?.lineVisible ?? true,
|
|
179
|
+
maColor: inheritColor(cfg?.maColor),
|
|
180
|
+
maWidth: cfg?.maWidth ?? -1,
|
|
181
|
+
bandColor: inheritColor(cfg?.bandColor),
|
|
182
|
+
bandsVisible: cfg?.bandsVisible ?? true
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
function vwapToSpec(cfg) {
|
|
186
|
+
return {
|
|
187
|
+
enabled: cfg?.enabled ?? false,
|
|
188
|
+
resetOffsetMin: cfg?.resetMinutes ?? 0,
|
|
189
|
+
color: inheritColor(cfg?.color),
|
|
190
|
+
width: cfg?.width ?? -1
|
|
191
|
+
};
|
|
192
|
+
}
|
|
164
193
|
var DEFAULT_BB_BAND_COLOR = 4280902399;
|
|
165
194
|
var DEFAULT_BB_BASIS_COLOR = 4294929664;
|
|
166
195
|
function bollingerToSpec(cfg) {
|
|
@@ -170,17 +199,79 @@ function bollingerToSpec(cfg) {
|
|
|
170
199
|
period: cfg?.period ?? 20,
|
|
171
200
|
mult: cfg?.stdDev ?? 2,
|
|
172
201
|
source: srcIdx < 0 ? 0 : srcIdx,
|
|
173
|
-
basisKind: cfg?.
|
|
202
|
+
basisKind: cfg?.maType === "ema" ? 1 : 0,
|
|
174
203
|
upperColor: (cfg?.upperColor != null ? parseColor(cfg.upperColor) : null) ?? DEFAULT_BB_BAND_COLOR,
|
|
175
204
|
upperWidth: cfg?.upperWidth ?? 1,
|
|
176
205
|
middleColor: (cfg?.middleColor != null ? parseColor(cfg.middleColor) : null) ?? DEFAULT_BB_BASIS_COLOR,
|
|
177
206
|
middleWidth: cfg?.middleWidth ?? 1,
|
|
178
207
|
lowerColor: (cfg?.lowerColor != null ? parseColor(cfg.lowerColor) : null) ?? DEFAULT_BB_BAND_COLOR,
|
|
179
208
|
lowerWidth: cfg?.lowerWidth ?? 1,
|
|
180
|
-
fillEnabled: cfg?.
|
|
209
|
+
fillEnabled: cfg?.fillVisible ?? true,
|
|
181
210
|
fillOpacity: cfg?.fillOpacity ?? 0.1
|
|
182
211
|
};
|
|
183
212
|
}
|
|
213
|
+
function macdToSpec(cfg) {
|
|
214
|
+
const srcIdx = cfg?.source ? MA_SOURCES.indexOf(cfg.source) : 0;
|
|
215
|
+
return {
|
|
216
|
+
enabled: cfg?.enabled ?? false,
|
|
217
|
+
fast: cfg?.fast ?? 12,
|
|
218
|
+
slow: cfg?.slow ?? 26,
|
|
219
|
+
signal: cfg?.signal ?? 9,
|
|
220
|
+
source: srcIdx < 0 ? 0 : srcIdx,
|
|
221
|
+
maKind: cfg?.maType === "sma" ? 0 : 1,
|
|
222
|
+
signalMaKind: cfg?.signalMaType === "sma" ? 0 : 1,
|
|
223
|
+
lineColor: inheritColor(cfg?.lineColor),
|
|
224
|
+
lineWidth: cfg?.lineWidth ?? -1,
|
|
225
|
+
lineVisible: cfg?.lineVisible ?? true,
|
|
226
|
+
signalColor: inheritColor(cfg?.signalColor),
|
|
227
|
+
signalWidth: cfg?.signalWidth ?? -1,
|
|
228
|
+
signalVisible: cfg?.signalVisible ?? true,
|
|
229
|
+
histVisible: cfg?.histogramVisible ?? true,
|
|
230
|
+
histUpColor: inheritColor(cfg?.histogramUpColor),
|
|
231
|
+
histUpFadingColor: inheritColor(cfg?.histogramUpFadingColor),
|
|
232
|
+
histDownColor: inheritColor(cfg?.histogramDownColor),
|
|
233
|
+
histDownFadingColor: inheritColor(cfg?.histogramDownFadingColor),
|
|
234
|
+
zeroColor: inheritColor(cfg?.zeroLineColor),
|
|
235
|
+
zeroVisible: cfg?.zeroLineVisible ?? true
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
function volumeToSpec(cfg) {
|
|
239
|
+
return {
|
|
240
|
+
enabled: cfg?.enabled ?? true,
|
|
241
|
+
heightFrac: cfg?.height ?? -1,
|
|
242
|
+
opacity: cfg?.opacity ?? -1,
|
|
243
|
+
radiusPx: cfg?.radius ?? -1,
|
|
244
|
+
upColor: (cfg?.upColor != null ? parseColor(cfg.upColor) : null) ?? 0,
|
|
245
|
+
downColor: (cfg?.downColor != null ? parseColor(cfg.downColor) : null) ?? 0
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
var DEFAULT_PRICE_LINE_COLOR = 4293874512;
|
|
249
|
+
var DEFAULT_PRICE_LINE_BODY_BG = 3642499368;
|
|
250
|
+
var DEFAULT_PRICE_LINE_HOVER_BOOST = 1.25;
|
|
251
|
+
var LINE_STYLES = { solid: 0, dotted: 1, dashed: 2 };
|
|
252
|
+
var PRICE_LINE_DRAGGABLE = 1 << 0;
|
|
253
|
+
var PRICE_LINE_CLOSABLE = 1 << 1;
|
|
254
|
+
var PRICE_LINE_AXIS_LABEL = 1 << 2;
|
|
255
|
+
var PRICE_LINE_EXTEND_LEFT = 1 << 3;
|
|
256
|
+
function priceLinesToSpec(cfg) {
|
|
257
|
+
return {
|
|
258
|
+
lines: cfg.lines.map((l) => ({
|
|
259
|
+
price: l.price,
|
|
260
|
+
color: (l.color != null ? parseColor(l.color) : null) ?? DEFAULT_PRICE_LINE_COLOR,
|
|
261
|
+
width: l.width ?? 1,
|
|
262
|
+
lineStyle: LINE_STYLES[l.lineStyle ?? "dotted"],
|
|
263
|
+
text: l.text ?? "",
|
|
264
|
+
quantity: l.quantity ?? "",
|
|
265
|
+
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)
|
|
266
|
+
})),
|
|
267
|
+
bodyBg: (cfg.style?.bodyBackground != null ? parseColor(cfg.style.bodyBackground) : null) ?? DEFAULT_PRICE_LINE_BODY_BG,
|
|
268
|
+
fontSizePx: cfg.style?.fontSize ?? 0,
|
|
269
|
+
lineLengthFrac: cfg.style?.inset ?? 0,
|
|
270
|
+
align: cfg.style?.align === "left" ? 0 : cfg.style?.align === "center" ? 1 : 2,
|
|
271
|
+
hoverBoost: cfg.style?.hoverBoost ?? DEFAULT_PRICE_LINE_HOVER_BOOST
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
var EMPTY_PRICE_LINES = priceLinesToSpec({ lines: [], hasCloseHandler: false });
|
|
184
275
|
var installed = false;
|
|
185
276
|
function ensureInstalled() {
|
|
186
277
|
if (installed) return;
|
|
@@ -191,9 +282,10 @@ function ensureInstalled() {
|
|
|
191
282
|
}
|
|
192
283
|
installed = true;
|
|
193
284
|
}
|
|
194
|
-
function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType, theme, rsi, macd, movingAverages, vwap, bollingerBands) {
|
|
285
|
+
function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType, theme, rsi, macd, movingAverages, vwap, bollingerBands, volume, priceLines) {
|
|
195
286
|
const handleRef = (0, import_react.useRef)(null);
|
|
196
287
|
const defaultWidthAppliedRef = (0, import_react.useRef)(false);
|
|
288
|
+
const volumeCollapseRef = (0, import_react.useRef)(null);
|
|
197
289
|
const [picture, setPicture] = (0, import_react.useState)(null);
|
|
198
290
|
if (!handleRef.current && size.width > 0 && size.height > 0) {
|
|
199
291
|
ensureInstalled();
|
|
@@ -208,6 +300,8 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
|
|
|
208
300
|
const maKey = movingAverages ? JSON.stringify(movingAverages) : "";
|
|
209
301
|
const vwapKey = vwap ? JSON.stringify(vwap) : "";
|
|
210
302
|
const bollingerKey = bollingerBands ? JSON.stringify(bollingerBands) : "";
|
|
303
|
+
const volumeKey = volume ? JSON.stringify(volume) : "";
|
|
304
|
+
const priceLinesKey = priceLines ? JSON.stringify(priceLines) : "";
|
|
211
305
|
(0, import_react.useEffect)(() => {
|
|
212
306
|
const h = handleRef.current;
|
|
213
307
|
if (!h) return;
|
|
@@ -225,31 +319,44 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
|
|
|
225
319
|
if (theme) {
|
|
226
320
|
applyTheme(h, theme);
|
|
227
321
|
}
|
|
228
|
-
h.setRSI(
|
|
229
|
-
|
|
230
|
-
rsi?.period ?? 14,
|
|
231
|
-
rsi?.upperBand ?? 70,
|
|
232
|
-
rsi?.lowerBand ?? 30,
|
|
233
|
-
rsi?.maEnabled ?? true,
|
|
234
|
-
rsi?.maPeriod ?? 14
|
|
235
|
-
);
|
|
236
|
-
h.setMACD(
|
|
237
|
-
macd?.enabled ?? false,
|
|
238
|
-
macd?.fast ?? 12,
|
|
239
|
-
macd?.slow ?? 26,
|
|
240
|
-
macd?.signal ?? 9
|
|
241
|
-
);
|
|
322
|
+
h.setRSI(rsiToSpec(rsi));
|
|
323
|
+
h.setMACD(macdToSpec(macd));
|
|
242
324
|
h.setOverlays((movingAverages ?? []).map(overlayToNumeric));
|
|
243
|
-
h.setVWAP(
|
|
244
|
-
vwap?.enabled ?? false,
|
|
245
|
-
vwap?.resetMinutes ?? 0,
|
|
246
|
-
(vwap?.color != null ? parseColor(vwap.color) : null) ?? 4278238420,
|
|
247
|
-
vwap?.width ?? 1.5
|
|
248
|
-
);
|
|
325
|
+
h.setVWAP(vwapToSpec(vwap));
|
|
249
326
|
h.setBollinger(bollingerToSpec(bollingerBands));
|
|
327
|
+
h.setVolume(volumeToSpec(volume));
|
|
328
|
+
const collapse = volumeCollapseRef.current;
|
|
329
|
+
if (collapse) h.setVolumeCollapse(collapse.t, collapse.easing);
|
|
330
|
+
h.setPriceLines(
|
|
331
|
+
priceLines?.lines.length ? priceLinesToSpec(priceLines) : EMPTY_PRICE_LINES
|
|
332
|
+
);
|
|
250
333
|
setPicture(h.render());
|
|
251
|
-
}, [candles, size.width, size.height, size.pxRatio, explicit, startMs, endMs, defaultCandleWidth, themeKey, rsiKey, macdKey, maKey, vwapKey, bollingerKey]);
|
|
252
|
-
return { handle: handleRef.current, picture };
|
|
334
|
+
}, [candles, size.width, size.height, size.pxRatio, explicit, startMs, endMs, defaultCandleWidth, themeKey, rsiKey, macdKey, maKey, vwapKey, bollingerKey, volumeKey, priceLinesKey]);
|
|
335
|
+
return { handle: handleRef.current, picture, volumeCollapseRef };
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// src/easing.ts
|
|
339
|
+
function ease(kind, p) {
|
|
340
|
+
switch (kind) {
|
|
341
|
+
case "linear":
|
|
342
|
+
return p;
|
|
343
|
+
case "ease-in":
|
|
344
|
+
return p * p;
|
|
345
|
+
case "ease-out":
|
|
346
|
+
return p * (2 - p);
|
|
347
|
+
default:
|
|
348
|
+
return p * p * (3 - 2 * p);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
var EASINGS = [
|
|
352
|
+
"linear",
|
|
353
|
+
"ease-in",
|
|
354
|
+
"ease-out",
|
|
355
|
+
"ease-in-out"
|
|
356
|
+
];
|
|
357
|
+
function easingIndex(kind) {
|
|
358
|
+
const i = kind ? EASINGS.indexOf(kind) : -1;
|
|
359
|
+
return i < 0 ? EASINGS.indexOf("ease-in-out") : i;
|
|
253
360
|
}
|
|
254
361
|
|
|
255
362
|
// src/VroomChart.tsx
|
|
@@ -263,15 +370,22 @@ function VroomChart(props) {
|
|
|
263
370
|
defaultCandleWidth,
|
|
264
371
|
chartType,
|
|
265
372
|
transitionMs,
|
|
373
|
+
transitionEasing,
|
|
266
374
|
theme,
|
|
267
375
|
rsi,
|
|
268
376
|
macd,
|
|
269
377
|
movingAverages,
|
|
270
378
|
vwap,
|
|
271
379
|
bollingerBands,
|
|
380
|
+
volume,
|
|
272
381
|
crosshairOffset = 40,
|
|
273
382
|
onCrosshair,
|
|
274
|
-
onViewportChange
|
|
383
|
+
onViewportChange,
|
|
384
|
+
priceLines,
|
|
385
|
+
priceLinesStyle,
|
|
386
|
+
onPriceLineDrag,
|
|
387
|
+
onPriceLineDragEnd,
|
|
388
|
+
onPriceLineClose
|
|
275
389
|
} = props;
|
|
276
390
|
const [measured, setMeasured] = (0, import_react2.useState)({ width: 0, height: 0 });
|
|
277
391
|
const width = widthProp ?? measured.width;
|
|
@@ -283,7 +397,15 @@ function VroomChart(props) {
|
|
|
283
397
|
(prev) => prev.width === w && prev.height === h ? prev : { width: w, height: h }
|
|
284
398
|
);
|
|
285
399
|
}, []);
|
|
286
|
-
const
|
|
400
|
+
const priceLinesProp = (0, import_react2.useMemo)(
|
|
401
|
+
() => priceLines ? {
|
|
402
|
+
lines: priceLines,
|
|
403
|
+
style: priceLinesStyle,
|
|
404
|
+
hasCloseHandler: onPriceLineClose != null
|
|
405
|
+
} : void 0,
|
|
406
|
+
[priceLines, priceLinesStyle, onPriceLineClose]
|
|
407
|
+
);
|
|
408
|
+
const { handle, picture, volumeCollapseRef } = useChartCore(
|
|
287
409
|
candles,
|
|
288
410
|
{ width, height },
|
|
289
411
|
visibleRange,
|
|
@@ -294,7 +416,9 @@ function VroomChart(props) {
|
|
|
294
416
|
macd,
|
|
295
417
|
movingAverages,
|
|
296
418
|
vwap,
|
|
297
|
-
bollingerBands
|
|
419
|
+
bollingerBands,
|
|
420
|
+
volume,
|
|
421
|
+
priceLinesProp
|
|
298
422
|
);
|
|
299
423
|
const emptyPicture = (0, import_react2.useMemo)(() => {
|
|
300
424
|
const rec = import_react_native_skia.Skia.PictureRecorder();
|
|
@@ -341,6 +465,8 @@ function VroomChart(props) {
|
|
|
341
465
|
const morphRaf = (0, import_react2.useRef)(null);
|
|
342
466
|
const morphFade = (0, import_react2.useRef)(null);
|
|
343
467
|
const morphHandle = (0, import_react2.useRef)(null);
|
|
468
|
+
const easingRef = (0, import_react2.useRef)(transitionEasing);
|
|
469
|
+
easingRef.current = transitionEasing;
|
|
344
470
|
(0, import_react2.useEffect)(() => {
|
|
345
471
|
if (!handle) return void 0;
|
|
346
472
|
const target = chartType === "line" ? 1 : 0;
|
|
@@ -370,8 +496,7 @@ function VroomChart(props) {
|
|
|
370
496
|
const step = (now) => {
|
|
371
497
|
if (startTs == null) startTs = now;
|
|
372
498
|
const prog = Math.min(1, (now - startTs) / dur);
|
|
373
|
-
const
|
|
374
|
-
const fade = from + (target - from) * e;
|
|
499
|
+
const fade = from + (target - from) * ease(easingRef.current, prog);
|
|
375
500
|
morphFade.current = fade;
|
|
376
501
|
handle.setMorph(fade, fade);
|
|
377
502
|
const p = handle.render();
|
|
@@ -394,6 +519,51 @@ function VroomChart(props) {
|
|
|
394
519
|
}
|
|
395
520
|
};
|
|
396
521
|
}, [handle, chartType, transitionMs, pictureSV]);
|
|
522
|
+
const volumeRaf = (0, import_react2.useRef)(null);
|
|
523
|
+
const volumeHandle = (0, import_react2.useRef)(null);
|
|
524
|
+
(0, import_react2.useEffect)(() => {
|
|
525
|
+
if (!handle) return void 0;
|
|
526
|
+
const target = volume?.enabled ?? true ? 0 : 1;
|
|
527
|
+
const easing = easingIndex(easingRef.current);
|
|
528
|
+
if (volumeHandle.current !== handle || volumeCollapseRef.current == null) {
|
|
529
|
+
volumeHandle.current = handle;
|
|
530
|
+
volumeCollapseRef.current = { t: target, easing };
|
|
531
|
+
return void 0;
|
|
532
|
+
}
|
|
533
|
+
if (volumeCollapseRef.current.t === target) return void 0;
|
|
534
|
+
if (volumeRaf.current != null) {
|
|
535
|
+
cancelAnimationFrame(volumeRaf.current);
|
|
536
|
+
volumeRaf.current = null;
|
|
537
|
+
}
|
|
538
|
+
const dur = Math.max(0, transitionMs ?? 300);
|
|
539
|
+
if (dur === 0) {
|
|
540
|
+
volumeCollapseRef.current = { t: target, easing };
|
|
541
|
+
handle.setVolumeCollapse(target, easing);
|
|
542
|
+
const p = handle.render();
|
|
543
|
+
if (p) pictureSV.value = p;
|
|
544
|
+
return void 0;
|
|
545
|
+
}
|
|
546
|
+
const from = volumeCollapseRef.current.t;
|
|
547
|
+
let startTs = null;
|
|
548
|
+
const step = (now) => {
|
|
549
|
+
if (startTs == null) startTs = now;
|
|
550
|
+
const prog = Math.min(1, (now - startTs) / dur);
|
|
551
|
+
const t = prog < 1 ? from + (target - from) * prog : target;
|
|
552
|
+
const kind = easingIndex(easingRef.current);
|
|
553
|
+
volumeCollapseRef.current = { t, easing: kind };
|
|
554
|
+
handle.setVolumeCollapse(t, kind);
|
|
555
|
+
const p = handle.render();
|
|
556
|
+
if (p) pictureSV.value = p;
|
|
557
|
+
volumeRaf.current = prog < 1 ? requestAnimationFrame(step) : null;
|
|
558
|
+
};
|
|
559
|
+
volumeRaf.current = requestAnimationFrame(step);
|
|
560
|
+
return () => {
|
|
561
|
+
if (volumeRaf.current != null) {
|
|
562
|
+
cancelAnimationFrame(volumeRaf.current);
|
|
563
|
+
volumeRaf.current = null;
|
|
564
|
+
}
|
|
565
|
+
};
|
|
566
|
+
}, [handle, volume?.enabled, transitionMs, pictureSV, volumeCollapseRef]);
|
|
397
567
|
const hitAxis = (0, import_react2.useCallback)(
|
|
398
568
|
(x, y) => {
|
|
399
569
|
if (!handle) return "chart";
|
|
@@ -407,12 +577,33 @@ function VroomChart(props) {
|
|
|
407
577
|
},
|
|
408
578
|
[handle, width, height]
|
|
409
579
|
);
|
|
410
|
-
const
|
|
411
|
-
|
|
580
|
+
const hitPriceLine = (0, import_react2.useCallback)(
|
|
581
|
+
(x, y) => {
|
|
582
|
+
if (!handle || !priceLines?.length) return null;
|
|
583
|
+
const hit = handle.hitTestPriceLine(x, y);
|
|
584
|
+
const line = hit ? priceLines[hit.index] : void 0;
|
|
585
|
+
return hit && line ? { index: hit.index, part: hit.part, line } : null;
|
|
586
|
+
},
|
|
587
|
+
[handle, priceLines]
|
|
412
588
|
);
|
|
589
|
+
const priceDrag = (0, import_react2.useRef)(
|
|
590
|
+
null
|
|
591
|
+
);
|
|
592
|
+
const panMode = (0, import_react2.useRef)("chart");
|
|
413
593
|
const pan = import_react_native_gesture_handler.Gesture.Pan().runOnJS(true).maxPointers(1).onStart((e) => {
|
|
414
594
|
cancelDecay();
|
|
415
595
|
panMode.current = hitAxis(e.x, e.y);
|
|
596
|
+
priceDrag.current = null;
|
|
597
|
+
if (handle && panMode.current === "chart" && !crosshairActive.current) {
|
|
598
|
+
const pl = hitPriceLine(e.x, e.y);
|
|
599
|
+
if (pl && pl.part === 0) {
|
|
600
|
+
panMode.current = "price-line";
|
|
601
|
+
priceDrag.current = { index: pl.index, id: pl.line.id, price: pl.line.price };
|
|
602
|
+
handle.setPriceLineDrag(pl.index, pl.line.price);
|
|
603
|
+
const p = handle.render();
|
|
604
|
+
if (p) pictureSV.value = p;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
416
607
|
}).onChange((e) => {
|
|
417
608
|
if (!handle) return;
|
|
418
609
|
let next = null;
|
|
@@ -422,6 +613,15 @@ function VroomChart(props) {
|
|
|
422
613
|
next = handle.scaleTimeAxis(e.changeX);
|
|
423
614
|
} else if (panMode.current === "indicator") {
|
|
424
615
|
next = handle.pan(e.changeX, 0);
|
|
616
|
+
} else if (panMode.current === "price-line") {
|
|
617
|
+
const g = priceDrag.current;
|
|
618
|
+
if (!g) return;
|
|
619
|
+
const c = handle.coordAt(e.x, e.y);
|
|
620
|
+
if (!c) return;
|
|
621
|
+
g.price = c.price;
|
|
622
|
+
handle.setPriceLineDrag(g.index, c.price);
|
|
623
|
+
onPriceLineDrag?.(g.id, c.price);
|
|
624
|
+
next = handle.render();
|
|
425
625
|
} else if (crosshairActive.current) {
|
|
426
626
|
const ch = handle.setCrosshair(e.x, e.y - crosshairOffset);
|
|
427
627
|
if (ch) pictureSV.value = ch;
|
|
@@ -439,6 +639,15 @@ function VroomChart(props) {
|
|
|
439
639
|
maybeStartAnim();
|
|
440
640
|
}).onEnd((e) => {
|
|
441
641
|
if (!handle) return;
|
|
642
|
+
if (panMode.current === "price-line") {
|
|
643
|
+
const g = priceDrag.current;
|
|
644
|
+
priceDrag.current = null;
|
|
645
|
+
handle.setPriceLineDrag(-1, 0);
|
|
646
|
+
const p = handle.render();
|
|
647
|
+
if (p) pictureSV.value = p;
|
|
648
|
+
if (g) onPriceLineDragEnd?.(g.id, g.price);
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
442
651
|
if (panMode.current === "chart" && crosshairActive.current) return;
|
|
443
652
|
onViewportChange?.(0, 0);
|
|
444
653
|
if (panMode.current !== "chart" && panMode.current !== "indicator") return;
|
|
@@ -515,6 +724,7 @@ function VroomChart(props) {
|
|
|
515
724
|
const longPress = import_react_native_gesture_handler.Gesture.LongPress().runOnJS(true).onStart((e) => {
|
|
516
725
|
if (!handle) return;
|
|
517
726
|
if (hitAxis(e.x, e.y) !== "chart") return;
|
|
727
|
+
if (hitPriceLine(e.x, e.y)) return;
|
|
518
728
|
cancelDecay();
|
|
519
729
|
crosshairActive.current = true;
|
|
520
730
|
const ch = handle.setCrosshair(e.x, e.y - crosshairOffset);
|
|
@@ -531,7 +741,13 @@ function VroomChart(props) {
|
|
|
531
741
|
});
|
|
532
742
|
});
|
|
533
743
|
const tap = import_react_native_gesture_handler.Gesture.Tap().runOnJS(true).onStart((e) => {
|
|
534
|
-
if (!handle
|
|
744
|
+
if (!handle) return;
|
|
745
|
+
const pl = hitPriceLine(e.x, e.y);
|
|
746
|
+
if (pl && pl.part === 1) {
|
|
747
|
+
onPriceLineClose?.(pl.line.id);
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
if (!crosshairActive.current) return;
|
|
535
751
|
if (hitAxis(e.x, e.y) !== "chart") return;
|
|
536
752
|
crosshairActive.current = false;
|
|
537
753
|
const ch = handle.clearCrosshair();
|