react-native-livechart 1.0.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/LICENSE +33 -0
- package/README.md +198 -0
- package/dist/components/AnimatedLabel.d.ts +21 -0
- package/dist/components/AnimatedLabel.d.ts.map +1 -0
- package/dist/components/BadgeOverlay.d.ts +16 -0
- package/dist/components/BadgeOverlay.d.ts.map +1 -0
- package/dist/components/CrosshairLine.d.ts +20 -0
- package/dist/components/CrosshairLine.d.ts.map +1 -0
- package/dist/components/CrosshairOverlay.d.ts +29 -0
- package/dist/components/CrosshairOverlay.d.ts.map +1 -0
- package/dist/components/DegenParticlesOverlay.d.ts +16 -0
- package/dist/components/DegenParticlesOverlay.d.ts.map +1 -0
- package/dist/components/DotOverlay.d.ts +17 -0
- package/dist/components/DotOverlay.d.ts.map +1 -0
- package/dist/components/LeftEdgeFade.d.ts +9 -0
- package/dist/components/LeftEdgeFade.d.ts.map +1 -0
- package/dist/components/LiveChart.d.ts +3 -0
- package/dist/components/LiveChart.d.ts.map +1 -0
- package/dist/components/LiveChartSeries.d.ts +3 -0
- package/dist/components/LiveChartSeries.d.ts.map +1 -0
- package/dist/components/LiveChartTransition.d.ts +28 -0
- package/dist/components/LiveChartTransition.d.ts.map +1 -0
- package/dist/components/LoadingOverlay.d.ts +24 -0
- package/dist/components/LoadingOverlay.d.ts.map +1 -0
- package/dist/components/MarkerOverlay.d.ts +25 -0
- package/dist/components/MarkerOverlay.d.ts.map +1 -0
- package/dist/components/MultiSeriesDots.d.ts +11 -0
- package/dist/components/MultiSeriesDots.d.ts.map +1 -0
- package/dist/components/MultiSeriesStroke.d.ts +14 -0
- package/dist/components/MultiSeriesStroke.d.ts.map +1 -0
- package/dist/components/MultiSeriesTooltipStack.d.ts +10 -0
- package/dist/components/MultiSeriesTooltipStack.d.ts.map +1 -0
- package/dist/components/MultiSeriesValueLabels.d.ts +11 -0
- package/dist/components/MultiSeriesValueLabels.d.ts.map +1 -0
- package/dist/components/MultiSeriesValueLines.d.ts +10 -0
- package/dist/components/MultiSeriesValueLines.d.ts.map +1 -0
- package/dist/components/ReferenceLineOverlay.d.ts +19 -0
- package/dist/components/ReferenceLineOverlay.d.ts.map +1 -0
- package/dist/components/SeriesToggleChips.d.ts +15 -0
- package/dist/components/SeriesToggleChips.d.ts.map +1 -0
- package/dist/components/TradeStreamOverlay.d.ts +14 -0
- package/dist/components/TradeStreamOverlay.d.ts.map +1 -0
- package/dist/components/ValueLineOverlay.d.ts +16 -0
- package/dist/components/ValueLineOverlay.d.ts.map +1 -0
- package/dist/components/ValueTextOverlay.d.ts +20 -0
- package/dist/components/ValueTextOverlay.d.ts.map +1 -0
- package/dist/components/XAxisOverlay.d.ts +14 -0
- package/dist/components/XAxisOverlay.d.ts.map +1 -0
- package/dist/components/YAxisOverlay.d.ts +23 -0
- package/dist/components/YAxisOverlay.d.ts.map +1 -0
- package/dist/components/seriesMetaSig.d.ts +4 -0
- package/dist/components/seriesMetaSig.d.ts.map +1 -0
- package/dist/constants.d.ts +38 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/core/liveChartEngineTick.d.ts +45 -0
- package/dist/core/liveChartEngineTick.d.ts.map +1 -0
- package/dist/core/liveChartSeriesEngineTick.d.ts +38 -0
- package/dist/core/liveChartSeriesEngineTick.d.ts.map +1 -0
- package/dist/core/multiSeriesLayout.d.ts +22 -0
- package/dist/core/multiSeriesLayout.d.ts.map +1 -0
- package/dist/core/resolveConfig.d.ts +188 -0
- package/dist/core/resolveConfig.d.ts.map +1 -0
- package/dist/core/useLiveChartEngine.d.ts +79 -0
- package/dist/core/useLiveChartEngine.d.ts.map +1 -0
- package/dist/core/useLiveChartSeriesEngine.d.ts +68 -0
- package/dist/core/useLiveChartSeriesEngine.d.ts.map +1 -0
- package/dist/draw/candle.d.ts +25 -0
- package/dist/draw/candle.d.ts.map +1 -0
- package/dist/draw/grid.d.ts +24 -0
- package/dist/draw/grid.d.ts.map +1 -0
- package/dist/draw/line.d.ts +86 -0
- package/dist/draw/line.d.ts.map +1 -0
- package/dist/draw/trade.d.ts +38 -0
- package/dist/draw/trade.d.ts.map +1 -0
- package/dist/hooks/crosshairSeries.d.ts +13 -0
- package/dist/hooks/crosshairSeries.d.ts.map +1 -0
- package/dist/hooks/crosshairShared.d.ts +72 -0
- package/dist/hooks/crosshairShared.d.ts.map +1 -0
- package/dist/hooks/index.d.ts +24 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/resolveChartLayout.d.ts +42 -0
- package/dist/hooks/resolveChartLayout.d.ts.map +1 -0
- package/dist/hooks/useBadge.d.ts +14 -0
- package/dist/hooks/useBadge.d.ts.map +1 -0
- package/dist/hooks/useCandlePaths.d.ts +19 -0
- package/dist/hooks/useCandlePaths.d.ts.map +1 -0
- package/dist/hooks/useCanvasLayout.d.ts +12 -0
- package/dist/hooks/useCanvasLayout.d.ts.map +1 -0
- package/dist/hooks/useChartColors.d.ts +20 -0
- package/dist/hooks/useChartColors.d.ts.map +1 -0
- package/dist/hooks/useChartPaths.d.ts +19 -0
- package/dist/hooks/useChartPaths.d.ts.map +1 -0
- package/dist/hooks/useChartReveal.d.ts +30 -0
- package/dist/hooks/useChartReveal.d.ts.map +1 -0
- package/dist/hooks/useChartSkiaFont.d.ts +8 -0
- package/dist/hooks/useChartSkiaFont.d.ts.map +1 -0
- package/dist/hooks/useCrosshair.d.ts +20 -0
- package/dist/hooks/useCrosshair.d.ts.map +1 -0
- package/dist/hooks/useCrosshairSeries.d.ts +10 -0
- package/dist/hooks/useCrosshairSeries.d.ts.map +1 -0
- package/dist/hooks/useDegen.d.ts +17 -0
- package/dist/hooks/useDegen.d.ts.map +1 -0
- package/dist/hooks/useLiveChartHasData.d.ts +17 -0
- package/dist/hooks/useLiveChartHasData.d.ts.map +1 -0
- package/dist/hooks/useLiveDot.d.ts +12 -0
- package/dist/hooks/useLiveDot.d.ts.map +1 -0
- package/dist/hooks/useMarkers.d.ts +15 -0
- package/dist/hooks/useMarkers.d.ts.map +1 -0
- package/dist/hooks/useModeBlend.d.ts +18 -0
- package/dist/hooks/useModeBlend.d.ts.map +1 -0
- package/dist/hooks/useMomentum.d.ts +16 -0
- package/dist/hooks/useMomentum.d.ts.map +1 -0
- package/dist/hooks/useMultiSeriesDegen.d.ts +25 -0
- package/dist/hooks/useMultiSeriesDegen.d.ts.map +1 -0
- package/dist/hooks/useMultiSeriesLinePaths.d.ts +15 -0
- package/dist/hooks/useMultiSeriesLinePaths.d.ts.map +1 -0
- package/dist/hooks/useReferenceLine.d.ts +32 -0
- package/dist/hooks/useReferenceLine.d.ts.map +1 -0
- package/dist/hooks/useReverseMorphEngineInputs.d.ts +24 -0
- package/dist/hooks/useReverseMorphEngineInputs.d.ts.map +1 -0
- package/dist/hooks/useTradeStream.d.ts +12 -0
- package/dist/hooks/useTradeStream.d.ts.map +1 -0
- package/dist/hooks/useXAxis.d.ts +32 -0
- package/dist/hooks/useXAxis.d.ts.map +1 -0
- package/dist/hooks/useYAxis.d.ts +13 -0
- package/dist/hooks/useYAxis.d.ts.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/lib/format.d.ts +5 -0
- package/dist/lib/format.d.ts.map +1 -0
- package/dist/lib/measureFontTextWidth.d.ts +7 -0
- package/dist/lib/measureFontTextWidth.d.ts.map +1 -0
- package/dist/lib/monoFontFamily.d.ts +6 -0
- package/dist/lib/monoFontFamily.d.ts.map +1 -0
- package/dist/math/color.d.ts +5 -0
- package/dist/math/color.d.ts.map +1 -0
- package/dist/math/degenTick.d.ts +57 -0
- package/dist/math/degenTick.d.ts.map +1 -0
- package/dist/math/interpolate.d.ts +7 -0
- package/dist/math/interpolate.d.ts.map +1 -0
- package/dist/math/intervals.d.ts +3 -0
- package/dist/math/intervals.d.ts.map +1 -0
- package/dist/math/lerp.d.ts +7 -0
- package/dist/math/lerp.d.ts.map +1 -0
- package/dist/math/markers.d.ts +44 -0
- package/dist/math/markers.d.ts.map +1 -0
- package/dist/math/momentum.d.ts +8 -0
- package/dist/math/momentum.d.ts.map +1 -0
- package/dist/math/pickCandle.d.ts +8 -0
- package/dist/math/pickCandle.d.ts.map +1 -0
- package/dist/math/range.d.ts +10 -0
- package/dist/math/range.d.ts.map +1 -0
- package/dist/math/referenceLines.d.ts +20 -0
- package/dist/math/referenceLines.d.ts.map +1 -0
- package/dist/math/spline.d.ts +45 -0
- package/dist/math/spline.d.ts.map +1 -0
- package/dist/math/squiggly.d.ts +36 -0
- package/dist/math/squiggly.d.ts.map +1 -0
- package/dist/theme.d.ts +30 -0
- package/dist/theme.d.ts.map +1 -0
- package/dist/types.d.ts +720 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +72 -0
- package/src/components/AnimatedLabel.tsx +41 -0
- package/src/components/BadgeOverlay.tsx +39 -0
- package/src/components/CrosshairLine.tsx +90 -0
- package/src/components/CrosshairOverlay.tsx +170 -0
- package/src/components/DegenParticlesOverlay.tsx +141 -0
- package/src/components/DotOverlay.tsx +69 -0
- package/src/components/LeftEdgeFade.tsx +36 -0
- package/src/components/LiveChart.tsx +821 -0
- package/src/components/LiveChartSeries.tsx +607 -0
- package/src/components/LiveChartTransition.tsx +135 -0
- package/src/components/LoadingOverlay.tsx +301 -0
- package/src/components/MarkerOverlay.tsx +331 -0
- package/src/components/MultiSeriesDots.tsx +117 -0
- package/src/components/MultiSeriesStroke.tsx +79 -0
- package/src/components/MultiSeriesTooltipStack.tsx +78 -0
- package/src/components/MultiSeriesValueLabels.tsx +103 -0
- package/src/components/MultiSeriesValueLines.tsx +115 -0
- package/src/components/ReferenceLineOverlay.tsx +317 -0
- package/src/components/SeriesToggleChips.tsx +201 -0
- package/src/components/TradeStreamOverlay.tsx +117 -0
- package/src/components/ValueLineOverlay.tsx +63 -0
- package/src/components/ValueTextOverlay.tsx +43 -0
- package/src/components/XAxisOverlay.tsx +109 -0
- package/src/components/YAxisOverlay.tsx +135 -0
- package/src/components/seriesMetaSig.ts +13 -0
- package/src/constants.ts +44 -0
- package/src/core/liveChartEngineTick.ts +185 -0
- package/src/core/liveChartSeriesEngineTick.ts +163 -0
- package/src/core/multiSeriesLayout.ts +80 -0
- package/src/core/resolveConfig.ts +538 -0
- package/src/core/useLiveChartEngine.ts +207 -0
- package/src/core/useLiveChartSeriesEngine.ts +215 -0
- package/src/draw/candle.ts +210 -0
- package/src/draw/grid.ts +174 -0
- package/src/draw/line.ts +257 -0
- package/src/draw/trade.ts +202 -0
- package/src/hooks/crosshairSeries.ts +78 -0
- package/src/hooks/crosshairShared.ts +296 -0
- package/src/hooks/index.ts +26 -0
- package/src/hooks/resolveChartLayout.ts +144 -0
- package/src/hooks/useBadge.ts +192 -0
- package/src/hooks/useCandlePaths.ts +165 -0
- package/src/hooks/useCanvasLayout.ts +22 -0
- package/src/hooks/useChartColors.ts +48 -0
- package/src/hooks/useChartPaths.ts +121 -0
- package/src/hooks/useChartReveal.ts +123 -0
- package/src/hooks/useChartSkiaFont.ts +34 -0
- package/src/hooks/useCrosshair.ts +250 -0
- package/src/hooks/useCrosshairSeries.ts +163 -0
- package/src/hooks/useDegen.ts +262 -0
- package/src/hooks/useLiveChartHasData.ts +31 -0
- package/src/hooks/useLiveDot.ts +31 -0
- package/src/hooks/useMarkers.ts +95 -0
- package/src/hooks/useModeBlend.ts +52 -0
- package/src/hooks/useMomentum.ts +37 -0
- package/src/hooks/useMultiSeriesDegen.ts +260 -0
- package/src/hooks/useMultiSeriesLinePaths.ts +76 -0
- package/src/hooks/useReferenceLine.ts +212 -0
- package/src/hooks/useReverseMorphEngineInputs.ts +154 -0
- package/src/hooks/useTradeStream.ts +64 -0
- package/src/hooks/useXAxis.ts +200 -0
- package/src/hooks/useYAxis.ts +48 -0
- package/src/index.ts +69 -0
- package/src/lib/format.ts +36 -0
- package/src/lib/measureFontTextWidth.ts +10 -0
- package/src/lib/monoFontFamily.ts +8 -0
- package/src/math/color.ts +31 -0
- package/src/math/degenTick.ts +151 -0
- package/src/math/interpolate.ts +32 -0
- package/src/math/intervals.ts +17 -0
- package/src/math/lerp.ts +17 -0
- package/src/math/markers.ts +175 -0
- package/src/math/momentum.ts +38 -0
- package/src/math/pickCandle.ts +42 -0
- package/src/math/range.ts +54 -0
- package/src/math/referenceLines.ts +56 -0
- package/src/math/spline.ts +183 -0
- package/src/math/squiggly.ts +108 -0
- package/src/theme.ts +231 -0
- package/src/types.ts +789 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import type { CandlePoint, LiveChartPoint } from "../types";
|
|
2
|
+
|
|
3
|
+
import { ADAPTIVE_SPEED_BOOST } from "../constants";
|
|
4
|
+
import { lerp } from "../math/lerp";
|
|
5
|
+
|
|
6
|
+
export interface EngineTickMutable {
|
|
7
|
+
displayValue: number;
|
|
8
|
+
displayMin: number;
|
|
9
|
+
displayMax: number;
|
|
10
|
+
displayWindow: number;
|
|
11
|
+
timestamp: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface EngineTickInput {
|
|
15
|
+
dt: number;
|
|
16
|
+
canvasWidth: number;
|
|
17
|
+
canvasHeight: number;
|
|
18
|
+
timeWindow: number;
|
|
19
|
+
smoothing: number;
|
|
20
|
+
exaggerate: boolean;
|
|
21
|
+
referenceValue: number | undefined;
|
|
22
|
+
/** Additional reference values (lines + bands) folded into the Y range. */
|
|
23
|
+
referenceValues?: number[];
|
|
24
|
+
/** Clamp the computed lower bound at 0. */
|
|
25
|
+
nonNegative?: boolean;
|
|
26
|
+
/** Hard cap for the computed upper bound. */
|
|
27
|
+
maxValue?: number;
|
|
28
|
+
targetValue: number;
|
|
29
|
+
points: LiveChartPoint[];
|
|
30
|
+
/** Seconds since Unix epoch; defaults to `Date.now() / 1000` */
|
|
31
|
+
nowSeconds?: number;
|
|
32
|
+
/** Override the engine's "now" (unix seconds) — e.g. fill historical data edge-to-edge. */
|
|
33
|
+
nowOverride?: number;
|
|
34
|
+
/** Right-edge buffer as a fraction of the time window (pushes the live edge past "now"). */
|
|
35
|
+
windowBuffer?: number;
|
|
36
|
+
/** When true, freeze the viewport timestamp and skip displayWindow lerp */
|
|
37
|
+
paused?: boolean;
|
|
38
|
+
/** Chart mode — `"candle"` uses OHLC bars for Y range instead of line points. */
|
|
39
|
+
mode?: "line" | "candle";
|
|
40
|
+
/** Committed OHLC bars (sorted by time). Used when mode is `"candle"`. */
|
|
41
|
+
candles?: CandlePoint[];
|
|
42
|
+
/** In-progress candle. Included in Y range when mode is `"candle"`. */
|
|
43
|
+
liveCandle?: CandlePoint | null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* One frame of the live chart engine (mirrors `useLiveChartEngine` worklet body).
|
|
48
|
+
* Mutates `state` in place for testability and reuse from the hook.
|
|
49
|
+
*/
|
|
50
|
+
export function tickLiveChartEngineFrame(
|
|
51
|
+
state: EngineTickMutable,
|
|
52
|
+
input: EngineTickInput,
|
|
53
|
+
): void {
|
|
54
|
+
"worklet";
|
|
55
|
+
const baseNow = input.nowOverride ?? input.nowSeconds ?? Date.now() / 1000;
|
|
56
|
+
if (!input.paused) {
|
|
57
|
+
state.timestamp = baseNow + (input.windowBuffer ?? 0) * input.timeWindow;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (input.canvasWidth === 0 || input.canvasHeight === 0) return;
|
|
61
|
+
|
|
62
|
+
const speed = input.smoothing;
|
|
63
|
+
let target = input.targetValue;
|
|
64
|
+
if (input.mode === "candle" && input.liveCandle) {
|
|
65
|
+
target = input.liveCandle.close;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const range = state.displayMax - state.displayMin;
|
|
69
|
+
const gapRatio =
|
|
70
|
+
range > 0 ? Math.min(Math.abs(target - state.displayValue) / range, 1) : 0;
|
|
71
|
+
const adaptiveSpeed = speed + (1 - gapRatio) * ADAPTIVE_SPEED_BOOST;
|
|
72
|
+
|
|
73
|
+
state.displayValue = lerp(
|
|
74
|
+
state.displayValue,
|
|
75
|
+
target,
|
|
76
|
+
adaptiveSpeed,
|
|
77
|
+
input.dt,
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
state.displayWindow = lerp(
|
|
81
|
+
state.displayWindow,
|
|
82
|
+
input.timeWindow,
|
|
83
|
+
speed,
|
|
84
|
+
input.dt,
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const winStart = state.timestamp - state.displayWindow;
|
|
88
|
+
|
|
89
|
+
let tMin = Infinity;
|
|
90
|
+
let tMax = -Infinity;
|
|
91
|
+
|
|
92
|
+
if (input.mode === "candle") {
|
|
93
|
+
const candles = input.candles;
|
|
94
|
+
if (candles && candles.length > 0) {
|
|
95
|
+
let lo = 0;
|
|
96
|
+
let hi = candles.length;
|
|
97
|
+
while (lo < hi) {
|
|
98
|
+
const mid = (lo + hi) >> 1;
|
|
99
|
+
if (candles[mid].time < winStart) lo = mid + 1;
|
|
100
|
+
else hi = mid;
|
|
101
|
+
}
|
|
102
|
+
for (let i = lo; i < candles.length; i++) {
|
|
103
|
+
if (candles[i].time > state.timestamp) break;
|
|
104
|
+
/* istanbul ignore next -- trivial min/max */
|
|
105
|
+
if (candles[i].low < tMin) tMin = candles[i].low;
|
|
106
|
+
/* istanbul ignore next -- trivial min/max */
|
|
107
|
+
if (candles[i].high > tMax) tMax = candles[i].high;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
const lc = input.liveCandle;
|
|
111
|
+
if (lc) {
|
|
112
|
+
/* istanbul ignore next -- trivial min/max */
|
|
113
|
+
if (lc.low < tMin) tMin = lc.low;
|
|
114
|
+
/* istanbul ignore next -- trivial min/max */
|
|
115
|
+
if (lc.high > tMax) tMax = lc.high;
|
|
116
|
+
}
|
|
117
|
+
} else {
|
|
118
|
+
const points = input.points;
|
|
119
|
+
let lo = 0;
|
|
120
|
+
let hi = points.length;
|
|
121
|
+
while (lo < hi) {
|
|
122
|
+
const mid = (lo + hi) >> 1;
|
|
123
|
+
if (points[mid].time < winStart) lo = mid + 1;
|
|
124
|
+
else hi = mid;
|
|
125
|
+
}
|
|
126
|
+
for (let i = lo; i < points.length; i++) {
|
|
127
|
+
const v = points[i].value;
|
|
128
|
+
if (v < tMin) tMin = v;
|
|
129
|
+
if (v > tMax) tMax = v;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const cv = state.displayValue;
|
|
134
|
+
if (cv < tMin) tMin = cv;
|
|
135
|
+
if (cv > tMax) tMax = cv;
|
|
136
|
+
|
|
137
|
+
const ref = input.referenceValue;
|
|
138
|
+
if (ref !== undefined) {
|
|
139
|
+
if (ref < tMin) tMin = ref;
|
|
140
|
+
if (ref > tMax) tMax = ref;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const refs = input.referenceValues;
|
|
144
|
+
if (refs !== undefined) {
|
|
145
|
+
for (let i = 0; i < refs.length; i++) {
|
|
146
|
+
const rv = refs[i];
|
|
147
|
+
if (rv < tMin) tMin = rv;
|
|
148
|
+
if (rv > tMax) tMax = rv;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const isExaggerate = input.exaggerate;
|
|
153
|
+
if (tMin !== Infinity && tMax !== -Infinity) {
|
|
154
|
+
const rawRange = tMax - tMin;
|
|
155
|
+
const marginFactor = isExaggerate ? 0.01 : 0.12;
|
|
156
|
+
const minRange =
|
|
157
|
+
rawRange * (isExaggerate ? 0.02 : 0.1) || (isExaggerate ? 0.04 : 0.4);
|
|
158
|
+
|
|
159
|
+
if (rawRange < minRange) {
|
|
160
|
+
const mid = (tMin + tMax) / 2;
|
|
161
|
+
tMin = mid - minRange / 2;
|
|
162
|
+
tMax = mid + minRange / 2;
|
|
163
|
+
} else {
|
|
164
|
+
const margin = rawRange * marginFactor;
|
|
165
|
+
tMin -= margin;
|
|
166
|
+
tMax += margin;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (input.nonNegative && tMin < 0) tMin = 0;
|
|
170
|
+
const maxV = input.maxValue;
|
|
171
|
+
if (maxV !== undefined && tMax > maxV) tMax = maxV;
|
|
172
|
+
|
|
173
|
+
if (tMin < state.displayMin) {
|
|
174
|
+
state.displayMin = tMin;
|
|
175
|
+
} else {
|
|
176
|
+
state.displayMin = lerp(state.displayMin, tMin, speed, input.dt);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (tMax > state.displayMax) {
|
|
180
|
+
state.displayMax = tMax;
|
|
181
|
+
} else {
|
|
182
|
+
state.displayMax = lerp(state.displayMax, tMax, speed, input.dt);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { ADAPTIVE_SPEED_BOOST } from "../constants";
|
|
2
|
+
import { lerp } from "../math/lerp";
|
|
3
|
+
import type { SeriesConfig } from "../types";
|
|
4
|
+
|
|
5
|
+
export interface MultiEngineTickMutable {
|
|
6
|
+
displayMin: number;
|
|
7
|
+
displayMax: number;
|
|
8
|
+
displayWindow: number;
|
|
9
|
+
timestamp: number;
|
|
10
|
+
displayValues: number[];
|
|
11
|
+
opacities: number[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface MultiEngineTickInput {
|
|
15
|
+
dt: number;
|
|
16
|
+
canvasWidth: number;
|
|
17
|
+
canvasHeight: number;
|
|
18
|
+
timeWindow: number;
|
|
19
|
+
smoothing: number;
|
|
20
|
+
exaggerate: boolean;
|
|
21
|
+
referenceValue: number | undefined;
|
|
22
|
+
/** Additional reference values (lines + bands) folded into the Y range. */
|
|
23
|
+
referenceValues?: number[];
|
|
24
|
+
/** Clamp the computed lower bound at 0. */
|
|
25
|
+
nonNegative?: boolean;
|
|
26
|
+
/** Hard cap for the computed upper bound. */
|
|
27
|
+
maxValue?: number;
|
|
28
|
+
series: SeriesConfig[];
|
|
29
|
+
nowSeconds?: number;
|
|
30
|
+
/** Override the engine's "now" (unix seconds). */
|
|
31
|
+
nowOverride?: number;
|
|
32
|
+
/** Right-edge buffer as a fraction of the time window. */
|
|
33
|
+
windowBuffer?: number;
|
|
34
|
+
paused?: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Multi-series frame tick: lerps per-series tips and visibility opacities,
|
|
39
|
+
* combines Y-range over visible series (same margin rules as single-series).
|
|
40
|
+
* Used by `useLiveChartSeriesEngine`.
|
|
41
|
+
*/
|
|
42
|
+
export function tickLiveChartSeriesEngineFrame(
|
|
43
|
+
state: MultiEngineTickMutable,
|
|
44
|
+
input: MultiEngineTickInput,
|
|
45
|
+
): void {
|
|
46
|
+
"worklet";
|
|
47
|
+
const baseNow = input.nowOverride ?? input.nowSeconds ?? Date.now() / 1000;
|
|
48
|
+
if (!input.paused) {
|
|
49
|
+
state.timestamp = baseNow + (input.windowBuffer ?? 0) * input.timeWindow;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (input.canvasWidth === 0 || input.canvasHeight === 0) return;
|
|
53
|
+
|
|
54
|
+
const speed = input.smoothing;
|
|
55
|
+
const series = input.series;
|
|
56
|
+
const n = series.length;
|
|
57
|
+
|
|
58
|
+
while (state.displayValues.length < n) {
|
|
59
|
+
const i = state.displayValues.length;
|
|
60
|
+
state.displayValues.push(series[i].value);
|
|
61
|
+
state.opacities.push(series[i].visible !== false ? 1 : 0);
|
|
62
|
+
}
|
|
63
|
+
if (state.displayValues.length > n) {
|
|
64
|
+
state.displayValues.length = n;
|
|
65
|
+
state.opacities.length = n;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
state.displayWindow = lerp(
|
|
69
|
+
state.displayWindow,
|
|
70
|
+
input.timeWindow,
|
|
71
|
+
speed,
|
|
72
|
+
input.dt,
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
const winStart = state.timestamp - state.displayWindow;
|
|
76
|
+
const range = state.displayMax - state.displayMin;
|
|
77
|
+
|
|
78
|
+
for (let i = 0; i < n; i++) {
|
|
79
|
+
const target = series[i].value;
|
|
80
|
+
const cur = state.displayValues[i];
|
|
81
|
+
const gapRatio =
|
|
82
|
+
range > 0 ? Math.min(Math.abs(target - cur) / range, 1) : 0;
|
|
83
|
+
const adaptiveSpeed = speed + (1 - gapRatio) * ADAPTIVE_SPEED_BOOST;
|
|
84
|
+
state.displayValues[i] = lerp(cur, target, adaptiveSpeed, input.dt);
|
|
85
|
+
|
|
86
|
+
const targetOp = series[i].visible !== false ? 1 : 0;
|
|
87
|
+
state.opacities[i] = lerp(state.opacities[i], targetOp, speed, input.dt);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
let tMin = Infinity;
|
|
91
|
+
let tMax = -Infinity;
|
|
92
|
+
|
|
93
|
+
for (let i = 0; i < n; i++) {
|
|
94
|
+
if (series[i].visible === false) continue;
|
|
95
|
+
const points = series[i].data;
|
|
96
|
+
let lo = 0;
|
|
97
|
+
let hi = points.length;
|
|
98
|
+
while (lo < hi) {
|
|
99
|
+
const mid = (lo + hi) >> 1;
|
|
100
|
+
if (points[mid].time < winStart) lo = mid + 1;
|
|
101
|
+
else hi = mid;
|
|
102
|
+
}
|
|
103
|
+
for (let j = lo; j < points.length; j++) {
|
|
104
|
+
const v = points[j].value;
|
|
105
|
+
/* istanbul ignore next -- trivial min/max */
|
|
106
|
+
if (v < tMin) tMin = v;
|
|
107
|
+
/* istanbul ignore next -- trivial min/max */
|
|
108
|
+
if (v > tMax) tMax = v;
|
|
109
|
+
}
|
|
110
|
+
const cv = state.displayValues[i];
|
|
111
|
+
if (cv < tMin) tMin = cv;
|
|
112
|
+
if (cv > tMax) tMax = cv;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const ref = input.referenceValue;
|
|
116
|
+
if (ref !== undefined) {
|
|
117
|
+
if (ref < tMin) tMin = ref;
|
|
118
|
+
if (ref > tMax) tMax = ref;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const refs = input.referenceValues;
|
|
122
|
+
if (refs !== undefined) {
|
|
123
|
+
for (let i = 0; i < refs.length; i++) {
|
|
124
|
+
const rv = refs[i];
|
|
125
|
+
if (rv < tMin) tMin = rv;
|
|
126
|
+
if (rv > tMax) tMax = rv;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const isExaggerate = input.exaggerate;
|
|
131
|
+
if (tMin !== Infinity && tMax !== -Infinity) {
|
|
132
|
+
const rawRange = tMax - tMin;
|
|
133
|
+
const marginFactor = isExaggerate ? 0.01 : 0.12;
|
|
134
|
+
const minRange =
|
|
135
|
+
rawRange * (isExaggerate ? 0.02 : 0.1) || (isExaggerate ? 0.04 : 0.4);
|
|
136
|
+
|
|
137
|
+
if (rawRange < minRange) {
|
|
138
|
+
const mid = (tMin + tMax) / 2;
|
|
139
|
+
tMin = mid - minRange / 2;
|
|
140
|
+
tMax = mid + minRange / 2;
|
|
141
|
+
} else {
|
|
142
|
+
const margin = rawRange * marginFactor;
|
|
143
|
+
tMin -= margin;
|
|
144
|
+
tMax += margin;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (input.nonNegative && tMin < 0) tMin = 0;
|
|
148
|
+
const maxV = input.maxValue;
|
|
149
|
+
if (maxV !== undefined && tMax > maxV) tMax = maxV;
|
|
150
|
+
|
|
151
|
+
if (tMin < state.displayMin) {
|
|
152
|
+
state.displayMin = tMin;
|
|
153
|
+
} else {
|
|
154
|
+
state.displayMin = lerp(state.displayMin, tMin, speed, input.dt);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (tMax > state.displayMax) {
|
|
158
|
+
state.displayMax = tMax;
|
|
159
|
+
} else {
|
|
160
|
+
state.displayMax = lerp(state.displayMax, tMax, speed, input.dt);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { MAX_MULTI_SERIES } from "../constants";
|
|
2
|
+
import { SERIES_COLORS } from "../theme";
|
|
3
|
+
import type { SeriesConfig } from "../types";
|
|
4
|
+
|
|
5
|
+
/** Stable signature when series id/color change (not every data tick). */
|
|
6
|
+
export function lineColorsSignatureFromArray(arr: SeriesConfig[]): string {
|
|
7
|
+
"worklet";
|
|
8
|
+
// U+001F (unit separator) and U+001E (record separator) are ASCII control
|
|
9
|
+
// chars that will never appear in series IDs or color strings.
|
|
10
|
+
// No Array#map — iterator callbacks are not worklets on the UI runtime.
|
|
11
|
+
let out = "";
|
|
12
|
+
for (let i = 0; i < arr.length; i++) {
|
|
13
|
+
if (i > 0) out += "\x1e";
|
|
14
|
+
const x = arr[i];
|
|
15
|
+
out += `${x.id}\x1f${x.color ?? ""}`;
|
|
16
|
+
}
|
|
17
|
+
return out;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Per-slot stroke colors for chips + Skia (empty slots → white). */
|
|
21
|
+
export function resolveMultiSeriesLineColorsSnapshot(
|
|
22
|
+
arr: SeriesConfig[],
|
|
23
|
+
maxSlots = MAX_MULTI_SERIES,
|
|
24
|
+
): string[] {
|
|
25
|
+
return Array.from({ length: maxSlots }, (_, i) =>
|
|
26
|
+
i < arr.length
|
|
27
|
+
? (arr[i].color ?? SERIES_COLORS[i % SERIES_COLORS.length])
|
|
28
|
+
: "#ffffff",
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Per-series visual stroke style resolved for one render slot. */
|
|
33
|
+
export interface SeriesLineStyle {
|
|
34
|
+
/** Per-series stroke width override, or `undefined` to use the chart default. */
|
|
35
|
+
strokeWidth: number | undefined;
|
|
36
|
+
dashed: boolean;
|
|
37
|
+
intervals: [number, number];
|
|
38
|
+
glow: boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const DEFAULT_LINE_STYLE: SeriesLineStyle = {
|
|
42
|
+
strokeWidth: undefined,
|
|
43
|
+
dashed: false,
|
|
44
|
+
intervals: [6, 4],
|
|
45
|
+
glow: false,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Stable signature over the per-series style fields (style / intervals /
|
|
50
|
+
* strokeWidth / glow), so a snapshot re-sync fires when styling changes but not
|
|
51
|
+
* on every data tick.
|
|
52
|
+
*/
|
|
53
|
+
export function lineStyleSignatureFromArray(arr: SeriesConfig[]): string {
|
|
54
|
+
"worklet";
|
|
55
|
+
let out = "";
|
|
56
|
+
for (let i = 0; i < arr.length; i++) {
|
|
57
|
+
if (i > 0) out += "\x1e";
|
|
58
|
+
const x = arr[i];
|
|
59
|
+
const iv = x.intervals ? `${x.intervals[0]},${x.intervals[1]}` : "";
|
|
60
|
+
out += `${x.id}\x1f${x.style ?? ""}\x1f${x.strokeWidth ?? ""}\x1f${x.glow ? 1 : 0}\x1f${iv}`;
|
|
61
|
+
}
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Per-slot resolved stroke style for Skia (empty slots → defaults). */
|
|
66
|
+
export function resolveMultiSeriesLineStylesSnapshot(
|
|
67
|
+
arr: SeriesConfig[],
|
|
68
|
+
maxSlots = MAX_MULTI_SERIES,
|
|
69
|
+
): SeriesLineStyle[] {
|
|
70
|
+
return Array.from({ length: maxSlots }, (_, i) => {
|
|
71
|
+
const s = arr[i];
|
|
72
|
+
if (!s) return DEFAULT_LINE_STYLE;
|
|
73
|
+
return {
|
|
74
|
+
strokeWidth: s.strokeWidth,
|
|
75
|
+
dashed: s.style === "dashed",
|
|
76
|
+
intervals: s.intervals ?? DEFAULT_LINE_STYLE.intervals,
|
|
77
|
+
glow: s.glow ?? false,
|
|
78
|
+
};
|
|
79
|
+
});
|
|
80
|
+
}
|