@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.
- package/dist/components/UnsuccessfulResult.component.d.ts.map +1 -1
- package/dist/components/accordion/Accordion.component.d.ts.map +1 -1
- package/dist/components/banner/Banner.component.d.ts +6 -1
- package/dist/components/banner/Banner.component.d.ts.map +1 -1
- package/dist/components/banner/Banner.component.js +30 -9
- package/dist/components/breadcrumb/Breadcrumb.component.d.ts.map +1 -1
- package/dist/components/buttonv2/CopyButton.component.d.ts.map +1 -1
- package/dist/components/charts/MetricsTimeSpanProvider.d.ts.map +1 -1
- package/dist/components/charts/barchart/Barchart.d.ts.map +1 -1
- package/dist/components/charts/barchart/Barchart.js +29 -19
- package/dist/components/charts/barchart/Barchart.utils.d.ts.map +1 -1
- package/dist/components/charts/barchart/BarchartTooltip.d.ts.map +1 -1
- package/dist/components/charts/common/ChartTooltip.d.ts.map +1 -1
- package/dist/components/charts/common/SharedComponents.d.ts +6 -6
- package/dist/components/charts/common/SharedComponents.d.ts.map +1 -1
- package/dist/components/charts/common/SharedComponents.js +7 -3
- package/dist/components/charts/common/chartUtils.d.ts +7 -2
- package/dist/components/charts/common/chartUtils.d.ts.map +1 -1
- package/dist/components/charts/common/chartUtils.js +55 -20
- package/dist/components/charts/globalhealthbar/GlobalHealthBar.hooks.d.ts.map +1 -1
- package/dist/components/charts/globalhealthbar/GlobalHealthBar.utils.d.ts +3 -1
- package/dist/components/charts/globalhealthbar/GlobalHealthBar.utils.d.ts.map +1 -1
- package/dist/components/charts/globalhealthbar/GlobalHealthBarTooltip.d.ts.map +1 -1
- package/dist/components/charts/globalhealthbar/HealthBarXAxis.d.ts.map +1 -1
- package/dist/components/charts/index.d.ts +1 -1
- package/dist/components/charts/index.d.ts.map +1 -1
- package/dist/components/charts/legend/ChartLegend.d.ts.map +1 -1
- package/dist/components/charts/legend/ChartLegendWrapper.d.ts.map +1 -1
- package/dist/components/charts/linetimeseries/LineTimeSerieChart.d.ts +12 -47
- package/dist/components/charts/linetimeseries/LineTimeSerieChart.d.ts.map +1 -1
- package/dist/components/charts/linetimeseries/LineTimeSerieChart.js +46 -220
- package/dist/components/charts/linetimeseries/LineTimeSerieChart.types.d.ts +77 -0
- package/dist/components/charts/linetimeseries/LineTimeSerieChart.types.d.ts.map +1 -0
- package/dist/components/charts/linetimeseries/LineTimeSerieChart.types.js +6 -0
- package/dist/components/charts/linetimeseries/LineTimeSerieChart.utils.d.ts.map +1 -1
- package/dist/components/charts/linetimeseries/LineTimeSerieChartTooltip.d.ts +18 -0
- package/dist/components/charts/linetimeseries/LineTimeSerieChartTooltip.d.ts.map +1 -0
- package/dist/components/charts/linetimeseries/LineTimeSerieChartTooltip.js +65 -0
- package/dist/components/charts/linetimeseries/useChartData.d.ts +44 -0
- package/dist/components/charts/linetimeseries/useChartData.d.ts.map +1 -0
- package/dist/components/charts/linetimeseries/useChartData.js +207 -0
- package/dist/components/charts/linetimeseries/useChartHover.d.ts +15 -0
- package/dist/components/charts/linetimeseries/useChartHover.d.ts.map +1 -0
- package/dist/components/charts/linetimeseries/useChartHover.js +29 -0
- package/dist/components/checkbox/Checkbox.component.d.ts.map +1 -1
- package/dist/components/checkbox/Checkbox.component.js +15 -7
- package/dist/components/constrainedtext/Constrainedtext.component.d.ts.map +1 -1
- package/dist/components/constrainedtext/Constrainedtext.component.js +3 -2
- package/dist/components/coreuithemeprovider/CoreUiThemeProvider.d.ts.map +1 -1
- package/dist/components/date/FormattedDateTime.d.ts.map +1 -1
- package/dist/components/dropdown/Dropdown.component.d.ts.map +1 -1
- package/dist/components/dropdown/Dropdown.component.js +3 -0
- package/dist/components/error-pages/ErrorPage401.component.js +1 -1
- package/dist/components/error-pages/ErrorPage404.component.js +1 -1
- package/dist/components/error-pages/ErrorPage500.component.js +1 -1
- package/dist/components/form/Form.component.d.ts.map +1 -1
- package/dist/components/form/Form.component.js +3 -3
- package/dist/components/icon/CustomsIcons.d.ts +10 -0
- package/dist/components/icon/CustomsIcons.d.ts.map +1 -1
- package/dist/components/icon/CustomsIcons.js +8 -0
- package/dist/components/icon/Icon.component.d.ts +2 -131
- package/dist/components/icon/Icon.component.d.ts.map +1 -1
- package/dist/components/icon/Icon.component.js +10 -133
- package/dist/components/icon/iconTable.d.ts +138 -0
- package/dist/components/icon/iconTable.d.ts.map +1 -0
- package/dist/components/icon/iconTable.js +137 -0
- package/dist/components/iconhelper/IconHelper.d.ts.map +1 -1
- package/dist/components/infomessage/InfoMessage.component.d.ts.map +1 -1
- package/dist/components/infomessage/InfoMessage.component.js +1 -1
- package/dist/components/infomessage/InfoMessageUtils.d.ts.map +1 -1
- package/dist/components/inlineinput/InlineInput.d.ts.map +1 -1
- package/dist/components/inputlist/InputButtons.d.ts.map +1 -1
- package/dist/components/inputlist/InputList.component.d.ts +2 -0
- package/dist/components/inputlist/InputList.component.d.ts.map +1 -1
- package/dist/components/inputlist/InputList.component.js +2 -2
- package/dist/components/inputv2/inputv2.d.ts +2 -0
- package/dist/components/inputv2/inputv2.d.ts.map +1 -1
- package/dist/components/inputv2/inputv2.js +6 -2
- package/dist/components/layout/v2/panels.d.ts.map +1 -1
- package/dist/components/modal/Modal.component.d.ts.map +1 -1
- package/dist/components/searchinput/SearchInput.component.d.ts.map +1 -1
- package/dist/components/searchinput/SearchInput.component.js +1 -1
- package/dist/components/statusicon/StatusIcon.component.d.ts.map +1 -1
- package/dist/components/tablev2/MultiSelectableContent.d.ts.map +1 -1
- package/dist/components/tablev2/Search.d.ts.map +1 -1
- package/dist/components/tablev2/TableCommon.d.ts.map +1 -1
- package/dist/components/tablev2/TableUtils.d.ts.map +1 -1
- package/dist/components/tablev2/Tablestyle.d.ts.map +1 -1
- package/dist/components/tablev2/Tablestyle.js +2 -3
- package/dist/components/tablev2/Tablev2.component.d.ts.map +1 -1
- package/dist/components/tabsv2/useScrollingTabs.d.ts.map +1 -1
- package/dist/components/text/Text.component.d.ts +9 -6
- package/dist/components/text/Text.component.d.ts.map +1 -1
- package/dist/components/text/Text.component.js +5 -0
- package/dist/components/toast/Toast.component.d.ts.map +1 -1
- package/dist/components/toast/useMutationsHandler.d.ts.map +1 -1
- package/dist/components/tooltip/Tooltip.component.js +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/next.d.ts +3 -3
- package/dist/next.d.ts.map +1 -1
- package/dist/organisms/attachments/AttachmentTable.d.ts.map +1 -1
- package/dist/spacing.d.ts.map +1 -1
- package/dist/utils.d.ts +16 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +27 -0
- package/jest.config.js +6 -1
- package/package.json +7 -7
- package/src/lib/components/banner/Banner.component.test.tsx +58 -0
- package/src/lib/components/banner/Banner.component.tsx +57 -10
- package/src/lib/components/charts/barchart/Barchart.test.tsx +3 -1
- package/src/lib/components/charts/barchart/Barchart.tsx +123 -106
- package/src/lib/components/charts/common/SharedComponents.tsx +15 -11
- package/src/lib/components/charts/common/chartUtils.test.ts +27 -12
- package/src/lib/components/charts/common/chartUtils.ts +67 -23
- package/src/lib/components/charts/index.ts +1 -1
- package/src/lib/components/charts/linetimeseries/LineTimeSerieChart.tsx +136 -516
- package/src/lib/components/charts/linetimeseries/LineTimeSerieChart.types.ts +93 -0
- package/src/lib/components/charts/linetimeseries/LineTimeSerieChartTooltip.tsx +137 -0
- package/src/lib/components/charts/linetimeseries/useChartData.ts +322 -0
- package/src/lib/components/charts/linetimeseries/useChartHover.ts +35 -0
- package/src/lib/components/checkbox/Checkbox.component.tsx +19 -20
- package/src/lib/components/constrainedtext/Constrainedtext.component.tsx +3 -2
- package/src/lib/components/dropdown/Dropdown.component.tsx +3 -0
- package/src/lib/components/error-pages/ErrorPage401.component.tsx +1 -1
- package/src/lib/components/error-pages/ErrorPage404.component.tsx +1 -1
- package/src/lib/components/error-pages/ErrorPage500.component.tsx +1 -1
- package/src/lib/components/form/Form.component.tsx +5 -19
- package/src/lib/components/icon/CustomsIcons.tsx +36 -0
- package/src/lib/components/icon/Icon.component.tsx +17 -137
- package/src/lib/components/icon/iconTable.ts +137 -0
- package/src/lib/components/iconhelper/IconHelper.test.tsx +2 -2
- package/src/lib/components/infomessage/InfoMessage.component.tsx +1 -1
- package/src/lib/components/inputlist/InputList.component.tsx +4 -2
- package/src/lib/components/inputv2/inputv2.tsx +11 -5
- package/src/lib/components/searchinput/SearchInput.component.tsx +1 -0
- package/src/lib/components/searchinput/SearchInput.test.tsx +6 -6
- package/src/lib/components/tablev2/Tablestyle.tsx +2 -4
- package/src/lib/components/text/Text.component.tsx +18 -10
- package/src/lib/components/tooltip/Tooltip.component.tsx +1 -1
- package/src/lib/index.ts +3 -2
- package/src/lib/next.ts +3 -3
- package/src/lib/utils.ts +42 -0
- package/stories/GlobalHealthBar/globalhealthbar.stories.tsx +1 -1
- package/stories/banner.stories.tsx +37 -5
- package/stories/inputlist.stories.tsx +18 -6
- package/stories/linetimeseriechart.stories.tsx +325 -6
- package/tsconfig.json +1 -1
- package/dist/components/date/FormattedDateTime.spec.d.ts +0 -2
- package/dist/components/date/FormattedDateTime.spec.d.ts.map +0 -1
- package/dist/components/date/FormattedDateTime.spec.js +0 -161
- package/dist/components/date/dateDiffer.spec.d.ts +0 -2
- package/dist/components/date/dateDiffer.spec.d.ts.map +0 -1
- package/dist/components/date/dateDiffer.spec.js +0 -6
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useCallback,
|
|
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
|
-
*
|
|
216
|
-
*
|
|
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
|
-
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
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
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
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
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
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
|
}
|