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,56 @@
|
|
|
1
|
+
import type { ReferenceLine } from "../types";
|
|
2
|
+
|
|
3
|
+
/** Which of the three reference-line forms a `ReferenceLine` resolves to. */
|
|
4
|
+
export type ReferenceLineForm = "line" | "value-band" | "time-band" | "none";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Classify a `ReferenceLine` into one of its three forms, applying the
|
|
8
|
+
* documented precedence A (line) > B (value band) > C (time band).
|
|
9
|
+
*/
|
|
10
|
+
export function referenceLineForm(rl: ReferenceLine): ReferenceLineForm {
|
|
11
|
+
"worklet";
|
|
12
|
+
if (rl.value !== undefined) return "line";
|
|
13
|
+
if (rl.valueFrom !== undefined && rl.valueTo !== undefined) {
|
|
14
|
+
return "value-band";
|
|
15
|
+
}
|
|
16
|
+
if (rl.from !== undefined && rl.to !== undefined) return "time-band";
|
|
17
|
+
return "none";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Gather every Y value a set of reference lines should contribute to the
|
|
22
|
+
* axis-range computation. Lines flagged `excludeFromRange` are skipped, as are
|
|
23
|
+
* time bands (Form C) which constrain time, not value.
|
|
24
|
+
*/
|
|
25
|
+
export function collectReferenceValues(lines: ReferenceLine[]): number[] {
|
|
26
|
+
const out: number[] = [];
|
|
27
|
+
for (let i = 0; i < lines.length; i++) {
|
|
28
|
+
const rl = lines[i];
|
|
29
|
+
if (rl.excludeFromRange) continue;
|
|
30
|
+
switch (referenceLineForm(rl)) {
|
|
31
|
+
case "line":
|
|
32
|
+
out.push(rl.value as number);
|
|
33
|
+
break;
|
|
34
|
+
case "value-band":
|
|
35
|
+
out.push(rl.valueFrom as number, rl.valueTo as number);
|
|
36
|
+
break;
|
|
37
|
+
// time-band / none contribute no Y values
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Where a Y value sits relative to the visible plot range:
|
|
45
|
+
* `"in"` (within [min, max]), `"above"` (greater than max), or `"below"`.
|
|
46
|
+
*/
|
|
47
|
+
export function classifyReferenceEdge(
|
|
48
|
+
value: number,
|
|
49
|
+
min: number,
|
|
50
|
+
max: number,
|
|
51
|
+
): "in" | "above" | "below" {
|
|
52
|
+
"worklet";
|
|
53
|
+
if (value > max) return "above";
|
|
54
|
+
if (value < min) return "below";
|
|
55
|
+
return "in";
|
|
56
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import type { SkPath } from "@shopify/react-native-skia";
|
|
2
|
+
import type { LiveChartPoint } from "../types";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Reusable scratch buffers for {@link drawSpline}. Pass a persistent instance
|
|
6
|
+
* (created once per chart via `useMemo`) to avoid allocating three arrays sized
|
|
7
|
+
* to the point count on every frame — the per-frame garbage scales with the
|
|
8
|
+
* number of points (large `timeWindow`s push 1000+ points), so pooling these
|
|
9
|
+
* meaningfully cuts UI-thread GC pressure on live charts.
|
|
10
|
+
*/
|
|
11
|
+
export interface SplineScratch {
|
|
12
|
+
delta: number[];
|
|
13
|
+
h: number[];
|
|
14
|
+
m: number[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Allocate an empty {@link SplineScratch} (arrays grow on demand). */
|
|
18
|
+
export function makeSplineScratch(): SplineScratch {
|
|
19
|
+
return { delta: [], h: [], m: [] };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Fritsch-Carlson monotone cubic interpolation.
|
|
24
|
+
* Guarantees no overshoots — the curve never exceeds local min/max.
|
|
25
|
+
* Same approach documented in liveline for smooth live line charts (adapted code; MIT).
|
|
26
|
+
*
|
|
27
|
+
* pts is a flat number array with stride 2: [x0, y0, x1, y1, ...].
|
|
28
|
+
* Caller must moveTo the first point before calling.
|
|
29
|
+
*
|
|
30
|
+
* Pass `scratch` (see {@link makeSplineScratch}) to reuse the tangent/interval
|
|
31
|
+
* buffers across frames instead of allocating them each call. Only indices
|
|
32
|
+
* `0..n-1` are read after being written, so stale tail entries are harmless.
|
|
33
|
+
*
|
|
34
|
+
* @see https://github.com/benjitaylor/liveline
|
|
35
|
+
*/
|
|
36
|
+
export function drawSpline(path: SkPath, pts: number[], scratch?: SplineScratch) {
|
|
37
|
+
"worklet";
|
|
38
|
+
const n = pts.length >> 1;
|
|
39
|
+
if (n < 2) return;
|
|
40
|
+
if (n === 2) {
|
|
41
|
+
path.lineTo(pts[2], pts[3]);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// 1. Secant slopes and x-intervals
|
|
46
|
+
const delta: number[] = scratch ? scratch.delta : new Array(n - 1);
|
|
47
|
+
const h: number[] = scratch ? scratch.h : new Array(n - 1);
|
|
48
|
+
for (let i = 0; i < n - 1; i++) {
|
|
49
|
+
const i2 = i * 2;
|
|
50
|
+
const j2 = i2 + 2;
|
|
51
|
+
h[i] = pts[j2] - pts[i2];
|
|
52
|
+
delta[i] = h[i] === 0 ? 0 : (pts[j2 + 1] - pts[i2 + 1]) / h[i];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 2. Initial tangent estimates
|
|
56
|
+
const m: number[] = scratch ? scratch.m : new Array(n);
|
|
57
|
+
m[0] = delta[0];
|
|
58
|
+
m[n - 1] = delta[n - 2];
|
|
59
|
+
for (let i = 1; i < n - 1; i++) {
|
|
60
|
+
if (delta[i - 1] * delta[i] <= 0) {
|
|
61
|
+
m[i] = 0;
|
|
62
|
+
} else {
|
|
63
|
+
m[i] = (delta[i - 1] + delta[i]) / 2;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 3. Fritsch-Carlson constraint: alpha^2 + beta^2 <= 9
|
|
68
|
+
for (let i = 0; i < n - 1; i++) {
|
|
69
|
+
if (delta[i] === 0) {
|
|
70
|
+
m[i] = 0;
|
|
71
|
+
m[i + 1] = 0;
|
|
72
|
+
} else {
|
|
73
|
+
const alpha = m[i] / delta[i];
|
|
74
|
+
const beta = m[i + 1] / delta[i];
|
|
75
|
+
const s2 = alpha * alpha + beta * beta;
|
|
76
|
+
if (s2 > 9) {
|
|
77
|
+
const s = 3 / Math.sqrt(s2);
|
|
78
|
+
m[i] = s * alpha * delta[i];
|
|
79
|
+
m[i + 1] = s * beta * delta[i];
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// 4. Draw bezier curves
|
|
85
|
+
for (let i = 0; i < n - 1; i++) {
|
|
86
|
+
const i2 = i * 2;
|
|
87
|
+
const j2 = i2 + 2;
|
|
88
|
+
const hi = h[i];
|
|
89
|
+
path.cubicTo(
|
|
90
|
+
pts[i2] + hi / 3,
|
|
91
|
+
pts[i2 + 1] + (m[i] * hi) / 3,
|
|
92
|
+
pts[j2] - hi / 3,
|
|
93
|
+
pts[j2 + 1] - (m[i + 1] * hi) / 3,
|
|
94
|
+
pts[j2],
|
|
95
|
+
pts[j2 + 1],
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Value of the Fritsch–Carlson monotone cubic — the same curve {@link drawSpline}
|
|
102
|
+
* renders — at `time`. Use this to anchor overlays (e.g. markers) exactly on the
|
|
103
|
+
* rendered line instead of on the straight chord between data points. Returns
|
|
104
|
+
* `null` for empty data; clamps to the endpoints outside the data range.
|
|
105
|
+
*
|
|
106
|
+
* The data→pixel mapping is affine, so evaluating the monotone cubic in data
|
|
107
|
+
* space and then projecting matches the pixel-space spline. Tangents use the
|
|
108
|
+
* immediate neighbors and the overshoot clamp is applied for this segment only
|
|
109
|
+
* (it does not cascade across segments as in `drawSpline`, which differs only on
|
|
110
|
+
* very steep runs where the clamp fires).
|
|
111
|
+
*/
|
|
112
|
+
export function splineValueAtTime(
|
|
113
|
+
points: LiveChartPoint[],
|
|
114
|
+
time: number,
|
|
115
|
+
): number | null {
|
|
116
|
+
"worklet";
|
|
117
|
+
const n = points.length;
|
|
118
|
+
if (n === 0) return null;
|
|
119
|
+
if (n === 1 || time <= points[0].time) return points[0].value;
|
|
120
|
+
if (time >= points[n - 1].time) return points[n - 1].value;
|
|
121
|
+
|
|
122
|
+
// Segment [i, i+1] with points[i].time <= time < points[i+1].time.
|
|
123
|
+
let lo = 0;
|
|
124
|
+
let hi = n - 1;
|
|
125
|
+
while (hi - lo > 1) {
|
|
126
|
+
const mid = (lo + hi) >> 1;
|
|
127
|
+
if (points[mid].time <= time) lo = mid;
|
|
128
|
+
else hi = mid;
|
|
129
|
+
}
|
|
130
|
+
const i = lo;
|
|
131
|
+
const ti = points[i].time;
|
|
132
|
+
const vi = points[i].value;
|
|
133
|
+
const tj = points[i + 1].time;
|
|
134
|
+
const vj = points[i + 1].value;
|
|
135
|
+
const hSeg = tj - ti;
|
|
136
|
+
/* istanbul ignore next -- guarded by the clamps above; defensive for duplicate times */
|
|
137
|
+
if (hSeg <= 0) return vi;
|
|
138
|
+
|
|
139
|
+
const deltaI = (vj - vi) / hSeg;
|
|
140
|
+
if (n === 2) return vi + deltaI * (time - ti); // drawSpline draws a straight line for n === 2
|
|
141
|
+
|
|
142
|
+
// Tangents at the segment ends (monotone-safe averages of adjacent secants).
|
|
143
|
+
let mi: number;
|
|
144
|
+
if (i === 0) {
|
|
145
|
+
mi = deltaI;
|
|
146
|
+
} else {
|
|
147
|
+
const dPrev = (vi - points[i - 1].value) / (ti - points[i - 1].time);
|
|
148
|
+
mi = dPrev * deltaI <= 0 ? 0 : (dPrev + deltaI) / 2;
|
|
149
|
+
}
|
|
150
|
+
let mj: number;
|
|
151
|
+
if (i + 1 === n - 1) {
|
|
152
|
+
mj = deltaI;
|
|
153
|
+
} else {
|
|
154
|
+
const dNext = (points[i + 2].value - vj) / (points[i + 2].time - tj);
|
|
155
|
+
mj = deltaI * dNext <= 0 ? 0 : (deltaI + dNext) / 2;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Fritsch–Carlson overshoot clamp (alpha² + beta² <= 9).
|
|
159
|
+
if (deltaI === 0) {
|
|
160
|
+
mi = 0;
|
|
161
|
+
mj = 0;
|
|
162
|
+
} else {
|
|
163
|
+
const alpha = mi / deltaI;
|
|
164
|
+
const beta = mj / deltaI;
|
|
165
|
+
const s2c = alpha * alpha + beta * beta;
|
|
166
|
+
if (s2c > 9) {
|
|
167
|
+
const sc = 3 / Math.sqrt(s2c);
|
|
168
|
+
mi = sc * alpha * deltaI;
|
|
169
|
+
mj = sc * beta * deltaI;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Cubic Hermite on [i, i+1]. x is linear in the Bézier parameter (the control
|
|
174
|
+
// points are equally spaced in x), so the parameter equals (time - ti) / hSeg.
|
|
175
|
+
const s = (time - ti) / hSeg;
|
|
176
|
+
const s2 = s * s;
|
|
177
|
+
const s3 = s2 * s;
|
|
178
|
+
const h00 = 2 * s3 - 3 * s2 + 1;
|
|
179
|
+
const h10 = s3 - 2 * s2 + s;
|
|
180
|
+
const h01 = -2 * s3 + 3 * s2;
|
|
181
|
+
const h11 = s3 - s2;
|
|
182
|
+
return h00 * vi + h10 * hSeg * mi + h01 * vj + h11 * hSeg * mj;
|
|
183
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type { ChartPadding } from "../draw/line";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Composite sine squiggly — two overlapping frequencies with a breathing
|
|
5
|
+
* amplitude envelope. Matches the original web LiveChart loading animation.
|
|
6
|
+
*
|
|
7
|
+
* amplitude = 14 * (0.4 + 0.6 * sin(0.8 * t)) // 5.6 → 22.4px breathing range
|
|
8
|
+
* y = centerY + amplitude * (sin(0.035*x + 1.2*t) + 0.45*sin(0.08*x + 2.1*t))
|
|
9
|
+
*/
|
|
10
|
+
export function squigglyYAt(x: number, centerY: number, t: number): number {
|
|
11
|
+
"worklet";
|
|
12
|
+
const amplitude = 14 * (0.4 + 0.6 * Math.sin(0.8 * t));
|
|
13
|
+
return (
|
|
14
|
+
centerY +
|
|
15
|
+
amplitude *
|
|
16
|
+
(Math.sin(0.035 * x + 1.2 * t) + 0.45 * Math.sin(0.08 * x + 2.1 * t))
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Build a flat [x0,y0,x1,y1,...] point array for a standalone squiggly line
|
|
22
|
+
* spanning the full chart area (used in loading / empty state).
|
|
23
|
+
* Points are spaced ~4px apart for smooth curves.
|
|
24
|
+
*/
|
|
25
|
+
export function buildSquigglyPts(
|
|
26
|
+
canvasWidth: number,
|
|
27
|
+
canvasHeight: number,
|
|
28
|
+
padding: ChartPadding,
|
|
29
|
+
t: number,
|
|
30
|
+
): number[] {
|
|
31
|
+
"worklet";
|
|
32
|
+
const leftEdge = padding.left;
|
|
33
|
+
const rightEdge = canvasWidth - padding.right;
|
|
34
|
+
const chartW = rightEdge - leftEdge;
|
|
35
|
+
if (chartW <= 0 || canvasHeight <= 0) return [];
|
|
36
|
+
|
|
37
|
+
const centerY = (canvasHeight - padding.bottom + padding.top) / 2;
|
|
38
|
+
const step = 4;
|
|
39
|
+
const count = Math.ceil(chartW / step) + 1;
|
|
40
|
+
const pts: number[] = new Array(count * 2);
|
|
41
|
+
for (let i = 0; i < count; i++) {
|
|
42
|
+
const x = leftEdge + Math.min(i * step, chartW);
|
|
43
|
+
pts[i * 2] = x;
|
|
44
|
+
pts[i * 2 + 1] = squigglyYAt(x, centerY, t);
|
|
45
|
+
}
|
|
46
|
+
return pts;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Given a real flat-pts array (from buildLinePoints), replace each Y with the
|
|
51
|
+
* squiggly Y at the same X. Used during the morph reveal.
|
|
52
|
+
*/
|
|
53
|
+
export function squigglifyPts(
|
|
54
|
+
flatPts: number[],
|
|
55
|
+
t: number,
|
|
56
|
+
centerY: number,
|
|
57
|
+
): number[] {
|
|
58
|
+
"worklet";
|
|
59
|
+
const n = flatPts.length;
|
|
60
|
+
const out: number[] = new Array(n);
|
|
61
|
+
for (let i = 0; i < n; i += 2) {
|
|
62
|
+
out[i] = flatPts[i];
|
|
63
|
+
out[i + 1] = squigglyYAt(flatPts[i], centerY, t);
|
|
64
|
+
}
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Smoothstep — maps t ∈ [0,1] to a smooth 0→1 curve (no clamping required
|
|
70
|
+
* when t is already in range, but clamps gracefully outside).
|
|
71
|
+
*/
|
|
72
|
+
export function smoothstep(t: number): number {
|
|
73
|
+
"worklet";
|
|
74
|
+
const c = Math.min(1, Math.max(0, t));
|
|
75
|
+
return c * c * (3 - 2 * c);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Blend two flat-pts arrays center-out, driven by morphT ∈ [0,1].
|
|
80
|
+
* Points near the horizontal centre of the chart reveal first; edges last.
|
|
81
|
+
*
|
|
82
|
+
* For each point, a per-point weight is computed:
|
|
83
|
+
* distFromCentre = |x - chartCentreX| / (chartW / 2) // 0=centre, 1=edge
|
|
84
|
+
* weight = smoothstep((morphT - distFromCentre * 0.5) / 0.5)
|
|
85
|
+
* blendedY = fromY + (toY - fromY) * weight
|
|
86
|
+
*/
|
|
87
|
+
export function blendPtsY(
|
|
88
|
+
from: number[],
|
|
89
|
+
to: number[],
|
|
90
|
+
morphT: number,
|
|
91
|
+
padding: ChartPadding,
|
|
92
|
+
canvasWidth: number,
|
|
93
|
+
): number[] {
|
|
94
|
+
"worklet";
|
|
95
|
+
const n = from.length;
|
|
96
|
+
if (n === 0) return to;
|
|
97
|
+
const chartCentreX = (padding.left + canvasWidth - padding.right) / 2;
|
|
98
|
+
const halfChartW = (canvasWidth - padding.left - padding.right) / 2;
|
|
99
|
+
const out: number[] = new Array(n);
|
|
100
|
+
for (let i = 0; i < n; i += 2) {
|
|
101
|
+
out[i] = to[i]; // X always from real pts
|
|
102
|
+
const distFromCentre =
|
|
103
|
+
halfChartW > 0 ? Math.abs(from[i] - chartCentreX) / halfChartW : 0;
|
|
104
|
+
const weight = smoothstep((morphT - distFromCentre * 0.5) / 0.5);
|
|
105
|
+
out[i + 1] = from[i + 1] + (to[i + 1] - from[i + 1]) * weight;
|
|
106
|
+
}
|
|
107
|
+
return out;
|
|
108
|
+
}
|
package/src/theme.ts
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import type { LiveChartPalette, SeriesConfig, ThemeMode } from "./types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Derives a full light/dark palette from one accent color — same idea as liveline’s
|
|
5
|
+
* theme system, implemented with static Tailwind-like hex literals (no CSS).
|
|
6
|
+
*
|
|
7
|
+
* @see https://github.com/benjitaylor/liveline
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Tailwind CSS v3 default palette — literal hex only (no tailwindcss dependency).
|
|
12
|
+
* Values match https://tailwindcss.com/docs/customizing-colors
|
|
13
|
+
*/
|
|
14
|
+
const TW = {
|
|
15
|
+
white: "#ffffff",
|
|
16
|
+
black: "#000000",
|
|
17
|
+
/** Neutral base scale (backgrounds + text). Tailwind zinc 50–950. */
|
|
18
|
+
zinc: {
|
|
19
|
+
/** zinc-50 */
|
|
20
|
+
50: "#fafafa",
|
|
21
|
+
/** zinc-100 */
|
|
22
|
+
100: "#f4f4f5",
|
|
23
|
+
/** zinc-200 */
|
|
24
|
+
200: "#e4e4e7",
|
|
25
|
+
/** zinc-300 */
|
|
26
|
+
300: "#d4d4d8",
|
|
27
|
+
/** zinc-400 */
|
|
28
|
+
400: "#a1a1aa",
|
|
29
|
+
/** zinc-500 */
|
|
30
|
+
500: "#71717a",
|
|
31
|
+
/** zinc-600 */
|
|
32
|
+
600: "#52525b",
|
|
33
|
+
/** zinc-700 */
|
|
34
|
+
700: "#3f3f46",
|
|
35
|
+
/** zinc-800 */
|
|
36
|
+
800: "#27272a",
|
|
37
|
+
/** zinc-900 */
|
|
38
|
+
900: "#18181b",
|
|
39
|
+
/** zinc-950 */
|
|
40
|
+
950: "#09090b",
|
|
41
|
+
},
|
|
42
|
+
green: {
|
|
43
|
+
/** green-500 */
|
|
44
|
+
500: "#22c55e",
|
|
45
|
+
/** green-600 */
|
|
46
|
+
600: "#16a34a",
|
|
47
|
+
},
|
|
48
|
+
red: {
|
|
49
|
+
/** red-500 */
|
|
50
|
+
500: "#ef4444",
|
|
51
|
+
/** red-600 */
|
|
52
|
+
600: "#dc2626",
|
|
53
|
+
},
|
|
54
|
+
blue: {
|
|
55
|
+
/** blue-500 */
|
|
56
|
+
500: "#3b82f6",
|
|
57
|
+
},
|
|
58
|
+
amber: {
|
|
59
|
+
/** amber-500 */
|
|
60
|
+
500: "#f59e0b",
|
|
61
|
+
},
|
|
62
|
+
violet: {
|
|
63
|
+
/** violet-500 */
|
|
64
|
+
500: "#8b5cf6",
|
|
65
|
+
},
|
|
66
|
+
pink: {
|
|
67
|
+
/** pink-500 */
|
|
68
|
+
500: "#ec4899",
|
|
69
|
+
},
|
|
70
|
+
cyan: {
|
|
71
|
+
/** cyan-500 */
|
|
72
|
+
500: "#06b6d4",
|
|
73
|
+
},
|
|
74
|
+
orange: {
|
|
75
|
+
/** orange-500 */
|
|
76
|
+
500: "#f97316",
|
|
77
|
+
},
|
|
78
|
+
} as const;
|
|
79
|
+
|
|
80
|
+
/** Parse a CSS color string to [r, g, b]. Supports `#rgb`, `#rrggbb`, `rgb()`, and `rgba()`. */
|
|
81
|
+
export function parseColorRgb(color: string): [number, number, number] {
|
|
82
|
+
const hex = color.match(/^#([0-9a-f]{3}|[0-9a-f]{6})$/i);
|
|
83
|
+
if (hex) {
|
|
84
|
+
let h = hex[1];
|
|
85
|
+
if (h.length === 3) h = h[0] + h[0] + h[1] + h[1] + h[2] + h[2];
|
|
86
|
+
return [
|
|
87
|
+
parseInt(h.slice(0, 2), 16),
|
|
88
|
+
parseInt(h.slice(2, 4), 16),
|
|
89
|
+
parseInt(h.slice(4, 6), 16),
|
|
90
|
+
];
|
|
91
|
+
}
|
|
92
|
+
const rgb = color.match(/rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/);
|
|
93
|
+
if (rgb) return [+rgb[1], +rgb[2], +rgb[3]];
|
|
94
|
+
return [128, 128, 128];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function rgba(r: number, g: number, b: number, a: number): string {
|
|
98
|
+
return `rgba(${r}, ${g}, ${b}, ${a})`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Default left-edge fade gradient stops for `dstOut` blending: same RGB as the chart
|
|
103
|
+
* background (`palette.bgRgb`), opacity 1 → 0. Matches the container `backgroundColor`.
|
|
104
|
+
*/
|
|
105
|
+
export function leftEdgeFadeColorsFromBgRgb(bgRgb: [number, number, number]): {
|
|
106
|
+
startColor: string;
|
|
107
|
+
endColor: string;
|
|
108
|
+
} {
|
|
109
|
+
const [r, g, b] = bgRgb;
|
|
110
|
+
return {
|
|
111
|
+
startColor: rgba(r, g, b, 1),
|
|
112
|
+
endColor: rgba(r, g, b, 0),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function twAlpha(hex: string, a: number): string {
|
|
117
|
+
const [r, g, b] = parseColorRgb(hex);
|
|
118
|
+
return rgba(r, g, b, a);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Derive a full palette from a single accent color + theme mode.
|
|
123
|
+
* Momentum colors are always semantic green/red regardless of accent.
|
|
124
|
+
*/
|
|
125
|
+
export function resolveTheme(color: string, mode: ThemeMode): LiveChartPalette {
|
|
126
|
+
const [r, g, b] = parseColorRgb(color);
|
|
127
|
+
const isDark = mode === "dark";
|
|
128
|
+
const [g5r, g5g, g5b] = parseColorRgb(TW.green[500]);
|
|
129
|
+
const [g6r, g6g, g6b] = parseColorRgb(TW.green[600]);
|
|
130
|
+
const [r5r, r5g, r5b] = parseColorRgb(TW.red[500]);
|
|
131
|
+
const [r6r, r6g, r6b] = parseColorRgb(TW.red[600]);
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
line: color,
|
|
135
|
+
lineWidth: 2,
|
|
136
|
+
|
|
137
|
+
fillTop: rgba(r, g, b, isDark ? 0.12 : 0.08),
|
|
138
|
+
fillBottom: rgba(r, g, b, 0),
|
|
139
|
+
|
|
140
|
+
// white/6, black/6 (Tailwind opacity on base)
|
|
141
|
+
gridLine: isDark ? twAlpha(TW.white, 0.06) : twAlpha(TW.black, 0.06),
|
|
142
|
+
gridLabel: isDark ? twAlpha(TW.white, 0.4) : twAlpha(TW.black, 0.35),
|
|
143
|
+
|
|
144
|
+
dotUp: TW.green[500],
|
|
145
|
+
dotDown: TW.red[500],
|
|
146
|
+
dotFlat: color,
|
|
147
|
+
glowUp: twAlpha(TW.green[500], 0.18),
|
|
148
|
+
glowDown: twAlpha(TW.red[500], 0.18),
|
|
149
|
+
glowFlat: rgba(r, g, b, 0.12),
|
|
150
|
+
|
|
151
|
+
badgeOuterBg: isDark
|
|
152
|
+
? twAlpha(TW.zinc[900], 0.95)
|
|
153
|
+
: twAlpha(TW.white, 0.95),
|
|
154
|
+
badgeOuterShadow: isDark ? twAlpha(TW.black, 0.4) : twAlpha(TW.black, 0.15),
|
|
155
|
+
badgeBg: color,
|
|
156
|
+
badgeText: TW.white,
|
|
157
|
+
|
|
158
|
+
candleUp: TW.green[500],
|
|
159
|
+
candleDown: TW.red[500],
|
|
160
|
+
wickUp: isDark ? rgba(g5r, g5g, g5b, 0.7) : rgba(g6r, g6g, g6b, 0.8),
|
|
161
|
+
wickDown: isDark ? rgba(r5r, r5g, r5b, 0.7) : rgba(r6r, r6g, r6b, 0.8),
|
|
162
|
+
|
|
163
|
+
dashLine: rgba(r, g, b, 0.4),
|
|
164
|
+
|
|
165
|
+
refLine: isDark ? twAlpha(TW.white, 0.15) : twAlpha(TW.black, 0.12),
|
|
166
|
+
refLabel: isDark ? twAlpha(TW.white, 0.45) : twAlpha(TW.black, 0.4),
|
|
167
|
+
|
|
168
|
+
timeLabel: isDark ? twAlpha(TW.white, 0.35) : twAlpha(TW.black, 0.3),
|
|
169
|
+
|
|
170
|
+
crosshairLine: isDark ? twAlpha(TW.white, 0.2) : twAlpha(TW.black, 0.12),
|
|
171
|
+
// Scrim over the region ahead of the crosshair: darken on dark, brighten on
|
|
172
|
+
// light, so it always reads as a recessed wash toward the background.
|
|
173
|
+
crosshairDim: isDark ? twAlpha(TW.black, 0.12) : twAlpha(TW.white, 0.5),
|
|
174
|
+
tooltipBg: isDark ? twAlpha(TW.zinc[950], 0.95) : twAlpha(TW.white, 0.95),
|
|
175
|
+
tooltipText: isDark ? TW.zinc[200] : TW.zinc[900],
|
|
176
|
+
tooltipBorder: isDark ? twAlpha(TW.white, 0.1) : twAlpha(TW.black, 0.08),
|
|
177
|
+
|
|
178
|
+
bgRgb: isDark
|
|
179
|
+
? (parseColorRgb(TW.zinc[950]) as [number, number, number])
|
|
180
|
+
: (parseColorRgb(TW.zinc[50]) as [number, number, number]),
|
|
181
|
+
|
|
182
|
+
labelFontSize: 11,
|
|
183
|
+
valueFontSize: 11,
|
|
184
|
+
badgeFontSize: 11,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Merge caller palette overrides on top of a derived palette. Only the keys
|
|
190
|
+
* present on `override` are replaced; everything else keeps the derived value.
|
|
191
|
+
* Returns the original palette unchanged when no override is supplied.
|
|
192
|
+
*/
|
|
193
|
+
export function applyPaletteOverride(
|
|
194
|
+
palette: LiveChartPalette,
|
|
195
|
+
override: Partial<LiveChartPalette> | undefined,
|
|
196
|
+
): LiveChartPalette {
|
|
197
|
+
if (!override) return palette;
|
|
198
|
+
return { ...palette, ...override };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Default multi-series line colors — Tailwind 500 scale, order: blue, red, green,
|
|
203
|
+
* amber, violet, pink, cyan, orange.
|
|
204
|
+
*/
|
|
205
|
+
export const SERIES_COLORS = [
|
|
206
|
+
TW.blue[500],
|
|
207
|
+
TW.red[500],
|
|
208
|
+
TW.green[500],
|
|
209
|
+
TW.amber[500],
|
|
210
|
+
TW.violet[500],
|
|
211
|
+
TW.pink[500],
|
|
212
|
+
TW.cyan[500],
|
|
213
|
+
TW.orange[500],
|
|
214
|
+
];
|
|
215
|
+
|
|
216
|
+
/** Derive per-series palettes from series definitions. */
|
|
217
|
+
export function resolveSeriesPalettes(
|
|
218
|
+
series: SeriesConfig[],
|
|
219
|
+
mode: ThemeMode,
|
|
220
|
+
): Map<string, LiveChartPalette> {
|
|
221
|
+
const map = new Map<string, LiveChartPalette>();
|
|
222
|
+
for (let i = 0; i < series.length; i++) {
|
|
223
|
+
const s = series[i];
|
|
224
|
+
const color =
|
|
225
|
+
s.color != null && s.color !== ""
|
|
226
|
+
? s.color
|
|
227
|
+
: SERIES_COLORS[i % SERIES_COLORS.length];
|
|
228
|
+
map.set(s.id, resolveTheme(color, mode));
|
|
229
|
+
}
|
|
230
|
+
return map;
|
|
231
|
+
}
|