@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.
Files changed (89) hide show
  1. package/__mocks__/uuid.js +11 -0
  2. package/dist/components/barchartv2/Barchart.component.d.ts.map +1 -1
  3. package/dist/components/barchartv2/Barchart.component.js +4 -4
  4. package/dist/components/barchartv2/BarchartTooltip.d.ts +11 -0
  5. package/dist/components/barchartv2/BarchartTooltip.d.ts.map +1 -0
  6. package/dist/components/barchartv2/BarchartTooltip.js +27 -0
  7. package/dist/components/chartlegend/ChartLegend.d.ts +3 -1
  8. package/dist/components/chartlegend/ChartLegend.d.ts.map +1 -1
  9. package/dist/components/chartlegend/ChartLegend.js +2 -2
  10. package/dist/components/chartlegend/ChartLegendWrapper.d.ts +3 -1
  11. package/dist/components/chartlegend/ChartLegendWrapper.d.ts.map +1 -1
  12. package/dist/components/chartlegend/ChartLegendWrapper.js +43 -9
  13. package/dist/components/charttooltip/ChartTooltip.d.ts +13 -0
  14. package/dist/components/charttooltip/ChartTooltip.d.ts.map +1 -0
  15. package/dist/components/charttooltip/ChartTooltip.js +49 -0
  16. package/dist/components/globalhealthbar/GlobalHealthBar.component.d.ts +4 -0
  17. package/dist/components/globalhealthbar/GlobalHealthBar.component.d.ts.map +1 -1
  18. package/dist/components/globalhealthbar/GlobalHealthBar.component.js +4 -0
  19. package/dist/components/globalhealthbar/GlobalHealthBarRecharts.component.d.ts +10 -0
  20. package/dist/components/globalhealthbar/GlobalHealthBarRecharts.component.d.ts.map +1 -0
  21. package/dist/components/globalhealthbar/GlobalHealthBarRecharts.component.js +78 -0
  22. package/dist/components/globalhealthbar/components/GlobalHealthBarTooltip.d.ts +18 -0
  23. package/dist/components/globalhealthbar/components/GlobalHealthBarTooltip.d.ts.map +1 -0
  24. package/dist/components/globalhealthbar/components/GlobalHealthBarTooltip.js +95 -0
  25. package/dist/components/globalhealthbar/components/HealthBarXAxis.d.ts +7 -0
  26. package/dist/components/globalhealthbar/components/HealthBarXAxis.d.ts.map +1 -0
  27. package/dist/components/globalhealthbar/components/HealthBarXAxis.js +25 -0
  28. package/dist/components/globalhealthbar/healthBarUtils.d.ts +77 -0
  29. package/dist/components/globalhealthbar/healthBarUtils.d.ts.map +1 -0
  30. package/dist/components/globalhealthbar/healthBarUtils.js +196 -0
  31. package/dist/components/globalhealthbar/healthBarUtils.spec.d.ts +2 -0
  32. package/dist/components/globalhealthbar/healthBarUtils.spec.d.ts.map +1 -0
  33. package/dist/components/globalhealthbar/healthBarUtils.spec.js +391 -0
  34. package/dist/components/globalhealthbar/useHealthBarData.d.ts +18 -0
  35. package/dist/components/globalhealthbar/useHealthBarData.d.ts.map +1 -0
  36. package/dist/components/globalhealthbar/useHealthBarData.js +46 -0
  37. package/dist/components/globalhealthbar/useHealthBarData.spec.d.ts +2 -0
  38. package/dist/components/globalhealthbar/useHealthBarData.spec.d.ts.map +1 -0
  39. package/dist/components/globalhealthbar/useHealthBarData.spec.js +207 -0
  40. package/dist/components/icon/Icon.component.d.ts +2 -0
  41. package/dist/components/icon/Icon.component.d.ts.map +1 -1
  42. package/dist/components/icon/Icon.component.js +2 -0
  43. package/dist/components/linetemporalchart/ChartUtil.d.ts.map +1 -1
  44. package/dist/components/linetemporalchart/ChartUtil.js +12 -0
  45. package/dist/components/linetimeseriechart/linetimeseriechart.component.d.ts +8 -5
  46. package/dist/components/linetimeseriechart/linetimeseriechart.component.d.ts.map +1 -1
  47. package/dist/components/linetimeseriechart/linetimeseriechart.component.js +95 -100
  48. package/dist/components/sparkline/sparkline.component.d.ts +16 -0
  49. package/dist/components/sparkline/sparkline.component.d.ts.map +1 -0
  50. package/dist/components/sparkline/sparkline.component.js +20 -0
  51. package/dist/components/text/Text.component.d.ts +2 -1
  52. package/dist/components/text/Text.component.d.ts.map +1 -1
  53. package/dist/components/text/Text.component.js +6 -1
  54. package/dist/next.d.ts +4 -2
  55. package/dist/next.d.ts.map +1 -1
  56. package/dist/next.js +4 -2
  57. package/package.json +4 -2
  58. package/src/lib/components/barchartv2/Barchart.component.tsx +5 -4
  59. package/src/lib/components/barchartv2/{ChartTooltip.test.tsx → BarchartTooltip.test.tsx} +35 -12
  60. package/src/lib/components/barchartv2/BarchartTooltip.tsx +89 -0
  61. package/src/lib/components/chartlegend/ChartLegend.tsx +4 -2
  62. package/src/lib/components/chartlegend/ChartLegendWrapper.test.tsx +197 -0
  63. package/src/lib/components/chartlegend/ChartLegendWrapper.tsx +65 -9
  64. package/src/lib/components/charttooltip/ChartTooltip.tsx +83 -0
  65. package/src/lib/components/globalhealthbar/GlobalHealthBar.component.tsx +4 -1
  66. package/src/lib/components/globalhealthbar/GlobalHealthBarRecharts.component.tsx +203 -0
  67. package/src/lib/components/globalhealthbar/components/GlobalHealthBarTooltip.tsx +173 -0
  68. package/src/lib/components/globalhealthbar/components/HealthBarXAxis.tsx +94 -0
  69. package/src/lib/components/globalhealthbar/healthBarUtils.spec.ts +701 -0
  70. package/src/lib/components/globalhealthbar/healthBarUtils.ts +311 -0
  71. package/src/lib/components/globalhealthbar/useHealthBarData.spec.tsx +487 -0
  72. package/src/lib/components/globalhealthbar/useHealthBarData.ts +74 -0
  73. package/src/lib/components/icon/Icon.component.tsx +2 -0
  74. package/src/lib/components/linetemporalchart/ChartUtil.ts +26 -0
  75. package/src/lib/components/linetimeseriechart/linetimeseriechart.component.tsx +272 -229
  76. package/src/lib/components/sparkline/sparkline.component.tsx +54 -0
  77. package/src/lib/components/text/Text.component.tsx +15 -2
  78. package/src/lib/next.ts +12 -2
  79. package/stories/BarChart/barchart.stories.tsx +7 -1
  80. package/stories/GlobalHealthBar/globalhealthbarRecharts.stories.tsx +145 -0
  81. package/stories/GlobalHealthBar/globalheathbarrecharts.guideline.mdx +5 -0
  82. package/stories/InlineInput/InlineInput.stories.tsx +7 -1
  83. package/stories/globalhealthbar.stories.tsx +25 -5
  84. package/stories/linetimeseriechart.stories.tsx +217 -1
  85. package/stories/sparkline.stories.tsx +168 -0
  86. package/dist/components/barchartv2/ChartTooltip.d.ts +0 -14
  87. package/dist/components/barchartv2/ChartTooltip.d.ts.map +0 -1
  88. package/dist/components/barchartv2/ChartTooltip.js +0 -41
  89. 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 type { Payload } from 'recharts/types/component/DefaultTooltipContent';
