react-native-livechart 4.18.0 → 4.19.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 (59) hide show
  1. package/dist/components/CrosshairOverlay.d.ts +3 -1
  2. package/dist/components/CrosshairOverlay.d.ts.map +1 -1
  3. package/dist/components/DotOverlay.d.ts +4 -2
  4. package/dist/components/DotOverlay.d.ts.map +1 -1
  5. package/dist/components/LiveChart.d.ts.map +1 -1
  6. package/dist/components/LiveChartSeries.d.ts.map +1 -1
  7. package/dist/components/MultiSeriesDots.d.ts +5 -3
  8. package/dist/components/MultiSeriesDots.d.ts.map +1 -1
  9. package/dist/constants.d.ts +6 -0
  10. package/dist/constants.d.ts.map +1 -1
  11. package/dist/core/resolveConfig.d.ts +17 -3
  12. package/dist/core/resolveConfig.d.ts.map +1 -1
  13. package/dist/core/useLiveChartEngine.d.ts.map +1 -1
  14. package/dist/draw/candle.d.ts +21 -0
  15. package/dist/draw/candle.d.ts.map +1 -1
  16. package/dist/draw/line.d.ts +7 -1
  17. package/dist/draw/line.d.ts.map +1 -1
  18. package/dist/hooks/resolveChartLayout.d.ts +8 -0
  19. package/dist/hooks/resolveChartLayout.d.ts.map +1 -1
  20. package/dist/hooks/useCandlePaths.d.ts +3 -1
  21. package/dist/hooks/useCandlePaths.d.ts.map +1 -1
  22. package/dist/hooks/useChartPaths.d.ts +3 -1
  23. package/dist/hooks/useChartPaths.d.ts.map +1 -1
  24. package/dist/hooks/useDegen.d.ts +1 -3
  25. package/dist/hooks/useDegen.d.ts.map +1 -1
  26. package/dist/hooks/useMarkers.d.ts.map +1 -1
  27. package/dist/hooks/useMultiSeriesLinePaths.d.ts +3 -1
  28. package/dist/hooks/useMultiSeriesLinePaths.d.ts.map +1 -1
  29. package/dist/hooks/useSeriesIndicatorOpacity.d.ts +13 -0
  30. package/dist/hooks/useSeriesIndicatorOpacity.d.ts.map +1 -0
  31. package/dist/hooks/useTradeStream.d.ts.map +1 -1
  32. package/dist/index.d.ts +1 -1
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/math/simplify.d.ts +20 -0
  35. package/dist/math/simplify.d.ts.map +1 -0
  36. package/dist/types.d.ts +77 -16
  37. package/dist/types.d.ts.map +1 -1
  38. package/package.json +1 -1
  39. package/src/components/CrosshairOverlay.tsx +14 -9
  40. package/src/components/DotOverlay.tsx +17 -1
  41. package/src/components/LiveChart.tsx +194 -54
  42. package/src/components/LiveChartSeries.tsx +28 -10
  43. package/src/components/MultiSeriesDots.tsx +22 -3
  44. package/src/constants.ts +7 -0
  45. package/src/core/resolveConfig.ts +61 -13
  46. package/src/core/useLiveChartEngine.ts +6 -1
  47. package/src/draw/candle.ts +78 -0
  48. package/src/draw/line.ts +20 -7
  49. package/src/hooks/resolveChartLayout.ts +30 -18
  50. package/src/hooks/useCandlePaths.ts +9 -1
  51. package/src/hooks/useChartPaths.ts +37 -7
  52. package/src/hooks/useDegen.ts +2 -6
  53. package/src/hooks/useMarkers.ts +6 -2
  54. package/src/hooks/useMultiSeriesLinePaths.ts +31 -4
  55. package/src/hooks/useSeriesIndicatorOpacity.ts +56 -0
  56. package/src/hooks/useTradeStream.ts +6 -1
  57. package/src/index.ts +1 -0
  58. package/src/math/simplify.ts +197 -0
  59. package/src/types.ts +78 -16
@@ -4,6 +4,10 @@ import { useDerivedValue, type SharedValue } from "react-native-reanimated";
4
4
  import { MAX_MULTI_SERIES } from "../constants";
