@scality/core-ui 0.193.0 → 0.195.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 (155) hide show
  1. package/dist/components/UnsuccessfulResult.component.d.ts.map +1 -1
  2. package/dist/components/accordion/Accordion.component.d.ts.map +1 -1
  3. package/dist/components/banner/Banner.component.d.ts +6 -1
  4. package/dist/components/banner/Banner.component.d.ts.map +1 -1
  5. package/dist/components/banner/Banner.component.js +30 -9
  6. package/dist/components/breadcrumb/Breadcrumb.component.d.ts.map +1 -1
  7. package/dist/components/buttonv2/CopyButton.component.d.ts.map +1 -1
  8. package/dist/components/charts/MetricsTimeSpanProvider.d.ts.map +1 -1
  9. package/dist/components/charts/barchart/Barchart.d.ts.map +1 -1
  10. package/dist/components/charts/barchart/Barchart.js +29 -19
  11. package/dist/components/charts/barchart/Barchart.utils.d.ts.map +1 -1
  12. package/dist/components/charts/barchart/BarchartTooltip.d.ts.map +1 -1
  13. package/dist/components/charts/common/ChartTooltip.d.ts.map +1 -1
  14. package/dist/components/charts/common/SharedComponents.d.ts +6 -6
  15. package/dist/components/charts/common/SharedComponents.d.ts.map +1 -1
  16. package/dist/components/charts/common/SharedComponents.js +7 -3
  17. package/dist/components/charts/common/chartUtils.d.ts +7 -2
  18. package/dist/components/charts/common/chartUtils.d.ts.map +1 -1
  19. package/dist/components/charts/common/chartUtils.js +55 -20
  20. package/dist/components/charts/globalhealthbar/GlobalHealthBar.hooks.d.ts.map +1 -1
  21. package/dist/components/charts/globalhealthbar/GlobalHealthBar.utils.d.ts +3 -1
  22. package/dist/components/charts/globalhealthbar/GlobalHealthBar.utils.d.ts.map +1 -1
  23. package/dist/components/charts/globalhealthbar/GlobalHealthBarTooltip.d.ts.map +1 -1
  24. package/dist/components/charts/globalhealthbar/HealthBarXAxis.d.ts.map +1 -1
  25. package/dist/components/charts/index.d.ts +1 -1
  26. package/dist/components/charts/index.d.ts.map +1 -1
  27. package/dist/components/charts/legend/ChartLegend.d.ts.map +1 -1
  28. package/dist/components/charts/legend/ChartLegendWrapper.d.ts.map +1 -1
  29. package/dist/components/charts/linetimeseries/LineTimeSerieChart.d.ts +12 -47
  30. package/dist/components/charts/linetimeseries/LineTimeSerieChart.d.ts.map +1 -1
  31. package/dist/components/charts/linetimeseries/LineTimeSerieChart.js +46 -220
  32. package/dist/components/charts/linetimeseries/LineTimeSerieChart.types.d.ts +77 -0
  33. package/dist/components/charts/linetimeseries/LineTimeSerieChart.types.d.ts.map +1 -0
  34. package/dist/components/charts/linetimeseries/LineTimeSerieChart.types.js +6 -0
  35. package/dist/components/charts/linetimeseries/LineTimeSerieChart.utils.d.ts.map +1 -1
  36. package/dist/components/charts/linetimeseries/LineTimeSerieChartTooltip.d.ts +18 -0
  37. package/dist/components/charts/linetimeseries/LineTimeSerieChartTooltip.d.ts.map +1 -0
  38. package/dist/components/charts/linetimeseries/LineTimeSerieChartTooltip.js +65 -0
  39. package/dist/components/charts/linetimeseries/useChartData.d.ts +44 -0
  40. package/dist/components/charts/linetimeseries/useChartData.d.ts.map +1 -0
  41. package/dist/components/charts/linetimeseries/useChartData.js +207 -0
  42. package/dist/components/charts/linetimeseries/useChartHover.d.ts +15 -0
  43. package/dist/components/charts/linetimeseries/useChartHover.d.ts.map +1 -0
  44. package/dist/components/charts/linetimeseries/useChartHover.js +29 -0
  45. package/dist/components/checkbox/Checkbox.component.d.ts.map +1 -1
  46. package/dist/components/checkbox/Checkbox.component.js +15 -7
  47. package/dist/components/constrainedtext/Constrainedtext.component.d.ts.map +1 -1
  48. package/dist/components/constrainedtext/Constrainedtext.component.js +3 -2
  49. package/dist/components/coreuithemeprovider/CoreUiThemeProvider.d.ts.map +1 -1
  50. package/dist/components/date/FormattedDateTime.d.ts.map +1 -1
  51. package/dist/components/dropdown/Dropdown.component.d.ts.map +1 -1
  52. package/dist/components/dropdown/Dropdown.component.js +3 -0
  53. package/dist/components/error-pages/ErrorPage401.component.js +1 -1
  54. package/dist/components/error-pages/ErrorPage404.component.js +1 -1
  55. package/dist/components/error-pages/ErrorPage500.component.js +1 -1
  56. package/dist/components/form/Form.component.d.ts.map +1 -1
  57. package/dist/components/form/Form.component.js +3 -3
  58. package/dist/components/icon/CustomsIcons.d.ts +10 -0
  59. package/dist/components/icon/CustomsIcons.d.ts.map +1 -1
  60. package/dist/components/icon/CustomsIcons.js +8 -0
  61. package/dist/components/icon/Icon.component.d.ts +2 -131
  62. package/dist/components/icon/Icon.component.d.ts.map +1 -1
  63. package/dist/components/icon/Icon.component.js +10 -133
  64. package/dist/components/icon/iconTable.d.ts +138 -0
  65. package/dist/components/icon/iconTable.d.ts.map +1 -0
  66. package/dist/components/icon/iconTable.js +137 -0
  67. package/dist/components/iconhelper/IconHelper.d.ts.map +1 -1
  68. package/dist/components/infomessage/InfoMessage.component.d.ts.map +1 -1
  69. package/dist/components/infomessage/InfoMessage.component.js +1 -1
  70. package/dist/components/infomessage/InfoMessageUtils.d.ts.map +1 -1
  71. package/dist/components/inlineinput/InlineInput.d.ts.map +1 -1
  72. package/dist/components/inputlist/InputButtons.d.ts.map +1 -1
  73. package/dist/components/inputlist/InputList.component.d.ts +2 -0
  74. package/dist/components/inputlist/InputList.component.d.ts.map +1 -1
  75. package/dist/components/inputlist/InputList.component.js +2 -2
  76. package/dist/components/inputv2/inputv2.d.ts +2 -0
  77. package/dist/components/inputv2/inputv2.d.ts.map +1 -1
  78. package/dist/components/inputv2/inputv2.js +6 -2
  79. package/dist/components/layout/v2/panels.d.ts.map +1 -1
  80. package/dist/components/modal/Modal.component.d.ts.map +1 -1
  81. package/dist/components/searchinput/SearchInput.component.d.ts.map +1 -1
  82. package/dist/components/searchinput/SearchInput.component.js +1 -1
  83. package/dist/components/statusicon/StatusIcon.component.d.ts.map +1 -1
  84. package/dist/components/tablev2/MultiSelectableContent.d.ts.map +1 -1
  85. package/dist/components/tablev2/Search.d.ts.map +1 -1
  86. package/dist/components/tablev2/TableCommon.d.ts.map +1 -1
  87. package/dist/components/tablev2/TableUtils.d.ts.map +1 -1
  88. package/dist/components/tablev2/Tablestyle.d.ts.map +1 -1
  89. package/dist/components/tablev2/Tablestyle.js +2 -3
  90. package/dist/components/tablev2/Tablev2.component.d.ts.map +1 -1
  91. package/dist/components/tabsv2/useScrollingTabs.d.ts.map +1 -1
  92. package/dist/components/text/Text.component.d.ts +9 -6
  93. package/dist/components/text/Text.component.d.ts.map +1 -1
  94. package/dist/components/text/Text.component.js +5 -0
  95. package/dist/components/toast/Toast.component.d.ts.map +1 -1
  96. package/dist/components/toast/useMutationsHandler.d.ts.map +1 -1
  97. package/dist/components/tooltip/Tooltip.component.js +1 -1
  98. package/dist/index.d.ts +4 -2
  99. package/dist/index.d.ts.map +1 -1
  100. package/dist/index.js +1 -0
  101. package/dist/next.d.ts +3 -3
  102. package/dist/next.d.ts.map +1 -1
  103. package/dist/organisms/attachments/AttachmentTable.d.ts.map +1 -1
  104. package/dist/spacing.d.ts.map +1 -1
  105. package/dist/utils.d.ts +16 -0
  106. package/dist/utils.d.ts.map +1 -1
  107. package/dist/utils.js +27 -0
  108. package/jest.config.js +6 -1
  109. package/package.json +7 -7
  110. package/src/lib/components/banner/Banner.component.test.tsx +58 -0
  111. package/src/lib/components/banner/Banner.component.tsx +57 -10
  112. package/src/lib/components/charts/barchart/Barchart.test.tsx +3 -1
  113. package/src/lib/components/charts/barchart/Barchart.tsx +123 -106
  114. package/src/lib/components/charts/common/SharedComponents.tsx +15 -11
  115. package/src/lib/components/charts/common/chartUtils.test.ts +27 -12
  116. package/src/lib/components/charts/common/chartUtils.ts +67 -23
  117. package/src/lib/components/charts/index.ts +1 -1
  118. package/src/lib/components/charts/linetimeseries/LineTimeSerieChart.tsx +136 -516
  119. package/src/lib/components/charts/linetimeseries/LineTimeSerieChart.types.ts +93 -0
  120. package/src/lib/components/charts/linetimeseries/LineTimeSerieChartTooltip.tsx +137 -0
  121. package/src/lib/components/charts/linetimeseries/useChartData.ts +322 -0
  122. package/src/lib/components/charts/linetimeseries/useChartHover.ts +35 -0
  123. package/src/lib/components/checkbox/Checkbox.component.tsx +19 -20
  124. package/src/lib/components/constrainedtext/Constrainedtext.component.tsx +3 -2
  125. package/src/lib/components/dropdown/Dropdown.component.tsx +3 -0
  126. package/src/lib/components/error-pages/ErrorPage401.component.tsx +1 -1
  127. package/src/lib/components/error-pages/ErrorPage404.component.tsx +1 -1
  128. package/src/lib/components/error-pages/ErrorPage500.component.tsx +1 -1
  129. package/src/lib/components/form/Form.component.tsx +5 -19
  130. package/src/lib/components/icon/CustomsIcons.tsx +36 -0
  131. package/src/lib/components/icon/Icon.component.tsx +17 -137
  132. package/src/lib/components/icon/iconTable.ts +137 -0
  133. package/src/lib/components/iconhelper/IconHelper.test.tsx +2 -2
  134. package/src/lib/components/infomessage/InfoMessage.component.tsx +1 -1
  135. package/src/lib/components/inputlist/InputList.component.tsx +4 -2
  136. package/src/lib/components/inputv2/inputv2.tsx +11 -5
  137. package/src/lib/components/searchinput/SearchInput.component.tsx +1 -0
  138. package/src/lib/components/searchinput/SearchInput.test.tsx +6 -6
  139. package/src/lib/components/tablev2/Tablestyle.tsx +2 -4
  140. package/src/lib/components/text/Text.component.tsx +18 -10
  141. package/src/lib/components/tooltip/Tooltip.component.tsx +1 -1
  142. package/src/lib/index.ts +3 -2
  143. package/src/lib/next.ts +3 -3
  144. package/src/lib/utils.ts +42 -0
  145. package/stories/GlobalHealthBar/globalhealthbar.stories.tsx +1 -1
  146. package/stories/banner.stories.tsx +37 -5
  147. package/stories/inputlist.stories.tsx +18 -6
  148. package/stories/linetimeseriechart.stories.tsx +325 -6
  149. package/tsconfig.json +1 -1
  150. package/dist/components/date/FormattedDateTime.spec.d.ts +0 -2
  151. package/dist/components/date/FormattedDateTime.spec.d.ts.map +0 -1
  152. package/dist/components/date/FormattedDateTime.spec.js +0 -161
  153. package/dist/components/date/dateDiffer.spec.d.ts +0 -2
  154. package/dist/components/date/dateDiffer.spec.d.ts.map +0 -1
  155. package/dist/components/date/dateDiffer.spec.js +0 -6
