@scality/core-ui 0.176.0 → 0.178.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 +2 -2
- package/dist/components/barchartv2/Barchart.component.d.ts.map +1 -1
- package/dist/components/barchartv2/Barchart.component.js +16 -10
- package/dist/components/barchartv2/BarchartTooltip.d.ts +3 -2
- package/dist/components/barchartv2/BarchartTooltip.d.ts.map +1 -1
- package/dist/components/barchartv2/BarchartTooltip.js +6 -8
- 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/charttooltip/ChartTooltip.d.ts +23 -0
- package/dist/components/charttooltip/ChartTooltip.d.ts.map +1 -1
- package/dist/components/charttooltip/ChartTooltip.js +85 -3
- 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.map +1 -1
- package/dist/components/linetimeseriechart/linetimeseriechart.component.js +43 -47
- package/dist/components/linetimeseriechart/utils.js +2 -2
- package/dist/style/theme.js +1 -1
- package/package.json +1 -1
- package/src/lib/components/barchartv2/Barchart.component.tsx +25 -16
- package/src/lib/components/barchartv2/BarchartTooltip.test.tsx +30 -0
- package/src/lib/components/barchartv2/BarchartTooltip.tsx +21 -8
- package/src/lib/components/barchartv2/utils.test.ts +72 -17
- package/src/lib/components/barchartv2/utils.ts +39 -7
- package/src/lib/components/charttooltip/ChartTooltip.tsx +136 -3
- 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 +86 -82
- package/src/lib/components/linetimeseriechart/utils.test.ts +3 -3
- package/src/lib/components/linetimeseriechart/utils.ts +2 -2
- package/src/lib/style/theme.ts +1 -1
- package/stories/BarChart/barchart.stories.tsx +23 -8
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { createPortal } from 'react-dom';
|
|
3
|
-
import { useEffect, useState } from 'react';
|
|
4
2
|
import styled, { css, useTheme } from 'styled-components';
|
|
5
|
-
import { useFloating, autoUpdate, offset, flip, shift, } from '@floating-ui/react';
|
|
6
3
|
import { FormattedDateTime, Stack, Text, Wrap, spacing } from '../../../index';
|
|
7
4
|
import { zIndex } from '../../../style/theme';
|
|
8
5
|
import { CHART_CONFIG, getTooltipPosition } from '../healthBarUtils';
|
|
6
|
+
import { ChartTooltipPortal } from '../../charttooltip/ChartTooltip';
|
|
9
7
|
const TooltipContainer = styled.div `
|
|
10
8
|
${(props) => {
|
|
11
9
|
const theme = useTheme();
|
|
@@ -25,28 +23,11 @@ const TooltipContainer = styled.div `
|
|
|
25
23
|
export const GlobalHealthBarTooltip = (props) => {
|
|
26
24
|
const { tooltipData, tooltipProps, chartContainerRef, isKeyboardActive = false, startTimestamp = 0, endTimestamp = 0, } = props;
|
|
27
25
|
const { coordinate } = tooltipProps;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
middleware: [
|
|
34
|
-
offset(({ placement }) => {
|
|
35
|
-
// Use larger offset when tooltip is on top
|
|
36
|
-
// to avoid tooltip over bar
|
|
37
|
-
return placement.includes('top') ? 20 : 30;
|
|
38
|
-
}),
|
|
39
|
-
flip(),
|
|
40
|
-
shift({ padding: 10 }),
|
|
41
|
-
],
|
|
42
|
-
whileElementsMounted: autoUpdate,
|
|
43
|
-
});
|
|
44
|
-
// Create virtual element from coordinate
|
|
45
|
-
useEffect(() => {
|
|
46
|
-
if (chartContainerRef.current) {
|
|
47
|
-
const chartRect = chartContainerRef.current.getBoundingClientRect();
|
|
48
|
-
let tooltipX;
|
|
49
|
-
let tooltipY;
|
|
26
|
+
if (!tooltipData)
|
|
27
|
+
return null;
|
|
28
|
+
const { description, startsAt, endsAt, severity } = tooltipData;
|
|
29
|
+
const tooltipContent = (_jsxs(Stack, { direction: "vertical", gap: "r8", children: [_jsxs(Wrap, { children: [_jsx(Text, { variant: "Smaller", children: "Severity" }), _jsx(Text, { color: "textPrimary", variant: "Smaller", children: severity })] }), _jsxs(Wrap, { children: [_jsx(Text, { variant: "Smaller", children: "Start" }), _jsx(Text, { color: "textPrimary", variant: "Smaller", children: _jsx(FormattedDateTime, { format: "date-time", value: new Date(startsAt) }) })] }), _jsxs(Wrap, { children: [_jsx(Text, { variant: "Smaller", children: "End" }), _jsx(Text, { color: "textPrimary", variant: "Smaller", children: _jsx(FormattedDateTime, { format: "date-time", value: new Date(endsAt) }) })] }), _jsxs(Wrap, { children: [_jsx(Text, { variant: "Smaller", style: { paddingRight: spacing.r32 }, children: "Description" }), _jsx(Text, { color: "textPrimary", variant: "Smaller", style: { whiteSpace: 'wrap', textAlign: 'justify' }, children: description })] })] }));
|
|
30
|
+
return (_jsx(ChartTooltipPortal, { coordinate: coordinate, chartContainerRef: chartContainerRef, isVisible: !!tooltipData, customPosition: (chartRect, coordinate) => {
|
|
50
31
|
if (isKeyboardActive && tooltipData && startTimestamp && endTimestamp) {
|
|
51
32
|
// Calculate the chart's usable width (excluding margins)
|
|
52
33
|
const chartUsableWidth = chartRect.width -
|
|
@@ -54,42 +35,21 @@ export const GlobalHealthBarTooltip = (props) => {
|
|
|
54
35
|
CHART_CONFIG.MARGINS.right;
|
|
55
36
|
// Use the same positioning logic as alert bars
|
|
56
37
|
const alertCenterX = getTooltipPosition(tooltipData, startTimestamp, endTimestamp, chartUsableWidth);
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
38
|
+
return {
|
|
39
|
+
x: chartRect.left + alertCenterX,
|
|
40
|
+
y: chartRect.top + CHART_CONFIG.BAR_SIZE,
|
|
41
|
+
};
|
|
61
42
|
}
|
|
62
43
|
else {
|
|
63
44
|
// For mouse navigation, use the provided coordinate
|
|
64
|
-
|
|
65
|
-
|
|
45
|
+
return {
|
|
46
|
+
x: chartRect.left + ((coordinate === null || coordinate === void 0 ? void 0 : coordinate.x) || 0),
|
|
47
|
+
y: chartRect.top + ((coordinate === null || coordinate === void 0 ? void 0 : coordinate.y) || 0),
|
|
48
|
+
};
|
|
66
49
|
}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
x: tooltipX,
|
|
73
|
-
y: tooltipY,
|
|
74
|
-
left: tooltipX,
|
|
75
|
-
top: tooltipY,
|
|
76
|
-
right: tooltipX,
|
|
77
|
-
bottom: tooltipY,
|
|
78
|
-
};
|
|
79
|
-
},
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
}, [
|
|
83
|
-
coordinate,
|
|
84
|
-
chartContainerRef,
|
|
85
|
-
isKeyboardActive,
|
|
86
|
-
tooltipData,
|
|
87
|
-
startTimestamp,
|
|
88
|
-
endTimestamp,
|
|
89
|
-
]);
|
|
90
|
-
if (!tooltipData)
|
|
91
|
-
return null;
|
|
92
|
-
const { description, startsAt, endsAt, severity } = tooltipData;
|
|
93
|
-
const tooltipContent = (_jsx(TooltipContainer, { ref: refs.setFloating, style: floatingStyles, children: _jsxs(Stack, { direction: "vertical", gap: "r8", children: [_jsxs(Wrap, { children: [_jsx(Text, { variant: "Smaller", children: "Severity" }), _jsx(Text, { color: "textPrimary", variant: "Smaller", children: severity })] }), _jsxs(Wrap, { children: [_jsx(Text, { variant: "Smaller", children: "Start" }), _jsx(Text, { color: "textPrimary", variant: "Smaller", children: _jsx(FormattedDateTime, { format: "date-time", value: new Date(startsAt) }) })] }), _jsxs(Wrap, { children: [_jsx(Text, { variant: "Smaller", children: "End" }), _jsx(Text, { color: "textPrimary", variant: "Smaller", children: _jsx(FormattedDateTime, { format: "date-time", value: new Date(endsAt) }) })] }), _jsxs(Wrap, { children: [_jsx(Text, { variant: "Smaller", style: { paddingRight: spacing.r32 }, children: "Description" }), _jsx(Text, { color: "textPrimary", variant: "Smaller", style: { whiteSpace: 'wrap', textAlign: 'justify' }, children: description })] })] }) }));
|
|
94
|
-
return createPortal(tooltipContent, document.body);
|
|
50
|
+
}, containerComponent: TooltipContainer, offset: ({ placement }) => {
|
|
51
|
+
// Use larger offset when tooltip is on top
|
|
52
|
+
// to avoid tooltip over bar
|
|
53
|
+
return placement.includes('top') ? 20 : 30;
|
|
54
|
+
}, children: tooltipContent }));
|
|
95
55
|
};
|
|
@@ -14,7 +14,7 @@ const CustomTick = ({ tickProps, startTimestamp, endTimestamp, }) => {
|
|
|
14
14
|
const edgeMargin = getEdgeMargin(index, visibleTicksCount, isDaySpan);
|
|
15
15
|
return (
|
|
16
16
|
// use coordinate to center the text
|
|
17
|
-
shouldShowLabel && (_jsx("g", { transform: `translate(${payload.coordinate},${y})`, children: _jsx("text", { textAnchor: "middle", dy: 10, dx: edgeMargin, fontSize: fontSize.smaller, fill: theme.textSecondary, children: is7DaySpan
|
|
17
|
+
shouldShowLabel && (_jsx("g", { transform: `translate(${payload.coordinate},${y})`, children: _jsx("text", { textAnchor: "middle", dy: 10, dx: edgeMargin, fontSize: fontSize.smaller, fill: theme.textSecondary, children: is7DaySpan ? (_jsx(FormattedDateTime, { format: "day-month-abbreviated-hour-minute", value: new Date(payload.value) })) : (_jsx(FormattedDateTime, { format: "time", value: new Date(payload.value) })) }) })));
|
|
18
18
|
};
|
|
19
19
|
export const HealthBarXAxis = ({ startTimestamp, endTimestamp, }) => {
|
|
20
20
|
const theme = useTheme();
|
|
@@ -3,6 +3,7 @@ export interface Alert {
|
|
|
3
3
|
startsAt: string;
|
|
4
4
|
endsAt: string;
|
|
5
5
|
severity: 'warning' | 'critical' | 'unavailable';
|
|
6
|
+
key: string;
|
|
6
7
|
}
|
|
7
8
|
export declare const useHealthBarData: (alerts: Alert[], startTimestamp: number, endTimestamp: number, id: string) => {
|
|
8
9
|
chartData: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useHealthBarData.d.ts","sourceRoot":"","sources":["../../../src/lib/components/globalhealthbar/useHealthBarData.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,KAAK;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,SAAS,GAAG,UAAU,GAAG,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"useHealthBarData.d.ts","sourceRoot":"","sources":["../../../src/lib/components/globalhealthbar/useHealthBarData.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,KAAK;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,SAAS,GAAG,UAAU,GAAG,aAAa,CAAC;IACjD,GAAG,EAAE,MAAM,CAAC;CACb;AAED,eAAO,MAAM,gBAAgB,WACnB,KAAK,EAAE,kBACC,MAAM,gBACR,MAAM,MAChB,MAAM;;;;;;;;;;CA6DX,CAAC"}
|
|
@@ -20,6 +20,7 @@ export const useHealthBarData = (alerts, startTimestamp, endTimestamp, id) => {
|
|
|
20
20
|
// Store alert data separately for tooltip access
|
|
21
21
|
alertsMapData[uniqueKey] = {
|
|
22
22
|
...alert,
|
|
23
|
+
key: uniqueKey, // Add the consistent key to the alert object
|
|
23
24
|
};
|
|
24
25
|
});
|
|
25
26
|
// Chart data - ready for BarChart (as array)
|
|
@@ -10,6 +10,7 @@ describe('useHealthBarData', () => {
|
|
|
10
10
|
severity,
|
|
11
11
|
startsAt,
|
|
12
12
|
endsAt,
|
|
13
|
+
key: `${severity}_${startsAt}`,
|
|
13
14
|
});
|
|
14
15
|
describe('Alert Filtering', () => {
|
|
15
16
|
it('should include alerts that are completely within the time range', () => {
|
|
@@ -93,6 +94,7 @@ describe('useHealthBarData', () => {
|
|
|
93
94
|
severity: 'warning',
|
|
94
95
|
startsAt: '2023-12-01T02:00:00Z',
|
|
95
96
|
endsAt: '2023-12-01T04:00:00Z',
|
|
97
|
+
key: 'warning_0',
|
|
96
98
|
});
|
|
97
99
|
});
|
|
98
100
|
it('should handle multiple alerts with correct indexing', () => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"linetimeseriechart.component.d.ts","sourceRoot":"","sources":["../../../src/lib/components/linetimeseriechart/linetimeseriechart.component.tsx"],"names":[],"mappings":"AAAA,OAAO,EAML,mBAAmB,EAGpB,MAAM,UAAU,CAAC;AAClB,OAAO,KAAiD,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"linetimeseriechart.component.d.ts","sourceRoot":"","sources":["../../../src/lib/components/linetimeseriechart/linetimeseriechart.component.tsx"],"names":[],"mappings":"AAAA,OAAO,EAML,mBAAmB,EAGpB,MAAM,UAAU,CAAC;AAClB,OAAO,KAAiD,MAAM,OAAO,CAAC;AAiCtE,MAAM,MAAM,KAAK,GAAG;IAElB,QAAQ,EAAE,MAAM,CAAC;IAEjB,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC;IAEzC,eAAe,EAAE,CAAC,YAAY,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAEtE,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,CAAC;AAEF,KAAK,wBAAwB,GAAG;IAC9B,SAAS,CAAC,EAAE,SAAS,GAAG,YAAY,CAAC;IACrC,MAAM,EAAE,KAAK,EAAE,GAAG,SAAS,CAAC;CAC7B,CAAC;AAGF,KAAK,qBAAqB,GAAG;IAC3B,SAAS,EAAE,aAAa,CAAC;IACzB,MAAM,EACF;QACE,KAAK,EAAE,KAAK,EAAE,GAAG,SAAS,CAAC;QAC3B,KAAK,EAAE,KAAK,EAAE,GAAG,SAAS,CAAC;KAC5B,GACD,SAAS,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG,CACzB,wBAAwB,GACxB,qBAAqB,CACxB,GAAG;IACF,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,iBAAiB,EAAE,MAAM,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE;QACV,SAAS,EAAE,MAAM,CAAC;QAClB,KAAK,EAAE,MAAM,CAAC;KACf,EAAE,CAAC;IACJ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;OAIG;IACH,UAAU,CAAC,EAAE,WAAW,GAAG,MAAM,CAAC;IAClC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,CACd,YAAY,EAAE,mBAAmB,CAAC,MAAM,EAAE,MAAM,CAAC,EACjD,SAAS,CAAC,EAAE,MAAM,EAClB,QAAQ,CAAC,EAAE,MAAM,KACd,KAAK,CAAC,SAAS,CAAC;CACtB,CAAC;AAiHF,wBAAgB,kBAAkB,CAAC,EACjC,MAAM,EACN,KAAK,EACL,MAAM,EACN,iBAAiB,EACjB,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,SAAiB,EACjB,UAAwB,EACxB,SAAqB,EACrB,UAAU,EACV,QAAQ,EACR,MAAM,EACN,aAAa,EACb,GAAG,IAAI,EACR,EAAE,cAAc,2CAqWhB"}
|
|
@@ -1,62 +1,58 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { CartesianGrid, Line, LineChart, ReferenceLine, Tooltip, XAxis, YAxis, } from 'recharts';
|
|
3
3
|
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
|
4
4
|
import styled, { useTheme } from 'styled-components';
|
|
5
|
-
import {
|
|
5
|
+
import { Stack } from '../../spacing';
|
|
6
6
|
import { fontSize } from '../../style/theme';
|
|
7
|
-
import { Box } from '../box/Box';
|
|
8
7
|
import { useChartLegend } from '../chartlegend/ChartLegendWrapper';
|
|
9
|
-
import { Icon } from '../icon/Icon.component';
|
|
10
8
|
import { addMissingDataPoint, getUnitLabel, } from '../linetemporalchart/ChartUtil';
|
|
11
9
|
import { Loader } from '../loader/Loader.component';
|
|
12
|
-
import { ChartTitleText
|
|
13
|
-
import { Tooltip as TooltipComponent } from '../tooltip/Tooltip.component';
|
|
10
|
+
import { ChartTitleText } from '../text/Text.component';
|
|
14
11
|
import { formatXAxisLabel } from './utils';
|
|
15
|
-
import {
|
|
12
|
+
import { ChartTooltipPortal, ChartTooltipItem, ChartTooltipHeader, ChartTooltipItemsContainer, ChartTooltipSeparator, TooltipHeader, } from '../charttooltip/ChartTooltip';
|
|
16
13
|
import { LegendShape } from '../chartlegend/ChartLegend';
|
|
17
14
|
import { StyledResponsiveContainer } from '../barchartv2/Barchart.component';
|
|
15
|
+
import { getRoundReferenceValue, getTicks } from '../barchartv2/utils';
|
|
16
|
+
import { IconHelp } from '../iconhelper/IconHelper';
|
|
17
|
+
const maxWidthTooltip = { maxWidth: '20rem' };
|
|
18
18
|
const LineTemporalChartWrapper = styled.div `
|
|
19
19
|
display: flex;
|
|
20
20
|
flex-direction: column;
|
|
21
21
|
justify-content: flex-start;
|
|
22
22
|
`;
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
align-items: center;
|
|
26
|
-
`;
|
|
27
|
-
const LineTimeSerieChartTooltip = ({ unitLabel, duration, isChartActive, tooltipProps, renderTooltip, hoveredValue, isSymmetrical, }) => {
|
|
28
|
-
const { active, payload, label } = tooltipProps;
|
|
23
|
+
const LineTimeSerieChartTooltip = ({ unitLabel, duration, isChartActive, tooltipProps, renderTooltip, hoveredValue, isSymmetrical, chartContainerRef, }) => {
|
|
24
|
+
const { active, payload, label, coordinate } = tooltipProps;
|
|
29
25
|
if (!active || !payload || !payload.length || !label || !isChartActive)
|
|
30
26
|
return null;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
27
|
+
const tooltipContent = renderTooltip ? (renderTooltip(tooltipProps, unitLabel, duration)) : (_jsxs(_Fragment, { children: [_jsx(ChartTooltipHeader, { children: _jsx(TooltipHeader, { duration: duration, value: label }) }), _jsx(ChartTooltipItemsContainer, { children: (() => {
|
|
28
|
+
// We can't use the default itemSorter method because it's a custom tooltip.
|
|
29
|
+
// Sort the payload here instead
|
|
30
|
+
const sortedPayload = [...payload].sort((a, b) => {
|
|
31
|
+
const aValue = a.value;
|
|
32
|
+
const bValue = b.value;
|
|
33
|
+
if (aValue >= 0 && bValue >= 0) {
|
|
34
|
+
return bValue - aValue; // Higher positive values first
|
|
35
|
+
}
|
|
36
|
+
if (aValue < 0 && bValue < 0) {
|
|
37
|
+
return bValue - aValue; // Lower negative values first
|
|
38
|
+
}
|
|
39
|
+
return bValue - aValue; // Positives before negatives
|
|
40
|
+
});
|
|
41
|
+
// Find the transition point between positive and negative values
|
|
42
|
+
const separatorIndex = sortedPayload.findIndex((entry) => entry.value < 0);
|
|
43
|
+
const hasBothPositiveAndNegative = separatorIndex > 0 && separatorIndex < sortedPayload.length;
|
|
44
|
+
return sortedPayload.map((entry, index) => {
|
|
45
|
+
const legendIcon = (_jsx(LegendShape, { color: entry.color, shape: "line", chartColors: { [entry.color]: entry.color } }));
|
|
46
|
+
const isHovered = entry.name === hoveredValue;
|
|
47
|
+
const formattedValue = !Number.isFinite(entry.value)
|
|
48
|
+
? '-'
|
|
49
|
+
: `${entry.value.toFixed(2)} ${unitLabel}`;
|
|
50
|
+
return (_jsxs(React.Fragment, { children: [isSymmetrical &&
|
|
51
|
+
hasBothPositiveAndNegative &&
|
|
52
|
+
index === separatorIndex && _jsx(ChartTooltipSeparator, {}), _jsx(ChartTooltipItem, { label: entry.name, value: formattedValue, legendIcon: legendIcon, isHovered: isHovered })] }, index));
|
|
53
|
+
});
|
|
54
|
+
})() })] }));
|
|
55
|
+
return (_jsx(ChartTooltipPortal, { coordinate: coordinate, chartContainerRef: chartContainerRef, isVisible: active && isChartActive, children: tooltipContent }));
|
|
60
56
|
};
|
|
61
57
|
const isSymmetricalSeries = (series) => {
|
|
62
58
|
return 'above' in series && 'below' in series;
|
|
@@ -172,7 +168,8 @@ export function LineTimeSerieChart({ series, title, height, startingTimeStamp, i
|
|
|
172
168
|
const bottom = Math.abs(Math.min(...values));
|
|
173
169
|
const maxValue = Math.max(top, bottom);
|
|
174
170
|
const { valueBase, unitLabel } = getUnitLabel(unitRange !== null && unitRange !== void 0 ? unitRange : [], maxValue);
|
|
175
|
-
|
|
171
|
+
// Use round reference value to add extra padding to the top value
|
|
172
|
+
const topValue = getRoundReferenceValue(maxValue / valueBase);
|
|
176
173
|
const rechartsData = chartData.map((dataPoint) => {
|
|
177
174
|
const normalizedDataPoint = { ...dataPoint };
|
|
178
175
|
Object.entries(dataPoint).forEach(([key, value]) => {
|
|
@@ -222,15 +219,14 @@ export function LineTimeSerieChart({ series, title, height, startingTimeStamp, i
|
|
|
222
219
|
}, [series, getColor, selectedResources]);
|
|
223
220
|
// Format time for display the tick in the x axis
|
|
224
221
|
const formatXAxisLabelCallback = useCallback((timestamp) => formatXAxisLabel(timestamp, duration), [duration]);
|
|
225
|
-
return (_jsxs(LineTemporalChartWrapper, { children: [_jsxs(
|
|
222
|
+
return (_jsxs(LineTemporalChartWrapper, { children: [_jsxs(Stack, { gap: "r4", children: [_jsxs(ChartTitleText, { children: [title, " ", unitLabel && `(${unitLabel})`] }), helpText && (_jsx(IconHelp, { tooltipMessage: helpText, overlayStyle: maxWidthTooltip })), isLoading && _jsx(Loader, {})] }), _jsx("div", { onFocus: () => setIsChartActive(true), onBlur: () => setIsChartActive(false), onFocusCapture: () => setIsChartActive(true), onBlurCapture: () => setIsChartActive(false), children: _jsx(StyledResponsiveContainer, { width: "100%", height: height, children: _jsxs(LineChart, { data: rechartsData, ref: chartRef, margin: { top: 0, right: 0, bottom: 0, left: 0 }, "aria-label": `Time series chart for ${title}`, syncId: syncId, onMouseEnter: () => setIsChartActive(true), onMouseLeave: () => setIsChartActive(false), accessibilityLayer: true, children: [_jsx(CartesianGrid, { vertical: true, horizontal: true, verticalPoints: [0], horizontalPoints: [0], stroke: theme.border, fill: theme.backgroundLevel4, strokeWidth: 1 }), _jsx(XAxis, { dataKey: "timestamp", type: "number", domain: ['dataMin', 'dataMax'], ticks: xAxisTicks, tickFormatter: formatXAxisLabelCallback, tickCount: 5, tick: {
|
|
226
223
|
fill: theme.textSecondary,
|
|
227
224
|
fontSize: fontSize.smaller,
|
|
228
225
|
}, axisLine: { stroke: theme.border } }), _jsx(YAxis, { orientation: "right", label: {
|
|
229
226
|
value: yAxisTitle,
|
|
230
227
|
angle: 90,
|
|
231
|
-
|
|
228
|
+
dx: 20,
|
|
232
229
|
style: {
|
|
233
|
-
textAnchor: 'middle',
|
|
234
230
|
fill: theme.textSecondary,
|
|
235
231
|
fontSize: fontSize.smaller,
|
|
236
232
|
},
|
|
@@ -241,7 +237,7 @@ export function LineTimeSerieChart({ series, title, height, startingTimeStamp, i
|
|
|
241
237
|
: [0, topValue], axisLine: { stroke: theme.border }, tick: {
|
|
242
238
|
fill: theme.textSecondary,
|
|
243
239
|
fontSize: fontSize.smaller,
|
|
244
|
-
}, tickFormatter: (value) => new Intl.NumberFormat('fr-FR').format(value
|
|
240
|
+
}, tickFormatter: (value) => new Intl.NumberFormat('fr-FR').format(value), ticks: getTicks(topValue, yAxisType === 'symmetrical'), interval: 0 }), _jsx(Tooltip, { content: (props) => (_jsx(LineTimeSerieChartTooltip, { unitLabel: unitLabel, duration: duration, renderTooltip: renderTooltip, isSymmetrical: yAxisType === 'symmetrical', tooltipProps: props, isChartActive: isChartActive, hoveredValue: hoveredValue, chartContainerRef: chartRef })) }), yAxisType === 'symmetrical' && (_jsx(ReferenceLine, { y: 0, stroke: theme.border })), Object.entries(groupedSeries).map(([resource, resourceSeries]) => resourceSeries.map((serie, serieIndex) => {
|
|
245
241
|
const label = serie.getTooltipLabel(serie.metricPrefix, serie.resource);
|
|
246
242
|
return (_jsx(Line, { type: "monotone", dataKey: label, stroke: colorMapping[resource], dot: false, isAnimationActive: false, strokeDasharray: serie.isLineDashed ? '4 4' : undefined, onMouseEnter: () => setHoveredValue(label), onMouseLeave: () => setHoveredValue(undefined) }, `${title}-${resource}-${serieIndex}`));
|
|
247
243
|
}))] }) }) })] }));
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TIME_FORMATER,
|
|
1
|
+
import { TIME_FORMATER, DAY_MONTH_ABBREVIATED_YEAR, DAY_MONTH_ABBREVIATED_HOUR_MINUTE, } from '../date/FormattedDateTime';
|
|
2
2
|
export const ONE_YEAR_MILLISECONDS = 366 * 24 * 60 * 60 * 1000;
|
|
3
3
|
/**
|
|
4
4
|
* Formats timestamp for X-axis labels based on time format and data range:
|
|
@@ -16,7 +16,7 @@ export const formatXAxisLabel = (timestamp, duration) => {
|
|
|
16
16
|
return TIME_FORMATER.format(date);
|
|
17
17
|
}
|
|
18
18
|
else if (duration <= 7 * 24 * 60 * 60) {
|
|
19
|
-
return
|
|
19
|
+
return DAY_MONTH_ABBREVIATED_HOUR_MINUTE.format(date)
|
|
20
20
|
.replace(',', '')
|
|
21
21
|
.replace(/Sept/g, 'Sep');
|
|
22
22
|
}
|
package/dist/style/theme.js
CHANGED
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState } from 'react';
|
|
1
|
+
import { useState, useRef } from 'react';
|
|
2
2
|
import {
|
|
3
3
|
Bar,
|
|
4
4
|
BarChart,
|
|
@@ -20,7 +20,7 @@ import { IconHelp } from '../iconhelper/IconHelper';
|
|
|
20
20
|
import { Loader } from '../loader/Loader.component';
|
|
21
21
|
import { Text } from '../text/Text.component';
|
|
22
22
|
import { BarchartTooltip } from './BarchartTooltip';
|
|
23
|
-
import { UnitRange, useChartData } from './utils';
|
|
23
|
+
import { getTicks, UnitRange, useChartData } from './utils';
|
|
24
24
|
|
|
25
25
|
const CHART_CONSTANTS = {
|
|
26
26
|
TICK_WIDTH_OFFSET: 4,
|
|
@@ -34,6 +34,7 @@ const CHART_CONSTANTS = {
|
|
|
34
34
|
bottom: 0,
|
|
35
35
|
},
|
|
36
36
|
};
|
|
37
|
+
const maxWidthTooltip = { maxWidth: '20rem' };
|
|
37
38
|
|
|
38
39
|
/* ---------------------------------- TYPE ---------------------------------- */
|
|
39
40
|
|
|
@@ -76,6 +77,7 @@ export type BarchartSortFn<T extends BarchartBars> = (
|
|
|
76
77
|
|
|
77
78
|
export type BarchartProps<T extends BarchartBars> = {
|
|
78
79
|
type: CategoryType | TimeType;
|
|
80
|
+
title: string;
|
|
79
81
|
bars?: T;
|
|
80
82
|
tooltip?: BarchartTooltipFn<T>;
|
|
81
83
|
defaultSort?: BarchartSortFn<T>;
|
|
@@ -89,7 +91,6 @@ export type BarchartProps<T extends BarchartBars> = {
|
|
|
89
91
|
* @default 'default'
|
|
90
92
|
*/
|
|
91
93
|
stackedBarSort?: 'default' | 'legend';
|
|
92
|
-
title?: string;
|
|
93
94
|
secondaryTitle?: string;
|
|
94
95
|
rightTitle?: React.ReactNode;
|
|
95
96
|
height?: number;
|
|
@@ -112,15 +113,15 @@ interface CustomTickProps {
|
|
|
112
113
|
|
|
113
114
|
/**
|
|
114
115
|
* Get the format of the date based on the duration
|
|
115
|
-
* @param duration - Duration in
|
|
116
|
+
* @param duration - Duration in seconds
|
|
116
117
|
* @returns Formatted string
|
|
117
118
|
*/
|
|
118
119
|
export const formatDate = (
|
|
119
120
|
duration: number,
|
|
120
121
|
): 'time' | 'day-month-abbreviated' | 'chart-long-term-date' => {
|
|
121
|
-
if (duration <= 24 * 60 * 60
|
|
122
|
+
if (duration <= 24 * 60 * 60) {
|
|
122
123
|
return 'time';
|
|
123
|
-
} else if (duration <= 7 * 24 * 60 * 60
|
|
124
|
+
} else if (duration <= 7 * 24 * 60 * 60) {
|
|
124
125
|
return 'day-month-abbreviated';
|
|
125
126
|
} else {
|
|
126
127
|
return 'chart-long-term-date';
|
|
@@ -142,7 +143,9 @@ export const CustomTick = ({
|
|
|
142
143
|
|
|
143
144
|
const duration =
|
|
144
145
|
type.type === 'time'
|
|
145
|
-
? type.timeRange.endDate.getTime() -
|
|
146
|
+
? (type.timeRange.endDate.getTime() -
|
|
147
|
+
type.timeRange.startDate.getTime()) /
|
|
148
|
+
1000
|
|
146
149
|
: 0;
|
|
147
150
|
|
|
148
151
|
return (
|
|
@@ -183,6 +186,7 @@ export const CustomTick = ({
|
|
|
183
186
|
export const StyledResponsiveContainer = styled(ResponsiveContainer)`
|
|
184
187
|
// Avoid tooltip over constrained text to be cut off
|
|
185
188
|
& .recharts-surface {
|
|
189
|
+
outline: none;
|
|
186
190
|
overflow: visible;
|
|
187
191
|
}
|
|
188
192
|
`;
|
|
@@ -202,7 +206,12 @@ const ChartHeader = ({
|
|
|
202
206
|
<Wrap>
|
|
203
207
|
<Stack gap="r4">
|
|
204
208
|
<Text variant="ChartTitle">{title}</Text>
|
|
205
|
-
{helpTooltip &&
|
|
209
|
+
{helpTooltip && (
|
|
210
|
+
<IconHelp
|
|
211
|
+
tooltipMessage={helpTooltip}
|
|
212
|
+
overlayStyle={maxWidthTooltip}
|
|
213
|
+
/>
|
|
214
|
+
)}
|
|
206
215
|
|
|
207
216
|
{secondaryTitle && (
|
|
208
217
|
<Text
|
|
@@ -257,6 +266,7 @@ export const Barchart = <T extends BarchartBars>(props: BarchartProps<T>) => {
|
|
|
257
266
|
const theme = useTheme();
|
|
258
267
|
const { getColor } = useChartLegend();
|
|
259
268
|
const [hoveredValue, setHoveredValue] = useState<string | undefined>();
|
|
269
|
+
const chartRef = useRef<HTMLDivElement>(null);
|
|
260
270
|
|
|
261
271
|
const {
|
|
262
272
|
height = CHART_CONSTANTS.DEFAULT_HEIGHT,
|
|
@@ -297,11 +307,11 @@ export const Barchart = <T extends BarchartBars>(props: BarchartProps<T>) => {
|
|
|
297
307
|
unitRange,
|
|
298
308
|
stackedBarSort,
|
|
299
309
|
);
|
|
300
|
-
|
|
310
|
+
const titleWithUnit = unitLabel ? `${title} (${unitLabel})` : title;
|
|
301
311
|
return (
|
|
302
|
-
<Stack direction="vertical">
|
|
312
|
+
<Stack direction="vertical" style={{ gap: '0' }}>
|
|
303
313
|
<ChartHeader
|
|
304
|
-
title={
|
|
314
|
+
title={titleWithUnit}
|
|
305
315
|
secondaryTitle={secondaryTitle}
|
|
306
316
|
helpTooltip={helpTooltip}
|
|
307
317
|
rightTitle={rightTitle}
|
|
@@ -311,7 +321,7 @@ export const Barchart = <T extends BarchartBars>(props: BarchartProps<T>) => {
|
|
|
311
321
|
) : isLoading ? (
|
|
312
322
|
<Loading height={height} />
|
|
313
323
|
) : (
|
|
314
|
-
<StyledResponsiveContainer width="100%" height={height}>
|
|
324
|
+
<StyledResponsiveContainer ref={chartRef} width="100%" height={height}>
|
|
315
325
|
<BarChart
|
|
316
326
|
data={rechartsData}
|
|
317
327
|
accessibilityLayer
|
|
@@ -352,13 +362,11 @@ export const Barchart = <T extends BarchartBars>(props: BarchartProps<T>) => {
|
|
|
352
362
|
})}
|
|
353
363
|
|
|
354
364
|
<YAxis
|
|
355
|
-
tickCount={1}
|
|
356
365
|
interval={0}
|
|
357
|
-
unit={` ${unitLabel}`}
|
|
358
366
|
domain={[0, roundReferenceValue]}
|
|
367
|
+
ticks={getTicks(roundReferenceValue, false)}
|
|
359
368
|
tickFormatter={
|
|
360
|
-
(value) =>
|
|
361
|
-
new Intl.NumberFormat('fr-FR').format(value.toFixed(0)) // Add a space as thousand separator
|
|
369
|
+
(value) => new Intl.NumberFormat('fr-FR').format(value) // Add a space as thousand separator
|
|
362
370
|
}
|
|
363
371
|
axisLine={{ stroke: theme.border }}
|
|
364
372
|
tick={{
|
|
@@ -391,6 +399,7 @@ export const Barchart = <T extends BarchartBars>(props: BarchartProps<T>) => {
|
|
|
391
399
|
hoveredValue={hoveredValue}
|
|
392
400
|
tooltip={tooltip}
|
|
393
401
|
unitLabel={unitLabel}
|
|
402
|
+
chartContainerRef={chartRef}
|
|
394
403
|
/>
|
|
395
404
|
)}
|
|
396
405
|
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/),
|
|
@@ -39,6 +63,7 @@ describe('ChartTooltip', () => {
|
|
|
39
63
|
tooltipProps={testTooltipProps}
|
|
40
64
|
hoveredValue="Success"
|
|
41
65
|
tooltip={undefined}
|
|
66
|
+
chartContainerRef={mockChartContainerRef}
|
|
42
67
|
/>,
|
|
43
68
|
);
|
|
44
69
|
expect(selectors.category()).toBeInTheDocument();
|
|
@@ -54,6 +79,7 @@ describe('ChartTooltip', () => {
|
|
|
54
79
|
tooltipProps={testTooltipProps}
|
|
55
80
|
hoveredValue="Success"
|
|
56
81
|
tooltip={testTooltip}
|
|
82
|
+
chartContainerRef={mockChartContainerRef}
|
|
57
83
|
/>,
|
|
58
84
|
);
|
|
59
85
|
expect(selectors.tooltip()).toBeInTheDocument();
|
|
@@ -65,6 +91,7 @@ describe('ChartTooltip', () => {
|
|
|
65
91
|
tooltipProps={{ ...testTooltipProps, active: false }}
|
|
66
92
|
hoveredValue="Success"
|
|
67
93
|
tooltip={testTooltip}
|
|
94
|
+
chartContainerRef={mockChartContainerRef}
|
|
68
95
|
/>,
|
|
69
96
|
);
|
|
70
97
|
expect(selectors.tooltip()).not.toBeInTheDocument();
|
|
@@ -84,6 +111,7 @@ describe('ChartTooltip', () => {
|
|
|
84
111
|
}}
|
|
85
112
|
tooltipProps={{ ...testTooltipProps, label }}
|
|
86
113
|
hoveredValue="Success"
|
|
114
|
+
chartContainerRef={mockChartContainerRef}
|
|
87
115
|
/>,
|
|
88
116
|
);
|
|
89
117
|
expect(selectors.success()).toBeInTheDocument();
|
|
@@ -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,7 +1,7 @@
|
|
|
1
1
|
import { TooltipContentProps } from 'recharts';
|
|
2
2
|
import { LegendShape } from '../chartlegend/ChartLegend';
|
|
3
3
|
import {
|
|
4
|
-
|
|
4
|
+
ChartTooltipPortal,
|
|
5
5
|
ChartTooltipHeader,
|
|
6
6
|
ChartTooltipItem,
|
|
7
7
|
ChartTooltipItemsContainer,
|
|
@@ -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,23 +30,25 @@ 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
|
-
|
|
41
|
-
return tooltip(currentPoint);
|
|
42
|
-
}
|
|
42
|
+
|
|
43
43
|
const duration =
|
|
44
44
|
type.type === 'time'
|
|
45
45
|
? type.timeRange.startDate.getTime() - type.timeRange.endDate.getTime()
|
|
46
46
|
: 0;
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
|
|
48
|
+
const tooltipContent = tooltip ? (
|
|
49
|
+
tooltip(currentPoint)
|
|
50
|
+
) : (
|
|
51
|
+
<>
|
|
49
52
|
<ChartTooltipHeader>
|
|
50
53
|
{type.type === 'time' ? (
|
|
51
54
|
<TooltipHeader duration={duration} value={currentPoint.category} />
|
|
@@ -80,6 +83,16 @@ export const BarchartTooltip = <T extends BarchartBars>({
|
|
|
80
83
|
);
|
|
81
84
|
})}
|
|
82
85
|
</ChartTooltipItemsContainer>
|
|
83
|
-
|
|
86
|
+
</>
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<ChartTooltipPortal
|
|
91
|
+
coordinate={coordinate}
|
|
92
|
+
chartContainerRef={chartContainerRef}
|
|
93
|
+
isVisible={active}
|
|
94
|
+
>
|
|
95
|
+
{tooltipContent}
|
|
96
|
+
</ChartTooltipPortal>
|
|
84
97
|
);
|
|
85
98
|
};
|