@scality/core-ui 0.170.0 → 0.172.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/__mocks__/uuid.js +11 -0
- package/dist/components/barchartv2/Barchart.component.d.ts.map +1 -1
- package/dist/components/barchartv2/Barchart.component.js +4 -4
- package/dist/components/barchartv2/BarchartTooltip.d.ts +11 -0
- package/dist/components/barchartv2/BarchartTooltip.d.ts.map +1 -0
- package/dist/components/barchartv2/BarchartTooltip.js +27 -0
- package/dist/components/chartlegend/ChartLegend.d.ts +3 -1
- package/dist/components/chartlegend/ChartLegend.d.ts.map +1 -1
- package/dist/components/chartlegend/ChartLegend.js +2 -2
- package/dist/components/chartlegend/ChartLegendWrapper.d.ts +3 -1
- package/dist/components/chartlegend/ChartLegendWrapper.d.ts.map +1 -1
- package/dist/components/chartlegend/ChartLegendWrapper.js +43 -9
- package/dist/components/charttooltip/ChartTooltip.d.ts +13 -0
- package/dist/components/charttooltip/ChartTooltip.d.ts.map +1 -0
- package/dist/components/charttooltip/ChartTooltip.js +49 -0
- package/dist/components/globalhealthbar/GlobalHealthBar.component.d.ts +4 -0
- package/dist/components/globalhealthbar/GlobalHealthBar.component.d.ts.map +1 -1
- package/dist/components/globalhealthbar/GlobalHealthBar.component.js +4 -0
- package/dist/components/globalhealthbar/GlobalHealthBarRecharts.component.d.ts +10 -0
- package/dist/components/globalhealthbar/GlobalHealthBarRecharts.component.d.ts.map +1 -0
- package/dist/components/globalhealthbar/GlobalHealthBarRecharts.component.js +78 -0
- package/dist/components/globalhealthbar/components/GlobalHealthBarTooltip.d.ts +18 -0
- package/dist/components/globalhealthbar/components/GlobalHealthBarTooltip.d.ts.map +1 -0
- package/dist/components/globalhealthbar/components/GlobalHealthBarTooltip.js +95 -0
- package/dist/components/globalhealthbar/components/HealthBarXAxis.d.ts +7 -0
- package/dist/components/globalhealthbar/components/HealthBarXAxis.d.ts.map +1 -0
- package/dist/components/globalhealthbar/components/HealthBarXAxis.js +25 -0
- package/dist/components/globalhealthbar/healthBarUtils.d.ts +77 -0
- package/dist/components/globalhealthbar/healthBarUtils.d.ts.map +1 -0
- package/dist/components/globalhealthbar/healthBarUtils.js +196 -0
- package/dist/components/globalhealthbar/healthBarUtils.spec.d.ts +2 -0
- package/dist/components/globalhealthbar/healthBarUtils.spec.d.ts.map +1 -0
- package/dist/components/globalhealthbar/healthBarUtils.spec.js +391 -0
- package/dist/components/globalhealthbar/useHealthBarData.d.ts +18 -0
- package/dist/components/globalhealthbar/useHealthBarData.d.ts.map +1 -0
- package/dist/components/globalhealthbar/useHealthBarData.js +46 -0
- package/dist/components/globalhealthbar/useHealthBarData.spec.d.ts +2 -0
- package/dist/components/globalhealthbar/useHealthBarData.spec.d.ts.map +1 -0
- package/dist/components/globalhealthbar/useHealthBarData.spec.js +207 -0
- package/dist/components/icon/Icon.component.d.ts +2 -0
- package/dist/components/icon/Icon.component.d.ts.map +1 -1
- package/dist/components/icon/Icon.component.js +2 -0
- package/dist/components/linetemporalchart/ChartUtil.d.ts.map +1 -1
- package/dist/components/linetemporalchart/ChartUtil.js +12 -0
- package/dist/components/linetimeseriechart/linetimeseriechart.component.d.ts +8 -5
- package/dist/components/linetimeseriechart/linetimeseriechart.component.d.ts.map +1 -1
- package/dist/components/linetimeseriechart/linetimeseriechart.component.js +95 -100
- package/dist/components/sparkline/sparkline.component.d.ts +16 -0
- package/dist/components/sparkline/sparkline.component.d.ts.map +1 -0
- package/dist/components/sparkline/sparkline.component.js +20 -0
- package/dist/components/text/Text.component.d.ts +2 -1
- package/dist/components/text/Text.component.d.ts.map +1 -1
- package/dist/components/text/Text.component.js +6 -1
- package/dist/next.d.ts +4 -2
- package/dist/next.d.ts.map +1 -1
- package/dist/next.js +4 -2
- package/package.json +4 -2
- package/src/lib/components/barchartv2/Barchart.component.tsx +5 -4
- package/src/lib/components/barchartv2/{ChartTooltip.test.tsx → BarchartTooltip.test.tsx} +35 -12
- package/src/lib/components/barchartv2/BarchartTooltip.tsx +89 -0
- package/src/lib/components/chartlegend/ChartLegend.tsx +4 -2
- package/src/lib/components/chartlegend/ChartLegendWrapper.test.tsx +197 -0
- package/src/lib/components/chartlegend/ChartLegendWrapper.tsx +65 -9
- package/src/lib/components/charttooltip/ChartTooltip.tsx +83 -0
- package/src/lib/components/globalhealthbar/GlobalHealthBar.component.tsx +4 -1
- package/src/lib/components/globalhealthbar/GlobalHealthBarRecharts.component.tsx +203 -0
- package/src/lib/components/globalhealthbar/components/GlobalHealthBarTooltip.tsx +173 -0
- package/src/lib/components/globalhealthbar/components/HealthBarXAxis.tsx +94 -0
- package/src/lib/components/globalhealthbar/healthBarUtils.spec.ts +701 -0
- package/src/lib/components/globalhealthbar/healthBarUtils.ts +311 -0
- package/src/lib/components/globalhealthbar/useHealthBarData.spec.tsx +487 -0
- package/src/lib/components/globalhealthbar/useHealthBarData.ts +74 -0
- package/src/lib/components/icon/Icon.component.tsx +2 -0
- package/src/lib/components/linetemporalchart/ChartUtil.ts +26 -0
- package/src/lib/components/linetimeseriechart/linetimeseriechart.component.tsx +272 -229
- package/src/lib/components/sparkline/sparkline.component.tsx +54 -0
- package/src/lib/components/text/Text.component.tsx +15 -2
- package/src/lib/next.ts +12 -2
- package/stories/BarChart/barchart.stories.tsx +7 -1
- package/stories/GlobalHealthBar/globalhealthbarRecharts.stories.tsx +145 -0
- package/stories/GlobalHealthBar/globalheathbarrecharts.guideline.mdx +5 -0
- package/stories/InlineInput/InlineInput.stories.tsx +7 -1
- package/stories/globalhealthbar.stories.tsx +25 -5
- package/stories/linetimeseriechart.stories.tsx +217 -1
- package/stories/sparkline.stories.tsx +168 -0
- package/dist/components/barchartv2/ChartTooltip.d.ts +0 -14
- package/dist/components/barchartv2/ChartTooltip.d.ts.map +0 -1
- package/dist/components/barchartv2/ChartTooltip.js +0 -41
- package/src/lib/components/barchartv2/ChartTooltip.tsx +0 -106
|
@@ -1,35 +1,37 @@
|
|
|
1
1
|
import {
|
|
2
|
+
CartesianGrid,
|
|
2
3
|
Line,
|
|
3
4
|
LineChart,
|
|
4
5
|
ReferenceLine,
|
|
5
6
|
ResponsiveContainer,
|
|
6
7
|
Tooltip,
|
|
8
|
+
TooltipContentProps,
|
|
7
9
|
XAxis,
|
|
8
10
|
YAxis,
|
|
9
|
-
CartesianGrid,
|
|
10
11
|
} from 'recharts';
|
|
11
|
-
import
|
|
12
|
-
import
|
|
13
|
-
import { useTheme } from 'styled-components';
|
|
14
|
-
import { addMissingDataPoint } from '../linetemporalchart/ChartUtil';
|
|
15
|
-
import styled from 'styled-components';
|
|
16
|
-
import { fontSize, fontWeight } from '../../style/theme';
|
|
17
|
-
import { useChartLegend } from '../chartlegend/ChartLegendWrapper';
|
|
18
|
-
import { ChartTitleText, SmallerText } from '../text/Text.component';
|
|
19
|
-
import { Loader } from '../loader/Loader.component';
|
|
12
|
+
import { useCallback, useMemo, useRef, useState } from 'react';
|
|
13
|
+
import styled, { useTheme } from 'styled-components';
|
|
20
14
|
import { spacing } from '../../spacing';
|
|
21
|
-
import {
|
|
15
|
+
import { fontSize } from '../../style/theme';
|
|
16
|
+
import { Box } from '../box/Box';
|
|
17
|
+
import { useChartLegend } from '../chartlegend/ChartLegendWrapper';
|
|
18
|
+
import { FormattedDateTime } from '../date/FormattedDateTime';
|
|
22
19
|
import { Icon } from '../icon/Icon.component';
|
|
20
|
+
import {
|
|
21
|
+
addMissingDataPoint,
|
|
22
|
+
getUnitLabel,
|
|
23
|
+
} from '../linetemporalchart/ChartUtil';
|
|
24
|
+
import { Loader } from '../loader/Loader.component';
|
|
25
|
+
import { ChartTitleText, SmallerText } from '../text/Text.component';
|
|
23
26
|
import { Tooltip as TooltipComponent } from '../tooltip/Tooltip.component';
|
|
24
|
-
import { FormattedDateTime } from '../date/FormattedDateTime';
|
|
25
|
-
import { Box } from '../box/Box';
|
|
26
27
|
import { formatXAxisLabel } from './utils';
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
};
|
|
28
|
+
import {
|
|
29
|
+
ChartTooltipContainer,
|
|
30
|
+
ChartTooltipItem,
|
|
31
|
+
ChartTooltipHeader,
|
|
32
|
+
ChartTooltipItemsContainer,
|
|
33
|
+
} from '../charttooltip/ChartTooltip';
|
|
34
|
+
import { LegendShape } from '../chartlegend/ChartLegend';
|
|
33
35
|
|
|
34
36
|
const LineTemporalChartWrapper = styled.div`
|
|
35
37
|
display: flex;
|
|
@@ -43,61 +45,6 @@ const ChartHeader = styled.div`
|
|
|
43
45
|
align-items: center;
|
|
44
46
|
`;
|
|
45
47
|
|
|
46
|
-
const TooltipContainer = styled.div`
|
|
47
|
-
background-color: ${(props) => props.theme.backgroundLevel1};
|
|
48
|
-
padding: ${spacing.r8};
|
|
49
|
-
border: 1px solid ${(props) => props.theme.border};
|
|
50
|
-
border-radius: 4px;
|
|
51
|
-
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
52
|
-
max-width: 250px;
|
|
53
|
-
`;
|
|
54
|
-
|
|
55
|
-
const TooltipTime = styled.div`
|
|
56
|
-
margin-bottom: ${spacing.r8};
|
|
57
|
-
color: ${(props) => props.theme.textPrimary};
|
|
58
|
-
font-size: ${fontSize.smaller};
|
|
59
|
-
font-weight: ${fontWeight.bold};
|
|
60
|
-
text-align: center;
|
|
61
|
-
`;
|
|
62
|
-
|
|
63
|
-
const TooltipValue = styled.div`
|
|
64
|
-
font-size: ${fontSize.smaller};
|
|
65
|
-
margin-top: 4px;
|
|
66
|
-
color: ${(props) => props.theme.textSecondary};
|
|
67
|
-
display: flex;
|
|
68
|
-
align-items: flex-start;
|
|
69
|
-
justify-content: space-between;
|
|
70
|
-
width: 100%;
|
|
71
|
-
`;
|
|
72
|
-
|
|
73
|
-
const TooltipLegend = styled.div<{ color: string }>`
|
|
74
|
-
width: 12px;
|
|
75
|
-
height: 3px;
|
|
76
|
-
background-color: ${(props) => props.color};
|
|
77
|
-
margin-right: 8px;
|
|
78
|
-
flex-shrink: 0;
|
|
79
|
-
margin-top: 8px;
|
|
80
|
-
`;
|
|
81
|
-
|
|
82
|
-
const TooltipLeftGroup = styled.div`
|
|
83
|
-
display: flex;
|
|
84
|
-
align-items: flex-start;
|
|
85
|
-
min-width: 0;
|
|
86
|
-
flex: 1;
|
|
87
|
-
`;
|
|
88
|
-
|
|
89
|
-
const TooltipName = styled.div`
|
|
90
|
-
word-wrap: break-word;
|
|
91
|
-
word-break: break-word;
|
|
92
|
-
flex: 1;
|
|
93
|
-
`;
|
|
94
|
-
|
|
95
|
-
const TooltipInstanceValue = styled.div`
|
|
96
|
-
margin-left: 16px;
|
|
97
|
-
flex-shrink: 0;
|
|
98
|
-
text-align: right;
|
|
99
|
-
`;
|
|
100
|
-
|
|
101
48
|
export type Serie = {
|
|
102
49
|
// the name of the resource
|
|
103
50
|
resource: string;
|
|
@@ -113,16 +60,18 @@ export type Serie = {
|
|
|
113
60
|
|
|
114
61
|
type NonSymmetricalChartSerie = {
|
|
115
62
|
yAxisType?: 'default' | 'percentage';
|
|
116
|
-
series: Serie[];
|
|
63
|
+
series: Serie[] | undefined;
|
|
117
64
|
};
|
|
118
65
|
|
|
119
66
|
// The symmetrical chart props are used to display two series on the same chart, such as in/out, write/read
|
|
120
67
|
type SymmetricalChartSerie = {
|
|
121
68
|
yAxisType: 'symmetrical';
|
|
122
|
-
series:
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
69
|
+
series:
|
|
70
|
+
| {
|
|
71
|
+
above: Serie[] | undefined;
|
|
72
|
+
below: Serie[] | undefined;
|
|
73
|
+
}
|
|
74
|
+
| undefined;
|
|
126
75
|
};
|
|
127
76
|
|
|
128
77
|
export type LineChartProps = (
|
|
@@ -138,6 +87,7 @@ export type LineChartProps = (
|
|
|
138
87
|
threshold: number;
|
|
139
88
|
label: string;
|
|
140
89
|
}[];
|
|
90
|
+
syncId?: string;
|
|
141
91
|
isLoading?: boolean;
|
|
142
92
|
/**
|
|
143
93
|
* The format of the x axis, default is 'date-time' which is like 01 Sep 16:00
|
|
@@ -147,22 +97,40 @@ export type LineChartProps = (
|
|
|
147
97
|
timeFormat?: 'date-time' | 'date';
|
|
148
98
|
yAxisTitle?: string;
|
|
149
99
|
helpText?: string;
|
|
100
|
+
renderTooltip?: (
|
|
101
|
+
tooltipProps: TooltipContentProps<number, string>,
|
|
102
|
+
unitLabel?: string,
|
|
103
|
+
timeFormat?: 'date-time' | 'date',
|
|
104
|
+
) => React.ReactNode;
|
|
150
105
|
};
|
|
151
106
|
|
|
152
|
-
const
|
|
153
|
-
active,
|
|
154
|
-
payload,
|
|
155
|
-
label,
|
|
107
|
+
const LineTimeSerieChartTooltip = ({
|
|
156
108
|
unitLabel,
|
|
157
109
|
timeFormat,
|
|
110
|
+
isChartActive,
|
|
111
|
+
tooltipProps,
|
|
112
|
+
renderTooltip,
|
|
113
|
+
hoveredValue,
|
|
158
114
|
}: {
|
|
159
|
-
|
|
160
|
-
payload?: Array<TooltipPayload>;
|
|
161
|
-
label?: string;
|
|
115
|
+
tooltipProps: TooltipContentProps<number, string>;
|
|
162
116
|
unitLabel?: string;
|
|
163
117
|
timeFormat?: 'date-time' | 'date';
|
|
118
|
+
isChartActive?: boolean;
|
|
119
|
+
renderTooltip?: (
|
|
120
|
+
tooltipProps: TooltipContentProps<number, string>,
|
|
121
|
+
unitLabel?: string,
|
|
122
|
+
timeFormat?: 'date-time' | 'date',
|
|
123
|
+
) => React.ReactNode;
|
|
124
|
+
hoveredValue?: string;
|
|
164
125
|
}) => {
|
|
165
|
-
|
|
126
|
+
const { active, payload, label } = tooltipProps;
|
|
127
|
+
|
|
128
|
+
if (!active || !payload || !payload.length || !label || !isChartActive)
|
|
129
|
+
return null;
|
|
130
|
+
|
|
131
|
+
if (renderTooltip) {
|
|
132
|
+
return renderTooltip(tooltipProps, unitLabel, timeFormat);
|
|
133
|
+
}
|
|
166
134
|
// We can't use the default itemSorter method because it's a custom tooltip.
|
|
167
135
|
// Sort the payload here instead
|
|
168
136
|
const sortedPayload = [...payload].sort((a, b) => {
|
|
@@ -179,8 +147,8 @@ const CustomTooltip = ({
|
|
|
179
147
|
});
|
|
180
148
|
|
|
181
149
|
return (
|
|
182
|
-
<
|
|
183
|
-
<
|
|
150
|
+
<ChartTooltipContainer>
|
|
151
|
+
<ChartTooltipHeader>
|
|
184
152
|
<FormattedDateTime
|
|
185
153
|
format={
|
|
186
154
|
timeFormat === 'date-time'
|
|
@@ -189,26 +157,40 @@ const CustomTooltip = ({
|
|
|
189
157
|
}
|
|
190
158
|
value={new Date(label)}
|
|
191
159
|
/>
|
|
192
|
-
</
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
<
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
160
|
+
</ChartTooltipHeader>
|
|
161
|
+
<ChartTooltipItemsContainer>
|
|
162
|
+
{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}`;
|
|
176
|
+
|
|
177
|
+
return (
|
|
178
|
+
<ChartTooltipItem
|
|
179
|
+
key={index}
|
|
180
|
+
label={entry.name}
|
|
181
|
+
value={formattedValue}
|
|
182
|
+
legendIcon={legendIcon}
|
|
183
|
+
isHovered={isHovered}
|
|
184
|
+
/>
|
|
185
|
+
);
|
|
186
|
+
})}
|
|
187
|
+
</ChartTooltipItemsContainer>
|
|
188
|
+
</ChartTooltipContainer>
|
|
207
189
|
);
|
|
208
190
|
};
|
|
209
191
|
|
|
210
192
|
const isSymmetricalSeries = (
|
|
211
|
-
series: Serie[] | { above: Serie[]; below: Serie[] },
|
|
193
|
+
series: Serie[] | { above: Serie[] | undefined; below: Serie[] | undefined },
|
|
212
194
|
): series is { above: Serie[]; below: Serie[] } => {
|
|
213
195
|
return 'above' in series && 'below' in series;
|
|
214
196
|
};
|
|
@@ -226,42 +208,67 @@ export function LineTimeSerieChart({
|
|
|
226
208
|
yAxisType = 'default',
|
|
227
209
|
yAxisTitle,
|
|
228
210
|
helpText,
|
|
211
|
+
syncId,
|
|
212
|
+
renderTooltip,
|
|
229
213
|
...rest
|
|
230
214
|
}: LineChartProps) {
|
|
231
215
|
const theme = useTheme();
|
|
232
|
-
const { getColor } = useChartLegend();
|
|
216
|
+
const { getColor, selectedResources } = useChartLegend();
|
|
233
217
|
const chartRef = useRef(null);
|
|
234
218
|
|
|
219
|
+
const [isChartActive, setIsChartActive] = useState(false);
|
|
220
|
+
const [hoveredValue, setHoveredValue] = useState<string | undefined>(
|
|
221
|
+
undefined,
|
|
222
|
+
);
|
|
235
223
|
const chartData = useMemo(() => {
|
|
224
|
+
// Guard against empty/undefined series data
|
|
225
|
+
if (!series || (Array.isArray(series) && series.length === 0)) {
|
|
226
|
+
return [];
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Handle symmetrical series with empty above/below arrays
|
|
230
|
+
if (isSymmetricalSeries(series)) {
|
|
231
|
+
if (
|
|
232
|
+
(!series.above || series.above.length === 0) &&
|
|
233
|
+
(!series.below || series.below.length === 0)
|
|
234
|
+
) {
|
|
235
|
+
return [];
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
236
239
|
// 1. Add missing data points
|
|
237
240
|
const normalizedSeries =
|
|
238
241
|
yAxisType === 'symmetrical' && isSymmetricalSeries(series)
|
|
239
242
|
? {
|
|
240
|
-
above: series.above
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
243
|
+
above: series.above
|
|
244
|
+
? series.above.map((line) => ({
|
|
245
|
+
...line,
|
|
246
|
+
data: addMissingDataPoint(
|
|
247
|
+
line.data,
|
|
248
|
+
startingTimeStamp,
|
|
249
|
+
duration,
|
|
250
|
+
interval,
|
|
251
|
+
),
|
|
252
|
+
}))
|
|
253
|
+
: [],
|
|
249
254
|
// Convert positive values to negative values
|
|
250
|
-
below: series.below
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
255
|
+
below: series.below
|
|
256
|
+
? series.below.map((line) => ({
|
|
257
|
+
...line,
|
|
258
|
+
data: addMissingDataPoint(
|
|
259
|
+
line.data,
|
|
260
|
+
startingTimeStamp,
|
|
261
|
+
duration,
|
|
262
|
+
interval,
|
|
263
|
+
).map(
|
|
264
|
+
([timestamp, value]) =>
|
|
265
|
+
[
|
|
266
|
+
timestamp,
|
|
267
|
+
value === null ? null : `-${Number(value)}`,
|
|
268
|
+
] as [number, string | null],
|
|
269
|
+
),
|
|
270
|
+
}))
|
|
271
|
+
: [],
|
|
265
272
|
}
|
|
266
273
|
: (series as Serie[]).map((line) => ({
|
|
267
274
|
...line,
|
|
@@ -356,6 +363,15 @@ export function LineTimeSerieChart({
|
|
|
356
363
|
.filter((value): value is number => value !== null),
|
|
357
364
|
);
|
|
358
365
|
|
|
366
|
+
// Guard against empty values array
|
|
367
|
+
if (values.length === 0) {
|
|
368
|
+
return {
|
|
369
|
+
topValue: 100, // Default value for empty charts
|
|
370
|
+
unitLabel: '',
|
|
371
|
+
rechartsData: [],
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
|
|
359
375
|
const top = Math.abs(Math.max(...values));
|
|
360
376
|
const bottom = Math.abs(Math.min(...values));
|
|
361
377
|
const maxValue = Math.max(top, bottom);
|
|
@@ -380,22 +396,30 @@ export function LineTimeSerieChart({
|
|
|
380
396
|
// Group series by resource and create color mapping
|
|
381
397
|
const { colorMapping, groupedSeries } = useMemo(() => {
|
|
382
398
|
const mapping: Record<string, string> = {};
|
|
399
|
+
|
|
400
|
+
// Guard against empty/undefined series
|
|
401
|
+
if (!series) {
|
|
402
|
+
return { colorMapping: mapping, groupedSeries: {} };
|
|
403
|
+
}
|
|
404
|
+
|
|
383
405
|
const allSeries = isSymmetricalSeries(series)
|
|
384
|
-
? [...series.above, ...series.below]
|
|
406
|
+
? [...(series.above || []), ...(series.below || [])]
|
|
385
407
|
: (series as Serie[]);
|
|
386
408
|
|
|
387
409
|
// Group series by resource
|
|
388
|
-
const groups = allSeries
|
|
389
|
-
(
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
410
|
+
const groups = allSeries
|
|
411
|
+
.filter((serie) => selectedResources.includes(serie.resource))
|
|
412
|
+
.reduce(
|
|
413
|
+
(acc, serie) => {
|
|
414
|
+
const key = serie.resource;
|
|
415
|
+
if (!acc[key]) {
|
|
416
|
+
acc[key] = [];
|
|
417
|
+
}
|
|
418
|
+
acc[key].push(serie);
|
|
419
|
+
return acc;
|
|
420
|
+
},
|
|
421
|
+
{} as Record<string, Serie[]>,
|
|
422
|
+
);
|
|
399
423
|
|
|
400
424
|
// Get colors from the ChartLegend context
|
|
401
425
|
Object.keys(groups).forEach((resource) => {
|
|
@@ -411,7 +435,7 @@ export function LineTimeSerieChart({
|
|
|
411
435
|
colorMapping: mapping,
|
|
412
436
|
groupedSeries: groups,
|
|
413
437
|
};
|
|
414
|
-
}, [series, getColor]);
|
|
438
|
+
}, [series, getColor, selectedResources]);
|
|
415
439
|
|
|
416
440
|
// Format time for display the tick in the x axis
|
|
417
441
|
const formatXAxisLabelCallback = useCallback(
|
|
@@ -437,95 +461,114 @@ export function LineTimeSerieChart({
|
|
|
437
461
|
)}
|
|
438
462
|
{isLoading && <Loader />}
|
|
439
463
|
</ChartHeader>
|
|
440
|
-
<
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
>
|
|
447
|
-
<
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
464
|
+
<div
|
|
465
|
+
onFocus={() => setIsChartActive(true)}
|
|
466
|
+
onBlur={() => setIsChartActive(false)}
|
|
467
|
+
onFocusCapture={() => setIsChartActive(true)}
|
|
468
|
+
onBlurCapture={() => setIsChartActive(false)}
|
|
469
|
+
>
|
|
470
|
+
<ResponsiveContainer width="100%" height={height}>
|
|
471
|
+
<LineChart
|
|
472
|
+
data={rechartsData}
|
|
473
|
+
ref={chartRef}
|
|
474
|
+
margin={{ top: 0, right: 0, bottom: 0, left: 0 }}
|
|
475
|
+
aria-label={`Time series chart for ${title}`}
|
|
476
|
+
syncId={syncId}
|
|
477
|
+
onMouseEnter={() => setIsChartActive(true)}
|
|
478
|
+
onMouseLeave={() => setIsChartActive(false)}
|
|
479
|
+
>
|
|
480
|
+
<CartesianGrid
|
|
481
|
+
vertical={true}
|
|
482
|
+
horizontal={true}
|
|
483
|
+
verticalPoints={[0]}
|
|
484
|
+
horizontalPoints={[0]}
|
|
485
|
+
stroke={theme.border}
|
|
486
|
+
fill={theme.backgroundLevel4}
|
|
487
|
+
strokeWidth={1}
|
|
488
|
+
/>
|
|
489
|
+
<XAxis
|
|
490
|
+
dataKey="timestamp"
|
|
491
|
+
type="number"
|
|
492
|
+
domain={['dataMin', 'dataMax']}
|
|
493
|
+
ticks={xAxisTicks}
|
|
494
|
+
tickFormatter={formatXAxisLabelCallback}
|
|
495
|
+
tickCount={5}
|
|
496
|
+
tick={{
|
|
497
|
+
fill: theme.textSecondary,
|
|
498
|
+
fontSize: fontSize.smaller,
|
|
499
|
+
}}
|
|
500
|
+
axisLine={{ stroke: theme.border }}
|
|
501
|
+
/>
|
|
502
|
+
<YAxis
|
|
503
|
+
orientation="right"
|
|
504
|
+
allowDataOverflow={false}
|
|
505
|
+
label={{
|
|
506
|
+
value: yAxisTitle,
|
|
507
|
+
angle: 90,
|
|
508
|
+
position: 'insideRight',
|
|
509
|
+
style: {
|
|
510
|
+
textAnchor: 'middle',
|
|
511
|
+
fill: theme.textSecondary,
|
|
512
|
+
fontSize: fontSize.smaller,
|
|
513
|
+
},
|
|
514
|
+
}}
|
|
515
|
+
domain={
|
|
516
|
+
yAxisType === 'percentage'
|
|
517
|
+
? [0, 100]
|
|
518
|
+
: yAxisType === 'symmetrical'
|
|
519
|
+
? [-topValue, topValue]
|
|
520
|
+
: [0, topValue]
|
|
521
|
+
}
|
|
522
|
+
axisLine={{ stroke: theme.border }}
|
|
523
|
+
tick={{
|
|
478
524
|
fill: theme.textSecondary,
|
|
479
525
|
fontSize: fontSize.smaller,
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
tickFormatter={(value) => Math.round(value).toString()}
|
|
495
|
-
tickCount={5}
|
|
496
|
-
interval={'preserveStartEnd'}
|
|
497
|
-
/>
|
|
498
|
-
<Tooltip
|
|
499
|
-
content={
|
|
500
|
-
<CustomTooltip unitLabel={unitLabel} timeFormat={timeFormat} />
|
|
501
|
-
}
|
|
502
|
-
/>
|
|
503
|
-
{/* Add horizontal line at y=0 for symmetrical charts */}
|
|
504
|
-
{yAxisType === 'symmetrical' && (
|
|
505
|
-
<ReferenceLine y={0} stroke={theme.border} />
|
|
506
|
-
)}
|
|
507
|
-
|
|
508
|
-
{/* Chart lines */}
|
|
509
|
-
{Object.entries(groupedSeries).map(([resource, resourceSeries]) =>
|
|
510
|
-
resourceSeries.map((serie, serieIndex) => {
|
|
511
|
-
const label = serie.getTooltipLabel(
|
|
512
|
-
serie.metricPrefix,
|
|
513
|
-
serie.resource,
|
|
514
|
-
);
|
|
515
|
-
return (
|
|
516
|
-
<Line
|
|
517
|
-
key={`${title}-${resource}-${serieIndex}`}
|
|
518
|
-
type="monotone"
|
|
519
|
-
dataKey={label}
|
|
520
|
-
stroke={colorMapping[resource]}
|
|
521
|
-
dot={false}
|
|
522
|
-
isAnimationActive={false}
|
|
526
|
+
}}
|
|
527
|
+
tickFormatter={(value) => Math.round(value).toString()}
|
|
528
|
+
tickCount={5}
|
|
529
|
+
interval={'preserveStartEnd'}
|
|
530
|
+
/>
|
|
531
|
+
<Tooltip
|
|
532
|
+
content={(props: TooltipContentProps<number, string>) => (
|
|
533
|
+
<LineTimeSerieChartTooltip
|
|
534
|
+
unitLabel={unitLabel}
|
|
535
|
+
timeFormat={timeFormat}
|
|
536
|
+
renderTooltip={renderTooltip}
|
|
537
|
+
tooltipProps={props}
|
|
538
|
+
isChartActive={isChartActive}
|
|
539
|
+
hoveredValue={hoveredValue}
|
|
523
540
|
/>
|
|
524
|
-
)
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
541
|
+
)}
|
|
542
|
+
/>
|
|
543
|
+
{/* Add horizontal line at y=0 for symmetrical charts */}
|
|
544
|
+
{yAxisType === 'symmetrical' && (
|
|
545
|
+
<ReferenceLine y={0} stroke={theme.border} />
|
|
546
|
+
)}
|
|
547
|
+
|
|
548
|
+
{/* Chart lines */}
|
|
549
|
+
{Object.entries(groupedSeries).map(([resource, resourceSeries]) =>
|
|
550
|
+
resourceSeries.map((serie, serieIndex) => {
|
|
551
|
+
const label = serie.getTooltipLabel(
|
|
552
|
+
serie.metricPrefix,
|
|
553
|
+
serie.resource,
|
|
554
|
+
);
|
|
555
|
+
return (
|
|
556
|
+
<Line
|
|
557
|
+
key={`${title}-${resource}-${serieIndex}`}
|
|
558
|
+
type="monotone"
|
|
559
|
+
dataKey={label}
|
|
560
|
+
stroke={colorMapping[resource]}
|
|
561
|
+
dot={false}
|
|
562
|
+
isAnimationActive={false}
|
|
563
|
+
onMouseEnter={() => setHoveredValue(label)}
|
|
564
|
+
onMouseLeave={() => setHoveredValue(undefined)}
|
|
565
|
+
/>
|
|
566
|
+
);
|
|
567
|
+
}),
|
|
568
|
+
)}
|
|
569
|
+
</LineChart>
|
|
570
|
+
</ResponsiveContainer>
|
|
571
|
+
</div>
|
|
529
572
|
</LineTemporalChartWrapper>
|
|
530
573
|
);
|
|
531
574
|
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
import { Area, AreaChart, CartesianGrid, ResponsiveContainer } from "recharts";
|
|
3
|
+
import { useTheme } from "styled-components";
|
|
4
|
+
import { chartColors } from "../../style/theme";
|
|
5
|
+
import { addMissingDataPoint } from "../linetemporalchart/ChartUtil";
|
|
6
|
+
|
|
7
|
+
type SparklineProps = {
|
|
8
|
+
serie: {
|
|
9
|
+
data: [number, number|null][],
|
|
10
|
+
color?: string, // exa color code like '#ff0000'
|
|
11
|
+
},
|
|
12
|
+
startingTimeStamp: number,
|
|
13
|
+
sampleDuration: number,
|
|
14
|
+
sampleInterval: number
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Sparkline is a simple dynamically sized area chart.
|
|
19
|
+
* Used to show trends in data over time.
|
|
20
|
+
*/
|
|
21
|
+
export function Sparkline({ serie, startingTimeStamp, sampleDuration, sampleInterval }: SparklineProps) {
|
|
22
|
+
const data = useMemo(
|
|
23
|
+
() => {
|
|
24
|
+
const dataMdp = addMissingDataPoint(serie.data, startingTimeStamp, sampleDuration, sampleInterval);
|
|
25
|
+
return dataMdp.map(([x, y]) => ({ x, y }));
|
|
26
|
+
},
|
|
27
|
+
[serie.data]
|
|
28
|
+
);
|
|
29
|
+
const color = serie.color ?? chartColors.lineColor1;
|
|
30
|
+
const strokeGridColor = useTheme().border;
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<ResponsiveContainer>
|
|
34
|
+
<AreaChart data={data}>
|
|
35
|
+
<defs>
|
|
36
|
+
<linearGradient id={`gradient-${color}`} x1="0" y1="0" x2="0" y2="1">
|
|
37
|
+
<stop offset="0%" stopColor={color} stopOpacity={0.7} />
|
|
38
|
+
<stop offset="100%" stopColor={color} stopOpacity={0.1} />
|
|
39
|
+
</linearGradient>
|
|
40
|
+
</defs>
|
|
41
|
+
<CartesianGrid horizontal={false} stroke={strokeGridColor} strokeOpacity={0.5} />
|
|
42
|
+
<Area
|
|
43
|
+
type="linear"
|
|
44
|
+
dataKey="y"
|
|
45
|
+
stroke={color}
|
|
46
|
+
fill={`url(#gradient-${color})`}
|
|
47
|
+
dot={false}
|
|
48
|
+
activeDot={false}
|
|
49
|
+
isAnimationActive={false}
|
|
50
|
+
/>
|
|
51
|
+
</AreaChart>
|
|
52
|
+
</ResponsiveContainer>
|
|
53
|
+
);
|
|
54
|
+
}
|