react-native-livechart 3.11.0 → 4.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 (138) hide show
  1. package/dist/components/BadgeOverlay.d.ts +9 -1
  2. package/dist/components/BadgeOverlay.d.ts.map +1 -1
  3. package/dist/components/ChartOverlayLayer.d.ts +19 -0
  4. package/dist/components/ChartOverlayLayer.d.ts.map +1 -0
  5. package/dist/components/CustomMarkerOverlay.d.ts +18 -6
  6. package/dist/components/CustomMarkerOverlay.d.ts.map +1 -1
  7. package/dist/components/CustomReferenceLineOverlay.d.ts +36 -0
  8. package/dist/components/CustomReferenceLineOverlay.d.ts.map +1 -0
  9. package/dist/components/CustomTooltipOverlay.d.ts +5 -2
  10. package/dist/components/CustomTooltipOverlay.d.ts.map +1 -1
  11. package/dist/components/DotOverlay.d.ts +7 -1
  12. package/dist/components/DotOverlay.d.ts.map +1 -1
  13. package/dist/components/LiveChart.d.ts.map +1 -1
  14. package/dist/components/LiveChartSeries.d.ts.map +1 -1
  15. package/dist/components/MarkerOverlay.d.ts +8 -3
  16. package/dist/components/MarkerOverlay.d.ts.map +1 -1
  17. package/dist/components/MultiSeriesDots.d.ts +4 -1
  18. package/dist/components/MultiSeriesDots.d.ts.map +1 -1
  19. package/dist/components/ReferenceLineGroupOverlay.d.ts +27 -0
  20. package/dist/components/ReferenceLineGroupOverlay.d.ts.map +1 -0
  21. package/dist/components/ReferenceLineOverlay.d.ts +25 -2
  22. package/dist/components/ReferenceLineOverlay.d.ts.map +1 -1
  23. package/dist/components/XAxisOverlay.d.ts +7 -1
  24. package/dist/components/XAxisOverlay.d.ts.map +1 -1
  25. package/dist/components/YAxisOverlay.d.ts +12 -1
  26. package/dist/components/YAxisOverlay.d.ts.map +1 -1
  27. package/dist/constants.d.ts +7 -0
  28. package/dist/constants.d.ts.map +1 -1
  29. package/dist/core/liveChartEngineTick.d.ts +29 -0
  30. package/dist/core/liveChartEngineTick.d.ts.map +1 -1
  31. package/dist/core/liveChartSeriesEngineTick.d.ts +18 -0
  32. package/dist/core/liveChartSeriesEngineTick.d.ts.map +1 -1
  33. package/dist/core/resolveConfig.d.ts +56 -2
  34. package/dist/core/resolveConfig.d.ts.map +1 -1
  35. package/dist/core/useLiveChartEngine.d.ts +58 -7
  36. package/dist/core/useLiveChartEngine.d.ts.map +1 -1
  37. package/dist/core/useLiveChartSeriesEngine.d.ts +8 -2
  38. package/dist/core/useLiveChartSeriesEngine.d.ts.map +1 -1
  39. package/dist/draw/grid.d.ts.map +1 -1
  40. package/dist/draw/markerAtlas.d.ts +20 -2
  41. package/dist/draw/markerAtlas.d.ts.map +1 -1
  42. package/dist/draw/volume.d.ts +25 -0
  43. package/dist/draw/volume.d.ts.map +1 -0
  44. package/dist/hooks/crosshairShared.d.ts +12 -6
  45. package/dist/hooks/crosshairShared.d.ts.map +1 -1
  46. package/dist/hooks/overlayScale.d.ts +22 -0
  47. package/dist/hooks/overlayScale.d.ts.map +1 -0
  48. package/dist/hooks/resolveChartLayout.d.ts +9 -0
  49. package/dist/hooks/resolveChartLayout.d.ts.map +1 -1
  50. package/dist/hooks/useBadge.d.ts +22 -1
  51. package/dist/hooks/useBadge.d.ts.map +1 -1
  52. package/dist/hooks/useCandlePaths.d.ts +6 -0
  53. package/dist/hooks/useCandlePaths.d.ts.map +1 -1
  54. package/dist/hooks/useChartOverlayContext.d.ts +45 -0
  55. package/dist/hooks/useChartOverlayContext.d.ts.map +1 -0
  56. package/dist/hooks/useChartPaths.d.ts +8 -1
  57. package/dist/hooks/useChartPaths.d.ts.map +1 -1
  58. package/dist/hooks/useCrosshair.d.ts +9 -2
  59. package/dist/hooks/useCrosshair.d.ts.map +1 -1
  60. package/dist/hooks/useLiveDot.d.ts +6 -1
  61. package/dist/hooks/useLiveDot.d.ts.map +1 -1
  62. package/dist/hooks/useMarkers.d.ts +12 -4
  63. package/dist/hooks/useMarkers.d.ts.map +1 -1
  64. package/dist/hooks/useMultiSeriesLinePaths.d.ts.map +1 -1
  65. package/dist/hooks/usePanScroll.d.ts +82 -0
  66. package/dist/hooks/usePanScroll.d.ts.map +1 -0
  67. package/dist/hooks/usePinchZoom.d.ts +74 -0
  68. package/dist/hooks/usePinchZoom.d.ts.map +1 -0
  69. package/dist/hooks/useReferenceDrag.d.ts +24 -0
  70. package/dist/hooks/useReferenceDrag.d.ts.map +1 -0
  71. package/dist/hooks/useReferenceLine.d.ts +23 -4
  72. package/dist/hooks/useReferenceLine.d.ts.map +1 -1
  73. package/dist/hooks/useReferenceLinePress.d.ts +5 -1
  74. package/dist/hooks/useReferenceLinePress.d.ts.map +1 -1
  75. package/dist/hooks/useVisibleRange.d.ts +39 -0
  76. package/dist/hooks/useVisibleRange.d.ts.map +1 -0
  77. package/dist/index.d.ts +2 -1
  78. package/dist/index.d.ts.map +1 -1
  79. package/dist/math/markerCluster.d.ts +47 -0
  80. package/dist/math/markerCluster.d.ts.map +1 -0
  81. package/dist/math/markers.d.ts +15 -0
  82. package/dist/math/markers.d.ts.map +1 -1
  83. package/dist/math/referenceDrag.d.ts +24 -0
  84. package/dist/math/referenceDrag.d.ts.map +1 -0
  85. package/dist/math/referenceGroup.d.ts +31 -0
  86. package/dist/math/referenceGroup.d.ts.map +1 -0
  87. package/dist/math/referenceLines.d.ts +33 -7
  88. package/dist/math/referenceLines.d.ts.map +1 -1
  89. package/dist/types.d.ts +512 -21
  90. package/dist/types.d.ts.map +1 -1
  91. package/package.json +1 -1
  92. package/src/components/BadgeOverlay.tsx +26 -1
  93. package/src/components/ChartOverlayLayer.tsx +32 -0
  94. package/src/components/CustomMarkerOverlay.tsx +153 -53
  95. package/src/components/CustomReferenceLineOverlay.tsx +250 -0
  96. package/src/components/CustomTooltipOverlay.tsx +27 -7
  97. package/src/components/DotOverlay.tsx +9 -0
  98. package/src/components/LiveChart.tsx +561 -34
  99. package/src/components/LiveChartSeries.tsx +110 -5
  100. package/src/components/MarkerOverlay.tsx +100 -6
  101. package/src/components/MultiSeriesDots.tsx +12 -3
  102. package/src/components/ReferenceLineGroupOverlay.tsx +207 -0
  103. package/src/components/ReferenceLineOverlay.tsx +105 -32
  104. package/src/components/XAxisOverlay.tsx +9 -2
  105. package/src/components/YAxisOverlay.tsx +48 -26
  106. package/src/constants.ts +10 -0
  107. package/src/core/liveChartEngineTick.ts +81 -8
  108. package/src/core/liveChartSeriesEngineTick.ts +50 -9
  109. package/src/core/resolveConfig.ts +122 -1
  110. package/src/core/useLiveChartEngine.ts +103 -10
  111. package/src/core/useLiveChartSeriesEngine.ts +26 -3
  112. package/src/draw/grid.ts +13 -1
  113. package/src/draw/markerAtlas.ts +117 -3
  114. package/src/draw/volume.ts +190 -0
  115. package/src/hooks/crosshairShared.ts +37 -9
  116. package/src/hooks/overlayScale.ts +59 -0
  117. package/src/hooks/resolveChartLayout.ts +33 -0
  118. package/src/hooks/useBadge.ts +89 -21
  119. package/src/hooks/useCandlePaths.ts +104 -7
  120. package/src/hooks/useChartOverlayContext.ts +93 -0
  121. package/src/hooks/useChartPaths.ts +14 -1
  122. package/src/hooks/useCrosshair.ts +29 -2
  123. package/src/hooks/useLiveDot.ts +16 -5
  124. package/src/hooks/useMarkers.ts +43 -11
  125. package/src/hooks/useMultiSeriesLinePaths.ts +2 -1
  126. package/src/hooks/usePanScroll.ts +253 -0
  127. package/src/hooks/usePinchZoom.ts +189 -0
  128. package/src/hooks/useReferenceDrag.ts +254 -0
  129. package/src/hooks/useReferenceLine.ts +56 -9
  130. package/src/hooks/useReferenceLinePress.ts +16 -2
  131. package/src/hooks/useVisibleRange.ts +119 -0
  132. package/src/index.ts +17 -1
  133. package/src/math/markerCluster.ts +214 -0
  134. package/src/math/markers.ts +54 -9
  135. package/src/math/referenceDrag.ts +66 -0
  136. package/src/math/referenceGroup.ts +65 -0
  137. package/src/math/referenceLines.ts +76 -19
  138. package/src/types.ts +532 -21