5
5
  import type { MultiEngineState } from "../core/useLiveChartEngine";
6
6
  import { buildLinePoints, type ChartPadding } from "../draw/line";
7
+ import {
8
+ makeLineSimplifyScratch,
9
+ simplifyLinePoints,
10
+ } from "../math/simplify";
7
11
  import { drawSpline, makeSplineScratch } from "../math/spline";
8
12
  import { usePathBuilders } from "./usePathBuilder";
9
13
 
@@ -21,25 +25,31 @@ export function useMultiSeriesLinePaths(
21
25
  engine: MultiEngineState,
22
26
  padding: ChartPadding,
23
27
  activeSeriesCount = MAX_MULTI_SERIES,
28
+ /** Shared screen-space simplification tolerance; a series can override it. */
29
+ simplifyTolerance = 0,
24
30
  ): SharedValue<SkPath[]> {
25
31
  const builders = usePathBuilders(MAX_MULTI_SERIES);
26
32
 
27
33
  const poolRef = useRef<{
28
34
  empty: SkPath;
29
35
  ptsBuf: number[];
36
+ simplifiedPts: number[];
30
37
  pathsA: SkPath[];
31
38
  pathsB: SkPath[];
32
39
  pathsTick: boolean;
33
40
  scratch: ReturnType<typeof makeSplineScratch>;
41
+ simplifyScratch: ReturnType<typeof makeLineSimplifyScratch>;
34
42
  } | null>(null);
35
43
  if (poolRef.current === null) {
36
44
  poolRef.current = {
37
45
  empty: Skia.Path.Make(),
38
46
  ptsBuf: [] as number[],
47
+ simplifiedPts: [] as number[],
39
48
  pathsA: [] as SkPath[],
40
49
  pathsB: [] as SkPath[],
41
50
  pathsTick: false,
42
51
  scratch: makeSplineScratch(),
52
+ simplifyScratch: makeLineSimplifyScratch(),
43
53
  };
44
54
  }
45
55
 
@@ -52,19 +62,36 @@ export function useMultiSeriesLinePaths(
52
62
  const out = pool.pathsTick ? pool.pathsA : pool.pathsB;
53
63
  out.length = 0;
54
64
  const count = Math.min(activeSeriesCount, s.length, MAX_MULTI_SERIES);
65
+ const now = engine.timestamp.get();
66
+ const windowSecs = engine.displayWindow.get();
67
+ const canvasWidth = engine.canvasWidth.get();
68
+ const chartWidth = canvasWidth - padding.left - padding.right;
69
+ const absoluteXOffset =
70
+ windowSecs > 0 ? (now - windowSecs) * (chartWidth / windowSecs) : 0;
55
71
  for (let i = 0; i < count; i++) {
56
- const pts = buildLinePoints(
72
+ const rawPts = buildLinePoints(
57
73
  s[i].data,
58
74
  displays[i] ?? s[i].value,
59
- engine.timestamp.get(),
60
- engine.displayWindow.get(),
75
+ now,
76
+ windowSecs,
61
77
  engine.displayMin.get(),
62
78
  engine.displayMax.get(),
63
- engine.canvasWidth.get(),
79
+ canvasWidth,
64
80
  engine.canvasHeight.get(),
65
81
  padding,
66
82
  pool.ptsBuf,
67
83
  );
84
+ const seriesTolerance = s[i].simplify ?? simplifyTolerance;
85
+ const pts =
86
+ Number.isFinite(seriesTolerance) && seriesTolerance > 0
87
+ ? simplifyLinePoints(
88
+ rawPts,
89
+ seriesTolerance,
90
+ pool.simplifiedPts,
91
+ pool.simplifyScratch,
92
+ absoluteXOffset,
93
+ )
94
+ : rawPts;
68
95
  const n = pts.length >> 1;
69
96
  if (n >= 2) {
70
97
  const b = slots[i];
@@ -0,0 +1,56 @@
1
+ import {
2
+ Easing,
3
+ useAnimatedReaction,
4
+ useSharedValue,
5
+ withTiming,
6
+ type SharedValue,
7
+ } from "react-native-reanimated";
8
+
9
+ /** Short independent fade used when replacement data dims the plotted series. */
10
+ export const SERIES_INDICATOR_FADE_MS = 180;
11
+
12
+ /** Treat tiny animation residue at the top end as fully restored. */
13
+ const SERIES_OPACITY_VISIBLE_EPSILON = 0.001;
14
+
15
+ /** Worklet-safe target for live dots and their pulse rings. */
16
+ export function seriesIndicatorOpacityTarget(seriesOpacity: number): 0 | 1 {
17
+ "worklet";
18
+ return seriesOpacity >= 1 - SERIES_OPACITY_VISIBLE_EPSILON ? 1 : 0;
19
+ }
20
+
21
+ /**
22
+ * Fades live dots and pulse rings away while `seriesOpacity` dims replacement
23
+ * data, then restores them once the plotted series is fully opaque again.
24
+ * Keeping this as a separate opacity avoids compositing a translucent dot over
25
+ * its own line, where the stroke would otherwise shine through.
26
+ */
27
+ export function useSeriesIndicatorOpacity(
28
+ seriesOpacity: SharedValue<number>,
29
+ duration: number = SERIES_INDICATOR_FADE_MS,
30
+ ): SharedValue<number> {
31
+ // Fully visible is the common mount state. The reaction snaps an initially
32
+ // dimmed series to 0 on its first UI-thread run so it never flashes a dot.
33
+ const opacity = useSharedValue(1);
34
+
35
+ useAnimatedReaction(
36
+ () => seriesIndicatorOpacityTarget(seriesOpacity.get()),
37
+ (target, previous) => {
38
+ "worklet";
39
+ if (previous === null || previous === undefined || duration === 0) {
40
+ opacity.set(target);
41
+ return;
42
+ }
43
+ if (target !== previous) {
44
+ opacity.set(
45
+ withTiming(target, {
46
+ duration,
47
+ easing: Easing.inOut(Easing.ease),
48
+ }),
49
+ );
50
+ }
51
+ },
52
+ [seriesOpacity, duration],
53
+ );
54
+
55
+ return opacity;
56
+ }
@@ -1,3 +1,4 @@
1
+ import { useEffect } from "react";
1
2
  import {
2
3
  useFrameCallback,
3
4
  useSharedValue,
@@ -31,7 +32,7 @@ export function useTradeStream(
31
32
  const markers = useSharedValue<TradeMarker[]>([]);
32
33
  const state = useSharedValue<TradeStreamState>(createTradeStreamState());
33
34
 
34
- useFrameCallback(
35
+ const tradeFrameCallback = useFrameCallback(
35
36
  /* istanbul ignore next -- worklet runs on UI thread, not in Jest */ (
36
37
  frameInfo,
37
38
  ) => {
@@ -62,6 +63,10 @@ export function useTradeStream(
62
63
  },
63
64
  autostart,
64
65
  );
66
+ useEffect(() => {
67
+ // Reanimated treats `autostart` as mount-time state; synchronize changes.
68
+ tradeFrameCallback.setActive(autostart);
69
+ }, [autostart, tradeFrameCallback]);
65
70
 
66
71
  return markers;
67
72
  }
package/src/index.ts CHANGED
@@ -49,6 +49,7 @@ export type {
49
49
  ChartSegment,
50
50
  DegenOptions,
51
51
  DegenShakePayload,
52
+ DotGlowConfig,
52
53
  EmptyStateMetrics,
53
54
  FontConfig,
54
55
  FontWeight,
@@ -0,0 +1,197 @@
1
+ /** Reusable buffers for {@link simplifyLinePoints}. */
2
+ export interface LineSimplifyScratch {
3
+ keep: number[];
4
+ stack: number[];
5
+ }
6
+
7
+ /** Allocate simplification scratch once, outside the per-frame worklet. */
8
+ export function makeLineSimplifyScratch(): LineSimplifyScratch {
9
+ return { keep: [], stack: [] };
10
+ }
11
+
12
+ /**
13
+ * Maximum number of points simplified as one Ramer-Douglas-Peucker range.
14
+ *
15
+ * The visible line is already bounded to roughly two points per horizontal
16
+ * pixel. Splitting that bounded input into overlapping ranges also bounds the
17
+ * simplifier's worst-case work: a pathological zig-zag cannot turn a frame into
18
+ * an O(n²) scan. Range endpoints are retained, so chunking can only keep a few
19
+ * extra points; it cannot erase additional structure.
20
+ */
21
+ const MAX_RDP_RANGE_POINTS = 64;
22
+
23
+ /**
24
+ * Width of each RDP range on the stable, absolute screen-pixel grid.
25
+ *
26
+ * Dense line input contains at most two representatives per pixel bucket. A
27
+ * 30 px range therefore stays within the 64-point work bound even when the
28
+ * decimator and simplifier grids have different phases. Fixed-width ranges
29
+ * also keep their membership stable as the viewport advances.
30
+ */
31
+ const RDP_RANGE_WIDTH_PX = 30;
32
+
33
+ function pointSegmentDistanceSq(
34
+ px: number,
35
+ py: number,
36
+ ax: number,
37
+ ay: number,
38
+ bx: number,
39
+ by: number,
40
+ ): number {
41
+ "worklet";
42
+ const dx = bx - ax;
43
+ const dy = by - ay;
44
+ const lengthSq = dx * dx + dy * dy;
45
+ if (lengthSq === 0) {
46
+ const ex = px - ax;
47
+ const ey = py - ay;
48
+ return ex * ex + ey * ey;
49
+ }
50
+
51
+ let t = ((px - ax) * dx + (py - ay) * dy) / lengthSq;
52
+ if (t < 0) t = 0;
53
+ else if (t > 1) t = 1;
54
+ const ex = px - (ax + t * dx);
55
+ const ey = py - (ay + t * dy);
56
+ return ex * ex + ey * ey;
57
+ }
58
+
59
+ function simplifyRdpGroup(
60
+ points: number[],
61
+ keep: number[],
62
+ stack: number[],
63
+ toleranceSq: number,
64
+ rangeStart: number,
65
+ rangeEnd: number,
66
+ ): void {
67
+ "worklet";
68
+ if (rangeStart === rangeEnd) {
69
+ keep[rangeStart] = 1;
70
+ return;
71
+ }
72
+
73
+ // Oversized fixed-X groups are split into overlapping subranges. The group
74
+ // contents are themselves stable, so this point-count guard cannot move
75
+ // until that absolute group reaches a viewport edge.
76
+ for (let chunkStart = rangeStart; chunkStart < rangeEnd; ) {
77
+ const chunkEnd = Math.min(
78
+ chunkStart + MAX_RDP_RANGE_POINTS - 1,
79
+ rangeEnd,
80
+ );
81
+ keep[chunkStart] = 1;
82
+ keep[chunkEnd] = 1;
83
+ stack.push(chunkStart, chunkEnd);
84
+
85
+ while (stack.length >= 2) {
86
+ const end = stack.pop()!;
87
+ const start = stack.pop()!;
88
+ if (end <= start + 1) continue;
89
+
90
+ const ax = points[start * 2];
91
+ const ay = points[start * 2 + 1];
92
+ const bx = points[end * 2];
93
+ const by = points[end * 2 + 1];
94
+ let furthest = -1;
95
+ let maxDistanceSq = toleranceSq;
96
+
97
+ for (let i = start + 1; i < end; i++) {
98
+ const distanceSq = pointSegmentDistanceSq(
99
+ points[i * 2],
100
+ points[i * 2 + 1],
101
+ ax,
102
+ ay,
103
+ bx,
104
+ by,
105
+ );
106
+ if (distanceSq > maxDistanceSq) {
107
+ maxDistanceSq = distanceSq;
108
+ furthest = i;
109
+ }
110
+ }
111
+
112
+ if (furthest >= 0) {
113
+ keep[furthest] = 1;
114
+ stack.push(start, furthest, furthest, end);
115
+ }
116
+ }
117
+
118
+ chunkStart = chunkEnd;
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Simplify flat screen-space points (`[x0, y0, x1, y1, ...]`) with a bounded
124
+ * Ramer-Douglas-Peucker pass.
125
+ *
126
+ * `tolerance` is the maximum removable deviation in pixels. The first and last
127
+ * point, larger peaks/valleys, and the original point order are retained. Pass
128
+ * distinct reusable `out` and `scratch` buffers to avoid per-frame allocation.
129
+ * `absoluteXOffset` maps the viewport-relative X coordinates back onto a fixed
130
+ * screen-pixel grid; live chart callers should pass `winStart * xScale` so
131
+ * historical range membership does not change as the window advances.
132
+ */
133
+ export function simplifyLinePoints(
134
+ points: number[],
135
+ tolerance: number,
136
+ out: number[],
137
+ scratch: LineSimplifyScratch,
138
+ absoluteXOffset = 0,
139
+ ): number[] {
140
+ "worklet";
141
+ if (out === points) return points;
142
+
143
+ out.length = 0;
144
+ const count = points.length >> 1;
145
+ if (!(tolerance > 0) || !Number.isFinite(tolerance) || count <= 2) {
146
+ for (let i = 0; i < count * 2; i++) out.push(points[i]);
147
+ return out;
148
+ }
149
+
150
+ const keep = scratch.keep;
151
+ keep.length = count;
152
+ for (let i = 0; i < count; i++) keep[i] = 0;
153
+
154
+ const stack = scratch.stack;
155
+ stack.length = 0;
156
+ const toleranceSq = tolerance * tolerance;
157
+ const stableXOffset = Number.isFinite(absoluteXOffset) ? absoluteXOffset : 0;
158
+
159
+ // Partition on a fixed absolute-X grid rather than by the first visible
160
+ // point's array index. Dropping a point from the left edge then affects only
161
+ // that edge range; fully visible historical ranges keep identical endpoints
162
+ // and RDP choices while translating across the viewport.
163
+ let groupStart = 0;
164
+ let groupKey = Math.floor(
165
+ (points[0] + stableXOffset) / RDP_RANGE_WIDTH_PX,
166
+ );
167
+ for (let i = 1; i < count; i++) {
168
+ const nextKey = Math.floor(
169
+ (points[i * 2] + stableXOffset) / RDP_RANGE_WIDTH_PX,
170
+ );
171
+ if (nextKey !== groupKey) {
172
+ simplifyRdpGroup(
173
+ points,
174
+ keep,
175
+ stack,
176
+ toleranceSq,
177
+ groupStart,
178
+ i - 1,
179
+ );
180
+ groupStart = i;
181
+ groupKey = nextKey;
182
+ }
183
+ }
184
+ simplifyRdpGroup(
185
+ points,
186
+ keep,
187
+ stack,
188
+ toleranceSq,
189
+ groupStart,
190
+ count - 1,
191
+ );
192
+
193
+ for (let i = 0; i < count; i++) {
194
+ if (keep[i] === 1) out.push(points[i * 2], points[i * 2 + 1]);
195
+ }
196
+ return out;
197
+ }
package/src/types.ts CHANGED
@@ -422,6 +422,15 @@ export interface LineConfig {
422
422
  * pair with `join: "miter"` + `cap: "butt"` for true sharp corners (no rounding).
423
423
  */
424
424
  curve?: "monotone" | "linear";
425
+ /**
426
+ * Screen-space path simplification tolerance in pixels. Values above `0`
427
+ * remove intermediate points that deviate by no more than this amount while
428
+ * retaining endpoints and larger peaks/valleys. This changes only the drawn
429
+ * line/fill; axis fitting, live values, markers, and scrubbing still use the
430
+ * original data. Default `0` (off). Start around `0.75`–`1.5` for sub-pixel
431
+ * noise reduction.
432
+ */
433
+ simplify?: number;
425
434
  /** Line color override. Defaults to palette-derived accent. */
426
435
  color?: string;
427
436
  /**
@@ -854,13 +863,36 @@ export interface ScrubConfig {
854
863
  */
855
864
  seriesTooltip?: boolean | PerSeriesTooltipConfig;
856
865
  /**
857
- * Opacity of the chart content to the *right* of the crosshair (the "future")
858
- * while scrubbing — `0` fully fades it out, `1` disables the dim. Implemented
859
- * by erasing the content's alpha (`dstOut`), so it reveals the real background
860
- * and works on any background color. Default `0.3`. Ignored when
861
- * `crosshairDimColor` is set (that uses the legacy colored mask instead).
866
+ * Which content {@link dimOpacity} fades while scrubbing.
867
+ *
868
+ * - `"future"` fades all chart content to the right of the crosshair.
869
+ * - `"otherCandles"` keeps the candle under the finger at full strength and
870
+ * fades every other candle body and wick. Outside candle mode it falls back
871
+ * to the standard `"future"` behavior. When the finger is in a gap between
872
+ * candle buckets no candle is selected. Volume bars are unchanged. The
873
+ * vertical crosshair and scrub selection dot are hidden so the focused
874
+ * candle is the sole position indicator.
875
+ *
876
+ * Default `"future"`.
877
+ */
878
+ dimTarget?: "future" | "otherCandles";
879
+ /**
880
+ * Opacity of the content selected by {@link dimTarget} while scrubbing — `0`
881
+ * fully fades it out, `1` disables the dim. Values are clamped to `[0, 1]`.
882
+ *
883
+ * With the default `dimTarget: "future"`, the fade is implemented by erasing
884
+ * the trailing content's alpha (`dstOut`), so it reveals the real background
885
+ * and works on any background color. Default `0.3`. `crosshairDimColor`
886
+ * overrides this opacity only for the `"future"` target.
862
887
  */
863
888
  dimOpacity?: number;
889
+ /**
890
+ * Duration in milliseconds for other candles to ease toward
891
+ * {@link dimOpacity} when a focused-candle scrub starts, and back to full
892
+ * strength on release. Used only with `dimTarget: "otherCandles"`.
893
+ * Set `0` for an instant change. Negative values clamp to `0`. Default `60`.
894
+ */
895
+ dimFadeMs?: number;
864
896
  /** Vertical crosshair line stroke. Omit to use theme `crosshairLine`. */
865
897
  crosshairLineColor?: string;
866
898
  /** Vertical crosshair line width in px. Default `1`. */
@@ -1522,11 +1554,23 @@ export interface DegenShakePayload {
1522
1554
  direction: "up" | "down";
1523
1555
  }
1524
1556
 
1525
- /** Contrasting outer ring drawn behind each series dot — the "haloed" look the
1526
- * single-series live dot has, so dots stand out against the lines and one
1527
- * another. */
1557
+ /** Optional soft, static color glow drawn behind a live dot. */
1558
+ export interface DotGlowConfig {
1559
+ /** Glow color. Defaults to the dot's resolved fill color. */
1560
+ color?: string;
1561
+ /** Radius of the glow's source circle in pixels. Default `7`. */
1562
+ radius?: number;
1563
+ /** Skia blur radius in pixels. Default `5`. */
1564
+ blur?: number;
1565
+ /** Glow opacity (0–1). Default `0.18`. */
1566
+ opacity?: number;
1567
+ }
1568
+
1569
+ /** Contrasting outer ring drawn behind each series dot — the crisp backing
1570
+ * circle the single-series live dot has, so dots stand out against the lines
1571
+ * and one another. */
1528
1572
  export interface DotRingConfig {
1529
- /** Ring color. Default: theme `badgeOuterBg` (a near-background halo). */
1573
+ /** Ring color. Default: theme `badgeOuterBg` (a near-background backing). */
1530
1574
  color?: string;
1531
1575
  /** Ring thickness in pixels — how far the halo extends past the dot. Default `2.5`. */
1532
1576
  width?: number;
@@ -1535,7 +1579,8 @@ export interface DotRingConfig {
1535
1579
  /**
1536
1580
  * Shared live-dot styling, used by both `LiveChart` (`dot`) and
1537
1581
  * `LiveChartSeries` (`dot`, which extends this). A dot is a color-filled circle
1538
- * of `radius` with an optional contrasting outer `ring` (halo).
1582
+ * of `radius` with an optional contrasting outer `ring` and opt-in soft
1583
+ * `glow`. The glow is static; use `pulse` for an animated heartbeat.
1539
1584
  */
1540
1585
  export interface DotConfig {
1541
1586
  /** Radius of the (color-filled) dot in pixels. Default `3.5`. */
@@ -1546,6 +1591,12 @@ export interface DotConfig {
1546
1591
  * `DotRingConfig`. Default `true`.
1547
1592
  */
1548
1593
  ring?: boolean | DotRingConfig;
1594
+ /**
1595
+ * Soft static glow behind the dot. `true` = restrained defaults, `false` =
1596
+ * off, or pass `DotGlowConfig` to tune its color, radius, blur, and opacity.
1597
+ * Default `false`.
1598
+ */
1599
+ glow?: boolean | DotGlowConfig;
1549
1600
  /**
1550
1601
  * Show the dot. `false` hides it (line, badge, and labels still render). Default `true`.
1551
1602
  *
@@ -1637,6 +1688,13 @@ export interface SeriesConfig {
1637
1688
  * anchored to this series snap to the straight chord to match.
1638
1689
  */
1639
1690
  curve?: "monotone" | "linear";
1691
+ /**
1692
+ * Per-series screen-space path simplification tolerance in pixels. Overrides
1693
+ * `LiveChartSeries.line.simplify`; `0` disables simplification for this series.
1694
+ * Only rendered geometry is simplified — values, range fitting, and scrubbing
1695
+ * continue to use the original points.
1696
+ */
1697
+ simplify?: number;
1640
1698
  /** Per-series stroke width override (px). Falls back to the chart line width. */
1641
1699
  strokeWidth?: number;
1642
1700
  /** Render a soft glow behind this series' line. Default `false`. */
@@ -1986,9 +2044,11 @@ export interface LiveChartCoreProps {
1986
2044
  /**
1987
2045
  * UI-thread opacity multiplier for the plotted data. Set a value from `0`
1988
2046
  * (hidden) to `1` (fully visible), such as `0.5` while replacement data loads.
1989
- * Applies to every plotted series — line/fill/candles, live dots, value lines,
1990
- * and inline values — without affecting axes, reference lines, legends, or
1991
- * scrub UI. Default `1`.
2047
+ * Applies to every plotted series — line/fill/candles, value lines, and inline
2048
+ * values — without affecting axes, reference lines, legends, or scrub UI.
2049
+ * While the multiplier is below `1`, live dots and pulse rings fade completely
2050
+ * out so the dimmed stroke cannot show through them; they fade back in when it
2051
+ * returns to `1`. Default `1`.
1992
2052
  */
1993
2053
  seriesOpacity?: SharedValue<number>;
1994
2054
  /**
@@ -2326,7 +2386,8 @@ export interface LiveChartProps extends LiveChartCoreProps {
2326
2386
  pulse?: boolean | PulseConfig;
2327
2387
  /**
2328
2388
  * Live dot styling. `true`/omitted = shown defaults, `false` = hidden, or pass
2329
- * `DotConfig` (`radius`, `ring` halo, `color`). See {@link DotConfig}. Default `true`.
2389
+ * `DotConfig` (`radius`, `ring`, opt-in `glow`, `color`). See
2390
+ * {@link DotConfig}. Default `true`.
2330
2391
  */
2331
2392
  dot?: boolean | DotConfig;
2332
2393
  /** Horizontal dashed line at the current live value. `true` = defaults, or pass `ValueLineConfig`. */
@@ -2374,8 +2435,9 @@ export interface LiveChartProps extends LiveChartCoreProps {
2374
2435
  /** Render once with no per-frame animation loop — for many small charts (sparklines)
2375
2436
  * in a list. The continuous animations (pulse, degen, the entry reveal) are disabled,
2376
2437
  * but `scrub` / `scrubAction` stay available — they're on-demand touch gestures with
2377
- * no per-frame cost, so a still chart is still scrubbable. Frame the data with
2378
- * `timeWindow` + `nowOverride` (see the historical-data-fill pattern). */
2438
+ * no per-frame cost, so a still chart is still scrubbable. May be toggled at runtime:
2439
+ * switching back to live restarts the suspended loops and catches up. Frame the data
2440
+ * with `timeWindow` + `nowOverride` (see the historical-data-fill pattern). */
2379
2441
  static?: boolean;
2380
2442
  /**
2381
2443
  * Render a custom overlay floated over the chart canvas, handed a price↔pixel /