@ledgerhq/lumen-ui-rnative-visualization 0.1.13 → 0.1.15

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 (182) hide show
  1. package/dist/module/lib/Components/Axis/Axis.constants.js +21 -0
  2. package/dist/module/lib/Components/Axis/Axis.constants.js.map +1 -0
  3. package/dist/module/lib/Components/Axis/index.js +5 -0
  4. package/dist/module/lib/Components/Axis/index.js.map +1 -0
  5. package/dist/module/lib/Components/CartesianChart/CartesianChart.js +34 -22
  6. package/dist/module/lib/Components/CartesianChart/CartesianChart.js.map +1 -1
  7. package/dist/module/lib/Components/CartesianChart/context/useBuildChartContext.js +5 -4
  8. package/dist/module/lib/Components/CartesianChart/context/useBuildChartContext.js.map +1 -1
  9. package/dist/module/lib/Components/CartesianChart/context/useBuildChartContext.test.js +54 -0
  10. package/dist/module/lib/Components/CartesianChart/context/useBuildChartContext.test.js.map +1 -1
  11. package/dist/module/lib/Components/CartesianChart/utils.js +18 -8
  12. package/dist/module/lib/Components/CartesianChart/utils.js.map +1 -1
  13. package/dist/module/lib/Components/Line/utils.js +6 -1
  14. package/dist/module/lib/Components/Line/utils.js.map +1 -1
  15. package/dist/module/lib/Components/Line/utils.test.js +88 -0
  16. package/dist/module/lib/Components/Line/utils.test.js.map +1 -0
  17. package/dist/module/lib/Components/LineChart/LineChart.js +19 -31
  18. package/dist/module/lib/Components/LineChart/LineChart.js.map +1 -1
  19. package/dist/module/lib/Components/LineChart/LineChart.stories.js +38 -0
  20. package/dist/module/lib/Components/LineChart/LineChart.stories.js.map +1 -1
  21. package/dist/module/lib/Components/LineChart/LineChart.test.js +192 -0
  22. package/dist/module/lib/Components/LineChart/LineChart.test.js.map +1 -1
  23. package/dist/module/lib/Components/Point/Point.js +8 -2
  24. package/dist/module/lib/Components/Point/Point.js.map +1 -1
  25. package/dist/module/lib/Components/Point/constants.js +9 -0
  26. package/dist/module/lib/Components/Point/constants.js.map +1 -0
  27. package/dist/module/lib/Components/Point/pointContext/MagneticPointsProvider.js +35 -0
  28. package/dist/module/lib/Components/Point/pointContext/MagneticPointsProvider.js.map +1 -0
  29. package/dist/module/lib/Components/Point/pointContext/MagneticPointsProvider.test.js +153 -0
  30. package/dist/module/lib/Components/Point/pointContext/MagneticPointsProvider.test.js.map +1 -0
  31. package/dist/module/lib/Components/Point/pointContext/index.js +5 -0
  32. package/dist/module/lib/Components/Point/pointContext/index.js.map +1 -0
  33. package/dist/module/lib/Components/Point/pointContext/magneticPointsContext.js +10 -0
  34. package/dist/module/lib/Components/Point/pointContext/magneticPointsContext.js.map +1 -0
  35. package/dist/module/lib/Components/Point/utils.js +26 -6
  36. package/dist/module/lib/Components/Point/utils.js.map +1 -1
  37. package/dist/module/lib/Components/Point/utils.test.js +84 -2
  38. package/dist/module/lib/Components/Point/utils.test.js.map +1 -1
  39. package/dist/module/lib/Components/ReferenceLine/utils.js.map +1 -1
  40. package/dist/module/lib/Components/Scrubber/DefaultScrubberTooltip/ChartTooltipItem.js +52 -0
  41. package/dist/module/lib/Components/Scrubber/DefaultScrubberTooltip/ChartTooltipItem.js.map +1 -0
  42. package/dist/module/lib/Components/Scrubber/DefaultScrubberTooltip/DefaultScrubberTooltip.js +81 -0
  43. package/dist/module/lib/Components/Scrubber/DefaultScrubberTooltip/DefaultScrubberTooltip.js.map +1 -0
  44. package/dist/module/lib/Components/Scrubber/DefaultScrubberTooltip/DefaultScrubberTooltip.test.js +340 -0
  45. package/dist/module/lib/Components/Scrubber/DefaultScrubberTooltip/DefaultScrubberTooltip.test.js.map +1 -0
  46. package/dist/module/lib/Components/Scrubber/DefaultScrubberTooltip/constants.js +12 -0
  47. package/dist/module/lib/Components/Scrubber/DefaultScrubberTooltip/constants.js.map +1 -0
  48. package/dist/module/lib/Components/Scrubber/DefaultScrubberTooltip/utils.js +166 -0
  49. package/dist/module/lib/Components/Scrubber/DefaultScrubberTooltip/utils.js.map +1 -0
  50. package/dist/module/lib/Components/Scrubber/DefaultScrubberTooltip/utils.test.js +253 -0
  51. package/dist/module/lib/Components/Scrubber/DefaultScrubberTooltip/utils.test.js.map +1 -0
  52. package/dist/module/lib/Components/Scrubber/Scrubber.js +40 -21
  53. package/dist/module/lib/Components/Scrubber/Scrubber.js.map +1 -1
  54. package/dist/module/lib/Components/Scrubber/Scrubber.stories.js +80 -30
  55. package/dist/module/lib/Components/Scrubber/Scrubber.stories.js.map +1 -1
  56. package/dist/module/lib/Components/Scrubber/Scrubber.test.js +10 -5
  57. package/dist/module/lib/Components/Scrubber/Scrubber.test.js.map +1 -1
  58. package/dist/module/lib/Components/Scrubber/ScrubberProvider.js +81 -29
  59. package/dist/module/lib/Components/Scrubber/ScrubberProvider.js.map +1 -1
  60. package/dist/module/lib/Components/Scrubber/utils.js +64 -0
  61. package/dist/module/lib/Components/Scrubber/utils.js.map +1 -1
  62. package/dist/module/lib/Components/Scrubber/utils.test.js +128 -1
  63. package/dist/module/lib/Components/Scrubber/utils.test.js.map +1 -1
  64. package/dist/module/lib/utils/domain/domain.js.map +1 -1
  65. package/dist/module/lib/utils/scales/scales.js +13 -4
  66. package/dist/module/lib/utils/scales/scales.js.map +1 -1
  67. package/dist/module/lib/utils/scales/scales.test.js +29 -0
  68. package/dist/module/lib/utils/scales/scales.test.js.map +1 -1
  69. package/dist/module/lib/utils/ticks/ticks.js +13 -3
  70. package/dist/module/lib/utils/ticks/ticks.js.map +1 -1
  71. package/dist/module/lib/utils/ticks/ticks.test.js +34 -0
  72. package/dist/module/lib/utils/ticks/ticks.test.js.map +1 -1
  73. package/dist/typescript/src/lib/Components/Axis/Axis.constants.d.ts +5 -0
  74. package/dist/typescript/src/lib/Components/Axis/Axis.constants.d.ts.map +1 -0
  75. package/dist/typescript/src/lib/Components/Axis/Axis.types.d.ts +39 -0
  76. package/dist/typescript/src/lib/Components/Axis/Axis.types.d.ts.map +1 -1
  77. package/dist/typescript/src/lib/Components/Axis/index.d.ts +3 -0
  78. package/dist/typescript/src/lib/Components/Axis/index.d.ts.map +1 -0
  79. package/dist/typescript/src/lib/Components/CartesianChart/CartesianChart.d.ts +1 -1
  80. package/dist/typescript/src/lib/Components/CartesianChart/CartesianChart.d.ts.map +1 -1
  81. package/dist/typescript/src/lib/Components/CartesianChart/context/useBuildChartContext.d.ts +5 -4
  82. package/dist/typescript/src/lib/Components/CartesianChart/context/useBuildChartContext.d.ts.map +1 -1
  83. package/dist/typescript/src/lib/Components/CartesianChart/types.d.ts +11 -3
  84. package/dist/typescript/src/lib/Components/CartesianChart/types.d.ts.map +1 -1
  85. package/dist/typescript/src/lib/Components/CartesianChart/utils.d.ts +18 -8
  86. package/dist/typescript/src/lib/Components/CartesianChart/utils.d.ts.map +1 -1
  87. package/dist/typescript/src/lib/Components/Line/utils.d.ts +4 -0
  88. package/dist/typescript/src/lib/Components/Line/utils.d.ts.map +1 -1
  89. package/dist/typescript/src/lib/Components/LineChart/LineChart.d.ts +1 -1
  90. package/dist/typescript/src/lib/Components/LineChart/LineChart.d.ts.map +1 -1
  91. package/dist/typescript/src/lib/Components/LineChart/types.d.ts +10 -3
  92. package/dist/typescript/src/lib/Components/LineChart/types.d.ts.map +1 -1
  93. package/dist/typescript/src/lib/Components/Point/Point.d.ts +1 -1
  94. package/dist/typescript/src/lib/Components/Point/Point.d.ts.map +1 -1
  95. package/dist/typescript/src/lib/Components/Point/constants.d.ts +7 -0
  96. package/dist/typescript/src/lib/Components/Point/constants.d.ts.map +1 -0
  97. package/dist/typescript/src/lib/Components/Point/pointContext/MagneticPointsProvider.d.ts +7 -0
  98. package/dist/typescript/src/lib/Components/Point/pointContext/MagneticPointsProvider.d.ts.map +1 -0
  99. package/dist/typescript/src/lib/Components/Point/pointContext/index.d.ts +3 -0
  100. package/dist/typescript/src/lib/Components/Point/pointContext/index.d.ts.map +1 -0
  101. package/dist/typescript/src/lib/Components/Point/pointContext/magneticPointsContext.d.ts +13 -0
  102. package/dist/typescript/src/lib/Components/Point/pointContext/magneticPointsContext.d.ts.map +1 -0
  103. package/dist/typescript/src/lib/Components/Point/types.d.ts +7 -0
  104. package/dist/typescript/src/lib/Components/Point/types.d.ts.map +1 -1
  105. package/dist/typescript/src/lib/Components/Point/utils.d.ts +8 -6
  106. package/dist/typescript/src/lib/Components/Point/utils.d.ts.map +1 -1
  107. package/dist/typescript/src/lib/Components/ReferenceLine/utils.d.ts +3 -2
  108. package/dist/typescript/src/lib/Components/ReferenceLine/utils.d.ts.map +1 -1
  109. package/dist/typescript/src/lib/Components/Scrubber/DefaultScrubberTooltip/ChartTooltipItem.d.ts +9 -0
  110. package/dist/typescript/src/lib/Components/Scrubber/DefaultScrubberTooltip/ChartTooltipItem.d.ts.map +1 -0
  111. package/dist/typescript/src/lib/Components/Scrubber/DefaultScrubberTooltip/DefaultScrubberTooltip.d.ts +12 -0
  112. package/dist/typescript/src/lib/Components/Scrubber/DefaultScrubberTooltip/DefaultScrubberTooltip.d.ts.map +1 -0
  113. package/dist/typescript/src/lib/Components/Scrubber/DefaultScrubberTooltip/constants.d.ts +10 -0
  114. package/dist/typescript/src/lib/Components/Scrubber/DefaultScrubberTooltip/constants.d.ts.map +1 -0
  115. package/dist/typescript/src/lib/Components/Scrubber/DefaultScrubberTooltip/utils.d.ts +69 -0
  116. package/dist/typescript/src/lib/Components/Scrubber/DefaultScrubberTooltip/utils.d.ts.map +1 -0
  117. package/dist/typescript/src/lib/Components/Scrubber/Scrubber.d.ts +14 -2
  118. package/dist/typescript/src/lib/Components/Scrubber/Scrubber.d.ts.map +1 -1
  119. package/dist/typescript/src/lib/Components/Scrubber/ScrubberProvider.d.ts +1 -1
  120. package/dist/typescript/src/lib/Components/Scrubber/ScrubberProvider.d.ts.map +1 -1
  121. package/dist/typescript/src/lib/Components/Scrubber/index.d.ts +1 -1
  122. package/dist/typescript/src/lib/Components/Scrubber/index.d.ts.map +1 -1
  123. package/dist/typescript/src/lib/Components/Scrubber/types.d.ts +116 -4
  124. package/dist/typescript/src/lib/Components/Scrubber/types.d.ts.map +1 -1
  125. package/dist/typescript/src/lib/Components/Scrubber/utils.d.ts +33 -3
  126. package/dist/typescript/src/lib/Components/Scrubber/utils.d.ts.map +1 -1
  127. package/dist/typescript/src/lib/utils/domain/domain.d.ts +5 -4
  128. package/dist/typescript/src/lib/utils/domain/domain.d.ts.map +1 -1
  129. package/dist/typescript/src/lib/utils/index.d.ts +1 -1
  130. package/dist/typescript/src/lib/utils/index.d.ts.map +1 -1
  131. package/dist/typescript/src/lib/utils/scales/scales.d.ts +11 -5
  132. package/dist/typescript/src/lib/utils/scales/scales.d.ts.map +1 -1
  133. package/dist/typescript/src/lib/utils/ticks/ticks.d.ts +13 -5
  134. package/dist/typescript/src/lib/utils/ticks/ticks.d.ts.map +1 -1
  135. package/dist/typescript/src/lib/utils/types.d.ts +3 -28
  136. package/dist/typescript/src/lib/utils/types.d.ts.map +1 -1
  137. package/package.json +4 -4
  138. package/src/lib/Components/Axis/Axis.constants.ts +23 -0
  139. package/src/lib/Components/Axis/Axis.types.ts +40 -0
  140. package/src/lib/Components/Axis/index.ts +2 -0
  141. package/src/lib/Components/CartesianChart/CartesianChart.tsx +39 -24
  142. package/src/lib/Components/CartesianChart/context/useBuildChartContext.test.ts +56 -0
  143. package/src/lib/Components/CartesianChart/context/useBuildChartContext.ts +8 -8
  144. package/src/lib/Components/CartesianChart/types.ts +11 -3
  145. package/src/lib/Components/CartesianChart/utils.ts +18 -8
  146. package/src/lib/Components/Line/utils.test.ts +90 -0
  147. package/src/lib/Components/Line/utils.ts +6 -1
  148. package/src/lib/Components/LineChart/LineChart.stories.tsx +36 -0
  149. package/src/lib/Components/LineChart/LineChart.test.tsx +156 -0
  150. package/src/lib/Components/LineChart/LineChart.tsx +30 -40
  151. package/src/lib/Components/LineChart/types.ts +10 -3
  152. package/src/lib/Components/Point/Point.tsx +9 -3
  153. package/src/lib/Components/Point/constants.ts +6 -0
  154. package/src/lib/Components/Point/pointContext/MagneticPointsProvider.test.tsx +162 -0
  155. package/src/lib/Components/Point/pointContext/MagneticPointsProvider.tsx +45 -0
  156. package/src/lib/Components/Point/pointContext/index.ts +5 -0
  157. package/src/lib/Components/Point/pointContext/magneticPointsContext.ts +19 -0
  158. package/src/lib/Components/Point/types.ts +7 -0
  159. package/src/lib/Components/Point/utils.test.ts +95 -5
  160. package/src/lib/Components/Point/utils.ts +35 -7
  161. package/src/lib/Components/ReferenceLine/utils.ts +4 -7
  162. package/src/lib/Components/Scrubber/DefaultScrubberTooltip/ChartTooltipItem.tsx +54 -0
  163. package/src/lib/Components/Scrubber/DefaultScrubberTooltip/DefaultScrubberTooltip.test.tsx +259 -0
  164. package/src/lib/Components/Scrubber/DefaultScrubberTooltip/DefaultScrubberTooltip.tsx +105 -0
  165. package/src/lib/Components/Scrubber/DefaultScrubberTooltip/constants.ts +9 -0
  166. package/src/lib/Components/Scrubber/DefaultScrubberTooltip/utils.test.ts +264 -0
  167. package/src/lib/Components/Scrubber/DefaultScrubberTooltip/utils.ts +243 -0
  168. package/src/lib/Components/Scrubber/Scrubber.stories.tsx +104 -42
  169. package/src/lib/Components/Scrubber/Scrubber.test.tsx +7 -5
  170. package/src/lib/Components/Scrubber/Scrubber.tsx +47 -24
  171. package/src/lib/Components/Scrubber/ScrubberProvider.tsx +92 -28
  172. package/src/lib/Components/Scrubber/index.ts +10 -1
  173. package/src/lib/Components/Scrubber/types.ts +122 -4
  174. package/src/lib/Components/Scrubber/utils.test.ts +150 -0
  175. package/src/lib/Components/Scrubber/utils.ts +93 -3
  176. package/src/lib/utils/domain/domain.ts +6 -5
  177. package/src/lib/utils/index.ts +0 -2
  178. package/src/lib/utils/scales/scales.test.ts +19 -0
  179. package/src/lib/utils/scales/scales.ts +15 -9
  180. package/src/lib/utils/ticks/ticks.test.ts +45 -0
  181. package/src/lib/utils/ticks/ticks.ts +19 -9
  182. package/src/lib/utils/types.ts +3 -26
