@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 React, { useCallback, useMemo, useRef, useState } from 'react';
1
+ import React, { useCallback, useRef } from 'react';
2
2
  import {
3
3
  CartesianGrid,
4
4
  Line,
@@ -15,25 +15,13 @@ import { fontSize } from '../../../style/theme';
15
15
  import { IconHelp } from '../../iconhelper/IconHelper';
16
16
  import { Loader } from '../../loader/Loader.component';
17
17
  import { ChartTitleText } from '../../text/Text.component';
18
- import { LegendShape } from '../legend/ChartLegend';
19
- import { useChartLegend } from '../legend/ChartLegendWrapper';
20
18
  import { StyledResponsiveContainer } from '../common/SharedComponents';
21
- import {
22
- ChartTooltipHeader,
23
- ChartTooltipItem,
24
- ChartTooltipItemsContainer,
25
- ChartTooltipPortal,
26
- ChartTooltipSeparator,
27
- TooltipHeader,
28
- } from '../common/ChartTooltip';
29
- import {
30
- addMissingDataPoint,
31
- formatToISONumber,
32
- getTicks,
33
- maxWidthTooltip,
34
- normalizeChartDataWithUnits,
35
- } from '../common/chartUtils';
19
+ import { formatTickValue, getTicks, maxWidthTooltip } from '../common/chartUtils';
36
20
  import { formatXAxisLabel } from './LineTimeSerieChart.utils';
21
+ import { LineChartProps } from './LineTimeSerieChart.types';
22
+ import { LineTimeSerieChartTooltip } from './LineTimeSerieChartTooltip';
23
+ import { useChartHover } from './useChartHover';
24
+ import { useChartData } from './useChartData';
37
25
 
38
26
  const LineTemporalChartWrapper = styled.div`
39
27
  display: flex;
@@ -41,185 +29,22 @@ const LineTemporalChartWrapper = styled.div`
41
29
  justify-content: flex-start;
42
30
  `;
43
31
 
44
- export type Serie = {
45
- // the name of the resource
46
- resource: string;
47
- // the original data format from prometheus, extend the value to include number type.
48
- data: [number, number | string | null][];
49
- // it's mandatory to display tooltip label in the tooltip
50
- getTooltipLabel: (metricPrefix?: string, resource?: string) => string;
51
- // the name of the metric prefix with read, write, in, out
52
- metricPrefix?: string;
53
- // to specify if the line is dash
54
- isLineDashed?: boolean;
55
- };
56
-
57
- type NonSymmetricalChartSerie = {
58
- yAxisType?: 'default' | 'percentage';
59
- series: Serie[] | undefined;
60
- };
61
-
62
- // The symmetrical chart props are used to display two series on the same chart, such as in/out, write/read
63
- type SymmetricalChartSerie = {
64
- yAxisType: 'symmetrical';
65
- series:
66
- | {
67
- above: Serie[] | undefined;
68
- below: Serie[] | undefined;
69
- }
70
- | undefined;
71
- };
72
-
73
- export type LineChartProps = (
74
- | NonSymmetricalChartSerie
75
- | SymmetricalChartSerie
76
- ) & {
77
- title: string;
78
- height: number;
79
- startingTimeStamp: number;
80
- interval: number;
81
- duration: number;
82
- unitRange?: {
83
- threshold: number;
84
- label: string;
85
- }[];
86
- syncId?: string;
87
- isLoading?: boolean;
88
- /**
89
- * The format of the x axis, default is 'date-time' which is like 01 Sep 16:00
90
- * If you want to display the date only, you can set it to 'date' which is like 2025-09-01
91
- * This will affect the format of the tooltip as well
92
- */
93
- timeFormat?: 'date-time' | 'date';
94
- yAxisTitle?: string;
95
- helpText?: string;
96
- renderTooltip?: (
97
- tooltipProps: TooltipContentProps<number, string>,
98
- unitLabel?: string,
99
- duration?: number,
100
- ) => React.ReactNode;
101
- };
102
-
103
- const LineTimeSerieChartTooltip = ({
104
- unitLabel,
105
- duration,
106
- isChartActive,
107
- tooltipProps,
108
- renderTooltip,
109
- hoveredValue,
110
- isSymmetrical,
111
- chartContainerRef,
112
- }: {
113
- tooltipProps: TooltipContentProps<number, string>;
114
- unitLabel?: string;
115
- duration: number;
116
- isChartActive?: boolean;
117
- renderTooltip?: (
118
- tooltipProps: TooltipContentProps<number, string>,
119
- unitLabel?: string,
120
- duration?: number,
121
- ) => React.ReactNode;
122
- hoveredValue?: string;
123
- isSymmetrical?: boolean;
124
- chartContainerRef: React.RefObject<HTMLDivElement>;
125
- }) => {
126
- const { active, payload, label, coordinate } = tooltipProps;
127
-
128
- if (!active || !payload || !payload.length || !label || !isChartActive)
129
- return null;
130
-
131
- const tooltipContent = renderTooltip ? (
132
- renderTooltip(tooltipProps, unitLabel, duration)
133
- ) : (
134
- <>
135
- <ChartTooltipHeader>
136
- <TooltipHeader duration={duration} value={label} />
137
- </ChartTooltipHeader>
138
- <ChartTooltipItemsContainer>
139
- {(() => {
140
- // We can't use the default itemSorter method because it's a custom tooltip.
141
- // Sort the payload here instead
142
- const sortedPayload = [...payload].sort((a, b) => {
143
- const aValue = a.value;
144
- const bValue = b.value;
145
-
146
- if (aValue >= 0 && bValue >= 0) {
147
- return bValue - aValue; // Higher positive values first
148
- }
149
- if (aValue < 0 && bValue < 0) {
150
- return bValue - aValue; // Lower negative values first
151
- }
152
- return bValue - aValue; // Positives before negatives
153
- });
154
-
155
- // Find the transition point between positive and negative values
156
- const separatorIndex = sortedPayload.findIndex(
157
- (entry) => entry.value < 0,
158
- );
159
- const hasBothPositiveAndNegative =
160
- separatorIndex > 0 && separatorIndex < sortedPayload.length;
161
-
162
- return sortedPayload.map((entry, index) => {
163
- const legendIcon = (
164
- <LegendShape
165
- color={entry.color}
166
- shape="line"
167
- chartColors={{ [entry.color]: entry.color }}
168
- />
169
- );
170
-
171
- const isHovered = entry.name === hoveredValue;
172
-
173
- const formattedValue = !Number.isFinite(entry.value)
174
- ? '-'
175
- : `${entry.value.toFixed(2)}${unitLabel ? ` ${unitLabel}` : ''}`;
176
-
177
- return (
178
- <React.Fragment key={index}>
179
- {/* Add separator between positive and negative values for symmetrical charts */}
180
- {isSymmetrical &&
181
- hasBothPositiveAndNegative &&
182
- index === separatorIndex && <ChartTooltipSeparator />}
183
- <ChartTooltipItem
184
- label={entry.name}
185
- value={formattedValue}
186
- legendIcon={legendIcon}
187
- isHovered={isHovered}
188
- />
189
- </React.Fragment>
190
- );
191
- });
192
- })()}
193
- </ChartTooltipItemsContainer>
194
- </>
195
- );
196
-
197
- return (
198
- <ChartTooltipPortal
199
- coordinate={coordinate}
200
- chartContainerRef={chartContainerRef}
201
- isVisible={active && isChartActive}
202
- >
203
- {tooltipContent}
204
- </ChartTooltipPortal>
205
- );
206
- };
207
-
208
- const isSymmetricalSeries = (
209
- series: Serie[] | { above: Serie[] | undefined; below: Serie[] | undefined },
210
- ): series is { above: Serie[]; below: Serie[] } => {
211
- return 'above' in series && 'below' in series;
212
- };
213
-
214
32
  /**
215
- * Props for LineTimeSerieChart component
216
- * @param series - The data series to display
33
+ * LineTimeSerieChart - A time series line chart component
34
+ *
35
+ * @param series - The data series to display (can be symmetrical with above/below)
217
36
  * @param title - The title of the chart
218
37
  * @param height - The height of the chart in pixels
219
38
  * @param startingTimeStamp - Starting timestamp in seconds
220
39
  * @param interval - Interval between data points in seconds
221
40
  * @param duration - Total duration of the chart in seconds
222
- *
41
+ * @param unitRange - Configuration for automatic unit scaling
42
+ * @param syncId - ID to synchronize multiple charts
43
+ * @param isLoading - Whether to show loading state
44
+ * @param yAxisType - Type of Y-axis: 'default', 'percentage', or 'symmetrical'
45
+ * @param yAxisTitle - Label for the Y-axis
46
+ * @param helpText - Help text shown as tooltip
47
+ * @param renderTooltip - Custom tooltip renderer
223
48
  */
224
49
  export function LineTimeSerieChart({
225
50
  series,
@@ -230,244 +55,48 @@ export function LineTimeSerieChart({
230
55
  duration,
231
56
  unitRange,
232
57
  isLoading = false,
233
- timeFormat = 'date-time',
234
58
  yAxisType = 'default',
235
59
  yAxisTitle,
236
60
  helpText,
237
61
  syncId,
238
62
  renderTooltip,
239
- ...rest
240
63
  }: LineChartProps) {
241
64
  const theme = useTheme();
242
- const { getColor, selectedResources } = useChartLegend();
243
65
  const chartRef = useRef(null);
244
66
 
245
- const [isChartActive, setIsChartActive] = useState(false);
246
- const [hoveredValue, setHoveredValue] = useState<string | undefined>(
247
- undefined,
248
- );
249
- const chartData = useMemo(() => {
250
- // Guard against empty/undefined series data
251
- if (!series || (Array.isArray(series) && series.length === 0)) {
252
- return [];
253
- }
254
-
255
- // Handle symmetrical series with empty above/below arrays
256
- if (isSymmetricalSeries(series)) {
257
- if (
258
- (!series.above || series.above.length === 0) &&
259
- (!series.below || series.below.length === 0)
260
- ) {
261
- return [];
262
- }
263
- }
264
-
265
- // 1. Add missing data points
266
- const normalizedSeries =
267
- yAxisType === 'symmetrical' && isSymmetricalSeries(series)
268
- ? {
269
- above: series.above
270
- ? series.above.map((line) => ({
271
- ...line,
272
- data: addMissingDataPoint(
273
- line.data,
274
- startingTimeStamp,
275
- duration,
276
- interval,
277
- ),
278
- }))
279
- : [],
280
- // Convert positive values to negative values
281
- below: series.below
282
- ? series.below.map((line) => ({
283
- ...line,
284
- data: addMissingDataPoint(
285
- line.data,
286
- startingTimeStamp,
287
- duration,
288
- interval,
289
- ).map(
290
- ([timestamp, value]) =>
291
- [
292
- timestamp,
293
- value === null ? null : `-${Number(value)}`,
294
- ] as [number, string | null],
295
- ),
296
- }))
297
- : [],
298
- }
299
- : (series as Serie[]).map((line) => ({
300
- ...line,
301
- data: addMissingDataPoint(
302
- line.data,
303
- startingTimeStamp,
304
- duration,
305
- interval,
306
- ),
307
- }));
308
-
309
- // 2. Convert directly to Recharts format
310
- // Initialize an object to hold data points by timestamp
311
- const dataPointsByTime: Record<
312
- number,
313
- { timestamp: number } & Record<string, number | null>
314
- > = {};
315
- const seriesToProcess =
316
- yAxisType === 'symmetrical' && isSymmetricalSeries(normalizedSeries)
317
- ? [...normalizedSeries.above, ...normalizedSeries.below]
318
- : (normalizedSeries as Serie[]);
319
-
320
- seriesToProcess.forEach((serie) => {
321
- const label = serie.getTooltipLabel(serie.metricPrefix, serie.resource);
322
-
323
- serie.data.forEach((point) => {
324
- const timestamp =
325
- typeof point[0] === 'number' ? point[0] * 1000 : Number(point[0]);
326
- const value = point[1];
327
- // Initialize this timestamp if it doesn't exist
328
- if (!dataPointsByTime[timestamp]) {
329
- dataPointsByTime[timestamp] = { timestamp };
330
- }
331
- // Add this metric's value to the data point, and convert the value to a number if it's a string
332
- dataPointsByTime[timestamp][label] =
333
- typeof value === 'string' ? Number(value) : value;
334
- });
335
- });
336
- // Convert object to array for Recharts
337
- return Object.values(dataPointsByTime).sort(
338
- (
339
- a: { timestamp: number } & Record<string, number | null>,
340
- b: { timestamp: number } & Record<string, number | null>,
341
- ) => (a.timestamp as number) - (b.timestamp as number),
342
- );
343
- }, [series, startingTimeStamp, duration, interval, yAxisType]);
344
-
345
- // Calculate evenly spaced ticks that avoid the very beginning and end
346
- const xAxisTicks = useMemo(() => {
347
- if (!chartData || chartData.length === 0) return [];
348
-
349
- const timestamps: number[] = chartData.map((d) => d.timestamp);
350
- const minTimestamp = Math.min(...timestamps);
351
- const maxTimestamp = Math.max(...timestamps);
352
-
353
- const timeRange = maxTimestamp - minTimestamp;
354
- // Add padding to avoid labels at the very edges (10% padding on each side)
355
- const padding = timeRange * 0.1;
356
- const paddedStart = minTimestamp + padding;
357
- const paddedEnd = maxTimestamp - padding;
358
- const paddedRange = paddedEnd - paddedStart;
359
-
360
- // Create 5 evenly spaced ticks within the padded range
361
- const numTicks = 5;
362
- const tickInterval = paddedRange / (numTicks - 1);
363
-
364
- const evenlySpacedTicks = Array.from(
365
- { length: numTicks },
366
- (_, index) => paddedStart + index * tickInterval,
367
- );
368
-
369
- return evenlySpacedTicks;
370
- }, [chartData]);
371
-
372
- // 3. Transform the data base on the valuebase
373
- const { topValue, unitLabel, rechartsData, topDomain } = useMemo(() => {
374
- const values = chartData.flatMap((dataPoint) =>
375
- Object.entries(dataPoint)
376
- .filter(([key]) => key !== 'timestamp')
377
- .map(([_, value]) => {
378
- if (value === null || value === undefined) return null;
379
- const num = typeof value === 'string' ? Number(value) : value;
380
- return !isNaN(num) ? num : null;
381
- })
382
- .filter((value): value is number => value !== null),
383
- );
384
-
385
- // Guard against empty values array
386
- if (values.length === 0) {
387
- return {
388
- topValue: 100, // Default value for empty charts
389
- unitLabel: yAxisType === 'percentage' ? '%' : undefined,
390
- rechartsData: [],
391
- topDomain: 100,
392
- };
393
- }
394
-
395
- const top = Math.abs(Math.max(...values));
396
- const bottom = Math.abs(Math.min(...values));
397
- const maxValue = Math.max(top, bottom);
398
-
399
- // Use shared normalization function
400
- const result = normalizeChartDataWithUnits(
401
- chartData,
402
- maxValue,
403
- unitRange,
404
- 'timestamp', // LineTimeSerieChart uses 'timestamp' as the key to exclude
405
- );
406
-
407
- // For percentage charts, ensure Y-axis goes to at least 100%
408
- const topDomain =
409
- yAxisType === 'percentage'
410
- ? Math.max(result.topDomain, 100)
411
- : result.topDomain;
412
-
413
- return {
414
- topValue: yAxisType === 'percentage' ? Math.max(result.topValue, 100) : result.topValue,
415
- unitLabel: result.unitLabel ?? (yAxisType === 'percentage' ? '%' : undefined),
416
- rechartsData: result.rechartsData,
417
- topDomain,
418
- };
419
- }, [chartData, yAxisType, unitRange]);
420
-
421
- // Group series by resource and create color mapping
422
- const { colorMapping, groupedSeries } = useMemo(() => {
423
- const mapping: Record<string, string> = {};
424
-
425
- // Guard against empty/undefined series
426
- if (!series) {
427
- return { colorMapping: mapping, groupedSeries: {} };
428
- }
429
-
430
- const allSeries = isSymmetricalSeries(series)
431
- ? [...(series.above || []), ...(series.below || [])]
432
- : (series as Serie[]);
433
-
434
- // Group series by resource
435
- const groups = allSeries
436
- .filter((serie) => selectedResources.includes(serie.resource))
437
- .reduce(
438
- (acc, serie) => {
439
- const key = serie.resource;
440
- if (!acc[key]) {
441
- acc[key] = [];
442
- }
443
- acc[key].push(serie);
444
- return acc;
445
- },
446
- {} as Record<string, Serie[]>,
447
- );
448
-
449
- // Get colors from the ChartLegend context
450
- Object.keys(groups).forEach((resource) => {
451
- const color = getColor(resource);
452
- if (color) {
453
- mapping[resource] = color;
454
- } else {
455
- console.warn(`Color not defined for resource: ${resource}`);
456
- }
457
- });
458
-
459
- return {
460
- colorMapping: mapping,
461
- groupedSeries: groups,
462
- };
463
- }, [series, getColor, selectedResources]);
464
-
465
- // Format time for display the tick in the x axis
67
+ // Hover state management for tooltip display
68
+ const { handleMouseEnter, handleMouseLeave, chartId } = useChartHover();
69
+
70
+ // Process chart data
71
+ const {
72
+ rechartsData,
73
+ topDomain,
74
+ topValue,
75
+ unitLabel,
76
+ xAxisTicks,
77
+ linesToRender,
78
+ belowSeriesLabels,
79
+ } = useChartData({
80
+ series,
81
+ startingTimeStamp,
82
+ duration,
83
+ interval,
84
+ yAxisType,
85
+ unitRange,
86
+ });
87
+
88
+ // Format X-axis labels based on duration
466
89
  const formatXAxisLabelCallback = useCallback(
467
90
  (timestamp: number) => formatXAxisLabel(timestamp, duration),
468
91
  [duration],
469
92
  );
470
93
 
94
+ // Format Y-axis tick values
95
+ const tickFormatter = useCallback(
96
+ (value: number) => formatTickValue(value, topValue),
97
+ [topValue],
98
+ );
99
+
471
100
  return (
472
101
  <LineTemporalChartWrapper>
473
102
  <Stack gap="r4">
@@ -479,109 +108,100 @@ export function LineTimeSerieChart({
479
108
  )}
480
109
  {isLoading && <Loader />}
481
110
  </Stack>
482
- <div>
483
- <StyledResponsiveContainer width="100%" height={height}>
484
- <LineChart
485
- data={rechartsData}
486
- ref={chartRef}
487
- margin={{ top: 0, right: 0, bottom: 0, left: 0 }}
488
- aria-label={`Time series chart for ${title}`}
489
- syncId={syncId}
490
- onMouseEnter={() => setIsChartActive(true)}
491
- onMouseLeave={() => setIsChartActive(false)}
492
- accessibilityLayer
493
- >
494
- <CartesianGrid
495
- vertical={true}
496
- horizontal={true}
497
- verticalPoints={[0]}
498
- horizontalPoints={[0]}
499
- stroke={theme.border}
500
- fill={theme.backgroundLevel4}
501
- strokeWidth={1}
502
- />
503
- <XAxis
504
- dataKey="timestamp"
505
- type="number"
506
- domain={['dataMin', 'dataMax']}
507
- ticks={xAxisTicks}
508
- tickFormatter={formatXAxisLabelCallback}
509
- tickCount={5}
510
- tick={{
511
- fill: theme.textSecondary,
512
- fontSize: fontSize.smaller,
513
- }}
514
- axisLine={{ stroke: theme.border }}
515
- />
516
- <YAxis
517
- orientation="right"
518
- label={{
519
- value: yAxisTitle,
520
- angle: 90,
521
- dx: 20,
522
- style: {
523
- fill: theme.textSecondary,
524
- fontSize: fontSize.smaller,
525
- },
526
- }}
527
- domain={
528
- yAxisType === 'symmetrical'
529
- ? [-topDomain, topDomain]
530
- : [0, topDomain]
531
- }
532
- axisLine={{ stroke: theme.border }}
533
- tick={{
111
+
112
+ <StyledResponsiveContainer width="100%" height={height}>
113
+ <LineChart
114
+ ref={chartRef}
115
+
116
+ data={rechartsData}
117
+ margin={{ top: 0, right: 0, bottom: 0, left: 0 }}
118
+ aria-label={`Time series chart for ${title}`}
119
+ syncId={syncId}
120
+ accessibilityLayer
121
+ onMouseEnter={handleMouseEnter}
122
+ onMouseLeave={handleMouseLeave}
123
+ >
124
+ <CartesianGrid
125
+ vertical={true}
126
+ horizontal={true}
127
+ verticalPoints={[0]}
128
+ horizontalPoints={[0]}
129
+ stroke={theme.border}
130
+ fill={theme.backgroundLevel4}
131
+ strokeWidth={1}
132
+ />
133
+ <XAxis
134
+ dataKey="timestamp"
135
+ type="number"
136
+ domain={['dataMin', 'dataMax']}
137
+ ticks={xAxisTicks}
138
+ tickFormatter={formatXAxisLabelCallback}
139
+ tickCount={5}
140
+ tick={{
141
+ fill: theme.textSecondary,
142
+ fontSize: fontSize.smaller,
143
+ }}
144
+ axisLine={{ stroke: theme.border }}
145
+ />
146
+ <YAxis
147
+ orientation="right"
148
+ label={{
149
+ value: yAxisTitle,
150
+ angle: 90,
151
+ dx: 20,
152
+ style: {
534
153
  fill: theme.textSecondary,
535
154
  fontSize: fontSize.smaller,
536
- }}
537
- tickFormatter={(value) => formatToISONumber(value)}
538
- ticks={getTicks(topValue, yAxisType === 'symmetrical')}
539
- interval={0}
540
- />
541
- <Tooltip
542
- content={(props: TooltipContentProps<number, string>) => (
543
- <LineTimeSerieChartTooltip
544
- unitLabel={unitLabel}
545
- duration={duration}
546
- renderTooltip={renderTooltip}
547
- isSymmetrical={yAxisType === 'symmetrical'}
548
- tooltipProps={props}
549
- isChartActive={isChartActive}
550
- hoveredValue={hoveredValue}
551
- chartContainerRef={chartRef}
552
- />
553
- )}
554
- />
555
- {/* Add horizontal line at y=0 for symmetrical charts */}
556
- {yAxisType === 'symmetrical' && (
557
- <ReferenceLine y={0} stroke={theme.border} />
558
- )}
559
-
560
- {/* Chart lines */}
561
- {Object.entries(groupedSeries).map(([resource, resourceSeries]) =>
562
- resourceSeries.map((serie, serieIndex) => {
563
- const label = serie.getTooltipLabel(
564
- serie.metricPrefix,
565
- serie.resource,
566
- );
567
- return (
568
- <Line
569
- key={`${title}-${resource}-${serieIndex}`}
570
- type="monotone"
571
- dataKey={label}
572
- stroke={colorMapping[resource]}
573
- dot={false}
574
- isAnimationActive={false}
575
- strokeDasharray={serie.isLineDashed ? '4 4' : undefined}
576
- onMouseEnter={() => setHoveredValue(label)}
577
- onMouseLeave={() => setHoveredValue(undefined)}
578
- />
579
- );
580
- }),
155
+ },
156
+ }}
157
+ domain={
158
+ yAxisType === 'symmetrical'
159
+ ? [-topDomain, topDomain]
160
+ : [0, topDomain]
161
+ }
162
+ allowDataOverflow={true}
163
+ axisLine={{ stroke: theme.border }}
164
+ tick={{
165
+ fill: theme.textSecondary,
166
+ fontSize: fontSize.smaller,
167
+ }}
168
+ tickFormatter={tickFormatter}
169
+ ticks={getTicks(topValue, yAxisType === 'symmetrical')}
170
+ interval={0}
171
+ />
172
+ <Tooltip
173
+ content={(props: TooltipContentProps<number, string>) => (
174
+ <LineTimeSerieChartTooltip
175
+ unitLabel={unitLabel}
176
+ duration={duration}
177
+ renderTooltip={renderTooltip}
178
+ isSymmetrical={yAxisType === 'symmetrical'}
179
+ belowSeriesLabels={belowSeriesLabels}
180
+ tooltipProps={props}
181
+ chartContainerRef={chartRef}
182
+ chartId={chartId}
183
+ />
581
184
  )}
582
- </LineChart>
583
- </StyledResponsiveContainer>
584
- </div>
585
- </LineTemporalChartWrapper>
185
+ />
186
+ {/* Add horizontal line at y=0 for symmetrical charts */}
187
+ {yAxisType === 'symmetrical' && (
188
+ <ReferenceLine y={0} stroke={theme.border} />
189
+ )}
190
+
191
+ {/* Chart lines */}
192
+ {linesToRender.map((line) => (
193
+ <Line
194
+ key={`${title}-${line.key}`}
195
+ type="monotone"
196
+ dataKey={line.dataKey}
197
+ stroke={line.stroke}
198
+ dot={false}
199
+ isAnimationActive={false}
200
+ strokeDasharray={line.strokeDasharray}
201
+ />
202
+ ))}
203
+ </LineChart>
204
+ </StyledResponsiveContainer>
205
+ </LineTemporalChartWrapper >
586
206
  );
587
207
  }