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.
Files changed (243) hide show
  1. package/LICENSE +33 -0
  2. package/README.md +198 -0
  3. package/dist/components/AnimatedLabel.d.ts +21 -0
  4. package/dist/components/AnimatedLabel.d.ts.map +1 -0
  5. package/dist/components/BadgeOverlay.d.ts +16 -0
  6. package/dist/components/BadgeOverlay.d.ts.map +1 -0
  7. package/dist/components/CrosshairLine.d.ts +20 -0
  8. package/dist/components/CrosshairLine.d.ts.map +1 -0
  9. package/dist/components/CrosshairOverlay.d.ts +29 -0
  10. package/dist/components/CrosshairOverlay.d.ts.map +1 -0
  11. package/dist/components/DegenParticlesOverlay.d.ts +16 -0
  12. package/dist/components/DegenParticlesOverlay.d.ts.map +1 -0
  13. package/dist/components/DotOverlay.d.ts +17 -0
  14. package/dist/components/DotOverlay.d.ts.map +1 -0
  15. package/dist/components/LeftEdgeFade.d.ts +9 -0
  16. package/dist/components/LeftEdgeFade.d.ts.map +1 -0
  17. package/dist/components/LiveChart.d.ts +3 -0
  18. package/dist/components/LiveChart.d.ts.map +1 -0
  19. package/dist/components/LiveChartSeries.d.ts +3 -0
  20. package/dist/components/LiveChartSeries.d.ts.map +1 -0
  21. package/dist/components/LiveChartTransition.d.ts +28 -0
  22. package/dist/components/LiveChartTransition.d.ts.map +1 -0
  23. package/dist/components/LoadingOverlay.d.ts +24 -0
  24. package/dist/components/LoadingOverlay.d.ts.map +1 -0
  25. package/dist/components/MarkerOverlay.d.ts +25 -0
  26. package/dist/components/MarkerOverlay.d.ts.map +1 -0
  27. package/dist/components/MultiSeriesDots.d.ts +11 -0
  28. package/dist/components/MultiSeriesDots.d.ts.map +1 -0
  29. package/dist/components/MultiSeriesStroke.d.ts +14 -0
  30. package/dist/components/MultiSeriesStroke.d.ts.map +1 -0
  31. package/dist/components/MultiSeriesTooltipStack.d.ts +10 -0
  32. package/dist/components/MultiSeriesTooltipStack.d.ts.map +1 -0
  33. package/dist/components/MultiSeriesValueLabels.d.ts +11 -0
  34. package/dist/components/MultiSeriesValueLabels.d.ts.map +1 -0
  35. package/dist/components/MultiSeriesValueLines.d.ts +10 -0
  36. package/dist/components/MultiSeriesValueLines.d.ts.map +1 -0
  37. package/dist/components/ReferenceLineOverlay.d.ts +19 -0
  38. package/dist/components/ReferenceLineOverlay.d.ts.map +1 -0
  39. package/dist/components/SeriesToggleChips.d.ts +15 -0
  40. package/dist/components/SeriesToggleChips.d.ts.map +1 -0
  41. package/dist/components/TradeStreamOverlay.d.ts +14 -0
  42. package/dist/components/TradeStreamOverlay.d.ts.map +1 -0
  43. package/dist/components/ValueLineOverlay.d.ts +16 -0
  44. package/dist/components/ValueLineOverlay.d.ts.map +1 -0
  45. package/dist/components/ValueTextOverlay.d.ts +20 -0
  46. package/dist/components/ValueTextOverlay.d.ts.map +1 -0
  47. package/dist/components/XAxisOverlay.d.ts +14 -0
  48. package/dist/components/XAxisOverlay.d.ts.map +1 -0
  49. package/dist/components/YAxisOverlay.d.ts +23 -0
  50. package/dist/components/YAxisOverlay.d.ts.map +1 -0
  51. package/dist/components/seriesMetaSig.d.ts +4 -0
  52. package/dist/components/seriesMetaSig.d.ts.map +1 -0
  53. package/dist/constants.d.ts +38 -0
  54. package/dist/constants.d.ts.map +1 -0
  55. package/dist/core/liveChartEngineTick.d.ts +45 -0
  56. package/dist/core/liveChartEngineTick.d.ts.map +1 -0
  57. package/dist/core/liveChartSeriesEngineTick.d.ts +38 -0
  58. package/dist/core/liveChartSeriesEngineTick.d.ts.map +1 -0
  59. package/dist/core/multiSeriesLayout.d.ts +22 -0
  60. package/dist/core/multiSeriesLayout.d.ts.map +1 -0
  61. package/dist/core/resolveConfig.d.ts +188 -0
  62. package/dist/core/resolveConfig.d.ts.map +1 -0
  63. package/dist/core/useLiveChartEngine.d.ts +79 -0
  64. package/dist/core/useLiveChartEngine.d.ts.map +1 -0
  65. package/dist/core/useLiveChartSeriesEngine.d.ts +68 -0
  66. package/dist/core/useLiveChartSeriesEngine.d.ts.map +1 -0
  67. package/dist/draw/candle.d.ts +25 -0
  68. package/dist/draw/candle.d.ts.map +1 -0
  69. package/dist/draw/grid.d.ts +24 -0
  70. package/dist/draw/grid.d.ts.map +1 -0
  71. package/dist/draw/line.d.ts +86 -0
  72. package/dist/draw/line.d.ts.map +1 -0
  73. package/dist/draw/trade.d.ts +38 -0
  74. package/dist/draw/trade.d.ts.map +1 -0
  75. package/dist/hooks/crosshairSeries.d.ts +13 -0
  76. package/dist/hooks/crosshairSeries.d.ts.map +1 -0
  77. package/dist/hooks/crosshairShared.d.ts +72 -0
  78. package/dist/hooks/crosshairShared.d.ts.map +1 -0
  79. package/dist/hooks/index.d.ts +24 -0
  80. package/dist/hooks/index.d.ts.map +1 -0
  81. package/dist/hooks/resolveChartLayout.d.ts +42 -0
  82. package/dist/hooks/resolveChartLayout.d.ts.map +1 -0
  83. package/dist/hooks/useBadge.d.ts +14 -0
  84. package/dist/hooks/useBadge.d.ts.map +1 -0
  85. package/dist/hooks/useCandlePaths.d.ts +19 -0
  86. package/dist/hooks/useCandlePaths.d.ts.map +1 -0
  87. package/dist/hooks/useCanvasLayout.d.ts +12 -0
  88. package/dist/hooks/useCanvasLayout.d.ts.map +1 -0
  89. package/dist/hooks/useChartColors.d.ts +20 -0
  90. package/dist/hooks/useChartColors.d.ts.map +1 -0
  91. package/dist/hooks/useChartPaths.d.ts +19 -0
  92. package/dist/hooks/useChartPaths.d.ts.map +1 -0
  93. package/dist/hooks/useChartReveal.d.ts +30 -0
  94. package/dist/hooks/useChartReveal.d.ts.map +1 -0
  95. package/dist/hooks/useChartSkiaFont.d.ts +8 -0
  96. package/dist/hooks/useChartSkiaFont.d.ts.map +1 -0
  97. package/dist/hooks/useCrosshair.d.ts +20 -0
  98. package/dist/hooks/useCrosshair.d.ts.map +1 -0
  99. package/dist/hooks/useCrosshairSeries.d.ts +10 -0
  100. package/dist/hooks/useCrosshairSeries.d.ts.map +1 -0
  101. package/dist/hooks/useDegen.d.ts +17 -0
  102. package/dist/hooks/useDegen.d.ts.map +1 -0
  103. package/dist/hooks/useLiveChartHasData.d.ts +17 -0
  104. package/dist/hooks/useLiveChartHasData.d.ts.map +1 -0
  105. package/dist/hooks/useLiveDot.d.ts +12 -0
  106. package/dist/hooks/useLiveDot.d.ts.map +1 -0
  107. package/dist/hooks/useMarkers.d.ts +15 -0
  108. package/dist/hooks/useMarkers.d.ts.map +1 -0
  109. package/dist/hooks/useModeBlend.d.ts +18 -0
  110. package/dist/hooks/useModeBlend.d.ts.map +1 -0
  111. package/dist/hooks/useMomentum.d.ts +16 -0
  112. package/dist/hooks/useMomentum.d.ts.map +1 -0
  113. package/dist/hooks/useMultiSeriesDegen.d.ts +25 -0
  114. package/dist/hooks/useMultiSeriesDegen.d.ts.map +1 -0
  115. package/dist/hooks/useMultiSeriesLinePaths.d.ts +15 -0
  116. package/dist/hooks/useMultiSeriesLinePaths.d.ts.map +1 -0
  117. package/dist/hooks/useReferenceLine.d.ts +32 -0
  118. package/dist/hooks/useReferenceLine.d.ts.map +1 -0
  119. package/dist/hooks/useReverseMorphEngineInputs.d.ts +24 -0
  120. package/dist/hooks/useReverseMorphEngineInputs.d.ts.map +1 -0
  121. package/dist/hooks/useTradeStream.d.ts +12 -0
  122. package/dist/hooks/useTradeStream.d.ts.map +1 -0
  123. package/dist/hooks/useXAxis.d.ts +32 -0
  124. package/dist/hooks/useXAxis.d.ts.map +1 -0
  125. package/dist/hooks/useYAxis.d.ts +13 -0
  126. package/dist/hooks/useYAxis.d.ts.map +1 -0
  127. package/dist/index.d.ts +18 -0
  128. package/dist/index.d.ts.map +1 -0
  129. package/dist/lib/format.d.ts +5 -0
  130. package/dist/lib/format.d.ts.map +1 -0
  131. package/dist/lib/measureFontTextWidth.d.ts +7 -0
  132. package/dist/lib/measureFontTextWidth.d.ts.map +1 -0
  133. package/dist/lib/monoFontFamily.d.ts +6 -0
  134. package/dist/lib/monoFontFamily.d.ts.map +1 -0
  135. package/dist/math/color.d.ts +5 -0
  136. package/dist/math/color.d.ts.map +1 -0
  137. package/dist/math/degenTick.d.ts +57 -0
  138. package/dist/math/degenTick.d.ts.map +1 -0
  139. package/dist/math/interpolate.d.ts +7 -0
  140. package/dist/math/interpolate.d.ts.map +1 -0
  141. package/dist/math/intervals.d.ts +3 -0
  142. package/dist/math/intervals.d.ts.map +1 -0
  143. package/dist/math/lerp.d.ts +7 -0
  144. package/dist/math/lerp.d.ts.map +1 -0
  145. package/dist/math/markers.d.ts +44 -0
  146. package/dist/math/markers.d.ts.map +1 -0
  147. package/dist/math/momentum.d.ts +8 -0
  148. package/dist/math/momentum.d.ts.map +1 -0
  149. package/dist/math/pickCandle.d.ts +8 -0
  150. package/dist/math/pickCandle.d.ts.map +1 -0
  151. package/dist/math/range.d.ts +10 -0
  152. package/dist/math/range.d.ts.map +1 -0
  153. package/dist/math/referenceLines.d.ts +20 -0
  154. package/dist/math/referenceLines.d.ts.map +1 -0
  155. package/dist/math/spline.d.ts +45 -0
  156. package/dist/math/spline.d.ts.map +1 -0
  157. package/dist/math/squiggly.d.ts +36 -0
  158. package/dist/math/squiggly.d.ts.map +1 -0
  159. package/dist/theme.d.ts +30 -0
  160. package/dist/theme.d.ts.map +1 -0
  161. package/dist/types.d.ts +720 -0
  162. package/dist/types.d.ts.map +1 -0
  163. package/package.json +72 -0
  164. package/src/components/AnimatedLabel.tsx +41 -0
  165. package/src/components/BadgeOverlay.tsx +39 -0
  166. package/src/components/CrosshairLine.tsx +90 -0
  167. package/src/components/CrosshairOverlay.tsx +170 -0
  168. package/src/components/DegenParticlesOverlay.tsx +141 -0
  169. package/src/components/DotOverlay.tsx +69 -0
  170. package/src/components/LeftEdgeFade.tsx +36 -0
  171. package/src/components/LiveChart.tsx +821 -0
  172. package/src/components/LiveChartSeries.tsx +607 -0
  173. package/src/components/LiveChartTransition.tsx +135 -0
  174. package/src/components/LoadingOverlay.tsx +301 -0
  175. package/src/components/MarkerOverlay.tsx +331 -0
  176. package/src/components/MultiSeriesDots.tsx +117 -0
  177. package/src/components/MultiSeriesStroke.tsx +79 -0
  178. package/src/components/MultiSeriesTooltipStack.tsx +78 -0
  179. package/src/components/MultiSeriesValueLabels.tsx +103 -0
  180. package/src/components/MultiSeriesValueLines.tsx +115 -0
  181. package/src/components/ReferenceLineOverlay.tsx +317 -0
  182. package/src/components/SeriesToggleChips.tsx +201 -0
  183. package/src/components/TradeStreamOverlay.tsx +117 -0
  184. package/src/components/ValueLineOverlay.tsx +63 -0
  185. package/src/components/ValueTextOverlay.tsx +43 -0
  186. package/src/components/XAxisOverlay.tsx +109 -0
  187. package/src/components/YAxisOverlay.tsx +135 -0
  188. package/src/components/seriesMetaSig.ts +13 -0
  189. package/src/constants.ts +44 -0
  190. package/src/core/liveChartEngineTick.ts +185 -0
  191. package/src/core/liveChartSeriesEngineTick.ts +163 -0
  192. package/src/core/multiSeriesLayout.ts +80 -0
  193. package/src/core/resolveConfig.ts +538 -0
  194. package/src/core/useLiveChartEngine.ts +207 -0
  195. package/src/core/useLiveChartSeriesEngine.ts +215 -0
  196. package/src/draw/candle.ts +210 -0
  197. package/src/draw/grid.ts +174 -0
  198. package/src/draw/line.ts +257 -0
  199. package/src/draw/trade.ts +202 -0
  200. package/src/hooks/crosshairSeries.ts +78 -0
  201. package/src/hooks/crosshairShared.ts +296 -0
  202. package/src/hooks/index.ts +26 -0
  203. package/src/hooks/resolveChartLayout.ts +144 -0
  204. package/src/hooks/useBadge.ts +192 -0
  205. package/src/hooks/useCandlePaths.ts +165 -0
  206. package/src/hooks/useCanvasLayout.ts +22 -0
  207. package/src/hooks/useChartColors.ts +48 -0
  208. package/src/hooks/useChartPaths.ts +121 -0
  209. package/src/hooks/useChartReveal.ts +123 -0
  210. package/src/hooks/useChartSkiaFont.ts +34 -0
  211. package/src/hooks/useCrosshair.ts +250 -0
  212. package/src/hooks/useCrosshairSeries.ts +163 -0
  213. package/src/hooks/useDegen.ts +262 -0
  214. package/src/hooks/useLiveChartHasData.ts +31 -0
  215. package/src/hooks/useLiveDot.ts +31 -0
  216. package/src/hooks/useMarkers.ts +95 -0
  217. package/src/hooks/useModeBlend.ts +52 -0
  218. package/src/hooks/useMomentum.ts +37 -0
  219. package/src/hooks/useMultiSeriesDegen.ts +260 -0
  220. package/src/hooks/useMultiSeriesLinePaths.ts +76 -0
  221. package/src/hooks/useReferenceLine.ts +212 -0
  222. package/src/hooks/useReverseMorphEngineInputs.ts +154 -0
  223. package/src/hooks/useTradeStream.ts +64 -0
  224. package/src/hooks/useXAxis.ts +200 -0
  225. package/src/hooks/useYAxis.ts +48 -0
  226. package/src/index.ts +69 -0
  227. package/src/lib/format.ts +36 -0
  228. package/src/lib/measureFontTextWidth.ts +10 -0
  229. package/src/lib/monoFontFamily.ts +8 -0
  230. package/src/math/color.ts +31 -0
  231. package/src/math/degenTick.ts +151 -0
  232. package/src/math/interpolate.ts +32 -0
  233. package/src/math/intervals.ts +17 -0
  234. package/src/math/lerp.ts +17 -0
  235. package/src/math/markers.ts +175 -0
  236. package/src/math/momentum.ts +38 -0
  237. package/src/math/pickCandle.ts +42 -0
  238. package/src/math/range.ts +54 -0
  239. package/src/math/referenceLines.ts +56 -0
  240. package/src/math/spline.ts +183 -0
  241. package/src/math/squiggly.ts +108 -0
  242. package/src/theme.ts +231 -0
  243. 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
+ }