@@ -4,10 +4,15 @@ import {
4
4
  isNumericScale,
5
5
  } from '../../utils/scales/scales';
6
6
  import type {
7
- AxisConfigProps,
8
7
  CartesianChartContextValue,
9
8
  ChartScaleFunction,
10
9
  } from '../../utils/types';
10
+ import type { BaseAxisProps } from '../Axis';
11
+
12
+ export type MagnetEntry = {
13
+ index: number;
14
+ pixelX: number;
15
+ };
11
16
 
12
17
  const isNumberArray = (arr: string[] | number[]): arr is number[] =>
13
18
  typeof arr[0] === 'number';
@@ -49,7 +54,7 @@ const findClosestIndex = (
49
54
  export const getDataIndexFromPosition = (
50
55
  pixelX: number,
51
56
  scale: ChartScaleFunction,
52
- axisConfig: Partial<AxisConfigProps> | undefined,
57
+ axisConfig: Partial<BaseAxisProps> | undefined,
53
58
  dataLength: number,
54
59
  ): number => {
55
60
  if (dataLength <= 0) return 0;
@@ -116,7 +121,7 @@ export const resolvePixelY = (
116
121
  export const resolvePixelX = (
117
122
  dataIndex: number,
118
123
  getXScale: CartesianChartContextValue['getXScale'],
119
- axisConfig?: AxisConfigProps,
124
+ axisConfig?: BaseAxisProps,
120
125
  ): number | undefined => {
121
126
  const scale = getXScale();
122
127
  if (!scale) return undefined;
@@ -124,3 +129,88 @@ export const resolvePixelX = (
124
129
  const xValue = typeof axisValue === 'number' ? axisValue : dataIndex;
125
130
  return getPointOnScale(xValue, scale);
126
131
  };
132
+
133
+ /**
134
+ * Resolves each magnetic index to its pixel position and returns them sorted
135
+ * by `pixelX` ascending. Indices that cannot be projected are filtered out.
136
+ * The sorted output enables early-exit in {@link applyMagnetisation}.
137
+ */
138
+ export const buildSortedMagnets = ({
139
+ magneticIndices,
140
+ getPixelForIndex,
141
+ }: {
142
+ magneticIndices: ReadonlySet<number>;
143
+ getPixelForIndex: (index: number) => number | undefined;
144
+ }): MagnetEntry[] => {
145
+ if (magneticIndices.size === 0) return [];
146
+
147
+ const magnets: MagnetEntry[] = [];
148
+
149
+ for (const index of magneticIndices) {
150
+ const pixelX = getPixelForIndex(index);
151
+ if (pixelX !== undefined) magnets.push({ index, pixelX });
152
+ }
153
+
154
+ magnets.sort((a, b) => a.pixelX - b.pixelX);
155
+
156
+ return magnets;
157
+ };
158
+
159
+ /**
160
+ * Binary search for the magnet whose `pixelX` is closest to `pixelX`.
161
+ * Returns -1 when the array is empty. Favors the left neighbor on ties.
162
+ */
163
+ export const nearestIndex = (
164
+ sortedMagnets: readonly MagnetEntry[],
165
+ pixelX: number,
166
+ ): number => {
167
+ if (sortedMagnets.length === 0) return -1;
168
+
169
+ let left = 0;
170
+ let right = sortedMagnets.length;
171
+
172
+ while (left < right) {
173
+ const mid = Math.floor((left + right) / 2);
174
+ if (sortedMagnets[mid].pixelX < pixelX) {
175
+ left = mid + 1;
176
+ } else {
177
+ right = mid;
178
+ }
179
+ }
180
+
181
+ if (left === 0) return 0;
182
+ if (left === sortedMagnets.length) return left - 1;
183
+
184
+ const distBefore = pixelX - sortedMagnets[left - 1].pixelX;
185
+ const distAfter = sortedMagnets[left].pixelX - pixelX;
186
+
187
+ return distBefore <= distAfter ? left - 1 : left;
188
+ };
189
+
190
+ /**
191
+ * Given a resolved data index and the raw pixel position, checks whether any
192
+ * magnetic point is within `magnetRadius` pixels of `pixelX`. If so, returns
193
+ * the closest magnetic index; otherwise returns the original `resolvedIndex`.
194
+ */
195
+ export const applyMagnetisation = ({
196
+ resolvedIndex,
197
+ pixelX,
198
+ sortedMagnets,
199
+ magnetRadius,
200
+ }: {
201
+ resolvedIndex: number;
202
+ pixelX: number;
203
+ sortedMagnets: readonly MagnetEntry[];
204
+ magnetRadius: number;
205
+ }): number => {
206
+ if (magnetRadius <= 0 || sortedMagnets.length === 0) return resolvedIndex;
207
+
208
+ const nearest = nearestIndex(sortedMagnets, pixelX);
209
+ if (
210
+ nearest >= 0 &&
211
+ Math.abs(sortedMagnets[nearest].pixelX - pixelX) <= magnetRadius
212
+ ) {
213
+ return sortedMagnets[nearest].index;
214
+ }
215
+ return resolvedIndex;
216
+ };
@@ -1,4 +1,5 @@
1
- import type { AxisBounds, AxisConfigProps, Series } from '../types';
1
+ import type { AxisBounds, BaseAxisProps } from '../../Components/Axis';
2
+ import type { Series } from '../types';
2
3
 
3
4
  /**
4
5
  * Compute the X domain (index-based) from series data and axis config.
@@ -7,7 +8,7 @@ import type { AxisBounds, AxisConfigProps, Series } from '../types';
7
8
  */
8
9
  export const computeXDomain = (
9
10
  series: Series[],
10
- axisConfig?: Partial<AxisConfigProps>,
11
+ axisConfig?: Partial<BaseAxisProps>,
11
12
  ): AxisBounds => {
12
13
  const axisData = axisConfig?.data;
13
14
 
@@ -44,7 +45,7 @@ export const computeXDomain = (
44
45
  */
45
46
  export const computeYDomain = (
46
47
  series: Series[],
47
- axisConfig?: Partial<AxisConfigProps>,
48
+ axisConfig?: Partial<BaseAxisProps>,
48
49
  ): AxisBounds => {
49
50
  let min = 0;
50
51
  let max = 0;
@@ -76,7 +77,7 @@ export const computeYDomain = (
76
77
  */
77
78
  export const computeDataLength = (
78
79
  series: Series[],
79
- axisConfig?: Partial<AxisConfigProps>,
80
+ axisConfig?: Partial<BaseAxisProps>,
80
81
  ): number => {
81
82
  if (axisConfig?.data && axisConfig.data.length > 0) {
82
83
  return axisConfig.data.length;
@@ -86,7 +87,7 @@ export const computeDataLength = (
86
87
 
87
88
  const applyDomainOverride = (
88
89
  autoBounds: AxisBounds,
89
- domainOverride?: AxisConfigProps['domain'],
90
+ domainOverride?: BaseAxisProps['domain'],
90
91
  ): AxisBounds => {
91
92
  if (!domainOverride) return autoBounds;
92
93
 
@@ -1,6 +1,4 @@
1
1
  export type {
2
- AxisBounds,
3
- AxisConfigProps,
4
2
  CartesianChartContextValue,
5
3
  CategoricalScale,
6
4
  ChartInset,
@@ -53,6 +53,25 @@ describe('getNumericScale', () => {
53
53
  expect(range[0]).toBe(20);
54
54
  expect(range[1]).toBe(400);
55
55
  });
56
+
57
+ it('rounds the domain outward when nice is true (default)', () => {
58
+ const scale = getNumericScale({
59
+ scaleType: 'linear',
60
+ domain: { min: 0, max: 3769 },
61
+ range: { min: 0, max: 400 },
62
+ });
63
+ expect(scale.domain()).toEqual([0, 4000]);
64
+ });
65
+
66
+ it('keeps the domain exact when nice is false', () => {
67
+ const scale = getNumericScale({
68
+ scaleType: 'linear',
69
+ domain: { min: 0, max: 3769 },
70
+ range: { min: 0, max: 400 },
71
+ nice: false,
72
+ });
73
+ expect(scale.domain()).toEqual([0, 3769]);
74
+ });
56
75
  });
57
76
 
58
77
  describe('getCategoricalScale', () => {
@@ -1,31 +1,37 @@
1
1
  import { scaleBand, scaleLinear, scaleLog } from 'd3-scale';
2
2
 
3
+ import type { AxisBounds, BaseAxisProps } from '../../Components/Axis';
3
4
  import type {
4
- AxisBounds,
5
- AxisConfigProps,
6
5
  CategoricalScale,
7
6
  ChartScaleFunction,
8
7
  NumericScale,
9
8
  } from '../types';
10
9
 
11
10
  /**
12
- * Creates a numeric scale with `.nice()` applied so the domain
13
- * extends to clean rounded boundaries (e.g. `[4, 98]` → `[0, 100]`).
11
+ * Creates a numeric scale.
12
+ *
13
+ * When `nice` is `true` (default), the domain is rounded outward to clean
14
+ * boundaries via d3's `.nice()` (e.g. `[4, 98]` → `[0, 100]`). Set `nice` to
15
+ * `false` to keep the domain exactly as provided so data fills the range
16
+ * boundary-to-boundary.
14
17
  */
15
18
  export const getNumericScale = ({
16
19
  scaleType,
17
20
  domain,
18
21
  range,
22
+ nice = true,
19
23
  }: {
20
24
  scaleType: 'linear' | 'log';
21
25
  domain: AxisBounds;
22
26
  range: AxisBounds;
27
+ nice?: boolean;
23
28
  }): NumericScale => {
24
29
  const scale = scaleType === 'log' ? scaleLog() : scaleLinear();
25
- return scale
26
- .domain([domain.min, domain.max])
27
- .nice()
28
- .range([range.min, range.max]);
30
+ scale.domain([domain.min, domain.max]);
31
+ if (nice) {
32
+ scale.nice();
33
+ }
34
+ return scale.range([range.min, range.max]);
29
35
  };
30
36
 
31
37
  export const getCategoricalScale = ({
@@ -52,7 +58,7 @@ export const getCategoricalScale = ({
52
58
  * Checks if a scale type config value refers to a band (categorical) scale.
53
59
  */
54
60
  export const isBandScaleType = (
55
- scaleType: AxisConfigProps['scaleType'],
61
+ scaleType: BaseAxisProps['scaleType'],
56
62
  ): scaleType is 'band' => scaleType === 'band';
57
63
 
58
64
  /**
@@ -36,6 +36,24 @@ describe('getTickValues', () => {
36
36
  it('should return full domain for band scales', () => {
37
37
  expect(getTickValues(bandScale)).toEqual([0, 1, 2, 3, 4]);
38
38
  });
39
+
40
+ it('should return numeric axis data verbatim (no d3-invented intermediate ticks)', () => {
41
+ expect(getTickValues(numericScale, undefined, [0, 2, 4])).toEqual([
42
+ 0, 2, 4,
43
+ ]);
44
+ });
45
+
46
+ it('should return indices for string axis data', () => {
47
+ expect(
48
+ getTickValues(numericScale, undefined, ['Jan', 'Feb', 'Mar']),
49
+ ).toEqual([0, 1, 2]);
50
+ });
51
+
52
+ it('should prioritize explicit ticks over axis data', () => {
53
+ expect(
54
+ getTickValues(numericScale, [10, 20], ['Jan', 'Feb', 'Mar']),
55
+ ).toEqual([10, 20]);
56
+ });
39
57
  });
40
58
 
41
59
  describe('getTickPosition', () => {
@@ -121,6 +139,33 @@ describe('buildTicksData', () => {
121
139
  expect(result[1].label).toBe('Tue');
122
140
  });
123
141
 
142
+ it('should derive ticks from numeric axis data (no intermediate ticks)', () => {
143
+ const result = buildTicksData(numericScale, { data: [0, 2, 4] });
144
+ expect(result.map((t: { value: number }) => t.value)).toEqual([0, 2, 4]);
145
+ expect(result.map((t: { label: string }) => t.label)).toEqual([
146
+ '0',
147
+ '2',
148
+ '4',
149
+ ]);
150
+ });
151
+
152
+ it('should derive ticks from string axis data without intermediate values', () => {
153
+ const stringScale = getNumericScale({
154
+ scaleType: 'linear',
155
+ domain: { min: 0, max: 2 },
156
+ range: { min: 0, max: 500 },
157
+ });
158
+ const result = buildTicksData(stringScale, {
159
+ data: ['Jan', 'Feb', 'Mar'],
160
+ });
161
+ expect(result.map((t: { value: number }) => t.value)).toEqual([0, 1, 2]);
162
+ expect(result.map((t: { label: string }) => t.label)).toEqual([
163
+ 'Jan',
164
+ 'Feb',
165
+ 'Mar',
166
+ ]);
167
+ });
168
+
124
169
  it('should apply tick formatter', () => {
125
170
  const formatter = (v: number | string): string => `$${v}`;
126
171
  const result = buildTicksData(numericScale, undefined, [0, 50], formatter);
@@ -1,9 +1,6 @@
1
+ import type { BaseAxisProps } from '../../Components/Axis';
1
2
  import { isCategoricalScale, isNumericScale } from '../scales/scales';
2
- import type {
3
- AxisConfigProps,
4
- ChartScaleFunction,
5
- DrawingArea,
6
- } from '../types';
3
+ import type { ChartScaleFunction, DrawingArea } from '../types';
7
4
 
8
5
  export const APPROXIMATE_TICK_COUNT = 5;
9
6
 
@@ -15,13 +12,26 @@ export type TickData = {
15
12
 
16
13
  /**
17
14
  * Resolves which numeric tick values should appear on the axis.
18
- * Explicit ticks take priority, then scale-specific defaults.
15
+ *
16
+ * Priority:
17
+ * 1. Explicit `ticks` provided by the consumer.
18
+ * 2. Axis `data` — when provided, ticks come from the data itself (numeric
19
+ * values for numeric data, indices for string data) so the rendered ticks
20
+ * mirror exactly what the consumer asked for, with no d3-invented
21
+ * intermediate values.
22
+ * 3. Scale-specific defaults (band domain, or `scale.ticks()` for numeric).
19
23
  */
20
24
  export const getTickValues = (
21
25
  scale: ChartScaleFunction,
22
26
  explicitTicks?: number[],
27
+ axisData?: BaseAxisProps['data'],
23
28
  ): number[] => {
24
29
  if (explicitTicks) return explicitTicks;
30
+ if (axisData && axisData.length > 0) {
31
+ return typeof axisData[0] === 'number'
32
+ ? (axisData as number[])
33
+ : axisData.map((_, i) => i);
34
+ }
25
35
  if (isCategoricalScale(scale)) return scale.domain();
26
36
  if (isNumericScale(scale)) return scale.ticks(APPROXIMATE_TICK_COUNT);
27
37
  return [];
@@ -47,7 +57,7 @@ export const getTickPosition = (
47
57
  */
48
58
  export const getTickLabel = (
49
59
  tick: number,
50
- axisData: AxisConfigProps['data'],
60
+ axisData: BaseAxisProps['data'],
51
61
  formatter?: (value: number | string) => string,
52
62
  ): string => {
53
63
  const hasStringLabels =
@@ -71,12 +81,12 @@ export const getTickLabel = (
71
81
  */
72
82
  export const buildTicksData = (
73
83
  scale: ChartScaleFunction,
74
- axisConfig?: AxisConfigProps,
84
+ axisConfig?: BaseAxisProps,
75
85
  explicitTicks?: number[],
76
86
  formatter?: (value: number | string) => string,
77
87
  ): TickData[] => {
78
- const tickValues = getTickValues(scale, explicitTicks);
79
88
  const axisData = axisConfig?.data;
89
+ const tickValues = getTickValues(scale, explicitTicks, axisData);
80
90
 
81
91
  return tickValues.map((tick) => ({
82
92
  position: getTickPosition(scale, tick),
@@ -1,6 +1,6 @@
1
1
  import type { ScaleBand, ScaleLinear, ScaleLogarithmic } from 'd3-scale';
2
2
 
3
- export type AxisBounds = { min: number; max: number };
3
+ import type { BaseAxisProps } from '../Components/Axis';
4
4
 
5
5
  export type ChartInset = {
6
6
  top: number;
@@ -38,29 +38,6 @@ export type Series = {
38
38
  stroke: string;
39
39
  };
40
40
 
41
- export type AxisConfigProps = {
42
- /**
43
- * Scale algorithm used by this axis.
44
- * @default 'linear'
45
- */
46
- scaleType?: 'linear' | 'log' | 'band';
47
- /**
48
- * Explicit data values for band scales or category labels.
49
- * For band scales, provides the discrete domain. For numeric scales, string values
50
- * are used as tick labels at corresponding indices.
51
- */
52
- data?: string[] | number[];
53
- /**
54
- * Fixed domain bounds or a function that adjusts the computed bounds.
55
- * A partial object overrides only the specified bound(s).
56
- * A function receives the auto-computed bounds and returns adjusted ones.
57
- *
58
- * The final domain is always rounded to nice boundaries via d3's `.nice()`,
59
- * ensuring tick marks land on clean values (e.g. `[4, 98]` becomes `[0, 100]`).
60
- */
61
- domain?: Partial<AxisBounds> | ((bounds: AxisBounds) => AxisBounds);
62
- };
63
-
64
41
  export type NumericScale =
65
42
  | ScaleLinear<number, number>
66
43
  | ScaleLogarithmic<number, number>;
@@ -89,11 +66,11 @@ export type CartesianChartContextValue = {
89
66
  /**
90
67
  * Returns the x-axis config. Accepts an optional axis ID for future multi-axis support.
91
68
  */
92
- getXAxisConfig: (id?: string) => AxisConfigProps | undefined;
69
+ getXAxisConfig: (id?: string) => BaseAxisProps | undefined;
93
70
  /**
94
71
  * Returns the y-axis config. Accepts an optional axis ID for future multi-axis support.
95
72
  */
96
- getYAxisConfig: (id?: string) => AxisConfigProps | undefined;
73
+ getYAxisConfig: (id?: string) => BaseAxisProps | undefined;
97
74
  /**
98
75
  * Pixel bounds of the drawable region.
99
76
  */