@@ -1,4 +1,5 @@
1
1
  import type { LiveChartPoint, Marker, SeriesConfig } from "../types";
2
+ import { interpolateAtTime } from "./interpolate";
2
3
  import { splineValueAtTime } from "./spline";
3
4
 
4
5
  /** Screen-space position for one marker, index-aligned with the marker array. */
@@ -6,6 +7,16 @@ export interface ProjectedMarker {
6
7
  x: number;
7
8
  y: number;
8
9
  visible: boolean;
10
+ /** Collapsed cluster member that the representative stands in for — not drawn,
11
+ * not hit-tested. Set by the cluster pass; `false` outside `markerCluster`. */
12
+ hidden: boolean;
13
+ /** This marker is the representative (count badge) of a collapsed cluster. */
14
+ isGrouped: boolean;
15
+ /** Size of the collapsed cluster this marker represents (`0` when not grouped). */
16
+ groupCount: number;
17
+ /** Index of the cluster's representative marker for collapsed members (and the
18
+ * representative itself), else `-1`. Used to gather members on tap. */
19
+ groupRep: number;
9
20
  }
10
21
 
11
22
  export interface ProjectMarkersOpts {
@@ -23,6 +34,11 @@ export interface ProjectMarkersOpts {
23
34
  series?: SeriesConfig[];
24
35
  /** Single-series line data; anchors markers that omit `value` (and `seriesId`). */
25
36
  lineData?: LiveChartPoint[];
37
+ /** When the single-series line is drawn with `line.curve === "linear"`, anchor
38
+ * `lineData` markers on the straight chord (linear interpolation) instead of the
39
+ * monotone spline so the glyph sits exactly on the rendered line. Multi-series
40
+ * markers read their curve per-series from {@link SeriesConfig.curve}. */
41
+ lineLinear?: boolean;
26
42
  }
27
43
 
28
44
  /** How far off-chart (px) a marker may sit before it is culled. */
@@ -41,6 +57,12 @@ function projectInto(
41
57
  target: ProjectedMarker,
42
58
  ): void {
43
59
  "worklet";
60
+ // Reset cluster fields so a reused buffer slot defaults to un-clustered; the
61
+ // cluster pass (when it runs) overwrites them. Lets anchored mode skip the pass.
62
+ target.hidden = false;
63
+ target.isGrouped = false;
64
+ target.groupCount = 0;
65
+ target.groupRep = -1;
44
66
  const w = opts.canvasWidth;
45
67
  const h = opts.canvasHeight;
46
68
  const chartLeft = opts.padLeft;
@@ -66,16 +88,23 @@ function projectInto(
66
88
  } else if (seriesId !== undefined && opts.series) {
67
89
  for (let j = 0; j < opts.series.length; j++) {
68
90
  if (opts.series[j].id === seriesId) {
69
- // Evaluate the rendered spline (not the linear chord) so the glyph sits
70
- // exactly on the curve between data points.
71
- v = splineValueAtTime(opts.series[j].data, time);
91
+ // Anchor on the same interpolation the series is rendered with, so the
92
+ // glyph sits exactly on the line: the linear chord for `curve: "linear"`,
93
+ // the monotone spline otherwise.
94
+ v =
95
+ opts.series[j].curve === "linear"
96
+ ? interpolateAtTime(opts.series[j].data, time)
97
+ : splineValueAtTime(opts.series[j].data, time);
72
98
  break;
73
99
  }
74
100
  }
75
101
  } else if (opts.lineData) {
76
- // Single-series anchor: omit `value` to pin the marker to the line at `time`
77
- // (on the rendered spline curve, not the linear chord between points).
78
- v = splineValueAtTime(opts.lineData, time);
102
+ // Single-series anchor: omit `value` to pin the marker to the line at `time`.
103
+ // Match the rendered curve straight chord when `line.curve === "linear"`,
104
+ // the monotone spline otherwise.
105
+ v = opts.lineLinear
106
+ ? interpolateAtTime(opts.lineData, time)
107
+ : splineValueAtTime(opts.lineData, time);
79
108
  }
80
109
  if (v === null) {
81
110
  target.visible = false;
@@ -108,7 +137,15 @@ export function projectMarkers(
108
137
  for (let i = 0; i < markers.length; i++) {
109
138
  let cur = out[i];
110
139
  if (!cur) {
111
- cur = { x: 0, y: 0, visible: false };
140
+ cur = {
141
+ x: 0,
142
+ y: 0,
143
+ visible: false,
144
+ hidden: false,
145
+ isGrouped: false,
146
+ groupCount: 0,
147
+ groupRep: -1,
148
+ };
112
149
  out[i] = cur;
113
150
  }
114
151
  const m = markers[i];
@@ -129,7 +166,15 @@ export function projectPoint(
129
166
  opts: ProjectMarkersOpts,
130
167
  ): ProjectedMarker {
131
168
  "worklet";
132
- const target: ProjectedMarker = { x: 0, y: 0, visible: false };
169
+ const target: ProjectedMarker = {
170
+ x: 0,
171
+ y: 0,
172
+ visible: false,
173
+ hidden: false,
174
+ isGrouped: false,
175
+ groupCount: 0,
176
+ groupRep: -1,
177
+ };
133
178
  projectInto(time, value, seriesId, opts, target);
134
179
  return target;
135
180
  }
@@ -162,7 +207,7 @@ export function nearestMarkerIndex(
162
207
  let bestD = radius * radius;
163
208
  for (let i = 0; i < positions.length; i++) {
164
209
  const p = positions[i];
165
- if (!p.visible) continue;
210
+ if (!p.visible || p.hidden) continue;
166
211
  const dx = p.x - x;
167
212
  const dy = p.y - y;
168
213
  const d = dx * dx + dy * dy;
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Pure, worklet-safe helpers for dragging a Form-A reference line along the
3
+ * Y-axis. Kept free of hooks / SharedValues so they unit-test without Reanimated
4
+ * (the gesture wiring lives in {@link useReferenceDrag}).
5
+ */
6
+
7
+ /** Clamp a value to an optional `[min, max]` bound (order-independent). No-op when
8
+ * `bounds` is omitted. */
9
+ export function clampToBounds(
10
+ value: number,
11
+ bounds?: [number, number],
12
+ ): number {
13
+ "worklet";
14
+ if (!bounds) return value;
15
+ const lo = Math.min(bounds[0], bounds[1]);
16
+ const hi = Math.max(bounds[0], bounds[1]);
17
+ return Math.min(hi, Math.max(lo, value));
18
+ }
19
+
20
+ /**
21
+ * Index of the draggable line whose handle-Y is nearest the touch `y`, within
22
+ * `slop` px — or `-1` when none is in reach. `handleYs` is index-aligned with the
23
+ * chart's `referenceLines`; entries `< 0` (not draggable / off-screen / not laid
24
+ * out) are skipped. Ties favor the later (topmost-drawn) line.
25
+ */
26
+ export function nearestDraggableIndex(
27
+ handleYs: number[],
28
+ y: number,
29
+ slop: number,
30
+ ): number {
31
+ "worklet";
32
+ let best = -1;
33
+ let bestDist = slop;
34
+ for (let i = 0; i < handleYs.length; i++) {
35
+ const hy = handleYs[i];
36
+ if (hy < 0) continue;
37
+ const d = Math.abs(hy - y);
38
+ if (d <= bestDist) {
39
+ bestDist = d;
40
+ best = i;
41
+ }
42
+ }
43
+ return best;
44
+ }
45
+
46
+ /**
47
+ * Whether a line's value sits **outside** its watched interval — the trigger for
48
+ * `onDragOut` / `onDragIn` (edge-detected by the caller). When `bounds` is set the
49
+ * watched interval is `bounds` (at-or-past a bound counts as out, since the drag
50
+ * clamps there); otherwise it's the visible `[min, max]` Y-range (out = scrolled /
51
+ * dragged off-screen).
52
+ */
53
+ export function referenceValueOut(
54
+ value: number,
55
+ min: number,
56
+ max: number,
57
+ bounds?: [number, number],
58
+ ): boolean {
59
+ "worklet";
60
+ if (bounds) {
61
+ const lo = Math.min(bounds[0], bounds[1]);
62
+ const hi = Math.max(bounds[0], bounds[1]);
63
+ return value <= lo || value >= hi;
64
+ }
65
+ return value < min || value > max;
66
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Pure, worklet-safe clustering for reference-line grouping — collapse Form-A
3
+ * lines whose value-Y positions sit near each other into one count handle. Free of
4
+ * hooks / SharedValues so it unit-tests without Reanimated; the per-frame call +
5
+ * rendering live in the controller / `ReferenceLineGroupOverlay`.
6
+ */
7
+
8
+ /** One collapsed cluster of nearby reference lines. */
9
+ export interface ReferenceGroup {
10
+ /** Centroid Y (canvas px) of the cluster — where the count handle is drawn. */
11
+ cy: number;
12
+ /** Number of lines collapsed into the cluster (≥ 2). */
13
+ count: number;
14
+ }
15
+
16
+ /** Result of clustering a frame's reference-line Y positions. */
17
+ export interface ReferenceGrouping {
18
+ /**
19
+ * Index-aligned with the input Ys: `true` when this line's individual tag is
20
+ * collapsed into a cluster (a multi-line group) and should be suppressed.
21
+ */
22
+ hidden: boolean[];
23
+ /** One entry per multi-line cluster (singletons render normally, never here). */
24
+ groups: ReferenceGroup[];
25
+ }
26
+
27
+ /**
28
+ * Single-linkage cluster of reference-line handle Ys: lines whose sorted Y gaps are
29
+ * all `<= radius` chain into one group. Entries `< 0` (not a Form-A line / off the
30
+ * canvas) are ignored. Returns which lines are collapsed (`hidden`) plus a centroid
31
+ * + count per multi-line cluster. A non-positive `radius` disables grouping.
32
+ */
33
+ export function groupReferenceLines(
34
+ ys: number[],
35
+ radius: number,
36
+ ): ReferenceGrouping {
37
+ "worklet";
38
+ const hidden: boolean[] = [];
39
+ for (let i = 0; i < ys.length; i++) hidden.push(false);
40
+ const groups: ReferenceGroup[] = [];
41
+ if (radius <= 0) return { hidden, groups };
42
+
43
+ const pts: { i: number; y: number }[] = [];
44
+ for (let i = 0; i < ys.length; i++) {
45
+ if (ys[i] >= 0) pts.push({ i, y: ys[i] });
46
+ }
47
+ pts.sort((a, b) => a.y - b.y);
48
+
49
+ let c = 0;
50
+ while (c < pts.length) {
51
+ let j = c;
52
+ let sum = pts[c].y;
53
+ while (j + 1 < pts.length && pts[j + 1].y - pts[j].y <= radius) {
54
+ j++;
55
+ sum += pts[j].y;
56
+ }
57
+ const count = j - c + 1;
58
+ if (count > 1) {
59
+ for (let k = c; k <= j; k++) hidden[pts[k].i] = true;
60
+ groups.push({ cy: sum / count, count });
61
+ }
62
+ c = j + 1;
63
+ }
64
+ return { hidden, groups };
65
+ }
@@ -1,4 +1,8 @@
1
- import type { ReferenceLine } from "../types";
1
+ import type {
2
+ FontWeight,
3
+ ReferenceLine,
4
+ ReferenceLineBadgeConfig,
5
+ } from "../types";
2
6
 
3
7
  /** Which of the three reference-line forms a `ReferenceLine` resolves to. */
4
8
  export type ReferenceLineForm = "line" | "value-band" | "time-band" | "none";
@@ -40,24 +44,86 @@ export function collectReferenceValues(lines: ReferenceLine[]): number[] {
40
44
  return out;
41
45
  }
42
46
 
43
- /** Fully-resolved badge presentation for a Form-A reference line. */
44
- export interface ResolvedReferenceBadge {
45
- position: "left" | "right";
47
+ /**
48
+ * Fully-resolved badge **style/shape** + anchor — the resolved counterpart of
49
+ * {@link BadgeStyleConfig} (`position` / `icon` / `showText` plus the style knobs).
50
+ * Shared by the reference-line badge ({@link ResolvedReferenceBadge}) and the
51
+ * grouping count pill ({@link ResolvedReferenceGroupBadge}). Unset colors stay
52
+ * `undefined` so the overlay applies the appropriate theme default at render time.
53
+ */
54
+ export interface ResolvedReferenceBadgeStyle {
55
+ position: "left" | "center" | "right";
46
56
  /** Leading glyph, or "" for none. */
47
57
  icon: string;
48
- /** Whether the text label is shown. */
58
+ /** Whether the text label is shown (false → icon-only). */
49
59
  showText: boolean;
50
- /** undefined → theme tooltipBg at render time. */
51
60
  background: string | undefined;
52
- /** undefined → the line color at render time. */
53
61
  borderColor: string | undefined;
62
+ /** Border stroke width in px. Default `1`. */
63
+ borderWidth: number;
54
64
  radius: number;
65
+ textColor: string | undefined;
66
+ /** Per-badge font knobs; undefined → the chart font. */
67
+ fontSize: number | undefined;
68
+ fontFamily: string | undefined;
69
+ fontWeight: FontWeight | undefined;
70
+ /** Nudge the whole badge from its anchor, in px. Default `0`. */
71
+ offsetX: number;
72
+ offsetY: number;
73
+ }
74
+
75
+ /** Fully-resolved presentation for a Form-A reference-line badge — the shared
76
+ * style/shape plus the in-range / legacy-text flags. */
77
+ export interface ResolvedReferenceBadge extends ResolvedReferenceBadgeStyle {
55
78
  /** Show the pill at the value when in range (`badge`) vs only off-screen (legacy `offAxisBadge`). */
56
79
  inRange: boolean;
57
80
  /** Legacy `"word: value"` text format (off-axis badge) vs the `label`/value format. */
58
81
  legacyText: boolean;
59
82
  }
60
83
 
84
+ /** Fully-resolved styling for the grouping **count pill** — exactly the shared
85
+ * badge style/shape (no in-range / legacy concepts). */
86
+ export type ResolvedReferenceGroupBadge = ResolvedReferenceBadgeStyle;
87
+
88
+ /**
89
+ * Resolves the shared style/shape + anchor of a {@link BadgeStyleConfig}-shaped
90
+ * badge config. `flat` supplies a reference line's flat `badge*` fallbacks
91
+ * (`badgeBackground` / `badgeBorderColor` / `badgeRadius`); omit it for the group
92
+ * pill. Theme defaults for the unset colors are applied at render time. Worklet —
93
+ * also called from the {@link resolveReferenceBadge} worklet.
94
+ */
95
+ function resolveReferenceBadgeStyle(
96
+ cfg: ReferenceLineBadgeConfig | undefined,
97
+ flat?: ReferenceLine,
98
+ ): ResolvedReferenceBadgeStyle {
99
+ "worklet";
100
+ return {
101
+ position: cfg?.position ?? "left",
102
+ icon: cfg?.icon ?? "",
103
+ showText: cfg?.text ?? true,
104
+ background: cfg?.background ?? flat?.badgeBackground,
105
+ borderColor: cfg?.borderColor ?? flat?.badgeBorderColor,
106
+ borderWidth: cfg?.borderWidth ?? 1,
107
+ radius: cfg?.radius ?? flat?.badgeRadius ?? 5,
108
+ textColor: cfg?.textColor,
109
+ fontSize: cfg?.fontSize,
110
+ fontFamily: cfg?.fontFamily,
111
+ fontWeight: cfg?.fontWeight,
112
+ offsetX: cfg?.offsetX ?? 0,
113
+ offsetY: cfg?.offsetY ?? 0,
114
+ };
115
+ }
116
+
117
+ /**
118
+ * Resolves the grouping count-pill styling from a {@link ReferenceLineBadgeConfig}
119
+ * (the same shape a per-line badge uses). Pure — driven only by the config.
120
+ */
121
+ export function resolveReferenceGroupBadge(
122
+ badge?: ReferenceLineBadgeConfig,
123
+ ): ResolvedReferenceGroupBadge {
124
+ return resolveReferenceBadgeStyle(badge);
125
+ }
126
+
61
127
  /**
62
128
  * Resolves a Form-A reference line's badge presentation from the new `badge`
63
129
  * config or the legacy `offAxisBadge` flag (the `badge` config wins). Returns
@@ -70,24 +136,15 @@ export function resolveReferenceBadge(
70
136
  if (rl.badge) {
71
137
  const cfg = rl.badge === true ? undefined : rl.badge;
72
138
  return {
73
- position: cfg?.position ?? "left",
74
- icon: cfg?.icon ?? "",
75
- showText: cfg?.text ?? true,
76
- background: cfg?.background ?? rl.badgeBackground,
77
- borderColor: cfg?.borderColor ?? rl.badgeBorderColor,
78
- radius: cfg?.radius ?? rl.badgeRadius ?? 5,
139
+ ...resolveReferenceBadgeStyle(cfg, rl),
79
140
  inRange: true,
80
141
  legacyText: false,
81
142
  };
82
143
  }
83
144
  if (rl.offAxisBadge) {
145
+ // Legacy off-axis badge: no config object, but the flat `badge*` fallbacks apply.
84
146
  return {
85
- position: "left",
86
- icon: "",
87
- showText: true,
88
- background: rl.badgeBackground,
89
- borderColor: rl.badgeBorderColor,
90
- radius: rl.badgeRadius ?? 5,
147
+ ...resolveReferenceBadgeStyle(undefined, rl),
91
148
  inRange: false,
92
149
  legacyText: true,
93
150
  };