react-native-vroom-chart 0.16.0 → 0.18.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/README.md +1 -1
- package/android/CMakeLists.txt +12 -2
- package/android/README.md +16 -6
- package/android/build.gradle +10 -0
- package/android/src/main/cpp/OnLoad.cpp +10 -0
- package/android/src/main/cpp/VroomChartJsiBindings.cpp +35 -0
- package/android/src/main/cpp/VroomChartJsiBindings.h +27 -0
- package/android/src/main/java/com/vroom/chart/VroomChartModule.kt +15 -11
- package/cpp/VroomChartHostObject.cpp +81 -0
- package/cpp/VroomJsiInstaller.cpp +6 -0
- package/cpp/_core_include/vroom/vroom_chart.h +41 -0
- package/cpp/_core_src/candles.cpp +15 -14
- package/cpp/_core_src/chart.cpp +297 -28
- package/cpp/_core_src/chart.h +71 -2
- package/cpp/_core_src/chart_facade.cpp +27 -0
- package/cpp/_core_src/color_lerp.h +28 -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 +15 -10
- package/cpp/_core_src/ma_overlay.h +7 -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/theme.cpp +5 -0
- package/cpp/_core_src/tip_geometry.h +55 -0
- package/cpp/_core_src/viewport.h +33 -0
- package/ios/README.md +17 -5
- package/ios/VroomChartModule.h +3 -6
- package/ios/VroomChartModule.mm +17 -23
- package/lib/index.d.mts +30 -0
- package/lib/index.d.ts +30 -0
- package/lib/index.js +75 -14
- package/lib/index.js.map +1 -1
- package/lib/index.mjs +75 -14
- package/lib/index.mjs.map +1 -1
- package/package.json +9 -9
- package/react-native-vroom-chart.podspec +1 -1
- package/src/NativeVroomChart.ts +7 -4
- package/src/VroomChart.tsx +12 -0
- package/src/jsi.d.ts +42 -0
- package/src/theme.ts +1 -0
- package/src/useChartCore.ts +129 -7
- package/android/src/main/cpp/VroomChartJni.cpp +0 -24
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-vroom-chart",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "Mobile-first Skia candlestick chart for React Native",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Darion Welch",
|
|
@@ -61,19 +61,19 @@
|
|
|
61
61
|
"peerDependencies": {
|
|
62
62
|
"@shopify/react-native-skia": ">=2.11.0",
|
|
63
63
|
"react": "*",
|
|
64
|
-
"react-native": "
|
|
64
|
+
"react-native": ">=0.78",
|
|
65
65
|
"react-native-gesture-handler": ">=2.16.0",
|
|
66
66
|
"react-native-reanimated": ">=4.0.0",
|
|
67
67
|
"react-native-worklets": ">=0.7.0"
|
|
68
68
|
},
|
|
69
69
|
"devDependencies": {
|
|
70
|
-
"@shopify/react-native-skia": "2.
|
|
71
|
-
"@types/react": "~19.
|
|
72
|
-
"react": "19.
|
|
73
|
-
"react-native": "0.
|
|
74
|
-
"react-native-gesture-handler": "~2.
|
|
75
|
-
"react-native-reanimated": "~4.1
|
|
76
|
-
"react-native-worklets": "0.
|
|
70
|
+
"@shopify/react-native-skia": "2.12.0",
|
|
71
|
+
"@types/react": "~19.2.0",
|
|
72
|
+
"react": "19.2.3",
|
|
73
|
+
"react-native": "0.86.3",
|
|
74
|
+
"react-native-gesture-handler": "~2.32.0",
|
|
75
|
+
"react-native-reanimated": "~4.5.1",
|
|
76
|
+
"react-native-worklets": "0.10.1",
|
|
77
77
|
"tsup": "^8.5.1",
|
|
78
78
|
"typescript": "~5.9.2",
|
|
79
79
|
"vitest": "^3.2.6",
|
|
@@ -37,7 +37,7 @@ Pod::Spec.new do |s|
|
|
|
37
37
|
s.homepage = "https://github.com/keepitreal/vroom"
|
|
38
38
|
s.license = { :type => "MIT" }
|
|
39
39
|
s.authors = { "vroom" => "noreply@example.com" }
|
|
40
|
-
s.platforms = { :ios => "
|
|
40
|
+
s.platforms = { :ios => "15.1" }
|
|
41
41
|
s.source = { :git => "" }
|
|
42
42
|
|
|
43
43
|
s.requires_arc = true
|
package/src/NativeVroomChart.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import type { TurboModule } from 'react-native';
|
|
2
2
|
import { TurboModuleRegistry } from 'react-native';
|
|
3
3
|
|
|
4
|
-
// TurboModule spec consumed by codegen. The
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
4
|
+
// TurboModule spec consumed by codegen. The native side installs the
|
|
5
|
+
// `global.VroomChartJSI` host object when React Native instantiates this
|
|
6
|
+
// module, not when `install` is called — see the JSI-bindings hooks in
|
|
7
|
+
// ../ios/VroomChartModule.mm and ../android/src/main/cpp/VroomChartJsiBindings.cpp.
|
|
8
|
+
// `install` remains so the spec has a method to generate and so JS has a way
|
|
9
|
+
// to force instantiation; all real chart operations go through the host
|
|
10
|
+
// object, not through the TurboModule itself.
|
|
8
11
|
export interface Spec extends TurboModule {
|
|
9
12
|
install(): boolean;
|
|
10
13
|
}
|
package/src/VroomChart.tsx
CHANGED
|
@@ -59,6 +59,7 @@ function isSkImage(frame: ChartFrame): frame is SkImage {
|
|
|
59
59
|
export function VroomChart(props: VroomChartProps) {
|
|
60
60
|
const {
|
|
61
61
|
candles,
|
|
62
|
+
loading,
|
|
62
63
|
seriesKey,
|
|
63
64
|
width: widthProp,
|
|
64
65
|
height: heightProp,
|
|
@@ -204,8 +205,15 @@ export function VroomChart(props: VroomChartProps) {
|
|
|
204
205
|
reduceMotion,
|
|
205
206
|
onFrame,
|
|
206
207
|
},
|
|
208
|
+
loading,
|
|
207
209
|
);
|
|
208
210
|
|
|
211
|
+
// Same condition useChartCore draws the line on: a refresh that still has
|
|
212
|
+
// data keeps the chart interactive. Every gesture below is gated on this —
|
|
213
|
+
// there's nothing to pan, zoom or inspect while the line is up, and a
|
|
214
|
+
// crosshair reading prices off a placeholder walk would be actively wrong.
|
|
215
|
+
const showLoadingLine = loading === true && candles.length === 0;
|
|
216
|
+
|
|
209
217
|
// When the crosshair is showing, pan moves it (instead of scrolling) and
|
|
210
218
|
// pinch is disabled. A ref (not state) so gesture callbacks read it
|
|
211
219
|
// synchronously without re-subscribing. Tap dismisses it.
|
|
@@ -600,6 +608,7 @@ export function VroomChart(props: VroomChartProps) {
|
|
|
600
608
|
};
|
|
601
609
|
|
|
602
610
|
const pan = Gesture.Pan()
|
|
611
|
+
.enabled(!showLoadingLine)
|
|
603
612
|
.runOnJS(true)
|
|
604
613
|
.maxPointers(1) // don't fight Pinch's two-finger gesture
|
|
605
614
|
.onStart((e) => {
|
|
@@ -745,6 +754,7 @@ export function VroomChart(props: VroomChartProps) {
|
|
|
745
754
|
enableY: false,
|
|
746
755
|
});
|
|
747
756
|
const pinch = Gesture.Pinch()
|
|
757
|
+
.enabled(!showLoadingLine)
|
|
748
758
|
.runOnJS(true)
|
|
749
759
|
.onTouchesDown((e) => {
|
|
750
760
|
if (e.numberOfTouches < 2) return;
|
|
@@ -795,6 +805,7 @@ export function VroomChart(props: VroomChartProps) {
|
|
|
795
805
|
// activates `pan` (it needs movement first), so the chart won't scroll under
|
|
796
806
|
// the hold. The dot/horizontal line are lifted above the fingertip.
|
|
797
807
|
const longPress = Gesture.LongPress()
|
|
808
|
+
.enabled(!showLoadingLine)
|
|
798
809
|
.runOnJS(true)
|
|
799
810
|
.onStart((e) => {
|
|
800
811
|
if (!handle) return;
|
|
@@ -826,6 +837,7 @@ export function VroomChart(props: VroomChartProps) {
|
|
|
826
837
|
// badge, and otherwise dismisses the crosshair while it's up. Any other tap is a
|
|
827
838
|
// no-op, so it never interferes with normal pan/pinch.
|
|
828
839
|
const tap = Gesture.Tap()
|
|
840
|
+
.enabled(!showLoadingLine)
|
|
829
841
|
.runOnJS(true)
|
|
830
842
|
.onStart((e) => {
|
|
831
843
|
if (!handle) return;
|
package/src/jsi.d.ts
CHANGED
|
@@ -92,6 +92,48 @@ export interface ChartHandle {
|
|
|
92
92
|
* the capture. Driven per-frame by the host animation loop.
|
|
93
93
|
*/
|
|
94
94
|
setIntervalMorph(t: number): void;
|
|
95
|
+
/**
|
|
96
|
+
* Shows or hides the loading line: a single stroke drawn across the plot in a
|
|
97
|
+
* slow travelling sine, for a chart that is laid out but has no data yet.
|
|
98
|
+
* Suppresses the axis text, price badge, crosshair and indicator panes while
|
|
99
|
+
* it's up.
|
|
100
|
+
*
|
|
101
|
+
* `on` must mean "loading *and* holding no data" — the core takes it at face
|
|
102
|
+
* value rather than checking its candle buffer, since a host mid-asset-switch
|
|
103
|
+
* can still be holding the previous asset's bars. Pass `animate: false` for
|
|
104
|
+
* reduced motion: the line draws still and the chart may go idle.
|
|
105
|
+
*
|
|
106
|
+
* Passing `false` abandons any hand-off in flight, which is what a failed or
|
|
107
|
+
* superseded fetch wants. To hand off to data instead, use the two stages
|
|
108
|
+
* below.
|
|
109
|
+
*/
|
|
110
|
+
setLoading(on: boolean, animate?: boolean): void;
|
|
111
|
+
/**
|
|
112
|
+
* Hand-off stage one: reshapes the loading line into the series. Freezes the
|
|
113
|
+
* sine where it is and aims each vertex at the vertical centre of the candle
|
|
114
|
+
* that will occupy its column, so the line resolves into the silhouette of
|
|
115
|
+
* the data.
|
|
116
|
+
*
|
|
117
|
+
* Call *after* setCandles — it reads them to know where to aim — then drive
|
|
118
|
+
* {@link setLoadingMorph} from 0 to 1. The axes and the candles stay
|
|
119
|
+
* suppressed throughout; stage two brings them in.
|
|
120
|
+
*/
|
|
121
|
+
beginLoadingMorph(): void;
|
|
122
|
+
/**
|
|
123
|
+
* Advances stage one. `t` (clamped to 0..1) is the eased progress: 0 renders
|
|
124
|
+
* the frozen sine, 1 the polyline through the candle centres.
|
|
125
|
+
*/
|
|
126
|
+
setLoadingMorph(t: number): void;
|
|
127
|
+
/**
|
|
128
|
+
* Hand-off stage two: the candles take over. Captures each one collapsed onto
|
|
129
|
+
* its own vertical centre at zero alpha and leaves the loading state, so the
|
|
130
|
+
* bars grow outward from the line and their color fades up as it fades out.
|
|
131
|
+
*
|
|
132
|
+
* Call once stage one has reached 1, then drive {@link setIntervalMorph} from
|
|
133
|
+
* 0 to 1 — the line's fade-out rides that same clock, so the two finish
|
|
134
|
+
* together.
|
|
135
|
+
*/
|
|
136
|
+
beginLoadingReveal(): void;
|
|
95
137
|
/** Shifts the visible range by `dx`/`dy` pixels and returns a fresh picture. */
|
|
96
138
|
pan(dx: number, dy: number): ChartFrame | null;
|
|
97
139
|
/**
|
package/src/theme.ts
CHANGED
|
@@ -20,6 +20,7 @@ export const COLOR_KEYS: Partial<Record<keyof VroomTheme, number>> = {
|
|
|
20
20
|
accentBull: 14, // VROOM_COLOR_ACCENT_BULL
|
|
21
21
|
accentBear: 15, // VROOM_COLOR_ACCENT_BEAR
|
|
22
22
|
lineColor: 16, // VROOM_COLOR_LINE
|
|
23
|
+
skeleton: 17, // VROOM_COLOR_SKELETON
|
|
23
24
|
};
|
|
24
25
|
|
|
25
26
|
// Maps each numeric VroomTheme field to its VroomFloatKey index.
|
package/src/useChartCore.ts
CHANGED
|
@@ -354,10 +354,17 @@ const EMPTY_FOOTPRINTS = footprintsToSpec({ prints: [] });
|
|
|
354
354
|
let installed = false;
|
|
355
355
|
function ensureInstalled(): void {
|
|
356
356
|
if (installed) return;
|
|
357
|
-
|
|
358
|
-
|
|
357
|
+
// The bindings are installed by the TurboModule itself, when the runtime
|
|
358
|
+
// hands it over (installJSIBindingsWithRuntime:callInvoker: on iOS,
|
|
359
|
+
// getBindingsInstaller() on Android). Touching the module is what forces
|
|
360
|
+
// that to have happened; the global is the thing worth checking.
|
|
361
|
+
NativeVroomChart.install();
|
|
359
362
|
if (typeof globalThis.VroomChartJSI === 'undefined') {
|
|
360
|
-
throw new Error(
|
|
363
|
+
throw new Error(
|
|
364
|
+
'global.VroomChartJSI is undefined — VroomChartModule did not install ' +
|
|
365
|
+
'its JSI bindings. This requires react-native >= 0.78 with the New ' +
|
|
366
|
+
'Architecture enabled.',
|
|
367
|
+
);
|
|
361
368
|
}
|
|
362
369
|
installed = true;
|
|
363
370
|
}
|
|
@@ -425,6 +432,9 @@ export function useChartCore(
|
|
|
425
432
|
priceLines?: PriceLinesProp,
|
|
426
433
|
footprints?: FootprintsProp,
|
|
427
434
|
transition?: TransitionOptions,
|
|
435
|
+
// Trails the config params because it's data state, not configuration: it
|
|
436
|
+
// pairs with `candles` above (see showLoadingLine below).
|
|
437
|
+
loading?: boolean,
|
|
428
438
|
): ChartCoreState {
|
|
429
439
|
const handleRef = useRef<ChartHandle | null>(null);
|
|
430
440
|
// Push setDefaultCandleWidth only once (first load): setCandles re-runs on
|
|
@@ -486,22 +496,39 @@ export function useChartCore(
|
|
|
486
496
|
onFrameRef.current = transition?.onFrame;
|
|
487
497
|
const seriesKey = transition?.seriesKey;
|
|
488
498
|
|
|
499
|
+
// Set only while the loading hand-off is in its *first* stage, which is the
|
|
500
|
+
// one an interruption can't simply land: stage one leaves `loading` set in
|
|
501
|
+
// the core, and only stage two releases it.
|
|
502
|
+
const loadingHandoffRef = useRef(false);
|
|
503
|
+
|
|
489
504
|
// Stop an in-flight interval morph and land the core on the new candles.
|
|
490
505
|
const endIntervalMorph = useCallback(() => {
|
|
491
506
|
if (intervalMorphRaf.current != null) {
|
|
492
507
|
cancelAnimationFrame(intervalMorphRaf.current);
|
|
493
508
|
intervalMorphRaf.current = null;
|
|
494
509
|
}
|
|
495
|
-
handleRef.current
|
|
510
|
+
const h = handleRef.current;
|
|
511
|
+
// Walk a half-finished hand-off through the rest of its stages rather than
|
|
512
|
+
// just stopping the clock, or the core would be left drawing the loading
|
|
513
|
+
// line over the data it was supposed to hand off to.
|
|
514
|
+
if (loadingHandoffRef.current) {
|
|
515
|
+
loadingHandoffRef.current = false;
|
|
516
|
+
h?.setLoadingMorph(1);
|
|
517
|
+
h?.beginLoadingReveal();
|
|
518
|
+
}
|
|
519
|
+
h?.setIntervalMorph(1);
|
|
496
520
|
}, []);
|
|
497
521
|
|
|
498
522
|
// Runs the interval morph clock. The core holds the pre-swap geometry (see
|
|
499
523
|
// beginIntervalMorph) and reshapes each candle slot toward its new counterpart.
|
|
500
|
-
|
|
524
|
+
// `durationMs` overrides transitionMs for the loading hand-off, which splits
|
|
525
|
+
// it across two stages.
|
|
526
|
+
const startIntervalMorph = useCallback((h: ChartHandle, durationMs?: number) => {
|
|
501
527
|
const { ms, easing } = animRef.current;
|
|
528
|
+
const dur = durationMs ?? ms;
|
|
502
529
|
const start = performance.now();
|
|
503
530
|
const step = (now: number) => {
|
|
504
|
-
const p = Math.min(1, (now - start) /
|
|
531
|
+
const p = Math.min(1, (now - start) / dur);
|
|
505
532
|
h.setIntervalMorph(p < 1 ? ease(easing, p) : 1);
|
|
506
533
|
const pic = h.render();
|
|
507
534
|
if (pic) onFrameRef.current?.(pic);
|
|
@@ -510,6 +537,44 @@ export function useChartCore(
|
|
|
510
537
|
intervalMorphRaf.current = requestAnimationFrame(step);
|
|
511
538
|
}, []);
|
|
512
539
|
|
|
540
|
+
// Hands the loading line over to the data that just landed, in two stages:
|
|
541
|
+
// the line reshapes into the series' silhouette, then the candles grow out of
|
|
542
|
+
// it while it fades.
|
|
543
|
+
//
|
|
544
|
+
// Sequential rather than overlapped — the shape has to read as the data
|
|
545
|
+
// before the bars start emerging from it — so the two split transitionMs and
|
|
546
|
+
// the whole hand-off costs what any other transition costs.
|
|
547
|
+
const startLoadingHandoff = useCallback(
|
|
548
|
+
(h: ChartHandle) => {
|
|
549
|
+
const { ms, easing } = animRef.current;
|
|
550
|
+
const half = ms / 2;
|
|
551
|
+
loadingHandoffRef.current = true;
|
|
552
|
+
h.beginLoadingMorph();
|
|
553
|
+
const start = performance.now();
|
|
554
|
+
const step = (now: number) => {
|
|
555
|
+
const p = Math.min(1, (now - start) / half);
|
|
556
|
+
h.setLoadingMorph(p < 1 ? ease(easing, p) : 1);
|
|
557
|
+
const pic = h.render();
|
|
558
|
+
if (pic) onFrameRef.current?.(pic);
|
|
559
|
+
if (p < 1) {
|
|
560
|
+
intervalMorphRaf.current = requestAnimationFrame(step);
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
intervalMorphRaf.current = null;
|
|
564
|
+
// Past here an interruption is an ordinary interval morph again: the
|
|
565
|
+
// core has left the loading state, so landing the clock is enough.
|
|
566
|
+
loadingHandoffRef.current = false;
|
|
567
|
+
// Stage two rides the interval-morph clock, which also carries the
|
|
568
|
+
// line's fade-out — so the bars' growth and the line's exit finish
|
|
569
|
+
// together instead of one outlasting the other.
|
|
570
|
+
h.beginLoadingReveal();
|
|
571
|
+
startIntervalMorph(h, half);
|
|
572
|
+
};
|
|
573
|
+
intervalMorphRaf.current = requestAnimationFrame(step);
|
|
574
|
+
},
|
|
575
|
+
[startIntervalMorph],
|
|
576
|
+
);
|
|
577
|
+
|
|
513
578
|
// Stops an in-flight stream animation and puts the chart somewhere coherent.
|
|
514
579
|
//
|
|
515
580
|
// A pending window shift always lands on its target: abandoned mid-slide it
|
|
@@ -606,6 +671,17 @@ export function useChartCore(
|
|
|
606
671
|
const startMs = visibleRange?.startMs ?? 0;
|
|
607
672
|
const endMs = visibleRange?.endMs ?? 0;
|
|
608
673
|
|
|
674
|
+
// The core trusts `setLoading` outright, so the "and no data yet" half of the
|
|
675
|
+
// condition is decided here. Both halves matter: without `loading` a chart
|
|
676
|
+
// that legitimately has no bars would wave a placeholder forever, and without
|
|
677
|
+
// the emptiness check a background refresh of a loaded series would blank the
|
|
678
|
+
// chart the user is already reading.
|
|
679
|
+
const showLoadingLine = loading === true && candles.length === 0;
|
|
680
|
+
// Tracks whether the *core* is currently showing the line, which is what
|
|
681
|
+
// decides if the next data push is a hand-off. Distinct from `showLoadingLine`:
|
|
682
|
+
// that is this render's intent, this is what's on screen.
|
|
683
|
+
const lineUpRef = useRef(false);
|
|
684
|
+
|
|
609
685
|
// Stable deps so inline `theme={{...}}` / `rsi={{...}}` literals don't re-run
|
|
610
686
|
// the effect every render — only when the actual values change.
|
|
611
687
|
const themeKey = theme ? JSON.stringify(theme) : '';
|
|
@@ -645,6 +721,31 @@ export function useChartCore(
|
|
|
645
721
|
// the viewport: a stream leaves it alone, a timeframe switch re-anchors and
|
|
646
722
|
// morphs into it, a different asset resets it.
|
|
647
723
|
let morphing = false;
|
|
724
|
+
|
|
725
|
+
if (showLoadingLine) {
|
|
726
|
+
// Clear the core's buffer, which the `candles.length > 0` gate below
|
|
727
|
+
// otherwise never does: pushing an empty array is treated as "hold the
|
|
728
|
+
// last frame" everywhere else, so a chart switching assets would still be
|
|
729
|
+
// holding the previous one's bars underneath the line — and would
|
|
730
|
+
// classify the incoming series as a timeframe switch rather than a fresh
|
|
731
|
+
// load. Scoped to the loading case so that hold-the-last-frame behavior
|
|
732
|
+
// is untouched for every other empty push.
|
|
733
|
+
if (!lineUpRef.current) {
|
|
734
|
+
endIntervalMorph();
|
|
735
|
+
settleStream();
|
|
736
|
+
h.setCandles(packCandles([]));
|
|
737
|
+
prevDataRef.current = null;
|
|
738
|
+
}
|
|
739
|
+
h.setLoading(true, !animRef.current.reduceMotion);
|
|
740
|
+
lineUpRef.current = true;
|
|
741
|
+
} else if (lineUpRef.current && candles.length === 0) {
|
|
742
|
+
// Loading resolved to nothing — an empty result, or an error the consumer
|
|
743
|
+
// handled. There's no geometry to morph into, so drop the line rather
|
|
744
|
+
// than leaving it waving at data that isn't coming.
|
|
745
|
+
h.setLoading(false, true);
|
|
746
|
+
lineUpRef.current = false;
|
|
747
|
+
}
|
|
748
|
+
|
|
648
749
|
if (candles.length > 0) {
|
|
649
750
|
const prev = prevDataRef.current;
|
|
650
751
|
const freshHandle = prev == null || prev.handle !== h;
|
|
@@ -667,6 +768,8 @@ export function useChartCore(
|
|
|
667
768
|
} | null = null;
|
|
668
769
|
// The pre-swap candle envelope, used to scale-lock the y-axis below.
|
|
669
770
|
let prevEnvelope: { low: number; high: number } | null = null;
|
|
771
|
+
// Set when this push is the loading line's hand-off to real data.
|
|
772
|
+
let handOff = false;
|
|
670
773
|
// Set for an animated live update: whether the last bar reshapes, and
|
|
671
774
|
// the window an appended bar should pull the view to.
|
|
672
775
|
let stream: { morph: boolean; window: VisibleRange | null } | null = null;
|
|
@@ -747,6 +850,22 @@ export function useChartCore(
|
|
|
747
850
|
endIntervalMorph();
|
|
748
851
|
}
|
|
749
852
|
|
|
853
|
+
// The loading line's data has landed, so it hands over to the series
|
|
854
|
+
// instead of the chart cutting to it. Always classified 'initial' (the
|
|
855
|
+
// loading branch above cleared prevDataRef), so this runs after that
|
|
856
|
+
// branch's endIntervalMorph.
|
|
857
|
+
if (lineUpRef.current) {
|
|
858
|
+
lineUpRef.current = false;
|
|
859
|
+
handOff =
|
|
860
|
+
animRef.current.ms > 0 &&
|
|
861
|
+
!animRef.current.reduceMotion &&
|
|
862
|
+
onFrameRef.current != null;
|
|
863
|
+
// Snap path only. The hand-off itself starts after setCandles and the
|
|
864
|
+
// framing below: both its stages aim at where the candles will
|
|
865
|
+
// actually sit, so neither can be set up until they're there.
|
|
866
|
+
if (!handOff) h.setLoading(false, true);
|
|
867
|
+
}
|
|
868
|
+
|
|
750
869
|
h.setCandles(packCandles(candles));
|
|
751
870
|
|
|
752
871
|
if (transitionKind === 'timeframe') {
|
|
@@ -778,6 +897,9 @@ export function useChartCore(
|
|
|
778
897
|
} else if (transitionKind === 'reset') {
|
|
779
898
|
h.resetView();
|
|
780
899
|
}
|
|
900
|
+
// After setCandles and the framing above, so the line aims at — and the
|
|
901
|
+
// candles grow from — the geometry each bar will actually occupy.
|
|
902
|
+
if (handOff) startLoadingHandoff(h);
|
|
781
903
|
prevDataRef.current = { handle: h, candles, seriesKey };
|
|
782
904
|
}
|
|
783
905
|
}
|
|
@@ -827,7 +949,7 @@ export function useChartCore(
|
|
|
827
949
|
// fairValueGaps/volume/priceLines/footprints are represented by their *Key
|
|
828
950
|
// deps.
|
|
829
951
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
830
|
-
}, [candles, seriesKey, size.width, size.height, size.pxRatio, explicit, startMs, endMs, defaultCandleWidth, themeKey, rsiKey, macdKey, atrKey, maKey, vwapKey, bollingerKey, ichimokuKey, fvgKey, volumeKey, priceLinesKey, footprintsKey, startIntervalMorph, endIntervalMorph, startStreamAnim, settleStream]);
|
|
952
|
+
}, [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]);
|
|
831
953
|
|
|
832
954
|
return { handle: handleRef.current, picture, volumeCollapseRef };
|
|
833
955
|
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
// JNI entry point for VroomChartModule.install() (see
|
|
2
|
-
// ../java/com/vroom/chart/VroomChartModule.kt). This is the Android
|
|
3
|
-
// equivalent of ../../ios/VroomChartModule.mm's `-install` method: it
|
|
4
|
-
// reinterprets the JSI runtime pointer handed over from Java and calls the
|
|
5
|
-
// same platform-agnostic vroom::installJsi() the iOS bridge uses.
|
|
6
|
-
|
|
7
|
-
#include <jni.h>
|
|
8
|
-
|
|
9
|
-
#include <jsi/jsi.h>
|
|
10
|
-
|
|
11
|
-
#include "VroomJsiInstaller.h"
|
|
12
|
-
|
|
13
|
-
extern "C" JNIEXPORT jboolean JNICALL
|
|
14
|
-
Java_com_vroom_chart_VroomChartModule_nativeInstall(JNIEnv* /*env*/,
|
|
15
|
-
jobject /*thiz*/,
|
|
16
|
-
jlong runtimePointer) {
|
|
17
|
-
auto* runtime =
|
|
18
|
-
reinterpret_cast<facebook::jsi::Runtime*>(runtimePointer);
|
|
19
|
-
if (runtime == nullptr) {
|
|
20
|
-
return JNI_FALSE;
|
|
21
|
-
}
|
|
22
|
-
vroom::installJsi(*runtime);
|
|
23
|
-
return JNI_TRUE;
|
|
24
|
-
}
|