react-native-vroom-chart 0.6.0 → 0.8.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 +281 -33
- package/cpp/_core_include/vroom/vroom_chart.h +231 -36
- 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 +91 -40
- package/cpp/_core_src/chart.h +69 -29
- package/cpp/_core_src/chart_facade.cpp +247 -69
- package/cpp/_core_src/drawings.cpp +147 -4
- package/cpp/_core_src/drawings.h +10 -5
- 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.h +1 -1
- 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 +206 -31
- package/lib/index.d.ts +206 -31
- package/lib/index.js +324 -44
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +329 -52
- package/lib/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/VroomChart.tsx +100 -17
- package/src/dataTransitions.ts +148 -0
- package/src/easing.ts +40 -0
- package/src/index.ts +9 -0
- package/src/jsi.d.ts +126 -19
- package/src/theme.ts +1 -0
- package/src/types.ts +3 -0
- package/src/useChartCore.ts +273 -30
package/lib/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/VroomChart.tsx
|
|
2
|
-
import React, { useEffect as useEffect2, useRef as useRef2, useCallback, useState as useState2, useMemo } from "react";
|
|
2
|
+
import React, { useEffect as useEffect2, useRef as useRef2, useCallback as useCallback2, useState as useState2, useMemo } from "react";
|
|
3
3
|
import { View } from "react-native";
|
|
4
4
|
import { Canvas, Picture, Skia } from "@shopify/react-native-skia";
|
|
5
5
|
import {
|
|
@@ -7,15 +7,95 @@ import {
|
|
|
7
7
|
GestureDetector,
|
|
8
8
|
GestureHandlerRootView
|
|
9
9
|
} from "react-native-gesture-handler";
|
|
10
|
-
import { useSharedValue } from "react-native-reanimated";
|
|
10
|
+
import { useReducedMotion, useSharedValue } from "react-native-reanimated";
|
|
11
11
|
|
|
12
12
|
// src/useChartCore.ts
|
|
13
|
-
import { useEffect, useRef, useState } from "react";
|
|
13
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
14
14
|
|
|
15
15
|
// src/NativeVroomChart.ts
|
|
16
16
|
import { TurboModuleRegistry } from "react-native";
|
|
17
17
|
var NativeVroomChart_default = TurboModuleRegistry.getEnforcing("VroomChartModule");
|
|
18
18
|
|
|
19
|
+
// src/dataTransitions.ts
|
|
20
|
+
var STEP_TOLERANCE = 0.01;
|
|
21
|
+
var MAX_SAME_ASSET_CLOSE_RATIO = 1.25;
|
|
22
|
+
var MAX_END_DRIFT_STEPS = 3;
|
|
23
|
+
var MAX_STREAM_ADVANCE_STEPS = 5;
|
|
24
|
+
function inferStepMs(candles) {
|
|
25
|
+
if (candles.length < 2) return null;
|
|
26
|
+
const k = Math.min(candles.length - 1, 8);
|
|
27
|
+
const diffs = [];
|
|
28
|
+
for (let i = 0; i < k; i++) diffs.push(candles[i + 1].timeMs - candles[i].timeMs);
|
|
29
|
+
diffs.sort((a, b) => a - b);
|
|
30
|
+
const median = diffs[Math.floor(diffs.length / 2)];
|
|
31
|
+
return median > 0 ? median : null;
|
|
32
|
+
}
|
|
33
|
+
function indexByTime(candles, t) {
|
|
34
|
+
let lo = 0;
|
|
35
|
+
let hi = candles.length - 1;
|
|
36
|
+
while (lo <= hi) {
|
|
37
|
+
const mid = lo + hi >>> 1;
|
|
38
|
+
const v = candles[mid].timeMs;
|
|
39
|
+
if (v === t) return mid;
|
|
40
|
+
if (v < t) lo = mid + 1;
|
|
41
|
+
else hi = mid - 1;
|
|
42
|
+
}
|
|
43
|
+
return -1;
|
|
44
|
+
}
|
|
45
|
+
function classifyTransition(prev, next, seriesKeyChanged) {
|
|
46
|
+
if (!prev || prev.length === 0) return "initial";
|
|
47
|
+
if (next.length === 0) return "stream";
|
|
48
|
+
if (seriesKeyChanged) return "reset";
|
|
49
|
+
const prevStep = inferStepMs(prev);
|
|
50
|
+
const nextStep = inferStepMs(next);
|
|
51
|
+
if (prevStep == null || nextStep == null) return "reset";
|
|
52
|
+
const prevLast = prev[prev.length - 1];
|
|
53
|
+
const nextLast = next[next.length - 1];
|
|
54
|
+
if (Math.abs(nextStep - prevStep) <= prevStep * STEP_TOLERANCE) {
|
|
55
|
+
const idx = indexByTime(next, prevLast.timeMs);
|
|
56
|
+
const aligned = idx >= 0;
|
|
57
|
+
const sharedBarRatio = aligned && next[idx].close > 0 && prevLast.close > 0 ? Math.max(next[idx].close / prevLast.close, prevLast.close / next[idx].close) : Infinity;
|
|
58
|
+
const advanced = nextLast.timeMs >= prevLast.timeMs && nextLast.timeMs - prevLast.timeMs <= MAX_STREAM_ADVANCE_STEPS * nextStep;
|
|
59
|
+
return sharedBarRatio <= MAX_SAME_ASSET_CLOSE_RATIO && advanced ? "stream" : "reset";
|
|
60
|
+
}
|
|
61
|
+
const closeRatio = prevLast.close > 0 && nextLast.close > 0 ? Math.max(nextLast.close / prevLast.close, prevLast.close / nextLast.close) : Infinity;
|
|
62
|
+
const prevEnd = prevLast.timeMs + prevStep;
|
|
63
|
+
const nextEnd = nextLast.timeMs + nextStep;
|
|
64
|
+
const endsTogether = Math.abs(nextEnd - prevEnd) <= MAX_END_DRIFT_STEPS * Math.max(prevStep, nextStep);
|
|
65
|
+
return closeRatio <= MAX_SAME_ASSET_CLOSE_RATIO && endsTogether ? "timeframe" : "reset";
|
|
66
|
+
}
|
|
67
|
+
function timeframeWindow(oldWindow, oldStepMs, oldLastMs, newStepMs, newLastMs) {
|
|
68
|
+
const slots = (oldWindow.endMs - oldWindow.startMs) / oldStepMs;
|
|
69
|
+
const offsetRaw = (oldWindow.endMs - oldLastMs) / oldStepMs;
|
|
70
|
+
const offsetSlots = Math.min(Math.max(offsetRaw, 0), slots * 0.75);
|
|
71
|
+
const endMs = Math.round(newLastMs + offsetSlots * newStepMs);
|
|
72
|
+
return { startMs: Math.round(endMs - slots * newStepMs), endMs };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// src/easing.ts
|
|
76
|
+
function ease(kind, p) {
|
|
77
|
+
switch (kind) {
|
|
78
|
+
case "linear":
|
|
79
|
+
return p;
|
|
80
|
+
case "ease-in":
|
|
81
|
+
return p * p;
|
|
82
|
+
case "ease-out":
|
|
83
|
+
return p * (2 - p);
|
|
84
|
+
default:
|
|
85
|
+
return p * p * (3 - 2 * p);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
var EASINGS = [
|
|
89
|
+
"linear",
|
|
90
|
+
"ease-in",
|
|
91
|
+
"ease-out",
|
|
92
|
+
"ease-in-out"
|
|
93
|
+
];
|
|
94
|
+
function easingIndex(kind) {
|
|
95
|
+
const i = kind ? EASINGS.indexOf(kind) : -1;
|
|
96
|
+
return i < 0 ? EASINGS.indexOf("ease-in-out") : i;
|
|
97
|
+
}
|
|
98
|
+
|
|
19
99
|
// src/packCandles.ts
|
|
20
100
|
var BYTES_PER_CANDLE = 48;
|
|
21
101
|
function packCandles(candles) {
|
|
@@ -72,8 +152,10 @@ var FLOAT_KEYS = {
|
|
|
72
152
|
// VROOM_FLOAT_CANDLE_RADIUS_PX
|
|
73
153
|
volumeRadius: 10,
|
|
74
154
|
// VROOM_FLOAT_VOLUME_RADIUS_PX
|
|
75
|
-
lineWidth: 11
|
|
155
|
+
lineWidth: 11,
|
|
76
156
|
// VROOM_FLOAT_LINE_WIDTH_PX
|
|
157
|
+
lineGradientOpacity: 12
|
|
158
|
+
// VROOM_FLOAT_LINE_GRADIENT_OPACITY
|
|
77
159
|
};
|
|
78
160
|
var BOOL_KEYS = {
|
|
79
161
|
wickRoundCap: 9
|
|
@@ -119,16 +201,43 @@ var MA_SOURCES = [
|
|
|
119
201
|
"hlc3",
|
|
120
202
|
"ohlc4"
|
|
121
203
|
];
|
|
204
|
+
var inheritColor = (v) => (v != null ? parseColor(v) : null) ?? 0;
|
|
122
205
|
function overlayToNumeric(o) {
|
|
123
206
|
const srcIdx = o.source ? MA_SOURCES.indexOf(o.source) : 0;
|
|
124
207
|
return {
|
|
125
|
-
kind: o.
|
|
126
|
-
period: o.
|
|
208
|
+
kind: o.maType === "ema" ? 1 : 0,
|
|
209
|
+
period: o.period,
|
|
127
210
|
source: srcIdx < 0 ? 0 : srcIdx,
|
|
128
211
|
color: (o.color != null ? parseColor(o.color) : null) ?? 4280902399,
|
|
129
212
|
width: o.width ?? 1.5
|
|
130
213
|
};
|
|
131
214
|
}
|
|
215
|
+
function rsiToSpec(cfg) {
|
|
216
|
+
return {
|
|
217
|
+
enabled: cfg?.enabled ?? false,
|
|
218
|
+
period: cfg?.period ?? 14,
|
|
219
|
+
upperBand: cfg?.upperBand ?? 70,
|
|
220
|
+
lowerBand: cfg?.lowerBand ?? 30,
|
|
221
|
+
maPeriod: cfg?.maPeriod ?? 14,
|
|
222
|
+
maKind: cfg?.maType === "ema" ? 1 : 0,
|
|
223
|
+
maVisible: cfg?.maVisible ?? true,
|
|
224
|
+
lineColor: inheritColor(cfg?.lineColor),
|
|
225
|
+
lineWidth: cfg?.lineWidth ?? -1,
|
|
226
|
+
lineVisible: cfg?.lineVisible ?? true,
|
|
227
|
+
maColor: inheritColor(cfg?.maColor),
|
|
228
|
+
maWidth: cfg?.maWidth ?? -1,
|
|
229
|
+
bandColor: inheritColor(cfg?.bandColor),
|
|
230
|
+
bandsVisible: cfg?.bandsVisible ?? true
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
function vwapToSpec(cfg) {
|
|
234
|
+
return {
|
|
235
|
+
enabled: cfg?.enabled ?? false,
|
|
236
|
+
resetOffsetMin: cfg?.resetMinutes ?? 0,
|
|
237
|
+
color: inheritColor(cfg?.color),
|
|
238
|
+
width: cfg?.width ?? -1
|
|
239
|
+
};
|
|
240
|
+
}
|
|
132
241
|
var DEFAULT_BB_BAND_COLOR = 4280902399;
|
|
133
242
|
var DEFAULT_BB_BASIS_COLOR = 4294929664;
|
|
134
243
|
function bollingerToSpec(cfg) {
|
|
@@ -138,17 +247,52 @@ function bollingerToSpec(cfg) {
|
|
|
138
247
|
period: cfg?.period ?? 20,
|
|
139
248
|
mult: cfg?.stdDev ?? 2,
|
|
140
249
|
source: srcIdx < 0 ? 0 : srcIdx,
|
|
141
|
-
basisKind: cfg?.
|
|
250
|
+
basisKind: cfg?.maType === "ema" ? 1 : 0,
|
|
142
251
|
upperColor: (cfg?.upperColor != null ? parseColor(cfg.upperColor) : null) ?? DEFAULT_BB_BAND_COLOR,
|
|
143
252
|
upperWidth: cfg?.upperWidth ?? 1,
|
|
144
253
|
middleColor: (cfg?.middleColor != null ? parseColor(cfg.middleColor) : null) ?? DEFAULT_BB_BASIS_COLOR,
|
|
145
254
|
middleWidth: cfg?.middleWidth ?? 1,
|
|
146
255
|
lowerColor: (cfg?.lowerColor != null ? parseColor(cfg.lowerColor) : null) ?? DEFAULT_BB_BAND_COLOR,
|
|
147
256
|
lowerWidth: cfg?.lowerWidth ?? 1,
|
|
148
|
-
fillEnabled: cfg?.
|
|
257
|
+
fillEnabled: cfg?.fillVisible ?? true,
|
|
149
258
|
fillOpacity: cfg?.fillOpacity ?? 0.1
|
|
150
259
|
};
|
|
151
260
|
}
|
|
261
|
+
function macdToSpec(cfg) {
|
|
262
|
+
const srcIdx = cfg?.source ? MA_SOURCES.indexOf(cfg.source) : 0;
|
|
263
|
+
return {
|
|
264
|
+
enabled: cfg?.enabled ?? false,
|
|
265
|
+
fast: cfg?.fast ?? 12,
|
|
266
|
+
slow: cfg?.slow ?? 26,
|
|
267
|
+
signal: cfg?.signal ?? 9,
|
|
268
|
+
source: srcIdx < 0 ? 0 : srcIdx,
|
|
269
|
+
maKind: cfg?.maType === "sma" ? 0 : 1,
|
|
270
|
+
signalMaKind: cfg?.signalMaType === "sma" ? 0 : 1,
|
|
271
|
+
lineColor: inheritColor(cfg?.lineColor),
|
|
272
|
+
lineWidth: cfg?.lineWidth ?? -1,
|
|
273
|
+
lineVisible: cfg?.lineVisible ?? true,
|
|
274
|
+
signalColor: inheritColor(cfg?.signalColor),
|
|
275
|
+
signalWidth: cfg?.signalWidth ?? -1,
|
|
276
|
+
signalVisible: cfg?.signalVisible ?? true,
|
|
277
|
+
histVisible: cfg?.histogramVisible ?? true,
|
|
278
|
+
histUpColor: inheritColor(cfg?.histogramUpColor),
|
|
279
|
+
histUpFadingColor: inheritColor(cfg?.histogramUpFadingColor),
|
|
280
|
+
histDownColor: inheritColor(cfg?.histogramDownColor),
|
|
281
|
+
histDownFadingColor: inheritColor(cfg?.histogramDownFadingColor),
|
|
282
|
+
zeroColor: inheritColor(cfg?.zeroLineColor),
|
|
283
|
+
zeroVisible: cfg?.zeroLineVisible ?? true
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
function volumeToSpec(cfg) {
|
|
287
|
+
return {
|
|
288
|
+
enabled: cfg?.enabled ?? true,
|
|
289
|
+
heightFrac: cfg?.height ?? -1,
|
|
290
|
+
opacity: cfg?.opacity ?? -1,
|
|
291
|
+
radiusPx: cfg?.radius ?? -1,
|
|
292
|
+
upColor: (cfg?.upColor != null ? parseColor(cfg.upColor) : null) ?? 0,
|
|
293
|
+
downColor: (cfg?.downColor != null ? parseColor(cfg.downColor) : null) ?? 0
|
|
294
|
+
};
|
|
295
|
+
}
|
|
152
296
|
var DEFAULT_PRICE_LINE_COLOR = 4293874512;
|
|
153
297
|
var DEFAULT_PRICE_LINE_BODY_BG = 3642499368;
|
|
154
298
|
var DEFAULT_PRICE_LINE_HOVER_BOOST = 1.25;
|
|
@@ -186,14 +330,53 @@ function ensureInstalled() {
|
|
|
186
330
|
}
|
|
187
331
|
installed = true;
|
|
188
332
|
}
|
|
189
|
-
function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType, theme, rsi, macd, movingAverages, vwap, bollingerBands, priceLines) {
|
|
333
|
+
function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType, theme, rsi, macd, movingAverages, vwap, bollingerBands, volume, priceLines, transition) {
|
|
190
334
|
const handleRef = useRef(null);
|
|
191
335
|
const defaultWidthAppliedRef = useRef(false);
|
|
336
|
+
const volumeCollapseRef = useRef(null);
|
|
337
|
+
const prevDataRef = useRef(null);
|
|
338
|
+
const intervalMorphRaf = useRef(null);
|
|
192
339
|
const [picture, setPicture] = useState(null);
|
|
193
340
|
if (!handleRef.current && size.width > 0 && size.height > 0) {
|
|
194
341
|
ensureInstalled();
|
|
195
342
|
handleRef.current = globalThis.VroomChartJSI.create();
|
|
196
343
|
}
|
|
344
|
+
const animRef = useRef({ ms: 300, easing: void 0, reduceMotion: false });
|
|
345
|
+
animRef.current = {
|
|
346
|
+
ms: Math.max(0, transition?.transitionMs ?? 300),
|
|
347
|
+
easing: transition?.transitionEasing,
|
|
348
|
+
reduceMotion: transition?.reduceMotion ?? false
|
|
349
|
+
};
|
|
350
|
+
const onFrameRef = useRef(transition?.onFrame);
|
|
351
|
+
onFrameRef.current = transition?.onFrame;
|
|
352
|
+
const seriesKey = transition?.seriesKey;
|
|
353
|
+
const endIntervalMorph = useCallback(() => {
|
|
354
|
+
if (intervalMorphRaf.current != null) {
|
|
355
|
+
cancelAnimationFrame(intervalMorphRaf.current);
|
|
356
|
+
intervalMorphRaf.current = null;
|
|
357
|
+
}
|
|
358
|
+
handleRef.current?.setIntervalMorph(1);
|
|
359
|
+
}, []);
|
|
360
|
+
const startIntervalMorph = useCallback((h) => {
|
|
361
|
+
const { ms, easing } = animRef.current;
|
|
362
|
+
const start = performance.now();
|
|
363
|
+
const step = (now) => {
|
|
364
|
+
const p = Math.min(1, (now - start) / ms);
|
|
365
|
+
h.setIntervalMorph(p < 1 ? ease(easing, p) : 1);
|
|
366
|
+
const pic = h.render();
|
|
367
|
+
if (pic) onFrameRef.current?.(pic);
|
|
368
|
+
intervalMorphRaf.current = p < 1 ? requestAnimationFrame(step) : null;
|
|
369
|
+
};
|
|
370
|
+
intervalMorphRaf.current = requestAnimationFrame(step);
|
|
371
|
+
}, []);
|
|
372
|
+
useEffect(() => {
|
|
373
|
+
return () => {
|
|
374
|
+
if (intervalMorphRaf.current != null) {
|
|
375
|
+
cancelAnimationFrame(intervalMorphRaf.current);
|
|
376
|
+
intervalMorphRaf.current = null;
|
|
377
|
+
}
|
|
378
|
+
};
|
|
379
|
+
}, []);
|
|
197
380
|
const explicit = visibleRange != null;
|
|
198
381
|
const startMs = visibleRange?.startMs ?? 0;
|
|
199
382
|
const endMs = visibleRange?.endMs ?? 0;
|
|
@@ -203,6 +386,7 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
|
|
|
203
386
|
const maKey = movingAverages ? JSON.stringify(movingAverages) : "";
|
|
204
387
|
const vwapKey = vwap ? JSON.stringify(vwap) : "";
|
|
205
388
|
const bollingerKey = bollingerBands ? JSON.stringify(bollingerBands) : "";
|
|
389
|
+
const volumeKey = volume ? JSON.stringify(volume) : "";
|
|
206
390
|
const priceLinesKey = priceLines ? JSON.stringify(priceLines) : "";
|
|
207
391
|
useEffect(() => {
|
|
208
392
|
const h = handleRef.current;
|
|
@@ -212,8 +396,54 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
|
|
|
212
396
|
h.setDefaultCandleWidth(defaultCandleWidth);
|
|
213
397
|
defaultWidthAppliedRef.current = true;
|
|
214
398
|
}
|
|
399
|
+
let morphing = false;
|
|
215
400
|
if (candles.length > 0) {
|
|
216
|
-
|
|
401
|
+
const prev = prevDataRef.current;
|
|
402
|
+
const freshHandle = prev == null || prev.handle !== h;
|
|
403
|
+
if (freshHandle || prev.candles !== candles || prev.seriesKey !== seriesKey) {
|
|
404
|
+
const transitionKind = freshHandle ? "initial" : explicit ? "stream" : classifyTransition(prev.candles, candles, seriesKey !== prev.seriesKey);
|
|
405
|
+
let tfArgs = null;
|
|
406
|
+
let prevEnvelope = null;
|
|
407
|
+
if (transitionKind === "timeframe" && prev != null) {
|
|
408
|
+
const oldWindow = h.getVisibleRange();
|
|
409
|
+
const oldStepMs = inferStepMs(prev.candles);
|
|
410
|
+
if (oldWindow.endMs > oldWindow.startMs && oldStepMs != null) {
|
|
411
|
+
tfArgs = {
|
|
412
|
+
oldWindow,
|
|
413
|
+
oldStepMs,
|
|
414
|
+
oldLastMs: prev.candles[prev.candles.length - 1].timeMs
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
prevEnvelope = h.getVisiblePriceEnvelope();
|
|
418
|
+
morphing = animRef.current.ms > 0 && !animRef.current.reduceMotion && onFrameRef.current != null;
|
|
419
|
+
if (morphing) {
|
|
420
|
+
endIntervalMorph();
|
|
421
|
+
h.beginIntervalMorph();
|
|
422
|
+
}
|
|
423
|
+
} else if (transitionKind === "initial" || transitionKind === "reset") {
|
|
424
|
+
endIntervalMorph();
|
|
425
|
+
}
|
|
426
|
+
h.setCandles(packCandles(candles));
|
|
427
|
+
if (transitionKind === "timeframe") {
|
|
428
|
+
const newStepMs = inferStepMs(candles);
|
|
429
|
+
if (tfArgs && newStepMs != null) {
|
|
430
|
+
const w = timeframeWindow(
|
|
431
|
+
tfArgs.oldWindow,
|
|
432
|
+
tfArgs.oldStepMs,
|
|
433
|
+
tfArgs.oldLastMs,
|
|
434
|
+
newStepMs,
|
|
435
|
+
candles[candles.length - 1].timeMs
|
|
436
|
+
);
|
|
437
|
+
h.setVisibleRange(w.startMs, w.endMs);
|
|
438
|
+
}
|
|
439
|
+
if (prevEnvelope) h.preservePriceEnvelope(prevEnvelope.low, prevEnvelope.high);
|
|
440
|
+
else h.resetPriceScale();
|
|
441
|
+
if (morphing) startIntervalMorph(h);
|
|
442
|
+
} else if (transitionKind === "reset") {
|
|
443
|
+
h.resetView();
|
|
444
|
+
}
|
|
445
|
+
prevDataRef.current = { handle: h, candles, seriesKey };
|
|
446
|
+
}
|
|
217
447
|
}
|
|
218
448
|
if (explicit) {
|
|
219
449
|
h.setVisibleRange(startMs, endMs);
|
|
@@ -221,40 +451,27 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
|
|
|
221
451
|
if (theme) {
|
|
222
452
|
applyTheme(h, theme);
|
|
223
453
|
}
|
|
224
|
-
h.setRSI(
|
|
225
|
-
|
|
226
|
-
rsi?.period ?? 14,
|
|
227
|
-
rsi?.upperBand ?? 70,
|
|
228
|
-
rsi?.lowerBand ?? 30,
|
|
229
|
-
rsi?.maEnabled ?? true,
|
|
230
|
-
rsi?.maPeriod ?? 14
|
|
231
|
-
);
|
|
232
|
-
h.setMACD(
|
|
233
|
-
macd?.enabled ?? false,
|
|
234
|
-
macd?.fast ?? 12,
|
|
235
|
-
macd?.slow ?? 26,
|
|
236
|
-
macd?.signal ?? 9
|
|
237
|
-
);
|
|
454
|
+
h.setRSI(rsiToSpec(rsi));
|
|
455
|
+
h.setMACD(macdToSpec(macd));
|
|
238
456
|
h.setOverlays((movingAverages ?? []).map(overlayToNumeric));
|
|
239
|
-
h.setVWAP(
|
|
240
|
-
vwap?.enabled ?? false,
|
|
241
|
-
vwap?.resetMinutes ?? 0,
|
|
242
|
-
(vwap?.color != null ? parseColor(vwap.color) : null) ?? 4278238420,
|
|
243
|
-
vwap?.width ?? 1.5
|
|
244
|
-
);
|
|
457
|
+
h.setVWAP(vwapToSpec(vwap));
|
|
245
458
|
h.setBollinger(bollingerToSpec(bollingerBands));
|
|
459
|
+
h.setVolume(volumeToSpec(volume));
|
|
460
|
+
const collapse = volumeCollapseRef.current;
|
|
461
|
+
if (collapse) h.setVolumeCollapse(collapse.t, collapse.easing);
|
|
246
462
|
h.setPriceLines(
|
|
247
463
|
priceLines?.lines.length ? priceLinesToSpec(priceLines) : EMPTY_PRICE_LINES
|
|
248
464
|
);
|
|
249
|
-
setPicture(h.render());
|
|
250
|
-
}, [candles, size.width, size.height, size.pxRatio, explicit, startMs, endMs, defaultCandleWidth, themeKey, rsiKey, macdKey, maKey, vwapKey, bollingerKey, priceLinesKey]);
|
|
251
|
-
return { handle: handleRef.current, picture };
|
|
465
|
+
if (!morphing) setPicture(h.render());
|
|
466
|
+
}, [candles, seriesKey, size.width, size.height, size.pxRatio, explicit, startMs, endMs, defaultCandleWidth, themeKey, rsiKey, macdKey, maKey, vwapKey, bollingerKey, volumeKey, priceLinesKey, startIntervalMorph, endIntervalMorph]);
|
|
467
|
+
return { handle: handleRef.current, picture, volumeCollapseRef };
|
|
252
468
|
}
|
|
253
469
|
|
|
254
470
|
// src/VroomChart.tsx
|
|
255
471
|
function VroomChart(props) {
|
|
256
472
|
const {
|
|
257
473
|
candles,
|
|
474
|
+
seriesKey,
|
|
258
475
|
width: widthProp,
|
|
259
476
|
height: heightProp,
|
|
260
477
|
style,
|
|
@@ -262,12 +479,14 @@ function VroomChart(props) {
|
|
|
262
479
|
defaultCandleWidth,
|
|
263
480
|
chartType,
|
|
264
481
|
transitionMs,
|
|
482
|
+
transitionEasing,
|
|
265
483
|
theme,
|
|
266
484
|
rsi,
|
|
267
485
|
macd,
|
|
268
486
|
movingAverages,
|
|
269
487
|
vwap,
|
|
270
488
|
bollingerBands,
|
|
489
|
+
volume,
|
|
271
490
|
crosshairOffset = 40,
|
|
272
491
|
onCrosshair,
|
|
273
492
|
onViewportChange,
|
|
@@ -280,7 +499,7 @@ function VroomChart(props) {
|
|
|
280
499
|
const [measured, setMeasured] = useState2({ width: 0, height: 0 });
|
|
281
500
|
const width = widthProp ?? measured.width;
|
|
282
501
|
const height = heightProp ?? measured.height;
|
|
283
|
-
const onLayout =
|
|
502
|
+
const onLayout = useCallback2((e) => {
|
|
284
503
|
const w = Math.round(e.nativeEvent.layout.width);
|
|
285
504
|
const h = Math.round(e.nativeEvent.layout.height);
|
|
286
505
|
setMeasured(
|
|
@@ -295,7 +514,20 @@ function VroomChart(props) {
|
|
|
295
514
|
} : void 0,
|
|
296
515
|
[priceLines, priceLinesStyle, onPriceLineClose]
|
|
297
516
|
);
|
|
298
|
-
const
|
|
517
|
+
const emptyPicture = useMemo(() => {
|
|
518
|
+
const rec = Skia.PictureRecorder();
|
|
519
|
+
rec.beginRecording(Skia.XYWHRect(0, 0, 1, 1));
|
|
520
|
+
return rec.finishRecordingAsPicture();
|
|
521
|
+
}, []);
|
|
522
|
+
const pictureSV = useSharedValue(emptyPicture);
|
|
523
|
+
const reduceMotion = useReducedMotion();
|
|
524
|
+
const onFrame = useCallback2(
|
|
525
|
+
(p) => {
|
|
526
|
+
pictureSV.value = p;
|
|
527
|
+
},
|
|
528
|
+
[pictureSV]
|
|
529
|
+
);
|
|
530
|
+
const { handle, picture, volumeCollapseRef } = useChartCore(
|
|
299
531
|
candles,
|
|
300
532
|
{ width, height },
|
|
301
533
|
visibleRange,
|
|
@@ -307,21 +539,17 @@ function VroomChart(props) {
|
|
|
307
539
|
movingAverages,
|
|
308
540
|
vwap,
|
|
309
541
|
bollingerBands,
|
|
310
|
-
|
|
542
|
+
volume,
|
|
543
|
+
priceLinesProp,
|
|
544
|
+
{ seriesKey, transitionMs, transitionEasing, reduceMotion, onFrame }
|
|
311
545
|
);
|
|
312
|
-
const emptyPicture = useMemo(() => {
|
|
313
|
-
const rec = Skia.PictureRecorder();
|
|
314
|
-
rec.beginRecording(Skia.XYWHRect(0, 0, 1, 1));
|
|
315
|
-
return rec.finishRecordingAsPicture();
|
|
316
|
-
}, []);
|
|
317
|
-
const pictureSV = useSharedValue(emptyPicture);
|
|
318
546
|
const crosshairActive = useRef2(false);
|
|
319
547
|
const lastCrosshairTime = useRef2(null);
|
|
320
548
|
useEffect2(() => {
|
|
321
549
|
if (picture) pictureSV.value = picture;
|
|
322
550
|
}, [picture, pictureSV]);
|
|
323
551
|
const decayRaf = useRef2(null);
|
|
324
|
-
const cancelDecay =
|
|
552
|
+
const cancelDecay = useCallback2(() => {
|
|
325
553
|
if (decayRaf.current != null) {
|
|
326
554
|
cancelAnimationFrame(decayRaf.current);
|
|
327
555
|
decayRaf.current = null;
|
|
@@ -329,7 +557,7 @@ function VroomChart(props) {
|
|
|
329
557
|
}, []);
|
|
330
558
|
useEffect2(() => cancelDecay, [cancelDecay]);
|
|
331
559
|
const animRaf = useRef2(null);
|
|
332
|
-
const animTick =
|
|
560
|
+
const animTick = useCallback2(() => {
|
|
333
561
|
animRaf.current = null;
|
|
334
562
|
if (!handle) return;
|
|
335
563
|
const next = handle.render();
|
|
@@ -338,7 +566,7 @@ function VroomChart(props) {
|
|
|
338
566
|
animRaf.current = requestAnimationFrame(animTick);
|
|
339
567
|
}
|
|
340
568
|
}, [handle, pictureSV]);
|
|
341
|
-
const maybeStartAnim =
|
|
569
|
+
const maybeStartAnim = useCallback2(() => {
|
|
342
570
|
if (animRaf.current != null) return;
|
|
343
571
|
if (!handle?.isAnimating()) return;
|
|
344
572
|
animRaf.current = requestAnimationFrame(animTick);
|
|
@@ -354,6 +582,8 @@ function VroomChart(props) {
|
|
|
354
582
|
const morphRaf = useRef2(null);
|
|
355
583
|
const morphFade = useRef2(null);
|
|
356
584
|
const morphHandle = useRef2(null);
|
|
585
|
+
const easingRef = useRef2(transitionEasing);
|
|
586
|
+
easingRef.current = transitionEasing;
|
|
357
587
|
useEffect2(() => {
|
|
358
588
|
if (!handle) return void 0;
|
|
359
589
|
const target = chartType === "line" ? 1 : 0;
|
|
@@ -383,10 +613,9 @@ function VroomChart(props) {
|
|
|
383
613
|
const step = (now) => {
|
|
384
614
|
if (startTs == null) startTs = now;
|
|
385
615
|
const prog = Math.min(1, (now - startTs) / dur);
|
|
386
|
-
const
|
|
387
|
-
const fade = from + (target - from) * e;
|
|
616
|
+
const fade = from + (target - from) * ease(easingRef.current, prog);
|
|
388
617
|
morphFade.current = fade;
|
|
389
|
-
handle.setMorph(fade, fade);
|
|
618
|
+
handle.setMorph(reduceMotion ? 0 : fade, fade);
|
|
390
619
|
const p = handle.render();
|
|
391
620
|
if (p) pictureSV.value = p;
|
|
392
621
|
if (prog < 1) {
|
|
@@ -406,8 +635,53 @@ function VroomChart(props) {
|
|
|
406
635
|
morphRaf.current = null;
|
|
407
636
|
}
|
|
408
637
|
};
|
|
409
|
-
}, [handle, chartType, transitionMs, pictureSV]);
|
|
410
|
-
const
|
|
638
|
+
}, [handle, chartType, transitionMs, reduceMotion, pictureSV]);
|
|
639
|
+
const volumeRaf = useRef2(null);
|
|
640
|
+
const volumeHandle = useRef2(null);
|
|
641
|
+
useEffect2(() => {
|
|
642
|
+
if (!handle) return void 0;
|
|
643
|
+
const target = volume?.enabled ?? true ? 0 : 1;
|
|
644
|
+
const easing = easingIndex(easingRef.current);
|
|
645
|
+
if (volumeHandle.current !== handle || volumeCollapseRef.current == null) {
|
|
646
|
+
volumeHandle.current = handle;
|
|
647
|
+
volumeCollapseRef.current = { t: target, easing };
|
|
648
|
+
return void 0;
|
|
649
|
+
}
|
|
650
|
+
if (volumeCollapseRef.current.t === target) return void 0;
|
|
651
|
+
if (volumeRaf.current != null) {
|
|
652
|
+
cancelAnimationFrame(volumeRaf.current);
|
|
653
|
+
volumeRaf.current = null;
|
|
654
|
+
}
|
|
655
|
+
const dur = Math.max(0, transitionMs ?? 300);
|
|
656
|
+
if (dur === 0 || reduceMotion) {
|
|
657
|
+
volumeCollapseRef.current = { t: target, easing };
|
|
658
|
+
handle.setVolumeCollapse(target, easing);
|
|
659
|
+
const p = handle.render();
|
|
660
|
+
if (p) pictureSV.value = p;
|
|
661
|
+
return void 0;
|
|
662
|
+
}
|
|
663
|
+
const from = volumeCollapseRef.current.t;
|
|
664
|
+
let startTs = null;
|
|
665
|
+
const step = (now) => {
|
|
666
|
+
if (startTs == null) startTs = now;
|
|
667
|
+
const prog = Math.min(1, (now - startTs) / dur);
|
|
668
|
+
const t = prog < 1 ? from + (target - from) * prog : target;
|
|
669
|
+
const kind = easingIndex(easingRef.current);
|
|
670
|
+
volumeCollapseRef.current = { t, easing: kind };
|
|
671
|
+
handle.setVolumeCollapse(t, kind);
|
|
672
|
+
const p = handle.render();
|
|
673
|
+
if (p) pictureSV.value = p;
|
|
674
|
+
volumeRaf.current = prog < 1 ? requestAnimationFrame(step) : null;
|
|
675
|
+
};
|
|
676
|
+
volumeRaf.current = requestAnimationFrame(step);
|
|
677
|
+
return () => {
|
|
678
|
+
if (volumeRaf.current != null) {
|
|
679
|
+
cancelAnimationFrame(volumeRaf.current);
|
|
680
|
+
volumeRaf.current = null;
|
|
681
|
+
}
|
|
682
|
+
};
|
|
683
|
+
}, [handle, volume?.enabled, transitionMs, reduceMotion, pictureSV, volumeCollapseRef]);
|
|
684
|
+
const hitAxis = useCallback2(
|
|
411
685
|
(x, y) => {
|
|
412
686
|
if (!handle) return "chart";
|
|
413
687
|
const { yAxisWidth, xAxisHeight, indicatorHeight } = handle.getAxisMetrics();
|
|
@@ -420,7 +694,7 @@ function VroomChart(props) {
|
|
|
420
694
|
},
|
|
421
695
|
[handle, width, height]
|
|
422
696
|
);
|
|
423
|
-
const hitPriceLine =
|
|
697
|
+
const hitPriceLine = useCallback2(
|
|
424
698
|
(x, y) => {
|
|
425
699
|
if (!handle || !priceLines?.length) return null;
|
|
426
700
|
const hit = handle.hitTestPriceLine(x, y);
|
|
@@ -617,6 +891,9 @@ function VroomChart(props) {
|
|
|
617
891
|
);
|
|
618
892
|
}
|
|
619
893
|
export {
|
|
620
|
-
VroomChart
|
|
894
|
+
VroomChart,
|
|
895
|
+
classifyTransition,
|
|
896
|
+
inferStepMs,
|
|
897
|
+
timeframeWindow
|
|
621
898
|
};
|
|
622
899
|
//# sourceMappingURL=index.mjs.map
|