@scality/core-ui 0.175.0 → 0.177.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 -5
- package/dist/components/barchartv2/Barchart.component.d.ts.map +1 -1
- package/dist/components/barchartv2/Barchart.component.js +23 -27
- package/dist/components/barchartv2/BarchartTooltip.d.ts +4 -3
- package/dist/components/barchartv2/BarchartTooltip.d.ts.map +1 -1
- package/dist/components/barchartv2/BarchartTooltip.js +10 -12
- package/dist/components/barchartv2/utils.d.ts +6 -1
- package/dist/components/barchartv2/utils.d.ts.map +1 -1
- package/dist/components/barchartv2/utils.js +34 -8
- package/dist/components/chartlegend/ChartLegendWrapper.js +1 -1
- package/dist/components/charttooltip/ChartTooltip.d.ts +29 -0
- package/dist/components/charttooltip/ChartTooltip.d.ts.map +1 -1
- package/dist/components/charttooltip/ChartTooltip.js +105 -1
- 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/globalhealthbar/GlobalHealthBarRecharts.component.d.ts.map +1 -1
- package/dist/components/globalhealthbar/GlobalHealthBarRecharts.component.js +27 -1
- package/dist/components/globalhealthbar/components/GlobalHealthBarTooltip.d.ts +1 -1
- package/dist/components/globalhealthbar/components/GlobalHealthBarTooltip.d.ts.map +1 -1
- package/dist/components/globalhealthbar/components/GlobalHealthBarTooltip.js +19 -59
- package/dist/components/globalhealthbar/components/HealthBarXAxis.js +1 -1
- package/dist/components/globalhealthbar/useHealthBarData.d.ts +1 -0
- package/dist/components/globalhealthbar/useHealthBarData.d.ts.map +1 -1
- package/dist/components/globalhealthbar/useHealthBarData.js +1 -0
- package/dist/components/globalhealthbar/useHealthBarData.spec.js +2 -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 +45 -47
- 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/style/theme.js +1 -1
- package/package.json +1 -1
- package/src/lib/components/barchartv2/Barchart.component.test.tsx +23 -25
- package/src/lib/components/barchartv2/Barchart.component.tsx +41 -39
- package/src/lib/components/barchartv2/BarchartTooltip.test.tsx +33 -3
- package/src/lib/components/barchartv2/BarchartTooltip.tsx +33 -24
- package/src/lib/components/barchartv2/utils.test.ts +72 -17
- package/src/lib/components/barchartv2/utils.ts +40 -12
- package/src/lib/components/chartlegend/ChartLegendWrapper.tsx +1 -1
- package/src/lib/components/charttooltip/ChartTooltip.tsx +174 -1
- package/src/lib/components/date/FormattedDateTime.tsx +73 -8
- package/src/lib/components/globalhealthbar/GlobalHealthBarRecharts.component.tsx +56 -11
- package/src/lib/components/globalhealthbar/components/GlobalHealthBarTooltip.tsx +75 -117
- package/src/lib/components/globalhealthbar/components/HealthBarXAxis.tsx +1 -1
- package/src/lib/components/globalhealthbar/useHealthBarData.spec.tsx +2 -0
- package/src/lib/components/globalhealthbar/useHealthBarData.ts +2 -0
- package/src/lib/components/linetimeseriechart/linetimeseriechart.component.tsx +101 -90
- package/src/lib/components/linetimeseriechart/utils.test.ts +30 -68
- package/src/lib/components/linetimeseriechart/utils.ts +13 -17
- package/src/lib/style/theme.ts +1 -1
- package/stories/BarChart/barchart.stories.tsx +23 -8
- package/stories/formattedate.stories.tsx +2 -0
- package/stories/linetimeseriechart.stories.tsx +1 -0
|
@@ -89,9 +89,9 @@ describe('Barchart', () => {
|
|
|
89
89
|
</Wrapper>,
|
|
90
90
|
);
|
|
91
91
|
|
|
92
|
-
expect(screen.getByText('
|
|
93
|
-
expect(screen.getByText('
|
|
94
|
-
expect(screen.getByText('
|
|
92
|
+
expect(screen.getByText('05 Jul')).toBeInTheDocument();
|
|
93
|
+
expect(screen.getByText('06 Jul')).toBeInTheDocument();
|
|
94
|
+
expect(screen.getByText('07 Jul')).toBeInTheDocument();
|
|
95
95
|
});
|
|
96
96
|
it('should render the Barchart component with error state', async () => {
|
|
97
97
|
const { Wrapper } = getWrapper();
|
|
@@ -153,11 +153,11 @@ describe('Barchart', () => {
|
|
|
153
153
|
</ChartLegendWrapper>
|
|
154
154
|
</Wrapper>,
|
|
155
155
|
);
|
|
156
|
-
expect(screen.getByText('
|
|
157
|
-
expect(screen.getByText('
|
|
158
|
-
expect(screen.getByText('
|
|
159
|
-
expect(screen.getByText('
|
|
160
|
-
expect(screen.getByText('
|
|
156
|
+
expect(screen.getByText('03 Jul')).toBeInTheDocument();
|
|
157
|
+
expect(screen.getByText('04 Jul')).toBeInTheDocument();
|
|
158
|
+
expect(screen.getByText('05 Jul')).toBeInTheDocument();
|
|
159
|
+
expect(screen.getByText('06 Jul')).toBeInTheDocument();
|
|
160
|
+
expect(screen.getByText('07 Jul')).toBeInTheDocument();
|
|
161
161
|
});
|
|
162
162
|
it('should render when there are missing data in the time range', async () => {
|
|
163
163
|
const bars = [
|
|
@@ -203,10 +203,10 @@ describe('Barchart', () => {
|
|
|
203
203
|
|
|
204
204
|
// Check that all days are present
|
|
205
205
|
await waitFor(() => {
|
|
206
|
-
expect(screen.getByText('
|
|
207
|
-
expect(screen.getByText('
|
|
208
|
-
expect(screen.getByText('
|
|
209
|
-
expect(screen.getByText('
|
|
206
|
+
expect(screen.getByText('05 Jul')).toBeInTheDocument();
|
|
207
|
+
expect(screen.getByText('06 Jul')).toBeInTheDocument();
|
|
208
|
+
expect(screen.getByText('07 Jul')).toBeInTheDocument();
|
|
209
|
+
expect(screen.getByText('08 Jul')).toBeInTheDocument();
|
|
210
210
|
});
|
|
211
211
|
});
|
|
212
212
|
it('should render for a specific time range', async () => {
|
|
@@ -244,13 +244,13 @@ describe('Barchart', () => {
|
|
|
244
244
|
</Wrapper>,
|
|
245
245
|
);
|
|
246
246
|
await waitFor(() => {
|
|
247
|
-
expect(screen.getByText('
|
|
248
|
-
expect(screen.getByText('
|
|
249
|
-
expect(screen.getByText('
|
|
250
|
-
expect(screen.getByText('
|
|
251
|
-
expect(screen.getByText('
|
|
252
|
-
expect(screen.getByText('
|
|
253
|
-
expect(screen.getByText('
|
|
247
|
+
expect(screen.getByText('05 Jul')).toBeInTheDocument();
|
|
248
|
+
expect(screen.getByText('06 Jul')).toBeInTheDocument();
|
|
249
|
+
expect(screen.getByText('07 Jul')).toBeInTheDocument();
|
|
250
|
+
expect(screen.getByText('08 Jul')).toBeInTheDocument();
|
|
251
|
+
expect(screen.getByText('09 Jul')).toBeInTheDocument();
|
|
252
|
+
expect(screen.getByText('10 Jul')).toBeInTheDocument();
|
|
253
|
+
expect(screen.getByText('11 Jul')).toBeInTheDocument();
|
|
254
254
|
});
|
|
255
255
|
});
|
|
256
256
|
it('should render the Barchart component with hourly intervals', async () => {
|
|
@@ -411,7 +411,7 @@ describe('Barchart', () => {
|
|
|
411
411
|
/>
|
|
412
412
|
</Wrapper>,
|
|
413
413
|
);
|
|
414
|
-
expect(screen.getByText('
|
|
414
|
+
expect(screen.getByText('05 Jul')).toBeInTheDocument();
|
|
415
415
|
});
|
|
416
416
|
|
|
417
417
|
it('should render the CustomTick component with day format', () => {
|
|
@@ -435,7 +435,7 @@ describe('Barchart', () => {
|
|
|
435
435
|
/>
|
|
436
436
|
</Wrapper>,
|
|
437
437
|
);
|
|
438
|
-
expect(screen.getByText('
|
|
438
|
+
expect(screen.getByText('05 Jul')).toBeInTheDocument();
|
|
439
439
|
});
|
|
440
440
|
it('should render the CustomTick component with hour format', () => {
|
|
441
441
|
const { Wrapper } = getWrapper();
|
|
@@ -458,7 +458,7 @@ describe('Barchart', () => {
|
|
|
458
458
|
/>
|
|
459
459
|
</Wrapper>,
|
|
460
460
|
);
|
|
461
|
-
expect(screen.getByText('
|
|
461
|
+
expect(screen.getByText('05 Jul')).toBeInTheDocument();
|
|
462
462
|
});
|
|
463
463
|
it('should render the CustomTick component with minute format', () => {
|
|
464
464
|
const { Wrapper } = getWrapper();
|
|
@@ -481,9 +481,7 @@ describe('Barchart', () => {
|
|
|
481
481
|
/>
|
|
482
482
|
</Wrapper>,
|
|
483
483
|
);
|
|
484
|
-
expect(
|
|
485
|
-
screen.getByText(new Date('2024-07-05T10:00:00').getTime()),
|
|
486
|
-
).toBeInTheDocument();
|
|
484
|
+
expect(screen.getByText('05 Jul')).toBeInTheDocument();
|
|
487
485
|
});
|
|
488
486
|
});
|
|
489
487
|
});
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
1
|
+
import { useState, useRef } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
Bar,
|
|
4
4
|
BarChart,
|
|
5
5
|
CartesianGrid,
|
|
6
|
-
ReferenceLine,
|
|
7
6
|
ResponsiveContainer,
|
|
8
7
|
Tooltip,
|
|
9
8
|
TooltipContentProps,
|
|
@@ -21,7 +20,7 @@ import { IconHelp } from '../iconhelper/IconHelper';
|
|
|
21
20
|
import { Loader } from '../loader/Loader.component';
|
|
22
21
|
import { Text } from '../text/Text.component';
|
|
23
22
|
import { BarchartTooltip } from './BarchartTooltip';
|
|
24
|
-
import { UnitRange, useChartData } from './utils';
|
|
23
|
+
import { getTicks, UnitRange, useChartData } from './utils';
|
|
25
24
|
|
|
26
25
|
const CHART_CONSTANTS = {
|
|
27
26
|
TICK_WIDTH_OFFSET: 4,
|
|
@@ -35,6 +34,7 @@ const CHART_CONSTANTS = {
|
|
|
35
34
|
bottom: 0,
|
|
36
35
|
},
|
|
37
36
|
};
|
|
37
|
+
const maxWidthTooltip = { maxWidth: '20rem' };
|
|
38
38
|
|
|
39
39
|
/* ---------------------------------- TYPE ---------------------------------- */
|
|
40
40
|
|
|
@@ -77,6 +77,7 @@ export type BarchartSortFn<T extends BarchartBars> = (
|
|
|
77
77
|
|
|
78
78
|
export type BarchartProps<T extends BarchartBars> = {
|
|
79
79
|
type: CategoryType | TimeType;
|
|
80
|
+
title: string;
|
|
80
81
|
bars?: T;
|
|
81
82
|
tooltip?: BarchartTooltipFn<T>;
|
|
82
83
|
defaultSort?: BarchartSortFn<T>;
|
|
@@ -90,7 +91,6 @@ export type BarchartProps<T extends BarchartBars> = {
|
|
|
90
91
|
* @default 'default'
|
|
91
92
|
*/
|
|
92
93
|
stackedBarSort?: 'default' | 'legend';
|
|
93
|
-
title?: string;
|
|
94
94
|
secondaryTitle?: string;
|
|
95
95
|
rightTitle?: React.ReactNode;
|
|
96
96
|
height?: number;
|
|
@@ -112,33 +112,19 @@ interface CustomTickProps {
|
|
|
112
112
|
/* ---------------------------------- COMPONENTS ---------------------------------- */
|
|
113
113
|
|
|
114
114
|
/**
|
|
115
|
-
*
|
|
116
|
-
* @param
|
|
117
|
-
* @param interval - Interval in milliseconds
|
|
115
|
+
* Get the format of the date based on the duration
|
|
116
|
+
* @param duration - Duration in milliseconds
|
|
118
117
|
* @returns Formatted string
|
|
119
118
|
*/
|
|
120
119
|
export const formatDate = (
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
return (
|
|
128
|
-
<>
|
|
129
|
-
<FormattedDateTime format="chart-date" value={date} />{' '}
|
|
130
|
-
<FormattedDateTime format="time" value={date} />
|
|
131
|
-
</>
|
|
132
|
-
);
|
|
133
|
-
} else if (interval === 24 * 60 * 60 * 1000) {
|
|
134
|
-
// Daily interval - use day format
|
|
135
|
-
return <FormattedDateTime format="chart-date" value={date} />;
|
|
136
|
-
} else if (interval >= 60 * 1000) {
|
|
137
|
-
//Hourly and minute intervals - use minute format
|
|
138
|
-
return <FormattedDateTime format="time" value={date} />;
|
|
120
|
+
duration: number,
|
|
121
|
+
): 'time' | 'day-month-abbreviated' | 'chart-long-term-date' => {
|
|
122
|
+
if (duration <= 24 * 60 * 60 * 1000) {
|
|
123
|
+
return 'time';
|
|
124
|
+
} else if (duration <= 7 * 24 * 60 * 60 * 1000) {
|
|
125
|
+
return 'day-month-abbreviated';
|
|
139
126
|
} else {
|
|
140
|
-
|
|
141
|
-
return timestamp;
|
|
127
|
+
return 'chart-long-term-date';
|
|
142
128
|
}
|
|
143
129
|
};
|
|
144
130
|
|
|
@@ -155,6 +141,11 @@ export const CustomTick = ({
|
|
|
155
141
|
width / visibleTicksCount - CHART_CONSTANTS.TICK_WIDTH_OFFSET;
|
|
156
142
|
const centerX = x - tickWidth / 2;
|
|
157
143
|
|
|
144
|
+
const duration =
|
|
145
|
+
type.type === 'time'
|
|
146
|
+
? type.timeRange.endDate.getTime() - type.timeRange.startDate.getTime()
|
|
147
|
+
: 0;
|
|
148
|
+
|
|
158
149
|
return (
|
|
159
150
|
<foreignObject
|
|
160
151
|
x={centerX}
|
|
@@ -167,9 +158,14 @@ export const CustomTick = ({
|
|
|
167
158
|
color="textSecondary"
|
|
168
159
|
text={
|
|
169
160
|
<Text variant="Smaller">
|
|
170
|
-
{type.type === 'time'
|
|
171
|
-
|
|
172
|
-
|
|
161
|
+
{type.type === 'time' ? (
|
|
162
|
+
<FormattedDateTime
|
|
163
|
+
format={formatDate(duration)}
|
|
164
|
+
value={new Date(payload.value)}
|
|
165
|
+
/>
|
|
166
|
+
) : (
|
|
167
|
+
String(payload.value)
|
|
168
|
+
)}
|
|
173
169
|
</Text>
|
|
174
170
|
}
|
|
175
171
|
centered
|
|
@@ -188,6 +184,7 @@ export const CustomTick = ({
|
|
|
188
184
|
export const StyledResponsiveContainer = styled(ResponsiveContainer)`
|
|
189
185
|
// Avoid tooltip over constrained text to be cut off
|
|
190
186
|
& .recharts-surface {
|
|
187
|
+
outline: none;
|
|
191
188
|
overflow: visible;
|
|
192
189
|
}
|
|
193
190
|
`;
|
|
@@ -207,7 +204,12 @@ const ChartHeader = ({
|
|
|
207
204
|
<Wrap>
|
|
208
205
|
<Stack gap="r4">
|
|
209
206
|
<Text variant="ChartTitle">{title}</Text>
|
|
210
|
-
{helpTooltip &&
|
|
207
|
+
{helpTooltip && (
|
|
208
|
+
<IconHelp
|
|
209
|
+
tooltipMessage={helpTooltip}
|
|
210
|
+
overlayStyle={maxWidthTooltip}
|
|
211
|
+
/>
|
|
212
|
+
)}
|
|
211
213
|
|
|
212
214
|
{secondaryTitle && (
|
|
213
215
|
<Text
|
|
@@ -262,6 +264,7 @@ export const Barchart = <T extends BarchartBars>(props: BarchartProps<T>) => {
|
|
|
262
264
|
const theme = useTheme();
|
|
263
265
|
const { getColor } = useChartLegend();
|
|
264
266
|
const [hoveredValue, setHoveredValue] = useState<string | undefined>();
|
|
267
|
+
const chartRef = useRef<HTMLDivElement>(null);
|
|
265
268
|
|
|
266
269
|
const {
|
|
267
270
|
height = CHART_CONSTANTS.DEFAULT_HEIGHT,
|
|
@@ -302,11 +305,11 @@ export const Barchart = <T extends BarchartBars>(props: BarchartProps<T>) => {
|
|
|
302
305
|
unitRange,
|
|
303
306
|
stackedBarSort,
|
|
304
307
|
);
|
|
305
|
-
|
|
308
|
+
const titleWithUnit = unitLabel ? `${title} (${unitLabel})` : title;
|
|
306
309
|
return (
|
|
307
|
-
<Stack direction="vertical">
|
|
310
|
+
<Stack direction="vertical" style={{ gap: '0' }}>
|
|
308
311
|
<ChartHeader
|
|
309
|
-
title={
|
|
312
|
+
title={titleWithUnit}
|
|
310
313
|
secondaryTitle={secondaryTitle}
|
|
311
314
|
helpTooltip={helpTooltip}
|
|
312
315
|
rightTitle={rightTitle}
|
|
@@ -316,7 +319,7 @@ export const Barchart = <T extends BarchartBars>(props: BarchartProps<T>) => {
|
|
|
316
319
|
) : isLoading ? (
|
|
317
320
|
<Loading height={height} />
|
|
318
321
|
) : (
|
|
319
|
-
<StyledResponsiveContainer width="100%" height={height}>
|
|
322
|
+
<StyledResponsiveContainer ref={chartRef} width="100%" height={height}>
|
|
320
323
|
<BarChart
|
|
321
324
|
data={rechartsData}
|
|
322
325
|
accessibilityLayer
|
|
@@ -357,13 +360,11 @@ export const Barchart = <T extends BarchartBars>(props: BarchartProps<T>) => {
|
|
|
357
360
|
})}
|
|
358
361
|
|
|
359
362
|
<YAxis
|
|
360
|
-
tickCount={1}
|
|
361
363
|
interval={0}
|
|
362
|
-
unit={` ${unitLabel}`}
|
|
363
364
|
domain={[0, roundReferenceValue]}
|
|
365
|
+
ticks={getTicks(roundReferenceValue, false)}
|
|
364
366
|
tickFormatter={
|
|
365
|
-
(value) =>
|
|
366
|
-
new Intl.NumberFormat('fr-FR').format(value.toFixed(0)) // Add a space as thousand separator
|
|
367
|
+
(value) => new Intl.NumberFormat('fr-FR').format(value) // Add a space as thousand separator
|
|
367
368
|
}
|
|
368
369
|
axisLine={{ stroke: theme.border }}
|
|
369
370
|
tick={{
|
|
@@ -396,6 +397,7 @@ export const Barchart = <T extends BarchartBars>(props: BarchartProps<T>) => {
|
|
|
396
397
|
hoveredValue={hoveredValue}
|
|
397
398
|
tooltip={tooltip}
|
|
398
399
|
unitLabel={unitLabel}
|
|
400
|
+
chartContainerRef={chartRef}
|
|
399
401
|
/>
|
|
400
402
|
)}
|
|
401
403
|
cursor={false}
|
|
@@ -19,6 +19,30 @@ const testTooltipProps = {
|
|
|
19
19
|
const testTooltip = () => <div>Test Tooltip</div>;
|
|
20
20
|
const date = new Date('2024-07-01T00:00:00').getTime();
|
|
21
21
|
|
|
22
|
+
// Create a mock DOM element for the chart container
|
|
23
|
+
const mockChartContainer = document.createElement('div');
|
|
24
|
+
mockChartContainer.getBoundingClientRect = jest.fn(() => ({
|
|
25
|
+
width: 800,
|
|
26
|
+
height: 400,
|
|
27
|
+
top: 0,
|
|
28
|
+
left: 0,
|
|
29
|
+
right: 800,
|
|
30
|
+
bottom: 400,
|
|
31
|
+
x: 0,
|
|
32
|
+
y: 0,
|
|
33
|
+
toJSON: () => ({
|
|
34
|
+
width: 800,
|
|
35
|
+
height: 400,
|
|
36
|
+
top: 0,
|
|
37
|
+
left: 0,
|
|
38
|
+
right: 800,
|
|
39
|
+
bottom: 400,
|
|
40
|
+
x: 0,
|
|
41
|
+
y: 0,
|
|
42
|
+
}),
|
|
43
|
+
}));
|
|
44
|
+
const mockChartContainerRef = { current: mockChartContainer };
|
|
45
|
+
|
|
22
46
|
describe('ChartTooltip', () => {
|
|
23
47
|
const selectors = {
|
|
24
48
|
tooltip: () => screen.queryByText(/Test Tooltip/),
|
|
@@ -30,6 +54,7 @@ describe('ChartTooltip', () => {
|
|
|
30
54
|
longDate: () => screen.queryByText(/01 July 2024/),
|
|
31
55
|
date: () => screen.queryByText(/\b01 Jul\b/),
|
|
32
56
|
time: () => screen.queryByText(/00:00:00/),
|
|
57
|
+
dateTime: () => screen.queryByText(/01 Jul 00:00:00/),
|
|
33
58
|
};
|
|
34
59
|
it('should render the BarchartTooltip component', () => {
|
|
35
60
|
render(
|
|
@@ -38,6 +63,7 @@ describe('ChartTooltip', () => {
|
|
|
38
63
|
tooltipProps={testTooltipProps}
|
|
39
64
|
hoveredValue="Success"
|
|
40
65
|
tooltip={undefined}
|
|
66
|
+
chartContainerRef={mockChartContainerRef}
|
|
41
67
|
/>,
|
|
42
68
|
);
|
|
43
69
|
expect(selectors.category()).toBeInTheDocument();
|
|
@@ -53,6 +79,7 @@ describe('ChartTooltip', () => {
|
|
|
53
79
|
tooltipProps={testTooltipProps}
|
|
54
80
|
hoveredValue="Success"
|
|
55
81
|
tooltip={testTooltip}
|
|
82
|
+
chartContainerRef={mockChartContainerRef}
|
|
56
83
|
/>,
|
|
57
84
|
);
|
|
58
85
|
expect(selectors.tooltip()).toBeInTheDocument();
|
|
@@ -64,6 +91,7 @@ describe('ChartTooltip', () => {
|
|
|
64
91
|
tooltipProps={{ ...testTooltipProps, active: false }}
|
|
65
92
|
hoveredValue="Success"
|
|
66
93
|
tooltip={testTooltip}
|
|
94
|
+
chartContainerRef={mockChartContainerRef}
|
|
67
95
|
/>,
|
|
68
96
|
);
|
|
69
97
|
expect(selectors.tooltip()).not.toBeInTheDocument();
|
|
@@ -83,15 +111,15 @@ describe('ChartTooltip', () => {
|
|
|
83
111
|
}}
|
|
84
112
|
tooltipProps={{ ...testTooltipProps, label }}
|
|
85
113
|
hoveredValue="Success"
|
|
114
|
+
chartContainerRef={mockChartContainerRef}
|
|
86
115
|
/>,
|
|
87
116
|
);
|
|
88
117
|
expect(selectors.success()).toBeInTheDocument();
|
|
89
118
|
expect(selectors.successValue()).toBeInTheDocument();
|
|
90
119
|
expect(selectors.failed()).toBeInTheDocument();
|
|
91
120
|
expect(selectors.failedValue()).toBeInTheDocument();
|
|
92
|
-
|
|
93
|
-
expect(selectors.
|
|
94
|
-
expect(selectors.time()).not.toBeInTheDocument();
|
|
121
|
+
|
|
122
|
+
expect(selectors.dateTime()).toBeInTheDocument();
|
|
95
123
|
});
|
|
96
124
|
it('should render time tooltip when type is time and interval is one hour', () => {
|
|
97
125
|
const label = date;
|
|
@@ -107,6 +135,7 @@ describe('ChartTooltip', () => {
|
|
|
107
135
|
}}
|
|
108
136
|
tooltipProps={{ ...testTooltipProps, label }}
|
|
109
137
|
hoveredValue="Success"
|
|
138
|
+
chartContainerRef={mockChartContainerRef}
|
|
110
139
|
/>,
|
|
111
140
|
);
|
|
112
141
|
expect(selectors.success()).toBeInTheDocument();
|
|
@@ -133,6 +162,7 @@ describe('ChartTooltip', () => {
|
|
|
133
162
|
tooltipProps={tooltipProps}
|
|
134
163
|
hoveredValue="Success"
|
|
135
164
|
unitLabel="kB"
|
|
165
|
+
chartContainerRef={mockChartContainerRef}
|
|
136
166
|
/>,
|
|
137
167
|
);
|
|
138
168
|
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
+
import { TooltipContentProps } from 'recharts';
|
|
2
|
+
import { LegendShape } from '../chartlegend/ChartLegend';
|
|
3
|
+
import {
|
|
4
|
+
ChartTooltipPortal,
|
|
5
|
+
ChartTooltipHeader,
|
|
6
|
+
ChartTooltipItem,
|
|
7
|
+
ChartTooltipItemsContainer,
|
|
8
|
+
TooltipHeader,
|
|
9
|
+
} from '../charttooltip/ChartTooltip';
|
|
1
10
|
import {
|
|
2
11
|
BarchartBars,
|
|
3
12
|
BarchartTooltipFn,
|
|
4
13
|
CategoryType,
|
|
5
14
|
TimeType,
|
|
6
15
|
} from './Barchart.component';
|
|
7
|
-
import { FormattedDateTime } from '../date/FormattedDateTime';
|
|
8
|
-
import { TooltipContentProps } from 'recharts';
|
|
9
16
|
import { getCurrentPoint } from './utils';
|
|
10
|
-
import {
|
|
11
|
-
ChartTooltipContainer,
|
|
12
|
-
ChartTooltipItem,
|
|
13
|
-
ChartTooltipHeader,
|
|
14
|
-
ChartTooltipItemsContainer,
|
|
15
|
-
} from '../charttooltip/ChartTooltip';
|
|
16
|
-
import { LegendShape } from '../chartlegend/ChartLegend';
|
|
17
17
|
|
|
18
18
|
export const BarchartTooltip = <T extends BarchartBars>({
|
|
19
19
|
type,
|
|
@@ -22,6 +22,7 @@ export const BarchartTooltip = <T extends BarchartBars>({
|
|
|
22
22
|
hoveredValue,
|
|
23
23
|
tooltip,
|
|
24
24
|
unitLabel,
|
|
25
|
+
chartContainerRef,
|
|
25
26
|
}: {
|
|
26
27
|
type: TimeType | CategoryType;
|
|
27
28
|
tooltipProps: TooltipContentProps<number, string>;
|
|
@@ -29,30 +30,28 @@ export const BarchartTooltip = <T extends BarchartBars>({
|
|
|
29
30
|
hoveredValue: string | undefined;
|
|
30
31
|
tooltip?: BarchartTooltipFn<T>;
|
|
31
32
|
unitLabel?: string;
|
|
33
|
+
chartContainerRef: React.RefObject<HTMLDivElement>;
|
|
32
34
|
}) => {
|
|
33
|
-
const { active } = tooltipProps;
|
|
35
|
+
const { active, coordinate } = tooltipProps;
|
|
34
36
|
|
|
35
37
|
if (!active) {
|
|
36
38
|
return null;
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
const currentPoint = getCurrentPoint(tooltipProps, hoveredValue);
|
|
40
|
-
if (tooltip) {
|
|
41
|
-
return tooltip(currentPoint);
|
|
42
|
-
}
|
|
43
42
|
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
const duration =
|
|
44
|
+
type.type === 'time'
|
|
45
|
+
? type.timeRange.startDate.getTime() - type.timeRange.endDate.getTime()
|
|
46
|
+
: 0;
|
|
47
|
+
|
|
48
|
+
const tooltipContent = tooltip ? (
|
|
49
|
+
tooltip(currentPoint)
|
|
50
|
+
) : (
|
|
51
|
+
<>
|
|
46
52
|
<ChartTooltipHeader>
|
|
47
53
|
{type.type === 'time' ? (
|
|
48
|
-
<
|
|
49
|
-
format={
|
|
50
|
-
type.timeRange.interval < 24 * 60 * 60 * 1000
|
|
51
|
-
? 'day-month-abbreviated-hour-minute-second'
|
|
52
|
-
: 'long-date-without-weekday'
|
|
53
|
-
}
|
|
54
|
-
value={new Date(currentPoint.category)}
|
|
55
|
-
/>
|
|
54
|
+
<TooltipHeader duration={duration} value={currentPoint.category} />
|
|
56
55
|
) : (
|
|
57
56
|
currentPoint.category
|
|
58
57
|
)}
|
|
@@ -84,6 +83,16 @@ export const BarchartTooltip = <T extends BarchartBars>({
|
|
|
84
83
|
);
|
|
85
84
|
})}
|
|
86
85
|
</ChartTooltipItemsContainer>
|
|
87
|
-
|
|
86
|
+
</>
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<ChartTooltipPortal
|
|
91
|
+
coordinate={coordinate}
|
|
92
|
+
chartContainerRef={chartContainerRef}
|
|
93
|
+
isVisible={active}
|
|
94
|
+
>
|
|
95
|
+
{tooltipContent}
|
|
96
|
+
</ChartTooltipPortal>
|
|
88
97
|
);
|
|
89
98
|
};
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
getCurrentPoint,
|
|
8
8
|
getMaxBarValue,
|
|
9
9
|
getRoundReferenceValue,
|
|
10
|
+
getTicks,
|
|
10
11
|
sortStackedBars,
|
|
11
12
|
transformCategoryData,
|
|
12
13
|
transformTimeData,
|
|
@@ -504,21 +505,73 @@ describe('applySortingToData', () => {
|
|
|
504
505
|
});
|
|
505
506
|
|
|
506
507
|
describe('getRoundReferenceValue', () => {
|
|
507
|
-
it('should return appropriate rounded values', () => {
|
|
508
|
-
|
|
509
|
-
expect(getRoundReferenceValue(
|
|
510
|
-
expect(getRoundReferenceValue(
|
|
511
|
-
expect(getRoundReferenceValue(
|
|
512
|
-
expect(getRoundReferenceValue(
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
expect(getRoundReferenceValue(
|
|
516
|
-
expect(getRoundReferenceValue(
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
expect(getRoundReferenceValue(
|
|
520
|
-
expect(getRoundReferenceValue(
|
|
521
|
-
expect(getRoundReferenceValue(
|
|
508
|
+
it('should return appropriate rounded values with 10% buffer', () => {
|
|
509
|
+
// Small values (< 10)
|
|
510
|
+
expect(getRoundReferenceValue(0.1)).toBe(0.2); // 0.1 → 0.11 → 0.2
|
|
511
|
+
expect(getRoundReferenceValue(1)).toBe(2); // 1.1 → 1.5 → 2
|
|
512
|
+
expect(getRoundReferenceValue(2)).toBe(5); // 2.2 → 3 → 5
|
|
513
|
+
expect(getRoundReferenceValue(3)).toBe(5); // 3.3 → 4 → 5
|
|
514
|
+
|
|
515
|
+
// Values 5-10 range
|
|
516
|
+
expect(getRoundReferenceValue(6)).toBe(10); // 6.6 → 10 (skip 7.5 for values < 10)
|
|
517
|
+
expect(getRoundReferenceValue(9)).toBe(10); // 9.9 → 10
|
|
518
|
+
|
|
519
|
+
// Larger values get 10% buffer applied
|
|
520
|
+
expect(getRoundReferenceValue(15)).toBe(20); // 16.5 → 20
|
|
521
|
+
expect(getRoundReferenceValue(35)).toBe(40); // 38.5 → 40
|
|
522
|
+
expect(getRoundReferenceValue(75)).toBe(100); // 82.5 → 100
|
|
523
|
+
expect(getRoundReferenceValue(150)).toBe(200); // 165 → 200
|
|
524
|
+
expect(getRoundReferenceValue(350)).toBe(400); // 385 → 400
|
|
525
|
+
expect(getRoundReferenceValue(750)).toBe(1000); // 825 → 1000
|
|
526
|
+
expect(getRoundReferenceValue(1500)).toBe(2000); // 1650 → 2000
|
|
527
|
+
expect(getRoundReferenceValue(3500)).toBe(4000); // 3850 → 4000
|
|
528
|
+
expect(getRoundReferenceValue(7500)).toBe(10000); // 8250 → 10000
|
|
529
|
+
expect(getRoundReferenceValue(15000)).toBe(20000); // 16500 → 20000
|
|
530
|
+
});
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
describe('getTicks', () => {
|
|
534
|
+
describe('small values (< 10)', () => {
|
|
535
|
+
it('should return 2 ticks for non-symmetrical small values', () => {
|
|
536
|
+
expect(getTicks(1, false)).toEqual([0, 1]);
|
|
537
|
+
expect(getTicks(2, false)).toEqual([0, 2]);
|
|
538
|
+
expect(getTicks(5, false)).toEqual([0, 5]);
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
it('should return 3 ticks for symmetrical small values', () => {
|
|
542
|
+
expect(getTicks(1, true)).toEqual([-1, 0, 1]);
|
|
543
|
+
expect(getTicks(2, true)).toEqual([-2, 0, 2]);
|
|
544
|
+
expect(getTicks(5, true)).toEqual([-5, 0, 5]);
|
|
545
|
+
});
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
describe('even topValue (divisible by 2)', () => {
|
|
549
|
+
it('should return 3 ticks for non-symmetrical even values', () => {
|
|
550
|
+
expect(getTicks(10, false)).toEqual([0, 5, 10]);
|
|
551
|
+
expect(getTicks(20, false)).toEqual([0, 10, 20]);
|
|
552
|
+
expect(getTicks(40, false)).toEqual([0, 20, 40]);
|
|
553
|
+
expect(getTicks(50, false)).toEqual([0, 25, 50]);
|
|
554
|
+
expect(getTicks(100, false)).toEqual([0, 50, 100]);
|
|
555
|
+
expect(getTicks(1000, false)).toEqual([0, 500, 1000]);
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
it('should return 5 ticks for symmetrical even values', () => {
|
|
559
|
+
expect(getTicks(10, true)).toEqual([-10, -5, 0, 5, 10]);
|
|
560
|
+
expect(getTicks(100, true)).toEqual([-100, -50, 0, 50, 100]);
|
|
561
|
+
});
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
describe('odd topValue (not divisible by 2) - 7.5 multiples', () => {
|
|
565
|
+
it('should return 4 ticks for non-symmetrical values from 7.5 × magnitude', () => {
|
|
566
|
+
expect(getTicks(75, false)).toEqual([0, 25, 50, 75]);
|
|
567
|
+
expect(getTicks(750, false)).toEqual([0, 250, 500, 750]);
|
|
568
|
+
expect(getTicks(7500, false)).toEqual([0, 2500, 5000, 7500]);
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
it('should return 7 ticks for symmetrical values from 7.5 × magnitude', () => {
|
|
572
|
+
expect(getTicks(75, true)).toEqual([-75, -50, -25, 0, 25, 50, 75]);
|
|
573
|
+
expect(getTicks(750, true)).toEqual([-750, -500, -250, 0, 250, 500, 750]);
|
|
574
|
+
});
|
|
522
575
|
});
|
|
523
576
|
});
|
|
524
577
|
|
|
@@ -685,7 +738,8 @@ describe('computeUnitLabelAndRoundReferenceValue', () => {
|
|
|
685
738
|
);
|
|
686
739
|
|
|
687
740
|
expect(result.unitLabel).toBe('kB');
|
|
688
|
-
|
|
741
|
+
// 1680 / 1000 = 1.68, with buffer: 1.848 → rounds to 2
|
|
742
|
+
expect(result.roundReferenceValue).toBe(2);
|
|
689
743
|
expect(result.rechartsData).toEqual([
|
|
690
744
|
{
|
|
691
745
|
category: 'category1',
|
|
@@ -718,7 +772,8 @@ describe('computeUnitLabelAndRoundReferenceValue', () => {
|
|
|
718
772
|
);
|
|
719
773
|
|
|
720
774
|
expect(result.unitLabel).toBe('B');
|
|
721
|
-
|
|
775
|
+
// 680 with buffer: 748 → rounds to 750 (7.5 * 100, value > 10)
|
|
776
|
+
expect(result.roundReferenceValue).toBe(750);
|
|
722
777
|
expect(result.rechartsData).toEqual([
|
|
723
778
|
{ category: 'category1', success: 680 },
|
|
724
779
|
]);
|
|
@@ -1,30 +1,58 @@
|
|
|
1
|
-
import {
|
|
2
|
-
BarchartProps,
|
|
3
|
-
BarchartBars,
|
|
4
|
-
BarchartTooltipFn,
|
|
5
|
-
} from './Barchart.component';
|
|
1
|
+
import { BarchartProps, BarchartBars } from './Barchart.component';
|
|
6
2
|
import { TooltipContentProps } from 'recharts';
|
|
7
3
|
import { chartColors, ChartColors } from '../../style/theme';
|
|
8
4
|
import { useChartLegend } from '../chartlegend/ChartLegendWrapper';
|
|
9
5
|
|
|
10
6
|
export const getRoundReferenceValue = (value: number): number => {
|
|
11
|
-
if (value <= 0) return
|
|
7
|
+
if (value <= 0) return 1; // Default for zero or negative values
|
|
12
8
|
|
|
13
9
|
// Get the magnitude (10^n where n is the number of digits - 1)
|
|
14
10
|
const magnitude = Math.pow(10, Math.floor(Math.log10(value)));
|
|
15
11
|
|
|
12
|
+
// Buffer the value by 10% to avoid being too close to the edge of the chart
|
|
13
|
+
const bufferedValue = value * 1.1;
|
|
14
|
+
|
|
16
15
|
// Normalized value between 1 and 10
|
|
17
|
-
const normalized =
|
|
16
|
+
const normalized = bufferedValue / magnitude;
|
|
18
17
|
|
|
19
18
|
// Round to nice numbers based on normalized value
|
|
19
|
+
// skip 1.5, 3, 4, 7.5 as top value for better chart
|
|
20
|
+
// appearance for small values
|
|
20
21
|
let result: number;
|
|
22
|
+
|
|
21
23
|
if (normalized <= 1) result = magnitude;
|
|
22
|
-
else if (normalized <= 2
|
|
24
|
+
else if (normalized <= 2) result = 2 * magnitude;
|
|
25
|
+
else if (value > 10 && normalized <= 4) result = 4 * magnitude;
|
|
23
26
|
else if (normalized <= 5) result = 5 * magnitude;
|
|
27
|
+
else if (value > 10 && normalized <= 7.5) result = 7.5 * magnitude;
|
|
24
28
|
else result = 10 * magnitude;
|
|
25
29
|
|
|
26
|
-
|
|
27
|
-
|
|
30
|
+
return result;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export const getTicks = (topValue: number, isSymmetrical: boolean) => {
|
|
34
|
+
if (topValue < 10) {
|
|
35
|
+
if (isSymmetrical) {
|
|
36
|
+
return [-topValue, 0, topValue];
|
|
37
|
+
} else {
|
|
38
|
+
return [0, topValue];
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const numberOfTicks = topValue % 3 === 0 ? 4 : 3;
|
|
42
|
+
const tickInterval = topValue / (numberOfTicks - 1);
|
|
43
|
+
const ticks = Array.from(
|
|
44
|
+
{ length: numberOfTicks },
|
|
45
|
+
(_, index) => index * tickInterval,
|
|
46
|
+
);
|
|
47
|
+
if (isSymmetrical) {
|
|
48
|
+
// Create negative ticks in order without 0
|
|
49
|
+
const negativeTicks = Array.from(
|
|
50
|
+
{ length: numberOfTicks - 1 },
|
|
51
|
+
(_, index) => -(numberOfTicks - 1 - index) * tickInterval,
|
|
52
|
+
);
|
|
53
|
+
ticks.unshift(...negativeTicks);
|
|
54
|
+
}
|
|
55
|
+
return ticks;
|
|
28
56
|
};
|
|
29
57
|
|
|
30
58
|
export const getMaxBarValue = (
|
|
@@ -304,11 +332,11 @@ export const computeUnitLabelAndRoundReferenceValue = (
|
|
|
304
332
|
) => {
|
|
305
333
|
if (!unitRange) {
|
|
306
334
|
const roundReferenceValue = getRoundReferenceValue(maxValue);
|
|
307
|
-
return { unitLabel:
|
|
335
|
+
return { unitLabel: undefined, roundReferenceValue, rechartsData: data };
|
|
308
336
|
}
|
|
309
337
|
|
|
310
338
|
const { valueBase, unitLabel } = getUnitLabel(unitRange, maxValue);
|
|
311
|
-
const topValue =
|
|
339
|
+
const topValue = maxValue / valueBase;
|
|
312
340
|
const roundReferenceValue = getRoundReferenceValue(topValue);
|
|
313
341
|
const rechartsData = data.map((dataPoint) => {
|
|
314
342
|
const normalizedDataPoint = { ...dataPoint };
|