12
- import { useCallback, useMemo, useRef } from 'react';
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 { getUnitLabel } from '../linetemporalchart/ChartUtil';
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
- type TooltipPayload = Payload<number, string> & {
29
- value: number;
30
- name: string;
31
- color: string;
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
- above: Serie[];
124
- below: Serie[];
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 CustomTooltip = ({
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
- active?: boolean;
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
- if (!active || !payload || !payload.length || !label) return null;
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
- <TooltipContainer>
183
- <TooltipTime>
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
- </TooltipTime>
193
- {sortedPayload.map((entry, index) => (
194
- <TooltipValue key={index}>
195
- <TooltipLeftGroup>
196
- <TooltipLegend color={entry.color} />
197
- <TooltipName>{entry.name}</TooltipName>
198
- </TooltipLeftGroup>
199
- <TooltipInstanceValue>
200
- {!Number.isFinite(entry.value)
201
- ? '-'
202
- : `${entry.value.toFixed(2)} ${unitLabel}`}
203
- </TooltipInstanceValue>
204
- </TooltipValue>
205
- ))}
206
- </TooltipContainer>
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.map((line) => ({
241
- ...line,
242
- data: addMissingDataPoint(
243
- line.data,
244
- startingTimeStamp,
245
- duration,
246
- interval,
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.map((line) => ({
251
- ...line,
252
- data: addMissingDataPoint(
253
- line.data,
254
- startingTimeStamp,
255
- duration,
256
- interval,
257
- ).map(
258
- ([timestamp, value]) =>
259
- [timestamp, value === null ? null : `-${Number(value)}`] as [
260
- number,
261
- string | null,
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.reduce(
389
- (acc, serie) => {
390
- const key = serie.resource;
391
- if (!acc[key]) {
392
- acc[key] = [];
393
- }
394
- acc[key].push(serie);
395
- return acc;
396
- },
397
- {} as Record<string, Serie[]>,
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
- <ResponsiveContainer width="100%" height={height}>
441
- <LineChart
442
- data={rechartsData}
443
- ref={chartRef}
444
- margin={{ top: 0, right: 0, bottom: 0, left: 0 }}
445
- aria-label={`Time series chart for ${title}`}
446
- >
447
- <CartesianGrid
448
- vertical={true}
449
- horizontal={true}
450
- verticalPoints={[0]}
451
- horizontalPoints={[0]}
452
- stroke={theme.border}
453
- fill={theme.backgroundLevel4}
454
- strokeWidth={1}
455
- />
456
- <XAxis
457
- dataKey="timestamp"
458
- type="number"
459
- domain={['dataMin', 'dataMax']}
460
- ticks={xAxisTicks}
461
- tickFormatter={formatXAxisLabelCallback}
462
- tickCount={5}
463
- tick={{
464
- fill: theme.textSecondary,
465
- fontSize: fontSize.smaller,
466
- }}
467
- axisLine={{ stroke: theme.border }}
468
- />
469
- <YAxis
470
- orientation="right"
471
- allowDataOverflow={false}
472
- label={{
473
- value: yAxisTitle,
474
- angle: 90,
475
- position: 'insideRight',
476
- style: {
477
- textAnchor: 'middle',
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
- domain={
483
- yAxisType === 'percentage'
484
- ? [0, 100]
485
- : yAxisType === 'symmetrical'
486
- ? [-topValue, topValue]
487
- : [0, topValue]
488
- }
489
- axisLine={{ stroke: theme.border }}
490
- tick={{
491
- fill: theme.textSecondary,
492
- fontSize: fontSize.smaller,
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
- </LineChart>
528
- </ResponsiveContainer>
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
+ }