@centreon/ui 24.4.1-MON-34459-fullscreen-mode.0 → 24.4.1-MON-develop-dashboard.1
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/package.json +4 -1
- package/src/Graph/BarStack/BarStack.cypress.spec.tsx +154 -0
- package/src/Graph/BarStack/BarStack.stories.tsx +123 -0
- package/src/Graph/BarStack/BarStack.styles.ts +36 -0
- package/src/Graph/BarStack/BarStack.tsx +14 -0
- package/src/Graph/BarStack/ResponsiveBarStack.tsx +208 -0
- package/src/Graph/BarStack/index.ts +1 -0
- package/src/Graph/BarStack/models.ts +19 -0
- package/src/Graph/BarStack/useResponsiveBarStack.ts +139 -0
- package/src/Graph/Legend/Legend.tsx +21 -0
- package/src/Graph/Legend/index.ts +1 -0
- package/src/Graph/Legend/models.ts +11 -0
- package/src/Graph/PieChart/PieChart.cypress.spec.tsx +169 -0
- package/src/Graph/PieChart/PieChart.stories.tsx +194 -0
- package/src/Graph/PieChart/PieChart.styles.ts +39 -0
- package/src/Graph/PieChart/PieChart.tsx +14 -0
- package/src/Graph/PieChart/ResponsivePie.tsx +251 -0
- package/src/Graph/PieChart/index.ts +1 -0
- package/src/Graph/PieChart/models.ts +19 -0
- package/src/Graph/PieChart/useResponsivePie.ts +86 -0
- package/src/Graph/common/utils.ts +19 -0
- package/src/Graph/index.ts +2 -0
- package/src/Graph/translatedLabels.ts +1 -0
- package/src/Listing/ActionBar/index.tsx +9 -8
- package/src/Listing/Cell/DataCell.styles.ts +3 -0
- package/src/Listing/Cell/DataCell.tsx +8 -4
- package/src/Listing/Listing.cypress.spec.tsx +80 -4
- package/src/Listing/Listing.styles.ts +3 -5
- package/src/Listing/index.stories.tsx +25 -2
- package/src/Listing/index.test.tsx +1 -1
- package/src/Listing/index.tsx +3 -1
- package/src/Listing/models.ts +1 -0
- package/src/components/Form/AccessRights/ShareInput/ContactSwitch.tsx +3 -3
- package/src/components/Form/AccessRights/ShareInput/ShareInput.tsx +1 -0
- package/src/utils/index.ts +0 -1
- package/src/utils/useFullscreen/Fullscreen.cypress.spec.tsx +0 -109
- package/src/utils/useFullscreen/atoms.ts +0 -3
- package/src/utils/useFullscreen/index.ts +0 -2
- package/src/utils/useFullscreen/translatedLabels.ts +0 -1
- package/src/utils/useFullscreen/useFullscreen.ts +0 -73
- package/src/utils/useFullscreen/useFullscreenListener.ts +0 -54
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@centreon/ui",
|
|
3
|
-
"version": "24.4.1-MON-
|
|
3
|
+
"version": "24.4.1-MON-develop-dashboard.1",
|
|
4
4
|
"description": "Centreon UI Components",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"eslint": "eslint ./src --ext .js,.jsx,.ts,.tsx --max-warnings 0",
|
|
@@ -117,9 +117,11 @@
|
|
|
117
117
|
"@react-spring/web": "^9.7.3",
|
|
118
118
|
"@visx/curve": "^2.1.0",
|
|
119
119
|
"@visx/group": "^3.3.0",
|
|
120
|
+
"@visx/legend": "^3.5.0",
|
|
120
121
|
"@visx/pattern": "^3.0.0",
|
|
121
122
|
"@visx/scale": "^3.0.0",
|
|
122
123
|
"@visx/shape": "^2.12.2",
|
|
124
|
+
"@visx/text": "^3.3.0",
|
|
123
125
|
"@visx/threshold": "^2.12.2",
|
|
124
126
|
"@visx/visx": "2.16.0",
|
|
125
127
|
"anylogger": "^1.0.11",
|
|
@@ -127,6 +129,7 @@
|
|
|
127
129
|
"humanize-duration": "^3.27.3",
|
|
128
130
|
"lexical": "^0.12.2",
|
|
129
131
|
"notistack": "^3.0.1",
|
|
132
|
+
"numeral": "^2.0.6",
|
|
130
133
|
"ramda": "0.29.1",
|
|
131
134
|
"react-grid-layout": "^1.3.4",
|
|
132
135
|
"react-html-parser": "^2.0.2",
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import numeral from 'numeral';
|
|
2
|
+
|
|
3
|
+
import BarStack from './BarStack';
|
|
4
|
+
import { BarType, BarStackProps } from './models';
|
|
5
|
+
|
|
6
|
+
const defaultData = [
|
|
7
|
+
{ color: '#88B922', label: 'Ok', value: 148 },
|
|
8
|
+
{ color: '#999999', label: 'Unknown', value: 13 },
|
|
9
|
+
{ color: '#F7931A', label: 'Warning', value: 16 },
|
|
10
|
+
{ color: '#FF6666', label: 'Down', value: 62 }
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
const dataWithNullValues = [
|
|
14
|
+
{ color: '#88B922', label: 'Ok', value: 0 },
|
|
15
|
+
{ color: '#999999', label: 'Unknown', value: 0 },
|
|
16
|
+
{ color: '#F7931A', label: 'Warning', value: 0 },
|
|
17
|
+
{ color: '#FF6666', label: 'Down', value: 0 }
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
const total = Math.floor(
|
|
21
|
+
defaultData.reduce((acc, { value }) => acc + value, 0)
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
const TooltipContent = ({ label, color, value }: BarType): JSX.Element => {
|
|
25
|
+
return (
|
|
26
|
+
<div data-testid={`tooltip-${label}`} style={{ color }}>
|
|
27
|
+
{label} : {value}
|
|
28
|
+
</div>
|
|
29
|
+
);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const initialize = ({
|
|
33
|
+
width = '400px',
|
|
34
|
+
height = '400px',
|
|
35
|
+
data = defaultData,
|
|
36
|
+
...args
|
|
37
|
+
}: Omit<BarStackProps, 'data'> & {
|
|
38
|
+
data?;
|
|
39
|
+
height?: string;
|
|
40
|
+
width?: string;
|
|
41
|
+
}): void => {
|
|
42
|
+
cy.mount({
|
|
43
|
+
Component: (
|
|
44
|
+
<div style={{ height, width }}>
|
|
45
|
+
<BarStack {...args} data={data} />
|
|
46
|
+
</div>
|
|
47
|
+
)
|
|
48
|
+
});
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
describe('Bar stack', () => {
|
|
52
|
+
it('renders Bar stack correctly with provided data', () => {
|
|
53
|
+
initialize({});
|
|
54
|
+
|
|
55
|
+
defaultData.forEach(({ label }) => {
|
|
56
|
+
cy.findByTestId(label).should('be.visible');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
cy.makeSnapshot();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('adjusts size based on the provided width and height', () => {
|
|
63
|
+
initialize({ displayLegend: false, height: '300px', width: '300px' });
|
|
64
|
+
|
|
65
|
+
cy.findByTestId('barStack').should('have.css', 'height', '300px');
|
|
66
|
+
|
|
67
|
+
cy.makeSnapshot();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('renders as a horizontal bar when variant is set to "horizontal"', () => {
|
|
71
|
+
initialize({ variant: 'horizontal' });
|
|
72
|
+
cy.get('[data-variant="horizontal"]').should('exist');
|
|
73
|
+
|
|
74
|
+
cy.makeSnapshot();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('renders as a vertical bar when variant is set to "vertical"', () => {
|
|
78
|
+
initialize({ variant: 'vertical' });
|
|
79
|
+
cy.get('[data-variant="vertical"]').should('exist');
|
|
80
|
+
|
|
81
|
+
cy.makeSnapshot();
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('displays tooltip with correct information on hover', () => {
|
|
85
|
+
initialize({ TooltipContent });
|
|
86
|
+
|
|
87
|
+
defaultData.forEach(({ label, value }) => {
|
|
88
|
+
cy.findByTestId(label).trigger('mouseover', { force: true });
|
|
89
|
+
|
|
90
|
+
cy.findByTestId(`tooltip-${label}`)
|
|
91
|
+
.should('contain', label)
|
|
92
|
+
.and('contain', numeral(value).format('0a').toUpperCase());
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
cy.makeSnapshot();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('conditionally displays values on rects based on displayValues prop', () => {
|
|
99
|
+
initialize({ displayValues: true });
|
|
100
|
+
defaultData.forEach(({ value }, index) => {
|
|
101
|
+
cy.findAllByTestId('value')
|
|
102
|
+
.eq(index)
|
|
103
|
+
.children()
|
|
104
|
+
.eq(0)
|
|
105
|
+
.should('have.text', value);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
initialize({ displayValues: false });
|
|
109
|
+
cy.findAllByTestId('value').should('not.exist');
|
|
110
|
+
|
|
111
|
+
cy.makeSnapshot();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('displays values on rects in percentage unit when displayValues is set to true and unit to percentage', () => {
|
|
115
|
+
initialize({ displayValues: true, unit: 'percentage' });
|
|
116
|
+
defaultData.forEach(({ value }, index) => {
|
|
117
|
+
cy.findAllByTestId('value')
|
|
118
|
+
.eq(index)
|
|
119
|
+
.children()
|
|
120
|
+
.eq(0)
|
|
121
|
+
.should('have.text', `${((value * 100) / total).toFixed(1)}%`);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
cy.makeSnapshot();
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('displays Legend component based on displayLegend prop', () => {
|
|
128
|
+
initialize({ displayLegend: true });
|
|
129
|
+
cy.findByTestId('Legend').should('be.visible');
|
|
130
|
+
|
|
131
|
+
initialize({ displayLegend: false });
|
|
132
|
+
cy.findByTestId('Legend').should('not.exist');
|
|
133
|
+
|
|
134
|
+
cy.makeSnapshot();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('displays the title when the title is giving', () => {
|
|
138
|
+
initialize({ title: 'host' });
|
|
139
|
+
cy.findByTestId('Title').should('be.visible');
|
|
140
|
+
|
|
141
|
+
initialize({});
|
|
142
|
+
cy.findByTestId('Title').should('not.exist');
|
|
143
|
+
|
|
144
|
+
cy.makeSnapshot();
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('displays a message "No Data Available" when all values are null', () => {
|
|
148
|
+
initialize({ data: dataWithNullValues, title: 'host' });
|
|
149
|
+
|
|
150
|
+
cy.contains('No Data Available');
|
|
151
|
+
|
|
152
|
+
cy.makeSnapshot();
|
|
153
|
+
});
|
|
154
|
+
});
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
|
|
3
|
+
import { BarType } from './models';
|
|
4
|
+
|
|
5
|
+
import { BarStack } from '.';
|
|
6
|
+
|
|
7
|
+
const data = [
|
|
8
|
+
{ color: '#88B922', label: 'Ok', value: 148 },
|
|
9
|
+
{ color: '#999999', label: 'Unknown', value: 63 },
|
|
10
|
+
{ color: '#F7931A', label: 'Warning', value: 16 },
|
|
11
|
+
{ color: '#FF6666', label: 'Down', value: 13 }
|
|
12
|
+
];
|
|
13
|
+
|
|
14
|
+
const dataWithBigNumbers = [
|
|
15
|
+
{ color: '#88B922', label: 'Ok', value: 260000 },
|
|
16
|
+
{ color: '#999999', label: 'Unknown', value: 1010900 },
|
|
17
|
+
{ color: '#F7931A', label: 'Warning', value: 63114 },
|
|
18
|
+
{ color: '#FF6666', label: 'Down', value: 122222 }
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const dataWithSmallNumber = [
|
|
22
|
+
{ color: '#88B922', label: 'Ok', value: 148 },
|
|
23
|
+
{ color: '#999999', label: 'Unknown', value: 42 },
|
|
24
|
+
{ color: '#F7931A', label: 'Warning', value: 7 },
|
|
25
|
+
{ color: '#FF6666', label: 'Down', value: 5 }
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
const meta: Meta<typeof BarStack> = {
|
|
29
|
+
component: BarStack
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export default meta;
|
|
33
|
+
type Story = StoryObj<typeof BarStack>;
|
|
34
|
+
|
|
35
|
+
const TooltipContent = ({ label, color, value }: BarType): JSX.Element => {
|
|
36
|
+
return (
|
|
37
|
+
<div style={{ color }}>
|
|
38
|
+
{label} : {value}
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const Template = (args): JSX.Element => {
|
|
44
|
+
return (
|
|
45
|
+
<div style={{ height: '300px', width: '500px' }}>
|
|
46
|
+
<BarStack {...args} />
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const Vertical: Story = {
|
|
52
|
+
args: { data, title: 'hosts' },
|
|
53
|
+
render: Template
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const WithoutTitle: Story = {
|
|
57
|
+
args: { data },
|
|
58
|
+
render: Template
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export const WithPencentage: Story = {
|
|
62
|
+
args: { data, title: 'hosts', unit: 'percentage' },
|
|
63
|
+
render: Template
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export const WithoutLegend: Story = {
|
|
67
|
+
args: { data, displayLegend: false, title: 'hosts' },
|
|
68
|
+
|
|
69
|
+
render: Template
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
export const withDisplayedValues: Story = {
|
|
73
|
+
args: { data, displayValues: true, title: 'hosts' },
|
|
74
|
+
render: Template
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export const WithTooltip: Story = {
|
|
78
|
+
args: { TooltipContent, data, title: 'hosts' },
|
|
79
|
+
render: Template
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export const WithBigNumbers: Story = {
|
|
83
|
+
args: {
|
|
84
|
+
TooltipContent,
|
|
85
|
+
data: dataWithBigNumbers,
|
|
86
|
+
displayValues: true,
|
|
87
|
+
title: 'hosts'
|
|
88
|
+
},
|
|
89
|
+
render: Template
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export const WithSmallNumbers: Story = {
|
|
93
|
+
args: {
|
|
94
|
+
TooltipContent,
|
|
95
|
+
data: dataWithSmallNumber,
|
|
96
|
+
displayValues: true,
|
|
97
|
+
title: 'hosts'
|
|
98
|
+
},
|
|
99
|
+
render: Template
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export const Horizontal: Story = {
|
|
103
|
+
args: {
|
|
104
|
+
TooltipContent,
|
|
105
|
+
data,
|
|
106
|
+
displayValues: true,
|
|
107
|
+
title: 'hosts',
|
|
108
|
+
variant: 'horizontal'
|
|
109
|
+
},
|
|
110
|
+
render: Template
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export const HorizontalWithoutLegend: Story = {
|
|
114
|
+
args: {
|
|
115
|
+
TooltipContent,
|
|
116
|
+
data,
|
|
117
|
+
displayLegend: false,
|
|
118
|
+
displayValues: true,
|
|
119
|
+
title: 'hosts',
|
|
120
|
+
variant: 'horizontal'
|
|
121
|
+
},
|
|
122
|
+
render: Template
|
|
123
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { makeStyles } from 'tss-react/mui';
|
|
2
|
+
|
|
3
|
+
export const useBarStackStyles = makeStyles()((theme) => ({
|
|
4
|
+
barStackTooltip: {
|
|
5
|
+
backgroundColor: theme.palette.background.paper,
|
|
6
|
+
color: theme.palette.text.primary,
|
|
7
|
+
padding: 0,
|
|
8
|
+
position: 'relative'
|
|
9
|
+
},
|
|
10
|
+
container: {
|
|
11
|
+
alignItems: 'center',
|
|
12
|
+
display: 'flex',
|
|
13
|
+
gap: theme.spacing(1.5),
|
|
14
|
+
justifyContent: 'center'
|
|
15
|
+
},
|
|
16
|
+
svgContainer: {
|
|
17
|
+
alignItems: 'center',
|
|
18
|
+
backgroundColor: theme.palette.background.panelGroups,
|
|
19
|
+
borderRadius: theme.shape.borderRadius,
|
|
20
|
+
display: 'flex',
|
|
21
|
+
justifyContent: 'center'
|
|
22
|
+
},
|
|
23
|
+
svgWrapper: {
|
|
24
|
+
alignItems: 'center',
|
|
25
|
+
display: 'flex',
|
|
26
|
+
flexDirection: 'column',
|
|
27
|
+
gap: theme.spacing(1),
|
|
28
|
+
justifyContent: 'center'
|
|
29
|
+
},
|
|
30
|
+
title: {
|
|
31
|
+
fontSize: theme.typography.h6.fontSize,
|
|
32
|
+
fontWeight: theme.typography.fontWeightMedium,
|
|
33
|
+
margin: 0,
|
|
34
|
+
padding: 0
|
|
35
|
+
}
|
|
36
|
+
}));
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ParentSize } from '../..';
|
|
2
|
+
|
|
3
|
+
import ResponsiveBarStack from './ResponsiveBarStack';
|
|
4
|
+
import { BarStackProps } from './models';
|
|
5
|
+
|
|
6
|
+
const Bar = (props: BarStackProps): JSX.Element => (
|
|
7
|
+
<ParentSize>
|
|
8
|
+
{({ width, height }) => (
|
|
9
|
+
<ResponsiveBarStack {...props} height={height} width={width} />
|
|
10
|
+
)}
|
|
11
|
+
</ParentSize>
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
export default Bar;
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { useRef } from 'react';
|
|
2
|
+
|
|
3
|
+
import { BarStack as BarStackVertical, BarStackHorizontal } from '@visx/shape';
|
|
4
|
+
import { Group } from '@visx/group';
|
|
5
|
+
import numeral from 'numeral';
|
|
6
|
+
import { Text } from '@visx/text';
|
|
7
|
+
import { useTranslation } from 'react-i18next';
|
|
8
|
+
|
|
9
|
+
import { Typography } from '@mui/material';
|
|
10
|
+
|
|
11
|
+
import { Tooltip } from '../../components';
|
|
12
|
+
import { LegendProps } from '../Legend/models';
|
|
13
|
+
import { Legend as LegendComponent } from '../Legend';
|
|
14
|
+
import { getValueByUnit } from '../common/utils';
|
|
15
|
+
import { labelNoDataFound } from '../translatedLabels';
|
|
16
|
+
|
|
17
|
+
import { BarStackProps } from './models';
|
|
18
|
+
import { useBarStackStyles } from './BarStack.styles';
|
|
19
|
+
import useResponsiveBarStack from './useResponsiveBarStack';
|
|
20
|
+
|
|
21
|
+
const DefaultLengd = ({ scale, direction }: LegendProps): JSX.Element => (
|
|
22
|
+
<LegendComponent direction={direction} scale={scale} />
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
const BarStack = ({
|
|
26
|
+
title,
|
|
27
|
+
data,
|
|
28
|
+
width,
|
|
29
|
+
height,
|
|
30
|
+
size = 72,
|
|
31
|
+
onSingleBarClick,
|
|
32
|
+
displayLegend = true,
|
|
33
|
+
TooltipContent,
|
|
34
|
+
Legend = DefaultLengd,
|
|
35
|
+
unit = 'number',
|
|
36
|
+
displayValues,
|
|
37
|
+
variant = 'vertical',
|
|
38
|
+
legendDirection = 'column'
|
|
39
|
+
}: BarStackProps & { height: number; width: number }): JSX.Element => {
|
|
40
|
+
const { t } = useTranslation();
|
|
41
|
+
const { classes } = useBarStackStyles();
|
|
42
|
+
|
|
43
|
+
const titleRef = useRef(null);
|
|
44
|
+
const legendRef = useRef(null);
|
|
45
|
+
|
|
46
|
+
const {
|
|
47
|
+
barSize,
|
|
48
|
+
colorScale,
|
|
49
|
+
input,
|
|
50
|
+
keys,
|
|
51
|
+
legendScale,
|
|
52
|
+
total,
|
|
53
|
+
xScale,
|
|
54
|
+
yScale,
|
|
55
|
+
svgWrapperWidth,
|
|
56
|
+
svgContainerSize,
|
|
57
|
+
isVerticalBar,
|
|
58
|
+
areAllValuesNull
|
|
59
|
+
} = useResponsiveBarStack({
|
|
60
|
+
data,
|
|
61
|
+
height,
|
|
62
|
+
legendRef,
|
|
63
|
+
size,
|
|
64
|
+
titleRef,
|
|
65
|
+
unit,
|
|
66
|
+
variant,
|
|
67
|
+
width
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const BarStackComponent = isVerticalBar
|
|
71
|
+
? BarStackVertical
|
|
72
|
+
: BarStackHorizontal;
|
|
73
|
+
|
|
74
|
+
if (areAllValuesNull) {
|
|
75
|
+
return (
|
|
76
|
+
<div className={classes.container} style={{ height, width }}>
|
|
77
|
+
<Typography variant="h3">{t(labelNoDataFound)}</Typography>
|
|
78
|
+
</div>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<div className={classes.container} style={{ height, width }}>
|
|
84
|
+
<div
|
|
85
|
+
className={classes.svgWrapper}
|
|
86
|
+
style={{
|
|
87
|
+
height,
|
|
88
|
+
width: svgWrapperWidth
|
|
89
|
+
}}
|
|
90
|
+
>
|
|
91
|
+
{title && (
|
|
92
|
+
<div className={classes.title} data-testid="Title" ref={titleRef}>
|
|
93
|
+
{`${numeral(total).format('0a').toUpperCase()} `} {title}
|
|
94
|
+
</div>
|
|
95
|
+
)}
|
|
96
|
+
<div
|
|
97
|
+
className={classes.svgContainer}
|
|
98
|
+
data-testid="barStack"
|
|
99
|
+
style={svgContainerSize}
|
|
100
|
+
>
|
|
101
|
+
<svg
|
|
102
|
+
data-variant={variant}
|
|
103
|
+
height={barSize.height}
|
|
104
|
+
width={barSize.width}
|
|
105
|
+
>
|
|
106
|
+
<Group>
|
|
107
|
+
<BarStackComponent
|
|
108
|
+
color={colorScale}
|
|
109
|
+
data={[input]}
|
|
110
|
+
keys={keys}
|
|
111
|
+
{...(isVerticalBar
|
|
112
|
+
? { x: () => undefined }
|
|
113
|
+
: { y: () => undefined })}
|
|
114
|
+
xScale={xScale}
|
|
115
|
+
yScale={yScale}
|
|
116
|
+
>
|
|
117
|
+
{(barStacks) =>
|
|
118
|
+
barStacks.map((barStack) =>
|
|
119
|
+
barStack.bars.map((bar) => {
|
|
120
|
+
const onClick = (): void => {
|
|
121
|
+
onSingleBarClick?.(bar);
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<Tooltip
|
|
126
|
+
hasCaret
|
|
127
|
+
classes={{
|
|
128
|
+
tooltip: classes.barStackTooltip
|
|
129
|
+
}}
|
|
130
|
+
followCursor={false}
|
|
131
|
+
key={`bar-stack-${barStack.index}-${bar.index}`}
|
|
132
|
+
label={
|
|
133
|
+
TooltipContent && (
|
|
134
|
+
<TooltipContent
|
|
135
|
+
color={bar.color}
|
|
136
|
+
label={bar.key}
|
|
137
|
+
title={title}
|
|
138
|
+
total={total}
|
|
139
|
+
value={barStack.bars[0].bar.data[barStack.key]}
|
|
140
|
+
/>
|
|
141
|
+
)
|
|
142
|
+
}
|
|
143
|
+
position={
|
|
144
|
+
isVerticalBar ? 'right-start' : 'bottom-start'
|
|
145
|
+
}
|
|
146
|
+
>
|
|
147
|
+
<g data-testid={bar.key}>
|
|
148
|
+
<rect
|
|
149
|
+
fill={bar.color}
|
|
150
|
+
height={
|
|
151
|
+
isVerticalBar ? bar.height - 1 : bar.height
|
|
152
|
+
}
|
|
153
|
+
key={`bar-stack-${barStack.index}-${bar.index}`}
|
|
154
|
+
ry={5}
|
|
155
|
+
width={isVerticalBar ? bar.width : bar.width - 1}
|
|
156
|
+
x={bar.x}
|
|
157
|
+
y={bar.y}
|
|
158
|
+
onClick={onClick}
|
|
159
|
+
/>
|
|
160
|
+
{displayValues &&
|
|
161
|
+
bar.height > 10 &&
|
|
162
|
+
bar.width > 10 && (
|
|
163
|
+
<Text
|
|
164
|
+
cursor="pointer"
|
|
165
|
+
data-testid="value"
|
|
166
|
+
fill="#000"
|
|
167
|
+
fontSize={12}
|
|
168
|
+
textAnchor="middle"
|
|
169
|
+
verticalAnchor="middle"
|
|
170
|
+
x={bar.x + bar.width / 2}
|
|
171
|
+
y={bar.y + bar.height / 2}
|
|
172
|
+
>
|
|
173
|
+
{getValueByUnit({
|
|
174
|
+
total,
|
|
175
|
+
unit,
|
|
176
|
+
value:
|
|
177
|
+
barStack.bars[0].bar.data[barStack.key]
|
|
178
|
+
})}
|
|
179
|
+
</Text>
|
|
180
|
+
)}
|
|
181
|
+
</g>
|
|
182
|
+
</Tooltip>
|
|
183
|
+
);
|
|
184
|
+
})
|
|
185
|
+
)
|
|
186
|
+
}
|
|
187
|
+
</BarStackComponent>
|
|
188
|
+
</Group>
|
|
189
|
+
</svg>
|
|
190
|
+
</div>
|
|
191
|
+
</div>
|
|
192
|
+
{displayLegend && (
|
|
193
|
+
<div data-testid="Legend" ref={legendRef}>
|
|
194
|
+
<Legend
|
|
195
|
+
data={data}
|
|
196
|
+
direction={legendDirection}
|
|
197
|
+
scale={legendScale}
|
|
198
|
+
title={title}
|
|
199
|
+
total={total}
|
|
200
|
+
unit={unit}
|
|
201
|
+
/>
|
|
202
|
+
</div>
|
|
203
|
+
)}
|
|
204
|
+
</div>
|
|
205
|
+
);
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
export default BarStack;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as BarStack } from './BarStack';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface BarType {
|
|
2
|
+
color: string;
|
|
3
|
+
label: string;
|
|
4
|
+
value: number;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export type BarStackProps = {
|
|
8
|
+
Legend?: ({ scale, data, title, total, unit, direction }) => JSX.Element;
|
|
9
|
+
TooltipContent?: (barData) => JSX.Element | boolean | null;
|
|
10
|
+
data: Array<BarType>;
|
|
11
|
+
displayLegend?: boolean;
|
|
12
|
+
displayValues?: boolean;
|
|
13
|
+
legendDirection?: 'row' | 'column';
|
|
14
|
+
onSingleBarClick?: (barData) => void;
|
|
15
|
+
size?: number;
|
|
16
|
+
title?: string;
|
|
17
|
+
unit?: 'percentage' | 'number';
|
|
18
|
+
variant?: 'vertical' | 'horizontal';
|
|
19
|
+
};
|