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.js
CHANGED
|
@@ -99,6 +99,13 @@ function classifyTransition(prev, next, seriesKeyChanged) {
|
|
|
99
99
|
const endsTogether = Math.abs(nextEnd - prevEnd) <= MAX_END_DRIFT_STEPS * Math.max(prevStep, nextStep);
|
|
100
100
|
return closeRatio <= MAX_SAME_ASSET_CLOSE_RATIO && endsTogether ? "timeframe" : "reset";
|
|
101
101
|
}
|
|
102
|
+
function classifyStream(prev, next) {
|
|
103
|
+
if (prev.length === 0 || next.length === 0) return "tick";
|
|
104
|
+
return next[next.length - 1].timeMs > prev[prev.length - 1].timeMs ? "append" : "tick";
|
|
105
|
+
}
|
|
106
|
+
function isPinnedToLatest(window, lastMs, stepMs) {
|
|
107
|
+
return window.endMs >= lastMs + stepMs;
|
|
108
|
+
}
|
|
102
109
|
function timeframeWindow(oldWindow, oldStepMs, oldLastMs, newStepMs, newLastMs) {
|
|
103
110
|
const slots = (oldWindow.endMs - oldWindow.startMs) / oldStepMs;
|
|
104
111
|
const offsetRaw = (oldWindow.endMs - oldLastMs) / oldStepMs;
|
|
@@ -179,8 +186,10 @@ var COLOR_KEYS = {
|
|
|
179
186
|
// VROOM_COLOR_ACCENT_BULL
|
|
180
187
|
accentBear: 15,
|
|
181
188
|
// VROOM_COLOR_ACCENT_BEAR
|
|
182
|
-
lineColor: 16
|
|
189
|
+
lineColor: 16,
|
|
183
190
|
// VROOM_COLOR_LINE
|
|
191
|
+
skeleton: 17
|
|
192
|
+
// VROOM_COLOR_SKELETON
|
|
184
193
|
};
|
|
185
194
|
var FLOAT_KEYS = {
|
|
186
195
|
wickWidth: 1,
|
|
@@ -244,6 +253,7 @@ var MA_SOURCES = [
|
|
|
244
253
|
"hlc3",
|
|
245
254
|
"ohlc4"
|
|
246
255
|
];
|
|
256
|
+
var ATR_SMOOTHINGS = ["rma", "sma", "ema"];
|
|
247
257
|
var inheritColor = (v) => (v != null ? parseColor(v) : null) ?? 0;
|
|
248
258
|
function overlayToNumeric(o) {
|
|
249
259
|
const srcIdx = o.source ? MA_SOURCES.indexOf(o.source) : 0;
|
|
@@ -270,7 +280,8 @@ function rsiToSpec(cfg) {
|
|
|
270
280
|
maColor: inheritColor(cfg?.maColor),
|
|
271
281
|
maWidth: cfg?.maWidth ?? -1,
|
|
272
282
|
bandColor: inheritColor(cfg?.bandColor),
|
|
273
|
-
bandsVisible: cfg?.bandsVisible ?? true
|
|
283
|
+
bandsVisible: cfg?.bandsVisible ?? true,
|
|
284
|
+
extremeFill: cfg?.extremeFill ?? true
|
|
274
285
|
};
|
|
275
286
|
}
|
|
276
287
|
function vwapToSpec(cfg) {
|
|
@@ -301,6 +312,79 @@ function bollingerToSpec(cfg) {
|
|
|
301
312
|
fillOpacity: cfg?.fillOpacity ?? 0.1
|
|
302
313
|
};
|
|
303
314
|
}
|
|
315
|
+
var DEFAULT_ICH_GREEN = 4280723098;
|
|
316
|
+
var DEFAULT_ICH_RED = 4293874512;
|
|
317
|
+
var DEFAULT_ICH_BLUE = 4280902399;
|
|
318
|
+
var DEFAULT_ICH_ORANGE = 4294929664;
|
|
319
|
+
var DEFAULT_ICH_TEAL = 4278238420;
|
|
320
|
+
function ichimokuToSpec(cfg) {
|
|
321
|
+
const color = (v, fallback) => (v != null ? parseColor(v) : null) ?? fallback;
|
|
322
|
+
return {
|
|
323
|
+
enabled: cfg?.enabled ?? false,
|
|
324
|
+
tenkanPeriod: cfg?.tenkanPeriod ?? 9,
|
|
325
|
+
kijunPeriod: cfg?.kijunPeriod ?? 26,
|
|
326
|
+
senkouBPeriod: cfg?.senkouBPeriod ?? 52,
|
|
327
|
+
displacement: cfg?.displacement ?? 26,
|
|
328
|
+
tenkanColor: color(cfg?.tenkanColor, DEFAULT_ICH_BLUE),
|
|
329
|
+
tenkanWidth: cfg?.tenkanWidth ?? 1,
|
|
330
|
+
tenkanEnabled: cfg?.tenkanVisible ?? true,
|
|
331
|
+
kijunColor: color(cfg?.kijunColor, DEFAULT_ICH_RED),
|
|
332
|
+
kijunWidth: cfg?.kijunWidth ?? 1,
|
|
333
|
+
kijunEnabled: cfg?.kijunVisible ?? true,
|
|
334
|
+
senkouAColor: color(cfg?.senkouAColor, DEFAULT_ICH_GREEN),
|
|
335
|
+
senkouAWidth: cfg?.senkouAWidth ?? 1,
|
|
336
|
+
senkouAEnabled: cfg?.senkouAVisible ?? true,
|
|
337
|
+
senkouBColor: color(cfg?.senkouBColor, DEFAULT_ICH_ORANGE),
|
|
338
|
+
senkouBWidth: cfg?.senkouBWidth ?? 1,
|
|
339
|
+
senkouBEnabled: cfg?.senkouBVisible ?? true,
|
|
340
|
+
chikouColor: color(cfg?.chikouColor, DEFAULT_ICH_TEAL),
|
|
341
|
+
chikouWidth: cfg?.chikouWidth ?? 1,
|
|
342
|
+
chikouEnabled: cfg?.chikouVisible ?? true,
|
|
343
|
+
cloudEnabled: cfg?.cloudVisible ?? true,
|
|
344
|
+
bullishCloudColor: color(cfg?.bullishCloudColor, DEFAULT_ICH_GREEN),
|
|
345
|
+
bearishCloudColor: color(cfg?.bearishCloudColor, DEFAULT_ICH_RED),
|
|
346
|
+
cloudOpacity: cfg?.cloudOpacity ?? 0.15
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
var DEFAULT_FVG_GREEN = 4280723098;
|
|
350
|
+
var DEFAULT_FVG_RED = 4293874512;
|
|
351
|
+
var FVG_FILL_TYPES = ["close", "wick"];
|
|
352
|
+
var FVG_BORDER_STYLES = ["solid", "dotted", "dashed"];
|
|
353
|
+
function fvgToSpec(cfg) {
|
|
354
|
+
const color = (v, fallback) => (v != null ? parseColor(v) : null) ?? fallback;
|
|
355
|
+
const bullish = color(cfg?.bullishColor, DEFAULT_FVG_GREEN);
|
|
356
|
+
const bearish = color(cfg?.bearishColor, DEFAULT_FVG_RED);
|
|
357
|
+
return {
|
|
358
|
+
enabled: cfg?.enabled ?? false,
|
|
359
|
+
maxBarsBack: cfg?.maxBarsBack ?? 300,
|
|
360
|
+
waitForClose: cfg?.waitForClose ?? false,
|
|
361
|
+
fillType: Math.max(0, FVG_FILL_TYPES.indexOf(cfg?.fillType ?? "close")),
|
|
362
|
+
deleteAfterFill: cfg?.deleteAfterFill ?? true,
|
|
363
|
+
extendBoxes: cfg?.extendBoxes ?? false,
|
|
364
|
+
boxLength: cfg?.boxLength ?? 20,
|
|
365
|
+
bullishColor: bullish,
|
|
366
|
+
bearishColor: bearish,
|
|
367
|
+
opacity: cfg?.opacity ?? 0.15,
|
|
368
|
+
borderEnabled: cfg?.borderVisible ?? true,
|
|
369
|
+
borderStyle: Math.max(
|
|
370
|
+
0,
|
|
371
|
+
FVG_BORDER_STYLES.indexOf(cfg?.borderStyle ?? "solid")
|
|
372
|
+
),
|
|
373
|
+
borderWidth: cfg?.borderWidth ?? 1,
|
|
374
|
+
bullishBorderColor: color(cfg?.bullishBorderColor, bullish),
|
|
375
|
+
bearishBorderColor: color(cfg?.bearishBorderColor, bearish),
|
|
376
|
+
labelsEnabled: cfg?.showLabels ?? true,
|
|
377
|
+
label: cfg?.label ?? "FVG",
|
|
378
|
+
labelDistance: cfg?.labelDistance ?? 10,
|
|
379
|
+
// Alpha 0 is the core's "inherit the border color" sentinel.
|
|
380
|
+
labelColor: color(cfg?.labelColor, 0),
|
|
381
|
+
labelFontSize: cfg?.labelFontSize ?? 0,
|
|
382
|
+
showInverse: cfg?.showInverse ?? false,
|
|
383
|
+
inverseBullishColor: color(cfg?.inverseBullishColor, bullish),
|
|
384
|
+
inverseBearishColor: color(cfg?.inverseBearishColor, bearish),
|
|
385
|
+
inverseLabel: cfg?.inverseLabel ?? "iFVG"
|
|
386
|
+
};
|
|
387
|
+
}
|
|
304
388
|
function macdToSpec(cfg) {
|
|
305
389
|
const srcIdx = cfg?.source ? MA_SOURCES.indexOf(cfg.source) : 0;
|
|
306
390
|
return {
|
|
@@ -326,6 +410,15 @@ function macdToSpec(cfg) {
|
|
|
326
410
|
zeroVisible: cfg?.zeroLineVisible ?? true
|
|
327
411
|
};
|
|
328
412
|
}
|
|
413
|
+
function atrToSpec(cfg) {
|
|
414
|
+
return {
|
|
415
|
+
enabled: cfg?.enabled ?? false,
|
|
416
|
+
period: cfg?.period ?? 14,
|
|
417
|
+
smoothing: Math.max(0, ATR_SMOOTHINGS.indexOf(cfg?.smoothing ?? "rma")),
|
|
418
|
+
lineColor: inheritColor(cfg?.lineColor),
|
|
419
|
+
lineWidth: cfg?.lineWidth ?? -1
|
|
420
|
+
};
|
|
421
|
+
}
|
|
329
422
|
function volumeToSpec(cfg) {
|
|
330
423
|
return {
|
|
331
424
|
enabled: cfg?.enabled ?? true,
|
|
@@ -396,39 +489,61 @@ function ensureInstalled() {
|
|
|
396
489
|
}
|
|
397
490
|
installed = true;
|
|
398
491
|
}
|
|
399
|
-
function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType, theme, rsi, macd, movingAverages, vwap, bollingerBands, volume, priceLines, footprints, transition) {
|
|
492
|
+
function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType, theme, rsi, macd, atr, movingAverages, vwap, bollingerBands, ichimoku, fairValueGaps, volume, priceLines, footprints, transition, loading) {
|
|
400
493
|
const handleRef = (0, import_react.useRef)(null);
|
|
401
494
|
const defaultWidthAppliedRef = (0, import_react.useRef)(false);
|
|
402
495
|
const volumeCollapseRef = (0, import_react.useRef)(null);
|
|
403
496
|
const prevDataRef = (0, import_react.useRef)(null);
|
|
404
497
|
const intervalMorphRaf = (0, import_react.useRef)(null);
|
|
498
|
+
const streamRaf = (0, import_react.useRef)(null);
|
|
499
|
+
const streamWindowRef = (0, import_react.useRef)(null);
|
|
500
|
+
const streamMorphRef = (0, import_react.useRef)(false);
|
|
405
501
|
const [picture, setPicture] = (0, import_react.useState)(null);
|
|
406
502
|
if (!handleRef.current && size.width > 0 && size.height > 0) {
|
|
407
503
|
ensureInstalled();
|
|
408
504
|
handleRef.current = globalThis.VroomChartJSI.create();
|
|
409
505
|
}
|
|
410
|
-
const animRef = (0, import_react.useRef)({
|
|
506
|
+
const animRef = (0, import_react.useRef)({
|
|
507
|
+
ms: 300,
|
|
508
|
+
easing: void 0,
|
|
509
|
+
reduceMotion: false,
|
|
510
|
+
interval: "transform",
|
|
511
|
+
stream: "none",
|
|
512
|
+
streamMs: 150
|
|
513
|
+
});
|
|
411
514
|
animRef.current = {
|
|
412
515
|
ms: Math.max(0, transition?.transitionMs ?? 300),
|
|
413
516
|
easing: transition?.transitionEasing,
|
|
414
517
|
reduceMotion: transition?.reduceMotion ?? false,
|
|
415
|
-
interval: transition?.intervalTransition === "fade" ? "fade" : "transform"
|
|
518
|
+
interval: transition?.intervalTransition === "fade" ? "fade" : "transform",
|
|
519
|
+
stream: transition?.streamTransition === "transform" ? "transform" : "none",
|
|
520
|
+
// Shorter than transitionMs by default: ticks can land faster than a 300ms
|
|
521
|
+
// curve, and every one that does interrupts the last.
|
|
522
|
+
streamMs: Math.max(0, transition?.streamTransitionMs ?? 150)
|
|
416
523
|
};
|
|
417
524
|
const onFrameRef = (0, import_react.useRef)(transition?.onFrame);
|
|
418
525
|
onFrameRef.current = transition?.onFrame;
|
|
419
526
|
const seriesKey = transition?.seriesKey;
|
|
527
|
+
const loadingHandoffRef = (0, import_react.useRef)(false);
|
|
420
528
|
const endIntervalMorph = (0, import_react.useCallback)(() => {
|
|
421
529
|
if (intervalMorphRaf.current != null) {
|
|
422
530
|
cancelAnimationFrame(intervalMorphRaf.current);
|
|
423
531
|
intervalMorphRaf.current = null;
|
|
424
532
|
}
|
|
425
|
-
handleRef.current
|
|
533
|
+
const h = handleRef.current;
|
|
534
|
+
if (loadingHandoffRef.current) {
|
|
535
|
+
loadingHandoffRef.current = false;
|
|
536
|
+
h?.setLoadingMorph(1);
|
|
537
|
+
h?.beginLoadingReveal();
|
|
538
|
+
}
|
|
539
|
+
h?.setIntervalMorph(1);
|
|
426
540
|
}, []);
|
|
427
|
-
const startIntervalMorph = (0, import_react.useCallback)((h) => {
|
|
541
|
+
const startIntervalMorph = (0, import_react.useCallback)((h, durationMs) => {
|
|
428
542
|
const { ms, easing } = animRef.current;
|
|
543
|
+
const dur = durationMs ?? ms;
|
|
429
544
|
const start = performance.now();
|
|
430
545
|
const step = (now) => {
|
|
431
|
-
const p = Math.min(1, (now - start) /
|
|
546
|
+
const p = Math.min(1, (now - start) / dur);
|
|
432
547
|
h.setIntervalMorph(p < 1 ? ease(easing, p) : 1);
|
|
433
548
|
const pic = h.render();
|
|
434
549
|
if (pic) onFrameRef.current?.(pic);
|
|
@@ -436,23 +551,111 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
|
|
|
436
551
|
};
|
|
437
552
|
intervalMorphRaf.current = requestAnimationFrame(step);
|
|
438
553
|
}, []);
|
|
554
|
+
const startLoadingHandoff = (0, import_react.useCallback)(
|
|
555
|
+
(h) => {
|
|
556
|
+
const { ms, easing } = animRef.current;
|
|
557
|
+
const half = ms / 2;
|
|
558
|
+
loadingHandoffRef.current = true;
|
|
559
|
+
h.beginLoadingMorph();
|
|
560
|
+
const start = performance.now();
|
|
561
|
+
const step = (now) => {
|
|
562
|
+
const p = Math.min(1, (now - start) / half);
|
|
563
|
+
h.setLoadingMorph(p < 1 ? ease(easing, p) : 1);
|
|
564
|
+
const pic = h.render();
|
|
565
|
+
if (pic) onFrameRef.current?.(pic);
|
|
566
|
+
if (p < 1) {
|
|
567
|
+
intervalMorphRaf.current = requestAnimationFrame(step);
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
intervalMorphRaf.current = null;
|
|
571
|
+
loadingHandoffRef.current = false;
|
|
572
|
+
h.beginLoadingReveal();
|
|
573
|
+
startIntervalMorph(h, half);
|
|
574
|
+
};
|
|
575
|
+
intervalMorphRaf.current = requestAnimationFrame(step);
|
|
576
|
+
},
|
|
577
|
+
[startIntervalMorph]
|
|
578
|
+
);
|
|
579
|
+
const settleStream = (0, import_react.useCallback)((keepMorph = false) => {
|
|
580
|
+
if (streamRaf.current != null) {
|
|
581
|
+
cancelAnimationFrame(streamRaf.current);
|
|
582
|
+
streamRaf.current = null;
|
|
583
|
+
}
|
|
584
|
+
const h = handleRef.current;
|
|
585
|
+
const target = streamWindowRef.current;
|
|
586
|
+
streamWindowRef.current = null;
|
|
587
|
+
if (target) h?.setVisibleRange(target.startMs, target.endMs);
|
|
588
|
+
if (streamMorphRef.current && !keepMorph) {
|
|
589
|
+
streamMorphRef.current = false;
|
|
590
|
+
h?.setIntervalMorph(1);
|
|
591
|
+
}
|
|
592
|
+
}, []);
|
|
593
|
+
const startStreamAnim = (0, import_react.useCallback)(
|
|
594
|
+
(h, morphing, window) => {
|
|
595
|
+
const { streamMs, easing } = animRef.current;
|
|
596
|
+
let from = window ? h.getVisibleRange() : null;
|
|
597
|
+
let applied = null;
|
|
598
|
+
streamWindowRef.current = window;
|
|
599
|
+
streamMorphRef.current = morphing;
|
|
600
|
+
const start = performance.now();
|
|
601
|
+
const step = (now) => {
|
|
602
|
+
if (from && applied) {
|
|
603
|
+
const now_w = h.getVisibleRange();
|
|
604
|
+
if (now_w.startMs !== applied.startMs || now_w.endMs !== applied.endMs) {
|
|
605
|
+
from = null;
|
|
606
|
+
streamWindowRef.current = null;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
const p = Math.min(1, (now - start) / streamMs);
|
|
610
|
+
const e = p < 1 ? ease(easing, p) : 1;
|
|
611
|
+
if (morphing) h.setIntervalMorph(e);
|
|
612
|
+
if (from && window) {
|
|
613
|
+
applied = {
|
|
614
|
+
startMs: Math.round(from.startMs + (window.startMs - from.startMs) * e),
|
|
615
|
+
endMs: Math.round(from.endMs + (window.endMs - from.endMs) * e)
|
|
616
|
+
};
|
|
617
|
+
h.setVisibleRange(applied.startMs, applied.endMs);
|
|
618
|
+
}
|
|
619
|
+
const pic = h.render();
|
|
620
|
+
if (pic) onFrameRef.current?.(pic);
|
|
621
|
+
if (p < 1) {
|
|
622
|
+
streamRaf.current = requestAnimationFrame(step);
|
|
623
|
+
} else {
|
|
624
|
+
streamRaf.current = null;
|
|
625
|
+
streamWindowRef.current = null;
|
|
626
|
+
streamMorphRef.current = false;
|
|
627
|
+
}
|
|
628
|
+
};
|
|
629
|
+
streamRaf.current = requestAnimationFrame(step);
|
|
630
|
+
},
|
|
631
|
+
[]
|
|
632
|
+
);
|
|
439
633
|
(0, import_react.useEffect)(() => {
|
|
440
634
|
return () => {
|
|
441
635
|
if (intervalMorphRaf.current != null) {
|
|
442
636
|
cancelAnimationFrame(intervalMorphRaf.current);
|
|
443
637
|
intervalMorphRaf.current = null;
|
|
444
638
|
}
|
|
639
|
+
if (streamRaf.current != null) {
|
|
640
|
+
cancelAnimationFrame(streamRaf.current);
|
|
641
|
+
streamRaf.current = null;
|
|
642
|
+
}
|
|
445
643
|
};
|
|
446
644
|
}, []);
|
|
447
645
|
const explicit = visibleRange != null;
|
|
448
646
|
const startMs = visibleRange?.startMs ?? 0;
|
|
449
647
|
const endMs = visibleRange?.endMs ?? 0;
|
|
648
|
+
const showLoadingLine = loading === true && candles.length === 0;
|
|
649
|
+
const lineUpRef = (0, import_react.useRef)(false);
|
|
450
650
|
const themeKey = theme ? JSON.stringify(theme) : "";
|
|
451
651
|
const rsiKey = rsi ? JSON.stringify(rsi) : "";
|
|
452
652
|
const macdKey = macd ? JSON.stringify(macd) : "";
|
|
653
|
+
const atrKey = atr ? JSON.stringify(atr) : "";
|
|
453
654
|
const maKey = movingAverages ? JSON.stringify(movingAverages) : "";
|
|
454
655
|
const vwapKey = vwap ? JSON.stringify(vwap) : "";
|
|
455
656
|
const bollingerKey = bollingerBands ? JSON.stringify(bollingerBands) : "";
|
|
657
|
+
const ichimokuKey = ichimoku ? JSON.stringify(ichimoku) : "";
|
|
658
|
+
const fvgKey = fairValueGaps ? JSON.stringify(fairValueGaps) : "";
|
|
456
659
|
const volumeKey = volume ? JSON.stringify(volume) : "";
|
|
457
660
|
const priceLinesKey = priceLines ? JSON.stringify(priceLines) : "";
|
|
458
661
|
const footprintsKey = footprints ? JSON.stringify(footprints) : "";
|
|
@@ -460,11 +663,25 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
|
|
|
460
663
|
const h = handleRef.current;
|
|
461
664
|
if (!h) return;
|
|
462
665
|
h.setSize(size.width, size.height, size.pxRatio ?? 1);
|
|
666
|
+
h.setIchimoku(ichimokuToSpec(ichimoku));
|
|
463
667
|
if (!defaultWidthAppliedRef.current && !explicit && defaultCandleWidth != null && defaultCandleWidth > 0) {
|
|
464
668
|
h.setDefaultCandleWidth(defaultCandleWidth);
|
|
465
669
|
defaultWidthAppliedRef.current = true;
|
|
466
670
|
}
|
|
467
671
|
let morphing = false;
|
|
672
|
+
if (showLoadingLine) {
|
|
673
|
+
if (!lineUpRef.current) {
|
|
674
|
+
endIntervalMorph();
|
|
675
|
+
settleStream();
|
|
676
|
+
h.setCandles(packCandles([]));
|
|
677
|
+
prevDataRef.current = null;
|
|
678
|
+
}
|
|
679
|
+
h.setLoading(true, !animRef.current.reduceMotion);
|
|
680
|
+
lineUpRef.current = true;
|
|
681
|
+
} else if (lineUpRef.current && candles.length === 0) {
|
|
682
|
+
h.setLoading(false, true);
|
|
683
|
+
lineUpRef.current = false;
|
|
684
|
+
}
|
|
468
685
|
if (candles.length > 0) {
|
|
469
686
|
const prev = prevDataRef.current;
|
|
470
687
|
const freshHandle = prev == null || prev.handle !== h;
|
|
@@ -472,6 +689,37 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
|
|
|
472
689
|
const transitionKind = freshHandle ? "initial" : explicit ? "stream" : classifyTransition(prev.candles, candles, seriesKey !== prev.seriesKey);
|
|
473
690
|
let tfArgs = null;
|
|
474
691
|
let prevEnvelope = null;
|
|
692
|
+
let handOff = false;
|
|
693
|
+
let stream = null;
|
|
694
|
+
if (transitionKind === "stream" && prev != null && !explicit) {
|
|
695
|
+
const { stream: mode, streamMs, reduceMotion } = animRef.current;
|
|
696
|
+
const stepMs = inferStepMs(candles);
|
|
697
|
+
if (mode === "transform" && streamMs > 0 && stepMs != null && !reduceMotion && onFrameRef.current != null) {
|
|
698
|
+
const lastMs = candles[candles.length - 1].timeMs;
|
|
699
|
+
const prevLastMs = prev.candles[prev.candles.length - 1].timeMs;
|
|
700
|
+
if (classifyStream(prev.candles, candles) === "append") {
|
|
701
|
+
const w = h.getVisibleRange();
|
|
702
|
+
const prevStepMs = inferStepMs(prev.candles) ?? stepMs;
|
|
703
|
+
if (isPinnedToLatest(w, prevLastMs, prevStepMs)) {
|
|
704
|
+
const by = lastMs - prevLastMs;
|
|
705
|
+
stream = {
|
|
706
|
+
morph: false,
|
|
707
|
+
window: { startMs: w.startMs + by, endMs: w.endMs + by }
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
} else {
|
|
711
|
+
stream = { morph: true, window: null };
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
if (stream?.morph) {
|
|
715
|
+
settleStream(true);
|
|
716
|
+
h.beginStreamMorph();
|
|
717
|
+
} else {
|
|
718
|
+
settleStream();
|
|
719
|
+
}
|
|
720
|
+
} else if (transitionKind === "stream") {
|
|
721
|
+
settleStream();
|
|
722
|
+
}
|
|
475
723
|
if (transitionKind === "timeframe" && prev != null) {
|
|
476
724
|
const oldWindow = h.getVisibleRange();
|
|
477
725
|
const oldStepMs = inferStepMs(prev.candles);
|
|
@@ -491,6 +739,11 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
|
|
|
491
739
|
} else if (transitionKind === "initial" || transitionKind === "reset") {
|
|
492
740
|
endIntervalMorph();
|
|
493
741
|
}
|
|
742
|
+
if (lineUpRef.current) {
|
|
743
|
+
lineUpRef.current = false;
|
|
744
|
+
handOff = animRef.current.ms > 0 && !animRef.current.reduceMotion && onFrameRef.current != null;
|
|
745
|
+
if (!handOff) h.setLoading(false, true);
|
|
746
|
+
}
|
|
494
747
|
h.setCandles(packCandles(candles));
|
|
495
748
|
if (transitionKind === "timeframe") {
|
|
496
749
|
const newStepMs = inferStepMs(candles);
|
|
@@ -507,9 +760,12 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
|
|
|
507
760
|
if (prevEnvelope) h.preservePriceEnvelope(prevEnvelope.low, prevEnvelope.high);
|
|
508
761
|
else h.resetPriceScale();
|
|
509
762
|
if (morphing) startIntervalMorph(h);
|
|
763
|
+
} else if (stream) {
|
|
764
|
+
startStreamAnim(h, stream.morph, stream.window);
|
|
510
765
|
} else if (transitionKind === "reset") {
|
|
511
766
|
h.resetView();
|
|
512
767
|
}
|
|
768
|
+
if (handOff) startLoadingHandoff(h);
|
|
513
769
|
prevDataRef.current = { handle: h, candles, seriesKey };
|
|
514
770
|
}
|
|
515
771
|
}
|
|
@@ -524,9 +780,11 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
|
|
|
524
780
|
}
|
|
525
781
|
h.setRSI(rsiToSpec(rsi));
|
|
526
782
|
h.setMACD(macdToSpec(macd));
|
|
783
|
+
h.setATR(atrToSpec(atr));
|
|
527
784
|
h.setOverlays((movingAverages ?? []).map(overlayToNumeric));
|
|
528
785
|
h.setVWAP(vwapToSpec(vwap));
|
|
529
786
|
h.setBollinger(bollingerToSpec(bollingerBands));
|
|
787
|
+
h.setFairValueGaps(fvgToSpec(fairValueGaps));
|
|
530
788
|
h.setVolume(volumeToSpec(volume));
|
|
531
789
|
const collapse = volumeCollapseRef.current;
|
|
532
790
|
if (collapse) h.setVolumeCollapse(collapse.t, collapse.easing);
|
|
@@ -537,7 +795,7 @@ function useChartCore(candles, size, visibleRange, defaultCandleWidth, chartType
|
|
|
537
795
|
footprints?.prints.length ? footprintsToSpec(footprints) : EMPTY_FOOTPRINTS
|
|
538
796
|
);
|
|
539
797
|
if (!morphing) setPicture(h.render());
|
|
540
|
-
}, [candles, seriesKey, size.width, size.height, size.pxRatio, explicit, startMs, endMs, defaultCandleWidth, themeKey, rsiKey, macdKey, maKey, vwapKey, bollingerKey, volumeKey, priceLinesKey, footprintsKey, startIntervalMorph, endIntervalMorph]);
|
|
798
|
+
}, [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]);
|
|
541
799
|
return { handle: handleRef.current, picture, volumeCollapseRef };
|
|
542
800
|
}
|
|
543
801
|
|
|
@@ -549,6 +807,7 @@ function isSkImage(frame) {
|
|
|
549
807
|
function VroomChart(props) {
|
|
550
808
|
const {
|
|
551
809
|
candles,
|
|
810
|
+
loading,
|
|
552
811
|
seriesKey,
|
|
553
812
|
width: widthProp,
|
|
554
813
|
height: heightProp,
|
|
@@ -559,12 +818,17 @@ function VroomChart(props) {
|
|
|
559
818
|
transitionMs,
|
|
560
819
|
transitionEasing,
|
|
561
820
|
intervalTransition,
|
|
821
|
+
streamTransition,
|
|
822
|
+
streamTransitionMs,
|
|
562
823
|
theme,
|
|
563
824
|
rsi,
|
|
564
825
|
macd,
|
|
826
|
+
atr,
|
|
565
827
|
movingAverages,
|
|
566
828
|
vwap,
|
|
567
829
|
bollingerBands,
|
|
830
|
+
ichimoku,
|
|
831
|
+
fairValueGaps,
|
|
568
832
|
volume,
|
|
569
833
|
crosshairOffset = 40,
|
|
570
834
|
onCrosshair,
|
|
@@ -643,14 +907,28 @@ function VroomChart(props) {
|
|
|
643
907
|
theme,
|
|
644
908
|
rsi,
|
|
645
909
|
macd,
|
|
910
|
+
atr,
|
|
646
911
|
movingAverages,
|
|
647
912
|
vwap,
|
|
648
913
|
bollingerBands,
|
|
914
|
+
ichimoku,
|
|
915
|
+
fairValueGaps,
|
|
649
916
|
volume,
|
|
650
917
|
priceLinesProp,
|
|
651
918
|
footprintsProp,
|
|
652
|
-
{
|
|
919
|
+
{
|
|
920
|
+
seriesKey,
|
|
921
|
+
transitionMs,
|
|
922
|
+
transitionEasing,
|
|
923
|
+
intervalTransition,
|
|
924
|
+
streamTransition,
|
|
925
|
+
streamTransitionMs,
|
|
926
|
+
reduceMotion,
|
|
927
|
+
onFrame
|
|
928
|
+
},
|
|
929
|
+
loading
|
|
653
930
|
);
|
|
931
|
+
const showLoadingLine = loading === true && candles.length === 0;
|
|
654
932
|
const crosshairActive = (0, import_react2.useRef)(false);
|
|
655
933
|
const footprintActive = (0, import_react2.useRef)(false);
|
|
656
934
|
const lastCrosshairTime = (0, import_react2.useRef)(null);
|
|
@@ -930,7 +1208,7 @@ function VroomChart(props) {
|
|
|
930
1208
|
pane: null
|
|
931
1209
|
});
|
|
932
1210
|
};
|
|
933
|
-
const pan = import_react_native_gesture_handler.Gesture.Pan().runOnJS(true).maxPointers(1).onStart((e) => {
|
|
1211
|
+
const pan = import_react_native_gesture_handler.Gesture.Pan().enabled(!showLoadingLine).runOnJS(true).maxPointers(1).onStart((e) => {
|
|
934
1212
|
cancelDecay();
|
|
935
1213
|
panMode.current = hitAxis(e.x, e.y);
|
|
936
1214
|
priceDrag.current = null;
|
|
@@ -1025,7 +1303,7 @@ function VroomChart(props) {
|
|
|
1025
1303
|
enableX: false,
|
|
1026
1304
|
enableY: false
|
|
1027
1305
|
});
|
|
1028
|
-
const pinch = import_react_native_gesture_handler.Gesture.Pinch().runOnJS(true).onTouchesDown((e) => {
|
|
1306
|
+
const pinch = import_react_native_gesture_handler.Gesture.Pinch().enabled(!showLoadingLine).runOnJS(true).onTouchesDown((e) => {
|
|
1029
1307
|
if (e.numberOfTouches < 2) return;
|
|
1030
1308
|
dismissFootprint();
|
|
1031
1309
|
const [a, b] = e.allTouches;
|
|
@@ -1063,7 +1341,7 @@ function VroomChart(props) {
|
|
|
1063
1341
|
if (next) applyFrame(next);
|
|
1064
1342
|
maybeStartAnim();
|
|
1065
1343
|
});
|
|
1066
|
-
const longPress = import_react_native_gesture_handler.Gesture.LongPress().runOnJS(true).onStart((e) => {
|
|
1344
|
+
const longPress = import_react_native_gesture_handler.Gesture.LongPress().enabled(!showLoadingLine).runOnJS(true).onStart((e) => {
|
|
1067
1345
|
if (!handle) return;
|
|
1068
1346
|
if (hitAxis(e.x, e.y) !== "chart") return;
|
|
1069
1347
|
if (hitPriceLine(e.x, e.y)) return;
|
|
@@ -1083,7 +1361,7 @@ function VroomChart(props) {
|
|
|
1083
1361
|
reason: "show"
|
|
1084
1362
|
});
|
|
1085
1363
|
});
|
|
1086
|
-
const tap = import_react_native_gesture_handler.Gesture.Tap().runOnJS(true).onStart((e) => {
|
|
1364
|
+
const tap = import_react_native_gesture_handler.Gesture.Tap().enabled(!showLoadingLine).runOnJS(true).onStart((e) => {
|
|
1087
1365
|
if (!handle) return;
|
|
1088
1366
|
const prints = footprints ?? [];
|
|
1089
1367
|
const fp = prints.length ? handle.hitTestFootprint(e.x, e.y) : null;
|