react-native-vroom-chart 0.15.0 → 0.17.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 +252 -1
- package/cpp/_core_include/vroom/vroom_chart.h +178 -4
- package/cpp/_core_src/atr.cpp +67 -0
- package/cpp/_core_src/atr.h +44 -0
- package/cpp/_core_src/atr_pane.cpp +162 -0
- package/cpp/_core_src/atr_pane.h +48 -0
- package/cpp/_core_src/candles.cpp +15 -14
- package/cpp/_core_src/chart.cpp +758 -200
- package/cpp/_core_src/chart.h +221 -3
- package/cpp/_core_src/chart_facade.cpp +236 -23
- package/cpp/_core_src/color_lerp.h +28 -0
- package/cpp/_core_src/fair_value_gaps.cpp +76 -0
- package/cpp/_core_src/fair_value_gaps.h +54 -0
- package/cpp/_core_src/fvg_overlay.cpp +262 -0
- package/cpp/_core_src/fvg_overlay.h +43 -0
- package/cpp/_core_src/ichimoku.cpp +65 -0
- package/cpp/_core_src/ichimoku.h +45 -0
- package/cpp/_core_src/labels.cpp +3 -0
- package/cpp/_core_src/line_morph.h +159 -0
- package/cpp/_core_src/loading_line.cpp +183 -0
- package/cpp/_core_src/loading_line.h +38 -0
- package/cpp/_core_src/loading_wave.h +140 -0
- package/cpp/_core_src/ma_overlay.cpp +273 -45
- package/cpp/_core_src/ma_overlay.h +64 -2
- package/cpp/_core_src/macd.cpp +24 -1
- package/cpp/_core_src/macd.h +16 -0
- package/cpp/_core_src/macd_pane.cpp +71 -62
- package/cpp/_core_src/macd_pane.h +13 -1
- package/cpp/_core_src/pane_series.h +169 -0
- package/cpp/_core_src/price_indicator.cpp +21 -7
- package/cpp/_core_src/price_indicator.h +11 -1
- package/cpp/_core_src/price_indicator_anim.h +81 -0
- package/cpp/_core_src/rsi.cpp +4 -0
- package/cpp/_core_src/rsi.h +7 -0
- package/cpp/_core_src/rsi_pane.cpp +85 -29
- package/cpp/_core_src/rsi_pane.h +11 -1
- package/cpp/_core_src/theme.cpp +5 -0
- package/cpp/_core_src/tip_geometry.h +55 -0
- package/cpp/_core_src/viewport.h +68 -0
- package/lib/index.d.mts +281 -3
- package/lib/index.d.ts +281 -3
- package/lib/index.js +292 -14
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +292 -14
- package/lib/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/VroomChart.tsx +33 -3
- package/src/dataTransitions.ts +47 -0
- package/src/index.ts +5 -0
- package/src/jsi.d.ts +139 -1
- package/src/theme.ts +1 -0
- package/src/types.ts +5 -0
- package/src/useChartCore.ts +402 -8
package/lib/index.mjs
CHANGED
|
@@ -71,6 +71,13 @@ function classifyTransition(prev, next, seriesKeyChanged) {
|
|
|
71
71
|
const endsTogether = Math.abs(nextEnd - prevEnd) <= MAX_END_DRIFT_STEPS * Math.max(prevStep, nextStep);
|
|
72
72
|
return closeRatio <= MAX_SAME_ASSET_CLOSE_RATIO && endsTogether ? "timeframe" : "reset";
|
|
73
73
|
}
|
|
74
|
+
function classifyStream(prev, next) {
|
|
75
|
+
if (prev.length === 0 || next.length === 0) return "tick";
|
|
76
|
+
return next[next.length - 1].timeMs > prev[prev.length - 1].timeMs ? "append" : "tick";
|
|
77
|
+
}
|
|
78
|
+
function isPinnedToLatest(window, lastMs, stepMs) {
|
|
79
|
+
return window.endMs >= lastMs + stepMs;
|
|
80
|
+
}
|
|
74
81
|
function timeframeWindow(oldWindow, oldStepMs, oldLastMs, newStepMs, newLastMs) {
|
|
75
82
|
const slots = (oldWindow.endMs - oldWindow.startMs) / oldStepMs;
|
|
76
83
|
const offsetRaw = (oldWindow.endMs - oldLastMs) / oldStepMs;
|
|
@@ -151,8 +158,10 @@ var COLOR_KEYS = {
|
|
|
151
158
|
// VROOM_COLOR_ACCENT_BULL
|
|
152
159
|
accentBear: 15,
|
|
153
160
|
// VROOM_COLOR_ACCENT_BEAR
|
|
154
|
-
lineColor: 16
|
|
161
|
+
lineColor: 16,
|
|
155
162
|
// VROOM_COLOR_LINE
|
|
163
|
+
skeleton: 17
|
|
164
|
+
// VROOM_COLOR_SKELETON
|
|
156
165
|
};
|
|
157
166
|
var FLOAT_KEYS = {
|
|
158
167
|
wickWidth: 1,
|
|
@@ -216,6 +225,7 @@ var MA_SOURCES = [
|
|
|
216
225
|
"hlc3",
|
|
217
226
|
"ohlc4"
|
|
218
227
|
];
|
|
228
|
+
var ATR_SMOOTHINGS = ["rma", "sma", "ema"];
|
|
219
229
|
var inheritColor = (v) => (v != null ? parseColor(v) : null) ?? 0;
|
|
220
230
|
function overlayToNumeric(o) {
|
|
221
231
|
const srcIdx = o.source ? MA_SOURCES.indexOf(o.source) : 0;
|
|
@@ -242,7 +252,8 @@ function rsiToSpec(cfg) {
|
|
|
242
252
|
maColor: inheritColor(cfg?.maColor),
|
|
243
253
|
maWidth: cfg?.maWidth ?? -1,
|
|
244
254
|
bandColor: inheritColor(cfg?.bandColor),
|
|
245
|
-
bandsVisible: cfg?.bandsVisible ?? true
|
|
255
|
+
bandsVisible: cfg?.bandsVisible ?? true,
|
|
256
|
+
extremeFill: cfg?.extremeFill ?? true
|
|
246
257
|
};
|
|
247
258
|
}
|
|
248
259
|
function vwapToSpec(cfg) {
|
|
@@ -273,6 +284,79 @@ function bollingerToSpec(cfg) {
|
|
|
273
284
|
fillOpacity: cfg?.fillOpacity ?? 0.1
|
|
274
285
|
};
|
|
275
286
|
}
|
|
287
|
+
var DEFAULT_ICH_GREEN = 4280723098;
|
|
288
|
+
var DEFAULT_ICH_RED = 4293874512;
|
|
289
|
+
var DEFAULT_ICH_BLUE = 4280902399;
|
|
290
|
+
var DEFAULT_ICH_ORANGE = 4294929664;
|
|
291
|
+
var DEFAULT_ICH_TEAL = 4278238420;
|
|
292
|
+
function ichimokuToSpec(cfg) {
|
|
293
|
+
const color = (v, fallback) => (v != null ? parseColor(v) : null) ?? fallback;
|
|
294
|
+
return {
|
|
295
|
+
enabled: cfg?.enabled ?? false,
|
|
296
|
+
tenkanPeriod: cfg?.tenkanPeriod ?? 9,
|
|
297
|
+
kijunPeriod: cfg?.kijunPeriod ?? 26,
|
|
298
|
+
senkouBPeriod: cfg?.senkouBPeriod ?? 52,
|
|
299
|
+
displacement: cfg?.displacement ?? 26,
|
|
300
|
+
tenkanColor: color(cfg?.tenkanColor, DEFAULT_ICH_BLUE),
|
|
301
|
+
tenkanWidth: cfg?.tenkanWidth ?? 1,
|
|
302
|
+
tenkanEnabled: cfg?.tenkanVisible ?? true,
|
|
303
|
+
kijunColor: color(cfg?.kijunColor, DEFAULT_ICH_RED),
|
|
304
|
+
kijunWidth: cfg?.kijunWidth ?? 1,
|
|
305
|
+
kijunEnabled: cfg?.kijunVisible ?? true,
|
|
306
|
+
senkouAColor: color(cfg?.senkouAColor, DEFAULT_ICH_GREEN),
|
|
307
|
+
senkouAWidth: cfg?.senkouAWidth ?? 1,
|
|
308
|
+
senkouAEnabled: cfg?.senkouAVisible ?? true,
|
|
309
|
+
senkouBColor: color(cfg?.senkouBColor, DEFAULT_ICH_ORANGE),
|
|
310
|
+
senkouBWidth: cfg?.senkouBWidth ?? 1,
|
|
311
|
+
senkouBEnabled: cfg?.senkouBVisible ?? true,
|
|
312
|
+
chikouColor: color(cfg?.chikouColor, DEFAULT_ICH_TEAL),
|
|
313
|
+
chikouWidth: cfg?.chikouWidth ?? 1,
|
|
314
|
+
chikouEnabled: cfg?.chikouVisible ?? true,
|
|
315
|
+
cloudEnabled: cfg?.cloudVisible ?? true,
|
|
316
|
+
bullishCloudColor: color(cfg?.bullishCloudColor, DEFAULT_ICH_GREEN),
|
|
317
|
+
bearishCloudColor: color(cfg?.bearishCloudColor, DEFAULT_ICH_RED),
|
|
318
|
+
cloudOpacity: cfg?.cloudOpacity ?? 0.15
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
var DEFAULT_FVG_GREEN = 4280723098;
|
|
322
|
+
var DEFAULT_FVG_RED = 4293874512;
|
|
323
|
+
var FVG_FILL_TYPES = ["close", "wick"];
|
|
324
|
+
var FVG_BORDER_STYLES = ["solid", "dotted", "dashed"];
|
|
325
|
+
function fvgToSpec(cfg) {
|
|
326
|
+
const color = (v, fallback) => (v != null ? parseColor(v) : null) ?? fallback;
|
|
327
|
+
const bullish = color(cfg?.bullishColor, DEFAULT_FVG_GREEN);
|
|
328
|
+
const bearish = color(cfg?.bearishColor, DEFAULT_FVG_RED);
|
|
329
|
+
return {
|
|
330
|
+
enabled: cfg?.enabled ?? false,
|
|
331
|
+
maxBarsBack: cfg?.maxBarsBack ?? 300,
|
|
332
|
+
waitForClose: cfg?.waitForClose ?? false,
|
|
333
|
+
fillType: Math.max(0, FVG_FILL_TYPES.indexOf(cfg?.fillType ?? "close")),
|
|
334
|
+
deleteAfterFill: cfg?.deleteAfterFill ?? true,
|
|
335
|
+
extendBoxes: cfg?.extendBoxes ?? false,
|
|
336
|
+
boxLength: cfg?.boxLength ?? 20,
|
|
337
|
+
bullishColor: bullish,
|
|
338
|
+
bearishColor: bearish,
|
|
339
|
+
opacity: cfg?.opacity ?? 0.15,
|
|
340
|
+
borderEnabled: cfg?.borderVisible ?? true,
|
|
341
|
+
borderStyle: Math.max(
|
|
342
|
+
0,
|
|
343
|
+
FVG_BORDER_STYLES.indexOf(cfg?.borderStyle ?? "solid")
|
|
344
|
+
),
|
|
345
|
+
borderWidth: cfg?.borderWidth ?? 1,
|
|
346
|
+
bullishBorderColor: color(cfg?.bullishBorderColor, bullish),
|
|
347
|
+
bearishBorderColor: color(cfg?.bearishBorderColor, bearish),
|
|
348
|
+
labelsEnabled: cfg?.showLabels ?? true,
|
|
349
|
+
label: cfg?.label ?? "FVG",
|
|
350
|
+
labelDistance: cfg?.labelDistance ?? 10,
|
|
351
|
+
// Alpha 0 is the core's "inherit the border color" sentinel.
|
|
352
|
+
labelColor: color(cfg?.labelColor, 0),
|
|
353
|
+
labelFontSize: cfg?.labelFontSize ?? 0,
|
|
354
|
+
showInverse: cfg?.showInverse ?? false,
|
|
355
|
+
inverseBullishColor: color(cfg?.inverseBullishColor, bullish),
|
|
356
|
+
inverseBearishColor: color(cfg?.inverseBearishColor, bearish),
|
|
357
|
+
inverseLabel: cfg?.inverseLabel ?? "iFVG"
|
|
358
|
+
};
|
|
359
|
+
}
|
|
276
360
|
function macdToSpec(cfg) {
|
|
277
361
|
const srcIdx = cfg?.source ? MA_SOURCES.indexOf(cfg.source) : 0;
|
|
278
362
|
return {
|
|
@@ -298,6 +382,15 @@ function macdToSpec(cfg) {
|
|
|
298
382
|
zeroVisible: cfg?.zeroLineVisible ?? true
|
|
299
383
|
};
|
|
300
384
|
}
|
|
385
|
+
function atrToSpec(cfg) {
|
|
386
|
+
return {
|
|
387
|
+
enabled: cfg?.enabled ?? false,
|
|
388
|
+
period: cfg?.period ?? 14,
|
|
389
|
+
smoothing: Math.max(0, ATR_SMOOTHINGS.indexOf(cfg?.smoothing ?? "rma")),
|
|
390
|
+
lineColor: inheritColor(cfg?.lineColor),
|
|
391
|
+
lineWidth: cfg?.lineWidth ?? -1
|
|
392
|
+
};
|
|
393
|
+
}
|
|
301
394
|
function volumeToSpec(cfg) {
|
|
302
395
|
return {
|
|
303
396
|
enabled: cfg?.enabled ?? true,
|
|
@@ -368,39 +461,61 @@ function ensureInstalled() {
|
|
|
368
461
|
}
|
|
369
462
|
installed = true;
|
|
370
463
|
}
|
|
371
|
-
function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType, theme, rsi, macd, movingAverages, vwap, bollingerBands, volume, priceLines, footprints, transition) {
|
|
464
|
+
function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType, theme, rsi, macd, atr, movingAverages, vwap, bollingerBands, ichimoku, fairValueGaps, volume, priceLines, footprints, transition, loading) {
|
|
372
465
|
const handleRef = useRef(null);
|
|
373
466
|
const defaultWidthAppliedRef = useRef(false);
|
|
374
467
|
const volumeCollapseRef = useRef(null);
|
|
375
468
|
const prevDataRef = useRef(null);
|
|
376
469
|
const intervalMorphRaf = useRef(null);
|
|
470
|
+
const streamRaf = useRef(null);
|
|
471
|
+
const streamWindowRef = useRef(null);
|
|
472
|
+
const streamMorphRef = useRef(false);
|
|
377
473
|
const [picture, setPicture] = useState(null);
|
|
378
474
|
if (!handleRef.current && size.width > 0 && size.height > 0) {
|
|
379
475
|
ensureInstalled();
|
|
380
476
|
handleRef.current = globalThis.VroomChartJSI.create();
|
|
381
477
|
}
|
|
382
|
-
const animRef = useRef({
|
|
478
|
+
const animRef = useRef({
|
|
479
|
+
ms: 300,
|
|
480
|
+
easing: void 0,
|
|
481
|
+
reduceMotion: false,
|
|
482
|
+
interval: "transform",
|
|
483
|
+
stream: "none",
|
|
484
|
+
streamMs: 150
|
|
485
|
+
});
|
|
383
486
|
animRef.current = {
|
|
384
487
|
ms: Math.max(0, transition?.transitionMs ?? 300),
|
|
385
488
|
easing: transition?.transitionEasing,
|
|
386
489
|
reduceMotion: transition?.reduceMotion ?? false,
|
|
387
|
-
interval: transition?.intervalTransition === "fade" ? "fade" : "transform"
|
|
490
|
+
interval: transition?.intervalTransition === "fade" ? "fade" : "transform",
|
|
491
|
+
stream: transition?.streamTransition === "transform" ? "transform" : "none",
|
|
492
|
+
// Shorter than transitionMs by default: ticks can land faster than a 300ms
|
|
493
|
+
// curve, and every one that does interrupts the last.
|
|
494
|
+
streamMs: Math.max(0, transition?.streamTransitionMs ?? 150)
|
|
388
495
|
};
|
|
389
496
|
const onFrameRef = useRef(transition?.onFrame);
|
|
390
497
|
onFrameRef.current = transition?.onFrame;
|
|
391
498
|
const seriesKey = transition?.seriesKey;
|
|
499
|
+
const loadingHandoffRef = useRef(false);
|
|
392
500
|
const endIntervalMorph = useCallback(() => {
|
|
393
501
|
if (intervalMorphRaf.current != null) {
|
|
394
502
|
cancelAnimationFrame(intervalMorphRaf.current);
|
|
395
503
|
intervalMorphRaf.current = null;
|
|
396
504
|
}
|
|
397
|
-
handleRef.current
|
|
505
|
+
const h = handleRef.current;
|
|
506
|
+
if (loadingHandoffRef.current) {
|
|
507
|
+
loadingHandoffRef.current = false;
|
|
508
|
+
h?.setLoadingMorph(1);
|
|
509
|
+
h?.beginLoadingReveal();
|
|
510
|
+
}
|
|
511
|
+
h?.setIntervalMorph(1);
|
|
398
512
|
}, []);
|
|
399
|
-
const startIntervalMorph = useCallback((h) => {
|
|
513
|
+
const startIntervalMorph = useCallback((h, durationMs) => {
|
|
400
514
|
const { ms, easing } = animRef.current;
|
|
515
|
+
const dur = durationMs ?? ms;
|
|
401
516
|
const start = performance.now();
|
|
402
517
|
const step = (now) => {
|
|
403
|
-
const p = Math.min(1, (now - start) /
|
|
518
|
+
const p = Math.min(1, (now - start) / dur);
|
|
404
519
|
h.setIntervalMorph(p < 1 ? ease(easing, p) : 1);
|
|
405
520
|
const pic = h.render();
|
|
406
521
|
if (pic) onFrameRef.current?.(pic);
|
|
@@ -408,23 +523,111 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
|
|
|
408
523
|
};
|
|
409
524
|
intervalMorphRaf.current = requestAnimationFrame(step);
|
|
410
525
|
}, []);
|
|
526
|
+
const startLoadingHandoff = useCallback(
|
|
527
|
+
(h) => {
|
|
528
|
+
const { ms, easing } = animRef.current;
|
|
529
|
+
const half = ms / 2;
|
|
530
|
+
loadingHandoffRef.current = true;
|
|
531
|
+
h.beginLoadingMorph();
|
|
532
|
+
const start = performance.now();
|
|
533
|
+
const step = (now) => {
|
|
534
|
+
const p = Math.min(1, (now - start) / half);
|
|
535
|
+
h.setLoadingMorph(p < 1 ? ease(easing, p) : 1);
|
|
536
|
+
const pic = h.render();
|
|
537
|
+
if (pic) onFrameRef.current?.(pic);
|
|
538
|
+
if (p < 1) {
|
|
539
|
+
intervalMorphRaf.current = requestAnimationFrame(step);
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
intervalMorphRaf.current = null;
|
|
543
|
+
loadingHandoffRef.current = false;
|
|
544
|
+
h.beginLoadingReveal();
|
|
545
|
+
startIntervalMorph(h, half);
|
|
546
|
+
};
|
|
547
|
+
intervalMorphRaf.current = requestAnimationFrame(step);
|
|
548
|
+
},
|
|
549
|
+
[startIntervalMorph]
|
|
550
|
+
);
|
|
551
|
+
const settleStream = useCallback((keepMorph = false) => {
|
|
552
|
+
if (streamRaf.current != null) {
|
|
553
|
+
cancelAnimationFrame(streamRaf.current);
|
|
554
|
+
streamRaf.current = null;
|
|
555
|
+
}
|
|
556
|
+
const h = handleRef.current;
|
|
557
|
+
const target = streamWindowRef.current;
|
|
558
|
+
streamWindowRef.current = null;
|
|
559
|
+
if (target) h?.setVisibleRange(target.startMs, target.endMs);
|
|
560
|
+
if (streamMorphRef.current && !keepMorph) {
|
|
561
|
+
streamMorphRef.current = false;
|
|
562
|
+
h?.setIntervalMorph(1);
|
|
563
|
+
}
|
|
564
|
+
}, []);
|
|
565
|
+
const startStreamAnim = useCallback(
|
|
566
|
+
(h, morphing, window) => {
|
|
567
|
+
const { streamMs, easing } = animRef.current;
|
|
568
|
+
let from = window ? h.getVisibleRange() : null;
|
|
569
|
+
let applied = null;
|
|
570
|
+
streamWindowRef.current = window;
|
|
571
|
+
streamMorphRef.current = morphing;
|
|
572
|
+
const start = performance.now();
|
|
573
|
+
const step = (now) => {
|
|
574
|
+
if (from && applied) {
|
|
575
|
+
const now_w = h.getVisibleRange();
|
|
576
|
+
if (now_w.startMs !== applied.startMs || now_w.endMs !== applied.endMs) {
|
|
577
|
+
from = null;
|
|
578
|
+
streamWindowRef.current = null;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
const p = Math.min(1, (now - start) / streamMs);
|
|
582
|
+
const e = p < 1 ? ease(easing, p) : 1;
|
|
583
|
+
if (morphing) h.setIntervalMorph(e);
|
|
584
|
+
if (from && window) {
|
|
585
|
+
applied = {
|
|
586
|
+
startMs: Math.round(from.startMs + (window.startMs - from.startMs) * e),
|
|
587
|
+
endMs: Math.round(from.endMs + (window.endMs - from.endMs) * e)
|
|
588
|
+
};
|
|
589
|
+
h.setVisibleRange(applied.startMs, applied.endMs);
|
|
590
|
+
}
|
|
591
|
+
const pic = h.render();
|
|
592
|
+
if (pic) onFrameRef.current?.(pic);
|
|
593
|
+
if (p < 1) {
|
|
594
|
+
streamRaf.current = requestAnimationFrame(step);
|
|
595
|
+
} else {
|
|
596
|
+
streamRaf.current = null;
|
|
597
|
+
streamWindowRef.current = null;
|
|
598
|
+
streamMorphRef.current = false;
|
|
599
|
+
}
|
|
600
|
+
};
|
|
601
|
+
streamRaf.current = requestAnimationFrame(step);
|
|
602
|
+
},
|
|
603
|
+
[]
|
|
604
|
+
);
|
|
411
605
|
useEffect(() => {
|
|
412
606
|
return () => {
|
|
413
607
|
if (intervalMorphRaf.current != null) {
|
|
414
608
|
cancelAnimationFrame(intervalMorphRaf.current);
|
|
415
609
|
intervalMorphRaf.current = null;
|
|
416
610
|
}
|
|
611
|
+
if (streamRaf.current != null) {
|
|
612
|
+
cancelAnimationFrame(streamRaf.current);
|
|
613
|
+
streamRaf.current = null;
|
|
614
|
+
}
|
|
417
615
|
};
|
|
418
616
|
}, []);
|
|
419
617
|
const explicit = visibleRange != null;
|
|
420
618
|
const startMs = visibleRange?.startMs ?? 0;
|
|
421
619
|
const endMs = visibleRange?.endMs ?? 0;
|
|
620
|
+
const showLoadingLine = loading === true && candles.length === 0;
|
|
621
|
+
const lineUpRef = useRef(false);
|
|
422
622
|
const themeKey = theme ? JSON.stringify(theme) : "";
|
|
423
623
|
const rsiKey = rsi ? JSON.stringify(rsi) : "";
|
|
424
624
|
const macdKey = macd ? JSON.stringify(macd) : "";
|
|
625
|
+
const atrKey = atr ? JSON.stringify(atr) : "";
|
|
425
626
|
const maKey = movingAverages ? JSON.stringify(movingAverages) : "";
|
|
426
627
|
const vwapKey = vwap ? JSON.stringify(vwap) : "";
|
|
427
628
|
const bollingerKey = bollingerBands ? JSON.stringify(bollingerBands) : "";
|
|
629
|
+
const ichimokuKey = ichimoku ? JSON.stringify(ichimoku) : "";
|
|
630
|
+
const fvgKey = fairValueGaps ? JSON.stringify(fairValueGaps) : "";
|
|
428
631
|
const volumeKey = volume ? JSON.stringify(volume) : "";
|
|
429
632
|
const priceLinesKey = priceLines ? JSON.stringify(priceLines) : "";
|
|
430
633
|
const footprintsKey = footprints ? JSON.stringify(footprints) : "";
|
|
@@ -432,11 +635,25 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
|
|
|
432
635
|
const h = handleRef.current;
|
|
433
636
|
if (!h) return;
|
|
434
637
|
h.setSize(size.width, size.height, size.pxRatio ?? 1);
|
|
638
|
+
h.setIchimoku(ichimokuToSpec(ichimoku));
|
|
435
639
|
if (!defaultWidthAppliedRef.current && !explicit && defaultCandleWidth != null && defaultCandleWidth > 0) {
|
|
436
640
|
h.setDefaultCandleWidth(defaultCandleWidth);
|
|
437
641
|
defaultWidthAppliedRef.current = true;
|
|
438
642
|
}
|
|
439
643
|
let morphing = false;
|
|
644
|
+
if (showLoadingLine) {
|
|
645
|
+
if (!lineUpRef.current) {
|
|
646
|
+
endIntervalMorph();
|
|
647
|
+
settleStream();
|
|
648
|
+
h.setCandles(packCandles([]));
|
|
649
|
+
prevDataRef.current = null;
|
|
650
|
+
}
|
|
651
|
+
h.setLoading(true, !animRef.current.reduceMotion);
|
|
652
|
+
lineUpRef.current = true;
|
|
653
|
+
} else if (lineUpRef.current && candles.length === 0) {
|
|
654
|
+
h.setLoading(false, true);
|
|
655
|
+
lineUpRef.current = false;
|
|
656
|
+
}
|
|
440
657
|
if (candles.length > 0) {
|
|
441
658
|
const prev = prevDataRef.current;
|
|
442
659
|
const freshHandle = prev == null || prev.handle !== h;
|
|
@@ -444,6 +661,37 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
|
|
|
444
661
|
const transitionKind = freshHandle ? "initial" : explicit ? "stream" : classifyTransition(prev.candles, candles, seriesKey !== prev.seriesKey);
|
|
445
662
|
let tfArgs = null;
|
|
446
663
|
let prevEnvelope = null;
|
|
664
|
+
let handOff = false;
|
|
665
|
+
let stream = null;
|
|
666
|
+
if (transitionKind === "stream" && prev != null && !explicit) {
|
|
667
|
+
const { stream: mode, streamMs, reduceMotion } = animRef.current;
|
|
668
|
+
const stepMs = inferStepMs(candles);
|
|
669
|
+
if (mode === "transform" && streamMs > 0 && stepMs != null && !reduceMotion && onFrameRef.current != null) {
|
|
670
|
+
const lastMs = candles[candles.length - 1].timeMs;
|
|
671
|
+
const prevLastMs = prev.candles[prev.candles.length - 1].timeMs;
|
|
672
|
+
if (classifyStream(prev.candles, candles) === "append") {
|
|
673
|
+
const w = h.getVisibleRange();
|
|
674
|
+
const prevStepMs = inferStepMs(prev.candles) ?? stepMs;
|
|
675
|
+
if (isPinnedToLatest(w, prevLastMs, prevStepMs)) {
|
|
676
|
+
const by = lastMs - prevLastMs;
|
|
677
|
+
stream = {
|
|
678
|
+
morph: false,
|
|
679
|
+
window: { startMs: w.startMs + by, endMs: w.endMs + by }
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
} else {
|
|
683
|
+
stream = { morph: true, window: null };
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
if (stream?.morph) {
|
|
687
|
+
settleStream(true);
|
|
688
|
+
h.beginStreamMorph();
|
|
689
|
+
} else {
|
|
690
|
+
settleStream();
|
|
691
|
+
}
|
|
692
|
+
} else if (transitionKind === "stream") {
|
|
693
|
+
settleStream();
|
|
694
|
+
}
|
|
447
695
|
if (transitionKind === "timeframe" && prev != null) {
|
|
448
696
|
const oldWindow = h.getVisibleRange();
|
|
449
697
|
const oldStepMs = inferStepMs(prev.candles);
|
|
@@ -463,6 +711,11 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
|
|
|
463
711
|
} else if (transitionKind === "initial" || transitionKind === "reset") {
|
|
464
712
|
endIntervalMorph();
|
|
465
713
|
}
|
|
714
|
+
if (lineUpRef.current) {
|
|
715
|
+
lineUpRef.current = false;
|
|
716
|
+
handOff = animRef.current.ms > 0 && !animRef.current.reduceMotion && onFrameRef.current != null;
|
|
717
|
+
if (!handOff) h.setLoading(false, true);
|
|
718
|
+
}
|
|
466
719
|
h.setCandles(packCandles(candles));
|
|
467
720
|
if (transitionKind === "timeframe") {
|
|
468
721
|
const newStepMs = inferStepMs(candles);
|
|
@@ -479,9 +732,12 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
|
|
|
479
732
|
if (prevEnvelope) h.preservePriceEnvelope(prevEnvelope.low, prevEnvelope.high);
|
|
480
733
|
else h.resetPriceScale();
|
|
481
734
|
if (morphing) startIntervalMorph(h);
|
|
735
|
+
} else if (stream) {
|
|
736
|
+
startStreamAnim(h, stream.morph, stream.window);
|
|
482
737
|
} else if (transitionKind === "reset") {
|
|
483
738
|
h.resetView();
|
|
484
739
|
}
|
|
740
|
+
if (handOff) startLoadingHandoff(h);
|
|
485
741
|
prevDataRef.current = { handle: h, candles, seriesKey };
|
|
486
742
|
}
|
|
487
743
|
}
|
|
@@ -496,9 +752,11 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
|
|
|
496
752
|
}
|
|
497
753
|
h.setRSI(rsiToSpec(rsi));
|
|
498
754
|
h.setMACD(macdToSpec(macd));
|
|
755
|
+
h.setATR(atrToSpec(atr));
|
|
499
756
|
h.setOverlays((movingAverages ?? []).map(overlayToNumeric));
|
|
500
757
|
h.setVWAP(vwapToSpec(vwap));
|
|
501
758
|
h.setBollinger(bollingerToSpec(bollingerBands));
|
|
759
|
+
h.setFairValueGaps(fvgToSpec(fairValueGaps));
|
|
502
760
|
h.setVolume(volumeToSpec(volume));
|
|
503
761
|
const collapse = volumeCollapseRef.current;
|
|
504
762
|
if (collapse) h.setVolumeCollapse(collapse.t, collapse.easing);
|
|
@@ -509,7 +767,7 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
|
|
|
509
767
|
footprints?.prints.length ? footprintsToSpec(footprints) : EMPTY_FOOTPRINTS
|
|
510
768
|
);
|
|
511
769
|
if (!morphing) setPicture(h.render());
|
|
512
|
-
}, [candles, seriesKey, size.width, size.height, size.pxRatio, explicit, startMs, endMs, defaultCandleWidth, themeKey, rsiKey, macdKey, maKey, vwapKey, bollingerKey, volumeKey, priceLinesKey, footprintsKey, startIntervalMorph, endIntervalMorph]);
|
|
770
|
+
}, [candles, showLoadingLine, seriesKey, size.width, size.height, size.pxRatio, explicit, startMs, endMs, defaultCandleWidth, themeKey, rsiKey, macdKey, atrKey, maKey, vwapKey, bollingerKey, ichimokuKey, fvgKey, volumeKey, priceLinesKey, footprintsKey, startIntervalMorph, startLoadingHandoff, endIntervalMorph, startStreamAnim, settleStream]);
|
|
513
771
|
return { handle: handleRef.current, picture, volumeCollapseRef };
|
|
514
772
|
}
|
|
515
773
|
|
|
@@ -521,6 +779,7 @@ function isSkImage(frame) {
|
|
|
521
779
|
function VroomChart(props) {
|
|
522
780
|
const {
|
|
523
781
|
candles,
|
|
782
|
+
loading,
|
|
524
783
|
seriesKey,
|
|
525
784
|
width: widthProp,
|
|
526
785
|
height: heightProp,
|
|
@@ -531,12 +790,17 @@ function VroomChart(props) {
|
|
|
531
790
|
transitionMs,
|
|
532
791
|
transitionEasing,
|
|
533
792
|
intervalTransition,
|
|
793
|
+
streamTransition,
|
|
794
|
+
streamTransitionMs,
|
|
534
795
|
theme,
|
|
535
796
|
rsi,
|
|
536
797
|
macd,
|
|
798
|
+
atr,
|
|
537
799
|
movingAverages,
|
|
538
800
|
vwap,
|
|
539
801
|
bollingerBands,
|
|
802
|
+
ichimoku,
|
|
803
|
+
fairValueGaps,
|
|
540
804
|
volume,
|
|
541
805
|
crosshairOffset = 40,
|
|
542
806
|
onCrosshair,
|
|
@@ -615,14 +879,28 @@ function VroomChart(props) {
|
|
|
615
879
|
theme,
|
|
616
880
|
rsi,
|
|
617
881
|
macd,
|
|
882
|
+
atr,
|
|
618
883
|
movingAverages,
|
|
619
884
|
vwap,
|
|
620
885
|
bollingerBands,
|
|
886
|
+
ichimoku,
|
|
887
|
+
fairValueGaps,
|
|
621
888
|
volume,
|
|
622
889
|
priceLinesProp,
|
|
623
890
|
footprintsProp,
|
|
624
|
-
{
|
|
891
|
+
{
|
|
892
|
+
seriesKey,
|
|
893
|
+
transitionMs,
|
|
894
|
+
transitionEasing,
|
|
895
|
+
intervalTransition,
|
|
896
|
+
streamTransition,
|
|
897
|
+
streamTransitionMs,
|
|
898
|
+
reduceMotion,
|
|
899
|
+
onFrame
|
|
900
|
+
},
|
|
901
|
+
loading
|
|
625
902
|
);
|
|
903
|
+
const showLoadingLine = loading === true && candles.length === 0;
|
|
626
904
|
const crosshairActive = useRef2(false);
|
|
627
905
|
const footprintActive = useRef2(false);
|
|
628
906
|
const lastCrosshairTime = useRef2(null);
|
|
@@ -902,7 +1180,7 @@ function VroomChart(props) {
|
|
|
902
1180
|
pane: null
|
|
903
1181
|
});
|
|
904
1182
|
};
|
|
905
|
-
const pan = Gesture.Pan().runOnJS(true).maxPointers(1).onStart((e) => {
|
|
1183
|
+
const pan = Gesture.Pan().enabled(!showLoadingLine).runOnJS(true).maxPointers(1).onStart((e) => {
|
|
906
1184
|
cancelDecay();
|
|
907
1185
|
panMode.current = hitAxis(e.x, e.y);
|
|
908
1186
|
priceDrag.current = null;
|
|
@@ -997,7 +1275,7 @@ function VroomChart(props) {
|
|
|
997
1275
|
enableX: false,
|
|
998
1276
|
enableY: false
|
|
999
1277
|
});
|
|
1000
|
-
const pinch = Gesture.Pinch().runOnJS(true).onTouchesDown((e) => {
|
|
1278
|
+
const pinch = Gesture.Pinch().enabled(!showLoadingLine).runOnJS(true).onTouchesDown((e) => {
|
|
1001
1279
|
if (e.numberOfTouches < 2) return;
|
|
1002
1280
|
dismissFootprint();
|
|
1003
1281
|
const [a, b] = e.allTouches;
|
|
@@ -1035,7 +1313,7 @@ function VroomChart(props) {
|
|
|
1035
1313
|
if (next) applyFrame(next);
|
|
1036
1314
|
maybeStartAnim();
|
|
1037
1315
|
});
|
|
1038
|
-
const longPress = Gesture.LongPress().runOnJS(true).onStart((e) => {
|
|
1316
|
+
const longPress = Gesture.LongPress().enabled(!showLoadingLine).runOnJS(true).onStart((e) => {
|
|
1039
1317
|
if (!handle) return;
|
|
1040
1318
|
if (hitAxis(e.x, e.y) !== "chart") return;
|
|
1041
1319
|
if (hitPriceLine(e.x, e.y)) return;
|
|
@@ -1055,7 +1333,7 @@ function VroomChart(props) {
|
|
|
1055
1333
|
reason: "show"
|
|
1056
1334
|
});
|
|
1057
1335
|
});
|
|
1058
|
-
const tap = Gesture.Tap().runOnJS(true).onStart((e) => {
|
|
1336
|
+
const tap = Gesture.Tap().enabled(!showLoadingLine).runOnJS(true).onStart((e) => {
|
|
1059
1337
|
if (!handle) return;
|
|
1060
1338
|
const prints = footprints ?? [];
|
|
1061
1339
|
const fp = prints.length ? handle.hitTestFootprint(e.x, e.y) : null;
|