@@ -1,4 +1,4 @@
1
- import { useState, useRef } from 'react';
1
+ import { useState, useRef, useMemo, useCallback } from 'react';
2
2
  import {
3
3
  Bar,
4
4
  BarChart as RechartsBarChart,
@@ -13,7 +13,7 @@ import { Stack } from '../../../spacing';
13
13
  import { chartColors, ChartColors, fontSize } from '../../../style/theme';
14
14
  import { useChartLegend } from '../legend/ChartLegendWrapper';
15
15
  import { BarchartTooltip } from './BarchartTooltip';
16
- import { formatToISONumber, getTicks } from '../common/chartUtils';
16
+ import { formatTickValue, getTicks } from '../common/chartUtils';
17
17
  import { useChartData } from './Barchart.utils';
18
18
  import {
19
19
  ChartHeader,
@@ -112,15 +112,19 @@ export const Barchart = <T extends BarchartBars>(props: BarchartProps<T>) => {
112
112
  } = props;
113
113
 
114
114
  // Create colorSet from ChartLegendWrapper
115
- const colorSet = bars?.reduce(
116
- (acc, bar) => {
117
- const color = getColor(bar.label);
118
- if (color) {
119
- acc[bar.label] = color;
120
- }
121
- return acc;
122
- },
123
- {} as Record<string, ChartColors | string>,
115
+ const colorSet = useMemo(
116
+ () =>
117
+ bars?.reduce(
118
+ (acc, bar) => {
119
+ const color = getColor(bar.label);
120
+ if (color) {
121
+ acc[bar.label] = color;
122
+ }
123
+ return acc;
124
+ },
125
+ {} as Record<string, ChartColors | string>,
126
+ ),
127
+ [bars, getColor],
124
128
  );
125
129
 
126
130
  const {
@@ -139,6 +143,113 @@ export const Barchart = <T extends BarchartBars>(props: BarchartProps<T>) => {
139
143
  stackedBarSort,
140
144
  );
141
145
  const titleWithUnit = unitLabel ? `${title} (${unitLabel})` : title;
146
+
147
+ const tickFormatter = useCallback(
148
+ (value: number) => formatTickValue(value, roundReferenceValue),
149
+ [roundReferenceValue],
150
+ );
151
+
152
+ const renderChartContent = () => {
153
+ if (isError || (!bars && !isLoading)) {
154
+ return <ChartError height={height} />;
155
+ }
156
+ if (isLoading) {
157
+ return <ChartLoading height={height} />;
158
+ }
159
+
160
+ return (
161
+ <StyledResponsiveContainer ref={chartRef} width="100%" height={height}>
162
+ <RechartsBarChart
163
+ data={rechartsData}
164
+ accessibilityLayer
165
+ barSize={
166
+ type.type === 'category'
167
+ ? type.gap === 0
168
+ ? undefined
169
+ : CHART_CONSTANTS.BAR_SIZE
170
+ : CHART_CONSTANTS.BAR_SIZE
171
+ }
172
+ height={height}
173
+ margin={CHART_CONSTANTS.CHART_MARGIN}
174
+ barCategoryGap={type.type === 'category' ? type.gap : undefined}
175
+ >
176
+ <CartesianGrid
177
+ vertical={true}
178
+ horizontal={true}
179
+ verticalPoints={[0]}
180
+ horizontalPoints={[0]}
181
+ stroke={theme.border}
182
+ fill={theme.backgroundLevel4}
183
+ strokeWidth={1}
184
+ />
185
+ {rechartsBars.map((bar) => {
186
+ const { fill, dataKey, stackId } = bar;
187
+ return (
188
+ <Bar
189
+ key={dataKey}
190
+ dataKey={dataKey}
191
+ fill={chartColors[fill] || fill}
192
+ minPointSize={stacked ? 0 : CHART_CONSTANTS.MIN_POINT_SIZE}
193
+ stackId={stackId}
194
+ isAnimationActive={false}
195
+ onMouseOver={() => setHoveredValue(dataKey)}
196
+ onMouseLeave={() => setHoveredValue(undefined)}
197
+ />
198
+ );
199
+ })}
200
+
201
+ <YAxis
202
+ interval={0}
203
+ domain={[0, topDomain]}
204
+ ticks={getTicks(roundReferenceValue, false)}
205
+ tickFormatter={tickFormatter}
206
+ axisLine={{ stroke: theme.border }}
207
+ tick={{
208
+ fill: theme.textSecondary,
209
+ fontSize: fontSize.smaller,
210
+ }}
211
+ orientation="right"
212
+ />
213
+
214
+ <XAxis
215
+ dataKey="category"
216
+ tick={(props) => (
217
+ <CustomTick
218
+ {...props}
219
+ type={type}
220
+ tickWidthOffset={CHART_CONSTANTS.TICK_WIDTH_OFFSET}
221
+ />
222
+ )}
223
+ type="category"
224
+ interval={0}
225
+ allowDataOverflow={true}
226
+ tickLine={{
227
+ stroke: theme.border,
228
+ }}
229
+ axisLine={{
230
+ stroke: theme.border,
231
+ }}
232
+ />
233
+
234
+ <Tooltip
235
+ content={(props: TooltipContentProps<number, string>) => (
236
+ <BarchartTooltip
237
+ type={type}
238
+ colorSet={colorSet}
239
+ tooltipProps={props}
240
+ hoveredValue={hoveredValue}
241
+ tooltip={tooltip}
242
+ unitLabel={unitLabel}
243
+ chartContainerRef={chartRef}
244
+ />
245
+ )}
246
+ cursor={false}
247
+ />
248
+ </RechartsBarChart>
249
+ </StyledResponsiveContainer>
250
+ );
251
+ };
252
+
142
253
  return (
143
254
  <Stack direction="vertical" style={{ gap: '0' }}>
144
255
  <ChartHeader
@@ -147,101 +258,7 @@ export const Barchart = <T extends BarchartBars>(props: BarchartProps<T>) => {
147
258
  helpTooltip={helpTooltip}
148
259
  rightTitle={rightTitle}
149
260
  />
150
- {isError || (!bars && !isLoading) ? (
151
- <ChartError height={height} />
152
- ) : isLoading ? (
153
- <ChartLoading height={height} />
154
- ) : (
155
- <StyledResponsiveContainer ref={chartRef} width="100%" height={height}>
156
- <RechartsBarChart
157
- data={rechartsData}
158
- accessibilityLayer
159
- barSize={
160
- type.type === 'category'
161
- ? type.gap === 0
162
- ? undefined
163
- : CHART_CONSTANTS.BAR_SIZE
164
- : CHART_CONSTANTS.BAR_SIZE
165
- }
166
- height={height}
167
- margin={CHART_CONSTANTS.CHART_MARGIN}
168
- barCategoryGap={type.type === 'category' ? type.gap : undefined}
169
- >
170
- <CartesianGrid
171
- vertical={true}
172
- horizontal={true}
173
- verticalPoints={[0]}
174
- horizontalPoints={[0]}
175
- stroke={theme.border}
176
- fill={theme.backgroundLevel4}
177
- strokeWidth={1}
178
- />
179
- {rechartsBars.map((bar) => {
180
- const { fill, dataKey, stackId } = bar;
181
- return (
182
- <Bar
183
- key={dataKey}
184
- dataKey={dataKey}
185
- fill={chartColors[fill] || fill}
186
- minPointSize={stacked ? 0 : CHART_CONSTANTS.MIN_POINT_SIZE}
187
- stackId={stackId}
188
- isAnimationActive={false}
189
- onMouseOver={() => setHoveredValue(dataKey)}
190
- onMouseLeave={() => setHoveredValue(undefined)}
191
- />
192
- );
193
- })}
194
-
195
- <YAxis
196
- interval={0}
197
- domain={[0, topDomain]}
198
- ticks={getTicks(roundReferenceValue, false)}
199
- tickFormatter={(value) => formatToISONumber(value)}
200
- axisLine={{ stroke: theme.border }}
201
- tick={{
202
- fill: theme.textSecondary,
203
- fontSize: fontSize.smaller,
204
- }}
205
- orientation="right"
206
- />
207
-
208
- <XAxis
209
- dataKey="category"
210
- tick={(props) => (
211
- <CustomTick
212
- {...props}
213
- type={type}
214
- tickWidthOffset={CHART_CONSTANTS.TICK_WIDTH_OFFSET}
215
- />
216
- )}
217
- type="category"
218
- interval={0}
219
- allowDataOverflow={true}
220
- tickLine={{
221
- stroke: theme.border,
222
- }}
223
- axisLine={{
224
- stroke: theme.border,
225
- }}
226
- />
227
-
228
- <Tooltip
229
- content={(props: TooltipContentProps<number, string>) => (
230
- <BarchartTooltip
231
- type={type}
232
- colorSet={colorSet}
233
- tooltipProps={props}
234
- hoveredValue={hoveredValue}
235
- tooltip={tooltip}
236
- unitLabel={unitLabel}
237
- chartContainerRef={chartRef}
238
- />
239
- )}
240
- cursor={false}
241
- />
242
- </RechartsBarChart>
243
- </StyledResponsiveContainer>
244
- )}
261
+ {renderChartContent()}
245
262
  </Stack>
246
263
  );
247
264
  };
@@ -8,7 +8,7 @@ import { Text } from '../../text/Text.component';
8
8
  import { ConstrainedText } from '../../constrainedtext/Constrainedtext.component';
9
9
  import { FormattedDateTime } from '../../date/FormattedDateTime';
10
10
  import { formatXAxisDate, maxWidthTooltip } from './chartUtils';
11
- import { TimeType } from '../types';
11
+ import { TimeType, CategoryType } from '../types';
12
12
 
13
13
  /**
14
14
  * Styled ResponsiveContainer for charts
@@ -117,14 +117,14 @@ export const ChartHeader = ({
117
117
  };
118
118
 
119
119
  interface CustomTickProps {
120
- x: number;
121
- y: number;
120
+ x: number | string;
121
+ y: number | string;
122
122
  payload: {
123
123
  value: number;
124
124
  };
125
- visibleTicksCount: number;
126
- width: number;
127
- type: TimeType;
125
+ visibleTicksCount?: number;
126
+ width?: number | string;
127
+ type: TimeType | CategoryType;
128
128
  tickWidthOffset?: number;
129
129
  }
130
130
 
@@ -142,20 +142,24 @@ export const CustomTick = ({
142
142
  tickWidthOffset = 4,
143
143
  }: CustomTickProps) => {
144
144
  const theme = useTheme();
145
- const tickWidth = width / visibleTicksCount - tickWidthOffset;
146
- const centerX = x - tickWidth / 2;
145
+ const numX = typeof x === 'string' ? parseFloat(x) : x;
146
+ const numY = typeof y === 'string' ? parseFloat(y) : y;
147
+ const numWidth = typeof width === 'string' ? parseFloat(width) : (width ?? 0);
148
+ const tickCount = visibleTicksCount ?? 1;
149
+ const tickWidth = numWidth / tickCount - tickWidthOffset;
150
+ const centerX = numX - tickWidth / 2;
147
151
 
148
152
  const duration =
149
153
  type.type === 'time'
150
154
  ? (type.timeRange.endDate.getTime() -
151
- type.timeRange.startDate.getTime()) /
152
- 1000
155
+ type.timeRange.startDate.getTime()) /
156
+ 1000
153
157
  : 0;
154
158
 
155
159
  return (
156
160
  <foreignObject
157
161
  x={centerX}
158
- y={y - 10}
162
+ y={numY - 10}
159
163
  width={tickWidth}
160
164
  height={30}
161
165
  style={{
@@ -23,16 +23,16 @@ describe('getRoundReferenceValue', () => {
23
23
  expect(getRoundReferenceValue(9)).toBe(9); // 9 → 9.9 → 9 (magnitude 1, remainder 0.9)
24
24
 
25
25
  // Larger values get 10% buffer applied
26
- expect(getRoundReferenceValue(15)).toBe(10); // 15 → 16.5, remainder 5, incremented 20 > 16.5, so round down to 10
27
- expect(getRoundReferenceValue(35)).toBe(30); // 35 → 38.5, remainder 5, incremented 40 > 38.5, so round down to 30
26
+ expect(getRoundReferenceValue(15)).toBe(15); // 15 → increment 5, remainder 0, return 15
27
+ expect(getRoundReferenceValue(35)).toBe(35); // 35 → increment 5, remainder 0, return 35
28
28
  expect(getRoundReferenceValue(75)).toBe(80); // 75 → 82.5, remainder 5, incremented 80 <= 82.5, so round up to 80
29
- expect(getRoundReferenceValue(150)).toBe(150); // 150 → 165, remainder 0, so return 150
30
- expect(getRoundReferenceValue(350)).toBe(350); // 350 → 385, remainder 0, so return 350
31
- expect(getRoundReferenceValue(750)).toBe(750); // 750 → 825, remainder 0, so return 750
32
- expect(getRoundReferenceValue(1500)).toBe(1500); // 1500 → 1650, remainder 0, so return 1500
33
- expect(getRoundReferenceValue(3500)).toBe(3500); // 3500 → 3850, remainder 0, so return 3500
34
- expect(getRoundReferenceValue(7500)).toBe(7500); // 7500 → 8250, remainder 0, so return 7500
35
- expect(getRoundReferenceValue(15000)).toBe(15000); // 15000 → 16500, remainder 0, so return 15000
29
+ expect(getRoundReferenceValue(150)).toBe(150); // increment 50, remainder 0
30
+ expect(getRoundReferenceValue(350)).toBe(350); // increment 50, remainder 0
31
+ expect(getRoundReferenceValue(750)).toBe(800); // increment 100, remainder 50, rounds up
32
+ expect(getRoundReferenceValue(1500)).toBe(1500); // increment 500, remainder 0
33
+ expect(getRoundReferenceValue(3500)).toBe(3500); // increment 500, remainder 0
34
+ expect(getRoundReferenceValue(7500)).toBe(8000); // increment 1000, remainder 500, rounds up
35
+ expect(getRoundReferenceValue(15000)).toBe(15000); // increment 5000, remainder 0
36
36
  });
37
37
  });
38
38
 
@@ -95,8 +95,23 @@ describe('getUnitLabel', () => {
95
95
  });
96
96
 
97
97
  describe('addMissingDataPoint', () => {
98
+ it('should generate placeholder timestamps when original data is empty', () => {
99
+ const result = addMissingDataPoint([], 0, 100, 10);
100
+ expect(result).toEqual([
101
+ [0, NAN_STRING],
102
+ [10, NAN_STRING],
103
+ [20, NAN_STRING],
104
+ [30, NAN_STRING],
105
+ [40, NAN_STRING],
106
+ [50, NAN_STRING],
107
+ [60, NAN_STRING],
108
+ [70, NAN_STRING],
109
+ [80, NAN_STRING],
110
+ [90, NAN_STRING],
111
+ ]);
112
+ });
113
+
98
114
  it('should return empty array for invalid inputs', () => {
99
- expect(addMissingDataPoint([], 0, 100, 10)).toEqual([]);
100
115
  expect(addMissingDataPoint([[10, 5]], undefined, 100, 10)).toEqual([]);
101
116
  expect(addMissingDataPoint([[10, 5]], 0, 0, 10)).toEqual([]);
102
117
  expect(addMissingDataPoint([[10, 5]], -1, 100, 10)).toEqual([]);
@@ -264,8 +279,8 @@ describe('normalizeChartDataWithUnits', () => {
264
279
  );
265
280
 
266
281
  expect(result.unitLabel).toBe('B');
267
- // 680 / 1 = 680 → getRoundReferenceValue(680) = 680
268
- expect(result.topValue).toBe(680);
282
+ // 680 / 1 = 680 → getRoundReferenceValue(680) = 700 (rounds up since 80 >= 50)
283
+ expect(result.topValue).toBe(700);
269
284
  expect(result.rechartsData).toEqual([
270
285
  { category: 'category1', success: 680 },
271
286
  ]);
@@ -1,6 +1,7 @@
1
1
  import { NAN_STRING } from '../../constants';
2
2
  import { TooltipDateFormat } from './ChartTooltip';
3
3
  import { UnitRange } from '../types';
4
+ import { formatISONumber } from '../../../utils';
4
5
 
5
6
  /* -------------------------------------------------------------------------- */
6
7
  /* constants */
@@ -12,6 +13,31 @@ export const maxWidthTooltip = { maxWidth: '20rem' };
12
13
  /* utils functions */
13
14
  /* -------------------------------------------------------------------------- */
14
15
 
16
+ /**
17
+ * Get the appropriate rounding increment based on value magnitude.
18
+ * - For values < 5 * magnitude: use half magnitude (finer granularity)
19
+ * - For values >= 5 * magnitude: use full magnitude
20
+ *
21
+ * Examples:
22
+ * - 150 → increment 50 (150 < 500, so use 100/2)
23
+ * - 550 → increment 100 (550 >= 500, so use 100)
24
+ * - 1500 → increment 500 (1500 < 5000, so use 1000/2)
25
+ * - 5500 → increment 1000 (5500 >= 5000, so use 1000)
26
+ */
27
+ const getIncrement = (value: number): number => {
28
+ if (value < 10) return 1;
29
+
30
+ const magnitude = Math.pow(10, Math.floor(Math.log10(value)));
31
+
32
+ // If value is in lower half of magnitude range, use half magnitude
33
+ if (value < 5 * magnitude) {
34
+ return magnitude / 2;
35
+ }
36
+
37
+ // Otherwise use full magnitude
38
+ return magnitude;
39
+ };
40
+
15
41
  /**
16
42
  * Round a value to a nice number for chart display
17
43
  * Used by Barchart and LineTimeSerieChart for Y-axis scaling
@@ -23,29 +49,29 @@ export const getRoundReferenceValue = (value: number): number => {
23
49
  const bufferedValue = value * 1.1;
24
50
 
25
51
  if (value >= 10) {
26
- const remainder = value % 10;
27
- const incremented = value + (10 - remainder);
28
-
29
- // If the remainder is less than 5, round down to the nearest 10
30
- if (remainder < 5) {
31
- return value - remainder;
52
+ const increment = getIncrement(value);
53
+ const remainder = value % increment;
54
+ const roundedDown = value - remainder;
55
+ const roundedUp = roundedDown + increment;
56
+
57
+ // If remainder is less than half the increment, round down
58
+ if (remainder < increment / 2) {
59
+ return roundedDown;
32
60
  }
33
61
 
34
- // If incrementing would exceed the buffered max, also round down
35
- if (incremented > bufferedValue) {
36
- return value - remainder;
62
+ // If rounding up would exceed the buffered max, round down
63
+ if (roundedUp > bufferedValue) {
64
+ return roundedDown;
37
65
  }
38
66
 
39
- // Otherwise, round up to the next 10
40
- return incremented;
67
+ // Otherwise, round up
68
+ return roundedUp;
41
69
  }
42
70
 
71
+ // For values < 10, use the magnitude-based approach
43
72
  const magnitude = Math.pow(10, Math.floor(Math.log10(value)));
44
-
45
73
  const remainder = bufferedValue % magnitude;
46
74
 
47
- // Round to nice numbers based on normalized value
48
- // appearance for small values
49
75
  return remainder === 0 ? bufferedValue : bufferedValue - remainder;
50
76
  };
51
77
 
@@ -53,7 +79,10 @@ export const getRoundReferenceValue = (value: number): number => {
53
79
  * Generate tick values for Y-axis
54
80
  * Used by Barchart and LineTimeSerieChart
55
81
  */
56
- export const getTicks = (topValue: number, isSymmetrical: boolean) => {
82
+ export const getTicks = (
83
+ topValue: number,
84
+ isSymmetrical: boolean,
85
+ ): number[] => {
57
86
  const possibleTickNumbers = [4, 3];
58
87
  const numberOfTicks =
59
88
  possibleTickNumbers.find((number) => topValue % (number - 1) === 0) || 3; // Default to 3 ticks if no match
@@ -191,7 +220,7 @@ export const normalizeChartDataWithUnits = <T extends Record<string, any>>(
191
220
  * Missing data points are only added when the gap between consecutive data points is bigger than 2 intervals
192
221
  * Used by LineTimeSerieChart and Sparkline
193
222
  *
194
- * @param orginalValues - The array of the data points are already sorted according to the time series
223
+ * @param originalValues - The array of the data points are already sorted according to the time series
195
224
  * @param startingTimeStamp - The starting timestamp in seconds
196
225
  * @param sampleDuration - The time span value in seconds
197
226
  * @param sampleInterval - The time difference between two data points in seconds
@@ -214,9 +243,17 @@ export function addMissingDataPoint(
214
243
  return [];
215
244
  }
216
245
 
217
- // If there are no original values, return empty array
246
+ // If there are no original values, generate placeholder timestamps for the entire duration
218
247
  if (originalValues.length === 0) {
219
- return [];
248
+ const newValues: [number, number | string | null][] = [];
249
+ for (
250
+ let i = startingTimeStamp;
251
+ i < startingTimeStamp + sampleDuration;
252
+ i += sampleInterval
253
+ ) {
254
+ newValues.push([i, NAN_STRING]);
255
+ }
256
+ return newValues;
220
257
  }
221
258
 
222
259
  const newValues: [number, number | string | null][] = [];
@@ -326,9 +363,16 @@ export const getTooltipDateFormat: (duration: number) => TooltipDateFormat = (
326
363
  }
327
364
  };
328
365
 
329
- export const formatToISONumber = (value: number): string => {
330
- const formattedValue = new Intl.NumberFormat('fr-FR')
331
- .format(value)
332
- .replace(',', '.');
333
- return formattedValue;
366
+ /**
367
+ * Formats a tick value for chart Y-axis display.
368
+ * - Fixed decimals for alignment when topValue < 1 (e.g., 0.1 → 0.10)
369
+ * - Compact notation for large values (>= 10k)
370
+ */
371
+ export const formatTickValue = (value: number, topValue: number): string => {
372
+ const decimals = topValue < 1 ? Math.ceil(-Math.log10(topValue)) + 1 : 2;
373
+ return formatISONumber(value, {
374
+ decimals,
375
+ fixedDecimals: topValue < 1,
376
+ compact: topValue >= 10000,
377
+ });
334
378
  };
@@ -12,7 +12,7 @@ export { LineTimeSerieChart } from './linetimeseries/LineTimeSerieChart';
12
12
  export type {
13
13
  LineChartProps,
14
14
  Serie,
15
- } from './linetimeseries/LineTimeSerieChart';
15
+ } from './linetimeseries/LineTimeSerieChart.types';
16
16
 
17
17
  export { GlobalHealthBar } from './globalhealthbar/GlobalHealthBar';
18
18
  export type { GlobalHealthProps } from './globalhealthbar/GlobalHealthBar';