@scality/core-ui 0.174.0 → 0.176.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/barchartv2/Barchart.component.d.ts +4 -4
- package/dist/components/barchartv2/Barchart.component.d.ts.map +1 -1
- package/dist/components/barchartv2/Barchart.component.js +22 -32
- package/dist/components/barchartv2/BarchartTooltip.d.ts +1 -1
- package/dist/components/barchartv2/BarchartTooltip.d.ts.map +1 -1
- package/dist/components/barchartv2/BarchartTooltip.js +6 -6
- package/dist/components/barchartv2/utils.d.ts.map +1 -1
- package/dist/components/chartlegend/ChartLegendWrapper.js +1 -1
- package/dist/components/charttooltip/ChartTooltip.d.ts +6 -0
- package/dist/components/charttooltip/ChartTooltip.d.ts.map +1 -1
- package/dist/components/charttooltip/ChartTooltip.js +22 -0
- package/dist/components/date/FormattedDateTime.d.ts +23 -8
- package/dist/components/date/FormattedDateTime.d.ts.map +1 -1
- package/dist/components/date/FormattedDateTime.js +51 -7
- package/dist/components/icon/Icon.component.d.ts +1 -0
- package/dist/components/icon/Icon.component.d.ts.map +1 -1
- package/dist/components/icon/Icon.component.js +1 -0
- package/dist/components/linetimeseriechart/linetimeseriechart.component.d.ts +2 -1
- package/dist/components/linetimeseriechart/linetimeseriechart.component.d.ts.map +1 -1
- package/dist/components/linetimeseriechart/linetimeseriechart.component.js +19 -17
- package/dist/components/linetimeseriechart/utils.d.ts +1 -1
- package/dist/components/linetimeseriechart/utils.d.ts.map +1 -1
- package/dist/components/linetimeseriechart/utils.js +13 -13
- package/dist/components/scrollbarwrapper/ScrollbarWrapper.component.d.ts.map +1 -1
- package/dist/components/scrollbarwrapper/ScrollbarWrapper.component.js +2 -0
- package/dist/components/sparkline/sparkline.component.d.ts +2 -1
- package/dist/components/sparkline/sparkline.component.d.ts.map +1 -1
- package/dist/components/sparkline/sparkline.component.js +3 -3
- package/package.json +1 -1
- package/src/lib/components/barchartv2/Barchart.component.test.tsx +23 -25
- package/src/lib/components/barchartv2/Barchart.component.tsx +36 -45
- package/src/lib/components/barchartv2/BarchartTooltip.test.tsx +3 -3
- package/src/lib/components/barchartv2/BarchartTooltip.tsx +14 -18
- package/src/lib/components/barchartv2/utils.ts +1 -5
- package/src/lib/components/chartlegend/ChartLegendWrapper.tsx +1 -1
- package/src/lib/components/charttooltip/ChartTooltip.tsx +40 -0
- package/src/lib/components/date/FormattedDateTime.tsx +73 -8
- package/src/lib/components/icon/Icon.component.tsx +1 -0
- package/src/lib/components/linetimeseriechart/linetimeseriechart.component.tsx +43 -34
- package/src/lib/components/linetimeseriechart/utils.test.ts +30 -68
- package/src/lib/components/linetimeseriechart/utils.ts +14 -18
- package/src/lib/components/scrollbarwrapper/ScrollbarWrapper.component.tsx +3 -1
- package/src/lib/components/sparkline/sparkline.component.tsx +5 -3
- package/stories/formattedate.stories.tsx +2 -0
- package/stories/linetimeseriechart.stories.tsx +1 -0
- package/stories/sparkline.stories.tsx +19 -0
|
@@ -3,7 +3,7 @@ import { Tooltip } from '../tooltip/Tooltip.component';
|
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* @description Long date formatter, with weekday, year, month and day. Used for describing long term date.
|
|
6
|
-
* @example Wednesday
|
|
6
|
+
* @example Wednesday 06 October 2025
|
|
7
7
|
*/
|
|
8
8
|
export const LONG_DATE_FORMATER = Intl.DateTimeFormat('en-GB', {
|
|
9
9
|
weekday: 'long',
|
|
@@ -35,7 +35,7 @@ export const DATE_FORMATER = Intl.DateTimeFormat('fr-CA', {
|
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
37
|
* @description Day month formatter, with weekday, day and month. Used for describing long term date.
|
|
38
|
-
* @example Wed
|
|
38
|
+
* @example Wed 06 Oct
|
|
39
39
|
*/
|
|
40
40
|
export const DAY_MONTH_FORMATER = Intl.DateTimeFormat('en-GB', {
|
|
41
41
|
weekday: 'short',
|
|
@@ -64,9 +64,30 @@ export const TIME_FORMATER = Intl.DateTimeFormat('en-GB', {
|
|
|
64
64
|
minute: '2-digit',
|
|
65
65
|
});
|
|
66
66
|
|
|
67
|
+
/**
|
|
68
|
+
* @description Day month abbreviated formatter. Used for describing long term date.
|
|
69
|
+
* @example 06 Oct
|
|
70
|
+
*/
|
|
71
|
+
export const DAY_MONTH_ABBREVIATED = Intl.DateTimeFormat('en-GB', {
|
|
72
|
+
day: '2-digit',
|
|
73
|
+
month: 'short',
|
|
74
|
+
hour12: false,
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* @description Day month abbreviated formatter. Used for describing long term date.
|
|
79
|
+
* @example 06 Oct 25
|
|
80
|
+
*/
|
|
81
|
+
export const DAY_MONTH_ABBREVIATED_YEAR = Intl.DateTimeFormat('en-GB', {
|
|
82
|
+
day: '2-digit',
|
|
83
|
+
month: 'short',
|
|
84
|
+
year: '2-digit',
|
|
85
|
+
hour12: false,
|
|
86
|
+
});
|
|
87
|
+
|
|
67
88
|
/**
|
|
68
89
|
* @description Day month abbreviated hour minute second formatter. Used for describing long term date.
|
|
69
|
-
* @example
|
|
90
|
+
* @example 06 Oct 18:33:00
|
|
70
91
|
*/
|
|
71
92
|
export const DAY_MONTH_ABBREVIATED_HOUR_MINUTE_SECOND = Intl.DateTimeFormat(
|
|
72
93
|
'en-GB',
|
|
@@ -82,7 +103,7 @@ export const DAY_MONTH_ABBREVIATED_HOUR_MINUTE_SECOND = Intl.DateTimeFormat(
|
|
|
82
103
|
|
|
83
104
|
/**
|
|
84
105
|
* @description Day month abbreviated hour minute formatter. Used for describing long term date.
|
|
85
|
-
* @example
|
|
106
|
+
* @example 06 Oct 18:33
|
|
86
107
|
*/
|
|
87
108
|
export const DAY_MONTH_ABBREVIATED_HOUR_MINUTE = Intl.DateTimeFormat('en-GB', {
|
|
88
109
|
day: '2-digit',
|
|
@@ -92,6 +113,22 @@ export const DAY_MONTH_ABBREVIATED_HOUR_MINUTE = Intl.DateTimeFormat('en-GB', {
|
|
|
92
113
|
hour12: false,
|
|
93
114
|
});
|
|
94
115
|
|
|
116
|
+
/**
|
|
117
|
+
* @description Day month abbreviated year hour minute formatter. Used for describing long term date.
|
|
118
|
+
* @example 06 Oct 2025 18:33
|
|
119
|
+
*/
|
|
120
|
+
export const DAY_MONTH_ABBREVIATED_YEAR_HOUR_MINUTE = Intl.DateTimeFormat(
|
|
121
|
+
'en-GB',
|
|
122
|
+
{
|
|
123
|
+
day: '2-digit',
|
|
124
|
+
month: 'short',
|
|
125
|
+
year: 'numeric',
|
|
126
|
+
hour: '2-digit',
|
|
127
|
+
minute: '2-digit',
|
|
128
|
+
hour12: false,
|
|
129
|
+
},
|
|
130
|
+
);
|
|
131
|
+
|
|
95
132
|
/**
|
|
96
133
|
* @description Year month day formatter, without time. Used for describing long term date.
|
|
97
134
|
* @example 2025-01-01
|
|
@@ -121,11 +158,14 @@ type FormattedDateTimeProps = {
|
|
|
121
158
|
| 'relative'
|
|
122
159
|
| 'day-month-abbreviated-hour-minute'
|
|
123
160
|
| 'day-month-abbreviated-hour-minute-second'
|
|
161
|
+
| 'day-month-abbreviated-year-hour-minute'
|
|
124
162
|
| 'long-date'
|
|
125
163
|
| 'long-date-without-weekday'
|
|
126
164
|
| 'chart-date'
|
|
127
165
|
| 'year-month-day'
|
|
128
|
-
| 'month-day'
|
|
166
|
+
| 'month-day'
|
|
167
|
+
| 'day-month-abbreviated'
|
|
168
|
+
| 'chart-long-term-date';
|
|
129
169
|
|
|
130
170
|
value: Date;
|
|
131
171
|
};
|
|
@@ -150,10 +190,10 @@ const isItFutureOrIsItPast = (
|
|
|
150
190
|
* time: '00:00'
|
|
151
191
|
* 'time-second': '00:00:00'
|
|
152
192
|
* relative: '1 month ago'
|
|
153
|
-
* 'day-month-abbreviated-hour-minute': '
|
|
154
|
-
* 'day-month-abbreviated-hour-minute-second': '
|
|
193
|
+
* 'day-month-abbreviated-hour-minute': '06 Oct 18:33'
|
|
194
|
+
* 'day-month-abbreviated-hour-minute-second': '06 Oct 18:33:00'
|
|
155
195
|
* 'long-date': 'Wednesday 6 October 2025'
|
|
156
|
-
* 'chart-date': '
|
|
196
|
+
* 'chart-date': '06 Oct'
|
|
157
197
|
* 'year-month-day': '2025-10-06'
|
|
158
198
|
*/
|
|
159
199
|
export const FormattedDateTime = ({
|
|
@@ -290,6 +330,31 @@ export const FormattedDateTime = ({
|
|
|
290
330
|
return <>{YEAR_MONTH_DAY_FORMATTER.format(value)}</>;
|
|
291
331
|
case 'month-day':
|
|
292
332
|
return <>{MONTH_DAY_FORMATTER.format(value)}</>;
|
|
333
|
+
case 'day-month-abbreviated-year-hour-minute':
|
|
334
|
+
return (
|
|
335
|
+
<>
|
|
336
|
+
{DAY_MONTH_ABBREVIATED_YEAR_HOUR_MINUTE.format(value)
|
|
337
|
+
.replace(',', '')
|
|
338
|
+
.replace(/Sept/g, 'Sep')}
|
|
339
|
+
</>
|
|
340
|
+
);
|
|
341
|
+
case 'day-month-abbreviated':
|
|
342
|
+
return (
|
|
343
|
+
<>
|
|
344
|
+
{DAY_MONTH_ABBREVIATED.format(value)
|
|
345
|
+
.replace(',', '')
|
|
346
|
+
.replace(/Sept/g, 'Sep')}
|
|
347
|
+
</>
|
|
348
|
+
);
|
|
349
|
+
case 'chart-long-term-date':
|
|
350
|
+
return (
|
|
351
|
+
<>
|
|
352
|
+
{DAY_MONTH_ABBREVIATED_YEAR.format(value)
|
|
353
|
+
.replace(/[ ,]/g, '')
|
|
354
|
+
// replace Sept with Sep to keep 3 letter month
|
|
355
|
+
.replace(/Sept/g, 'Sep')}
|
|
356
|
+
</>
|
|
357
|
+
);
|
|
293
358
|
default:
|
|
294
359
|
return <></>;
|
|
295
360
|
}
|
|
@@ -3,26 +3,24 @@ import {
|
|
|
3
3
|
Line,
|
|
4
4
|
LineChart,
|
|
5
5
|
ReferenceLine,
|
|
6
|
-
ResponsiveContainer,
|
|
7
6
|
Tooltip,
|
|
8
7
|
TooltipContentProps,
|
|
9
8
|
XAxis,
|
|
10
9
|
YAxis,
|
|
11
10
|
} from 'recharts';
|
|
12
|
-
import { useCallback, useMemo, useRef, useState } from 'react';
|
|
11
|
+
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
|
13
12
|
import styled, { useTheme } from 'styled-components';
|
|
14
13
|
import { spacing } from '../../spacing';
|
|
15
14
|
import { fontSize } from '../../style/theme';
|
|
16
15
|
import { Box } from '../box/Box';
|
|
17
16
|
import { useChartLegend } from '../chartlegend/ChartLegendWrapper';
|
|
18
|
-
import { FormattedDateTime } from '../date/FormattedDateTime';
|
|
19
17
|
import { Icon } from '../icon/Icon.component';
|
|
20
18
|
import {
|
|
21
19
|
addMissingDataPoint,
|
|
22
20
|
getUnitLabel,
|
|
23
21
|
} from '../linetemporalchart/ChartUtil';
|
|
24
22
|
import { Loader } from '../loader/Loader.component';
|
|
25
|
-
import { ChartTitleText,
|
|
23
|
+
import { ChartTitleText, Text } from '../text/Text.component';
|
|
26
24
|
import { Tooltip as TooltipComponent } from '../tooltip/Tooltip.component';
|
|
27
25
|
import { formatXAxisLabel } from './utils';
|
|
28
26
|
import {
|
|
@@ -30,14 +28,16 @@ import {
|
|
|
30
28
|
ChartTooltipItem,
|
|
31
29
|
ChartTooltipHeader,
|
|
32
30
|
ChartTooltipItemsContainer,
|
|
31
|
+
ChartTooltipSeparator,
|
|
32
|
+
TooltipHeader,
|
|
33
33
|
} from '../charttooltip/ChartTooltip';
|
|
34
34
|
import { LegendShape } from '../chartlegend/ChartLegend';
|
|
35
|
+
import { StyledResponsiveContainer } from '../barchartv2/Barchart.component';
|
|
35
36
|
|
|
36
37
|
const LineTemporalChartWrapper = styled.div`
|
|
37
38
|
display: flex;
|
|
38
39
|
flex-direction: column;
|
|
39
40
|
justify-content: flex-start;
|
|
40
|
-
flex: 1;
|
|
41
41
|
`;
|
|
42
42
|
|
|
43
43
|
const ChartHeader = styled.div`
|
|
@@ -100,28 +100,30 @@ export type LineChartProps = (
|
|
|
100
100
|
renderTooltip?: (
|
|
101
101
|
tooltipProps: TooltipContentProps<number, string>,
|
|
102
102
|
unitLabel?: string,
|
|
103
|
-
|
|
103
|
+
duration?: number,
|
|
104
104
|
) => React.ReactNode;
|
|
105
105
|
};
|
|
106
106
|
|
|
107
107
|
const LineTimeSerieChartTooltip = ({
|
|
108
108
|
unitLabel,
|
|
109
|
-
|
|
109
|
+
duration,
|
|
110
110
|
isChartActive,
|
|
111
111
|
tooltipProps,
|
|
112
112
|
renderTooltip,
|
|
113
113
|
hoveredValue,
|
|
114
|
+
isSymmetrical,
|
|
114
115
|
}: {
|
|
115
116
|
tooltipProps: TooltipContentProps<number, string>;
|
|
116
117
|
unitLabel?: string;
|
|
117
|
-
|
|
118
|
+
duration: number;
|
|
118
119
|
isChartActive?: boolean;
|
|
119
120
|
renderTooltip?: (
|
|
120
121
|
tooltipProps: TooltipContentProps<number, string>,
|
|
121
122
|
unitLabel?: string,
|
|
122
|
-
|
|
123
|
+
duration?: number,
|
|
123
124
|
) => React.ReactNode;
|
|
124
125
|
hoveredValue?: string;
|
|
126
|
+
isSymmetrical?: boolean;
|
|
125
127
|
}) => {
|
|
126
128
|
const { active, payload, label } = tooltipProps;
|
|
127
129
|
|
|
@@ -129,7 +131,7 @@ const LineTimeSerieChartTooltip = ({
|
|
|
129
131
|
return null;
|
|
130
132
|
|
|
131
133
|
if (renderTooltip) {
|
|
132
|
-
return renderTooltip(tooltipProps, unitLabel,
|
|
134
|
+
return renderTooltip(tooltipProps, unitLabel, duration);
|
|
133
135
|
}
|
|
134
136
|
// We can't use the default itemSorter method because it's a custom tooltip.
|
|
135
137
|
// Sort the payload here instead
|
|
@@ -146,17 +148,15 @@ const LineTimeSerieChartTooltip = ({
|
|
|
146
148
|
return bValue - aValue; // Positives before negatives
|
|
147
149
|
});
|
|
148
150
|
|
|
151
|
+
// Find the transition point between positive and negative values
|
|
152
|
+
const separatorIndex = sortedPayload.findIndex((entry) => entry.value < 0);
|
|
153
|
+
const hasBothPositiveAndNegative =
|
|
154
|
+
separatorIndex > 0 && separatorIndex < sortedPayload.length;
|
|
155
|
+
|
|
149
156
|
return (
|
|
150
157
|
<ChartTooltipContainer>
|
|
151
158
|
<ChartTooltipHeader>
|
|
152
|
-
<
|
|
153
|
-
format={
|
|
154
|
-
timeFormat === 'date-time'
|
|
155
|
-
? 'day-month-abbreviated-hour-minute-second'
|
|
156
|
-
: 'long-date-without-weekday'
|
|
157
|
-
}
|
|
158
|
-
value={new Date(label)}
|
|
159
|
-
/>
|
|
159
|
+
<TooltipHeader duration={duration} value={label} />
|
|
160
160
|
</ChartTooltipHeader>
|
|
161
161
|
<ChartTooltipItemsContainer>
|
|
162
162
|
{sortedPayload.map((entry, index) => {
|
|
@@ -175,13 +175,18 @@ const LineTimeSerieChartTooltip = ({
|
|
|
175
175
|
: `${entry.value.toFixed(2)} ${unitLabel}`;
|
|
176
176
|
|
|
177
177
|
return (
|
|
178
|
-
<
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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>
|
|
185
190
|
);
|
|
186
191
|
})}
|
|
187
192
|
</ChartTooltipItemsContainer>
|
|
@@ -439,8 +444,8 @@ export function LineTimeSerieChart({
|
|
|
439
444
|
|
|
440
445
|
// Format time for display the tick in the x axis
|
|
441
446
|
const formatXAxisLabelCallback = useCallback(
|
|
442
|
-
(timestamp: number) => formatXAxisLabel(timestamp,
|
|
443
|
-
[
|
|
447
|
+
(timestamp: number) => formatXAxisLabel(timestamp, duration),
|
|
448
|
+
[duration],
|
|
444
449
|
);
|
|
445
450
|
|
|
446
451
|
return (
|
|
@@ -453,7 +458,7 @@ export function LineTimeSerieChart({
|
|
|
453
458
|
<Box ml={spacing.r4}>
|
|
454
459
|
<TooltipComponent
|
|
455
460
|
placement={'right'}
|
|
456
|
-
overlay={<
|
|
461
|
+
overlay={<Text>{helpText}</Text>}
|
|
457
462
|
>
|
|
458
463
|
<Icon name="Info" color={theme.buttonSecondary} />
|
|
459
464
|
</TooltipComponent>
|
|
@@ -467,7 +472,7 @@ export function LineTimeSerieChart({
|
|
|
467
472
|
onFocusCapture={() => setIsChartActive(true)}
|
|
468
473
|
onBlurCapture={() => setIsChartActive(false)}
|
|
469
474
|
>
|
|
470
|
-
<
|
|
475
|
+
<StyledResponsiveContainer width="100%" height={height}>
|
|
471
476
|
<LineChart
|
|
472
477
|
data={rechartsData}
|
|
473
478
|
ref={chartRef}
|
|
@@ -476,6 +481,7 @@ export function LineTimeSerieChart({
|
|
|
476
481
|
syncId={syncId}
|
|
477
482
|
onMouseEnter={() => setIsChartActive(true)}
|
|
478
483
|
onMouseLeave={() => setIsChartActive(false)}
|
|
484
|
+
accessibilityLayer
|
|
479
485
|
>
|
|
480
486
|
<CartesianGrid
|
|
481
487
|
vertical={true}
|
|
@@ -501,7 +507,6 @@ export function LineTimeSerieChart({
|
|
|
501
507
|
/>
|
|
502
508
|
<YAxis
|
|
503
509
|
orientation="right"
|
|
504
|
-
allowDataOverflow={false}
|
|
505
510
|
label={{
|
|
506
511
|
value: yAxisTitle,
|
|
507
512
|
angle: 90,
|
|
@@ -524,16 +529,19 @@ export function LineTimeSerieChart({
|
|
|
524
529
|
fill: theme.textSecondary,
|
|
525
530
|
fontSize: fontSize.smaller,
|
|
526
531
|
}}
|
|
527
|
-
tickFormatter={(value) =>
|
|
532
|
+
tickFormatter={(value) =>
|
|
533
|
+
new Intl.NumberFormat('fr-FR').format(value.toFixed(0))
|
|
534
|
+
}
|
|
528
535
|
tickCount={5}
|
|
529
|
-
interval={
|
|
536
|
+
interval={0}
|
|
530
537
|
/>
|
|
531
538
|
<Tooltip
|
|
532
539
|
content={(props: TooltipContentProps<number, string>) => (
|
|
533
540
|
<LineTimeSerieChartTooltip
|
|
534
541
|
unitLabel={unitLabel}
|
|
535
|
-
|
|
542
|
+
duration={duration}
|
|
536
543
|
renderTooltip={renderTooltip}
|
|
544
|
+
isSymmetrical={yAxisType === 'symmetrical'}
|
|
537
545
|
tooltipProps={props}
|
|
538
546
|
isChartActive={isChartActive}
|
|
539
547
|
hoveredValue={hoveredValue}
|
|
@@ -560,6 +568,7 @@ export function LineTimeSerieChart({
|
|
|
560
568
|
stroke={colorMapping[resource]}
|
|
561
569
|
dot={false}
|
|
562
570
|
isAnimationActive={false}
|
|
571
|
+
strokeDasharray={serie.isLineDashed ? '4 4' : undefined}
|
|
563
572
|
onMouseEnter={() => setHoveredValue(label)}
|
|
564
573
|
onMouseLeave={() => setHoveredValue(undefined)}
|
|
565
574
|
/>
|
|
@@ -567,7 +576,7 @@ export function LineTimeSerieChart({
|
|
|
567
576
|
}),
|
|
568
577
|
)}
|
|
569
578
|
</LineChart>
|
|
570
|
-
</
|
|
579
|
+
</StyledResponsiveContainer>
|
|
571
580
|
</div>
|
|
572
581
|
</LineTemporalChartWrapper>
|
|
573
582
|
);
|
|
@@ -1,87 +1,49 @@
|
|
|
1
1
|
import { formatXAxisLabel } from './utils';
|
|
2
2
|
|
|
3
|
-
const createChartData = (startDate: Date, endDate: Date) => [
|
|
4
|
-
{ timestamp: startDate.getTime() },
|
|
5
|
-
{ timestamp: endDate.getTime() },
|
|
6
|
-
];
|
|
7
|
-
|
|
8
3
|
describe('formatXAxisLabel', () => {
|
|
9
4
|
const mockTimestamp = new Date('2025-09-15T14:30:00Z').getTime();
|
|
10
5
|
|
|
11
|
-
describe('
|
|
12
|
-
it('should format timestamp with
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
);
|
|
17
|
-
const result = formatXAxisLabel(mockTimestamp, 'date-time', chartData);
|
|
18
|
-
expect(result).toBe('15 Sept 14:30');
|
|
6
|
+
describe('short duration (≤ 24 hours)', () => {
|
|
7
|
+
it('should format timestamp with time format', () => {
|
|
8
|
+
const duration = 12 * 60 * 60; // 12 hours
|
|
9
|
+
const result = formatXAxisLabel(mockTimestamp, duration);
|
|
10
|
+
expect(result).toBe('14:30');
|
|
19
11
|
});
|
|
20
12
|
});
|
|
21
13
|
|
|
22
|
-
describe('
|
|
23
|
-
it('should
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const result = formatXAxisLabel(mockTimestamp, 'date', chartData);
|
|
29
|
-
expect(result).toBe('2025-09-15');
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it('should use MM-DD format for time ranges less than 1 year', () => {
|
|
33
|
-
const startDate = new Date('2023-09-01');
|
|
34
|
-
const endDate = new Date('2023-12-01'); // Less than 1 year
|
|
35
|
-
const chartData = createChartData(startDate, endDate);
|
|
36
|
-
|
|
37
|
-
const result = formatXAxisLabel(mockTimestamp, 'date', chartData);
|
|
38
|
-
expect(result).toBe('09-15');
|
|
14
|
+
describe('medium duration (≤ 7 days)', () => {
|
|
15
|
+
it('should format timestamp with day-month-abbreviated format', () => {
|
|
16
|
+
const duration = 3 * 24 * 60 * 60; // 3 days
|
|
17
|
+
const result = formatXAxisLabel(mockTimestamp, duration);
|
|
18
|
+
expect(result).toBe('15 Sep');
|
|
39
19
|
});
|
|
20
|
+
});
|
|
40
21
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
it('should handle edge case of exactly 1 year time range', () => {
|
|
47
|
-
const startDate = new Date('2022-09-15');
|
|
48
|
-
const endDate = new Date('2023-09-15'); // Exactly 1 year
|
|
49
|
-
const chartData = createChartData(startDate, endDate);
|
|
50
|
-
|
|
51
|
-
const result = formatXAxisLabel(mockTimestamp, 'date', chartData);
|
|
52
|
-
expect(result).toBe('09-15');
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it('should handle leap year calculation correctly', () => {
|
|
56
|
-
const startDate = new Date('2023-01-01');
|
|
57
|
-
const endDate = new Date('2024-01-02'); // Just over 1 year including leap year
|
|
58
|
-
const chartData = createChartData(startDate, endDate);
|
|
59
|
-
|
|
60
|
-
const result = formatXAxisLabel(mockTimestamp, 'date', chartData);
|
|
61
|
-
|
|
62
|
-
expect(result).toBe('2025-09-15');
|
|
22
|
+
describe('long duration (> 7 days)', () => {
|
|
23
|
+
it('should format timestamp with day-month-abbreviated-year format', () => {
|
|
24
|
+
const duration = 30 * 24 * 60 * 60; // 30 days
|
|
25
|
+
const result = formatXAxisLabel(mockTimestamp, duration);
|
|
26
|
+
expect(result).toBe('15Sep25');
|
|
63
27
|
});
|
|
64
28
|
});
|
|
65
29
|
|
|
66
|
-
describe('
|
|
67
|
-
it('should handle
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
expect(result).toBe('09-15');
|
|
30
|
+
describe('edge cases', () => {
|
|
31
|
+
it('should handle exactly 24 hours duration', () => {
|
|
32
|
+
const duration = 24 * 60 * 60; // exactly 24 hours
|
|
33
|
+
const result = formatXAxisLabel(mockTimestamp, duration);
|
|
34
|
+
expect(result).toBe('14:30');
|
|
73
35
|
});
|
|
74
36
|
|
|
75
|
-
it('should handle
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
];
|
|
81
|
-
|
|
82
|
-
const result = formatXAxisLabel(mockTimestamp, 'date', chartData);
|
|
37
|
+
it('should handle exactly 7 days duration', () => {
|
|
38
|
+
const duration = 7 * 24 * 60 * 60; // exactly 7 days
|
|
39
|
+
const result = formatXAxisLabel(mockTimestamp, duration);
|
|
40
|
+
expect(result).toBe('15 Sep');
|
|
41
|
+
});
|
|
83
42
|
|
|
84
|
-
|
|
43
|
+
it('should handle just over 7 days duration', () => {
|
|
44
|
+
const duration = 8 * 24 * 60 * 60; // 8 days
|
|
45
|
+
const result = formatXAxisLabel(mockTimestamp, duration);
|
|
46
|
+
expect(result).toBe('15Sep25');
|
|
85
47
|
});
|
|
86
48
|
});
|
|
87
49
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
TIME_FORMATER,
|
|
3
|
+
DAY_MONTH_ABBREVIATED,
|
|
4
|
+
DAY_MONTH_ABBREVIATED_YEAR,
|
|
5
5
|
} from '../date/FormattedDateTime';
|
|
6
6
|
|
|
7
7
|
export const ONE_YEAR_MILLISECONDS = 366 * 24 * 60 * 60 * 1000;
|
|
@@ -22,22 +22,18 @@ export type ChartDataPoint = {
|
|
|
22
22
|
*/
|
|
23
23
|
export const formatXAxisLabel = (
|
|
24
24
|
timestamp: number,
|
|
25
|
-
|
|
26
|
-
chartData: ChartDataPoint[] = [],
|
|
25
|
+
duration: number,
|
|
27
26
|
): string => {
|
|
28
27
|
const date = new Date(timestamp);
|
|
29
|
-
if (
|
|
30
|
-
return
|
|
28
|
+
if (duration <= 24 * 60 * 60) {
|
|
29
|
+
return TIME_FORMATER.format(date);
|
|
30
|
+
} else if (duration <= 7 * 24 * 60 * 60) {
|
|
31
|
+
return DAY_MONTH_ABBREVIATED.format(date)
|
|
32
|
+
.replace(',', '')
|
|
33
|
+
.replace(/Sept/g, 'Sep');
|
|
34
|
+
} else {
|
|
35
|
+
return DAY_MONTH_ABBREVIATED_YEAR.format(date)
|
|
36
|
+
.replace(/[ ,]/g, '')
|
|
37
|
+
.replace(/Sept/g, 'Sep');
|
|
31
38
|
}
|
|
32
|
-
if (timeFormat === 'date-time') {
|
|
33
|
-
return DAY_MONTH_ABBREVIATED_HOUR_MINUTE.format(date).replace(',', '');
|
|
34
|
-
}
|
|
35
|
-
const timestamps = chartData.map((d) => d.timestamp);
|
|
36
|
-
const minTimestamp = Math.min(...timestamps);
|
|
37
|
-
const maxTimestamp = Math.max(...timestamps);
|
|
38
|
-
const timeRangeMilliseconds = maxTimestamp - minTimestamp;
|
|
39
|
-
|
|
40
|
-
return timeRangeMilliseconds >= ONE_YEAR_MILLISECONDS
|
|
41
|
-
? YEAR_MONTH_DAY_FORMATTER.format(date)
|
|
42
|
-
: MONTH_DAY_FORMATTER.format(date);
|
|
43
39
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { createGlobalStyle, css } from 'styled-components';
|
|
2
2
|
|
|
3
3
|
type Props = {
|
|
4
4
|
children: React.ReactNode;
|
|
@@ -24,6 +24,7 @@ ${(props) => {
|
|
|
24
24
|
width: 4px;
|
|
25
25
|
height: 4px;
|
|
26
26
|
min-height: 20px;
|
|
27
|
+
background: ${brand.border}; // fallback for gradient themes
|
|
27
28
|
background: ${brand.buttonSecondary};
|
|
28
29
|
border-radius: 4px;
|
|
29
30
|
-webkit-border-radius: 4px;
|
|
@@ -46,6 +47,7 @@ ${(props) => {
|
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
// Firefox
|
|
50
|
+
scrollbar-color: ${brand.border} ${brand.backgroundLevel3}; // fallback for gradient themes
|
|
49
51
|
scrollbar-color: ${brand.buttonSecondary} ${brand.backgroundLevel3};
|
|
50
52
|
scrollbar-width: thin;
|
|
51
53
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useMemo } from "react";
|
|
2
|
-
import { Area, AreaChart, CartesianGrid, ResponsiveContainer } from "recharts";
|
|
2
|
+
import { Area, AreaChart, CartesianGrid, ResponsiveContainer, YAxis } from "recharts";
|
|
3
3
|
import { useTheme } from "styled-components";
|
|
4
4
|
import { chartColors } from "../../style/theme";
|
|
5
5
|
import { addMissingDataPoint } from "../linetemporalchart/ChartUtil";
|
|
@@ -11,14 +11,15 @@ type SparklineProps = {
|
|
|
11
11
|
},
|
|
12
12
|
startingTimeStamp: number,
|
|
13
13
|
sampleDuration: number,
|
|
14
|
-
sampleInterval: number
|
|
14
|
+
sampleInterval: number,
|
|
15
|
+
yAxisType?: 'default' | 'percentage',
|
|
15
16
|
};
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Sparkline is a simple dynamically sized area chart.
|
|
19
20
|
* Used to show trends in data over time.
|
|
20
21
|
*/
|
|
21
|
-
export function Sparkline({ serie, startingTimeStamp, sampleDuration, sampleInterval }: SparklineProps) {
|
|
22
|
+
export function Sparkline({ serie, startingTimeStamp, sampleDuration, sampleInterval, yAxisType }: SparklineProps) {
|
|
22
23
|
const data = useMemo(
|
|
23
24
|
() => {
|
|
24
25
|
const dataMdp = addMissingDataPoint(serie.data, startingTimeStamp, sampleDuration, sampleInterval);
|
|
@@ -48,6 +49,7 @@ export function Sparkline({ serie, startingTimeStamp, sampleDuration, sampleInte
|
|
|
48
49
|
activeDot={false}
|
|
49
50
|
isAnimationActive={false}
|
|
50
51
|
/>
|
|
52
|
+
{yAxisType === 'percentage' && <YAxis domain={[0, 100]} hide />}
|
|
51
53
|
</AreaChart>
|
|
52
54
|
</ResponsiveContainer>
|
|
53
55
|
);
|
|
@@ -166,3 +166,22 @@ export const FlatWithMissingData: Story = {
|
|
|
166
166
|
},
|
|
167
167
|
},
|
|
168
168
|
};
|
|
169
|
+
|
|
170
|
+
export const PercentageYAxis: Story = {
|
|
171
|
+
args: {
|
|
172
|
+
serie: {
|
|
173
|
+
data: trendingUpData,
|
|
174
|
+
},
|
|
175
|
+
startingTimeStamp: 1740405600,
|
|
176
|
+
sampleDuration: 7200,
|
|
177
|
+
sampleInterval: 720,
|
|
178
|
+
yAxisType: 'percentage',
|
|
179
|
+
},
|
|
180
|
+
parameters: {
|
|
181
|
+
docs: {
|
|
182
|
+
description: {
|
|
183
|
+
story: 'Sparkline with Y-axis range is set as [0-100].',
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
};
|