@centreon/ui 24.10.12 → 24.10.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. package/package.json +3 -2
  2. package/public/mockServiceWorker.js +1 -1
  3. package/src/Button/Icon/index.tsx +3 -1
  4. package/src/Dashboard/Dashboard.styles.ts +3 -4
  5. package/src/Dashboard/DashboardLayout.stories.tsx +1 -1
  6. package/src/Dashboard/Grid.tsx +11 -17
  7. package/src/Dashboard/Layout.tsx +27 -56
  8. package/src/Dialog/UnsavedChanges/index.tsx +15 -13
  9. package/src/Dialog/UnsavedChanges/translatedLabels.ts +15 -13
  10. package/src/Form/Form.tsx +0 -1
  11. package/src/Form/Inputs/Autocomplete.tsx +1 -1
  12. package/src/Form/Inputs/ConnectedAutocomplete.tsx +5 -2
  13. package/src/Form/Inputs/Grid.tsx +7 -1
  14. package/src/Form/Inputs/Radio.tsx +1 -1
  15. package/src/Form/Inputs/Switch.tsx +1 -1
  16. package/src/Form/Inputs/Text.tsx +1 -1
  17. package/src/Form/Inputs/index.tsx +25 -24
  18. package/src/Form/Inputs/models.ts +2 -0
  19. package/src/Graph/BarChart/BarChart.cypress.spec.tsx +3 -3
  20. package/src/Graph/BarChart/BarChart.tsx +24 -31
  21. package/src/Graph/BarChart/BarGroup.tsx +32 -59
  22. package/src/Graph/BarChart/BarStack.tsx +64 -13
  23. package/src/Graph/BarChart/MemoizedGroup.tsx +123 -0
  24. package/src/Graph/BarChart/ResponsiveBarChart.tsx +21 -7
  25. package/src/Graph/BarStack/BarStack.cypress.spec.tsx +87 -9
  26. package/src/Graph/BarStack/BarStack.stories.tsx +13 -4
  27. package/src/Graph/BarStack/BarStack.styles.ts +57 -33
  28. package/src/Graph/BarStack/Graph.tsx +173 -0
  29. package/src/Graph/BarStack/GraphAndLegend.tsx +117 -0
  30. package/src/Graph/BarStack/ResponsiveBarStack.tsx +61 -168
  31. package/src/Graph/BarStack/constants.ts +5 -0
  32. package/src/Graph/BarStack/models.ts +0 -1
  33. package/src/Graph/BarStack/useGraphAndLegend.ts +84 -0
  34. package/src/Graph/BarStack/useResponsiveBarStack.ts +73 -97
  35. package/src/Graph/Chart/Chart.cypress.spec.tsx +14 -26
  36. package/src/Graph/Chart/Chart.stories.tsx +1 -1
  37. package/src/Graph/Chart/Chart.tsx +53 -37
  38. package/src/Graph/Chart/InteractiveComponents/AnchorPoint/GuidingLines.tsx +3 -3
  39. package/src/Graph/Chart/InteractiveComponents/AnchorPoint/useTickGraph.ts +19 -6
  40. package/src/Graph/Chart/Legend/Legend.styles.ts +25 -11
  41. package/src/Graph/Chart/Legend/index.tsx +6 -24
  42. package/src/Graph/Chart/index.tsx +34 -43
  43. package/src/Graph/Chart/models.ts +0 -1
  44. package/src/Graph/Chart/useChartData.ts +19 -1
  45. package/src/Graph/HeatMap/ResponsiveHeatMap.tsx +20 -2
  46. package/src/Graph/HeatMap/model.ts +6 -2
  47. package/src/Graph/Legend/Legend.styles.ts +10 -0
  48. package/src/Graph/Legend/Legend.tsx +6 -1
  49. package/src/Graph/SingleBar/ResponsiveSingleBar.tsx +9 -10
  50. package/src/Graph/SingleBar/ThresholdLine.tsx +6 -6
  51. package/src/Graph/Text/Text.styles.ts +2 -2
  52. package/src/Graph/Text/Text.tsx +23 -10
  53. package/src/Graph/Timeline/ResponsiveTimeline.tsx +152 -0
  54. package/src/Graph/Timeline/Timeline.cypress.spec.tsx +148 -0
  55. package/src/Graph/Timeline/Timeline.stories.tsx +91 -0
  56. package/src/Graph/Timeline/Timeline.tsx +28 -0
  57. package/src/Graph/Timeline/index.ts +1 -0
  58. package/src/Graph/Timeline/models.ts +20 -0
  59. package/src/Graph/Timeline/timeline.styles.ts +11 -0
  60. package/src/Graph/Timeline/translatedLabel.ts +6 -0
  61. package/src/Graph/Timeline/useTimeline.ts +90 -0
  62. package/src/Graph/Tree/Links.tsx +2 -2
  63. package/src/Graph/Tree/Tree.tsx +2 -2
  64. package/src/Graph/Tree/constants.ts +1 -1
  65. package/src/Graph/common/Axes/index.tsx +1 -1
  66. package/src/Graph/common/Axes/useAxisY.ts +8 -4
  67. package/src/Graph/common/BaseChart/BaseChart.tsx +3 -12
  68. package/src/Graph/common/BaseChart/ChartSvgWrapper.tsx +12 -4
  69. package/src/Graph/common/BaseChart/Header/index.tsx +3 -1
  70. package/src/Graph/common/BaseChart/useComputeBaseChartDimensions.ts +23 -11
  71. package/src/Graph/common/BaseChart/useComputeYAxisMaxCharacters.ts +92 -0
  72. package/src/Graph/common/models.ts +7 -8
  73. package/src/Graph/common/timeSeries/index.test.ts +1 -1
  74. package/src/Graph/common/timeSeries/index.ts +56 -29
  75. package/src/Graph/common/timeSeries/models.ts +2 -0
  76. package/src/Graph/common/utils.ts +51 -3
  77. package/src/Graph/index.ts +4 -1
  78. package/src/Graph/mockedData/lastDayWithNullValues.json +6 -6
  79. package/src/Graph/mockedData/pingServiceLinesBars.json +47 -47
  80. package/src/Icon/DowntimeIcon.tsx +8 -1
  81. package/src/Icon/FlappingIcon.tsx +22 -0
  82. package/src/Icon/index.ts +1 -0
  83. package/src/InputField/Select/Autocomplete/Connected/Multi/index.test.tsx +21 -1
  84. package/src/InputField/Select/Autocomplete/Connected/index.test.tsx +2 -2
  85. package/src/InputField/Select/Autocomplete/Connected/index.tsx +52 -15
  86. package/src/InputField/Select/Autocomplete/Multi/index.stories.tsx +19 -0
  87. package/src/InputField/Select/Autocomplete/Multi/index.tsx +8 -5
  88. package/src/InputField/Select/Autocomplete/index.tsx +79 -54
  89. package/src/InputField/Text/index.tsx +6 -4
  90. package/src/InputField/translatedLabels.ts +2 -0
  91. package/src/Listing/ActionBar/index.tsx +1 -1
  92. package/src/Listing/Listing.styles.ts +3 -3
  93. package/src/Listing/index.tsx +40 -37
  94. package/src/Listing/models.ts +0 -8
  95. package/src/Listing/useStyleTable.ts +58 -32
  96. package/src/MultiSelectEntries/index.tsx +2 -0
  97. package/src/PopoverMenu/index.tsx +2 -9
  98. package/src/SortableItems/index.tsx +0 -1
  99. package/src/ThemeProvider/index.tsx +1 -1
  100. package/src/ThemeProvider/palettes.ts +6 -0
  101. package/src/TimePeriods/CustomTimePeriod/PopoverCustomTimePeriod/PickersStartEndDate.tsx +2 -3
  102. package/src/TimePeriods/DateTimePickerInput.tsx +3 -1
  103. package/src/api/buildListingEndpoint/getSearchQueryParameterValue.ts +7 -1
  104. package/src/api/buildListingEndpoint/models.ts +1 -0
  105. package/src/api/customFetch.ts +4 -1
  106. package/src/api/models.ts +9 -0
  107. package/src/api/useGraphQuery/index.ts +117 -20
  108. package/src/api/useGraphQuery/models.ts +1 -0
  109. package/src/api/useMutationQuery/index.ts +1 -1
  110. package/src/components/DataTable/DataTable.styles.ts +1 -1
  111. package/src/components/DataTable/EmptyState/DataTableEmptyState.styles.ts +2 -1
  112. package/src/components/DataTable/EmptyState/DataTableEmptyState.tsx +4 -1
  113. package/src/components/DataTable/Item/DataTableItem.styles.ts +28 -2
  114. package/src/components/DataTable/Item/DataTableItem.tsx +19 -4
  115. package/src/components/Form/FormActions.tsx +21 -12
  116. package/src/components/Layout/AreaIndicator.tsx +1 -1
  117. package/src/components/Layout/PageLayout/PageLayout.styles.ts +2 -7
  118. package/src/components/Layout/PageLayout/PageLayoutBody.tsx +0 -1
  119. package/src/components/Zoom/Zoom.tsx +9 -2
  120. package/src/components/Zoom/ZoomContent.tsx +143 -136
  121. package/src/components/Zoom/models.ts +18 -15
  122. package/src/components/Zoom/useMinimap.ts +5 -8
  123. package/src/components/Zoom/useZoom.ts +3 -3
  124. package/src/index.ts +2 -0
  125. package/src/utils/index.ts +1 -0
  126. package/src/utils/useLocale/index.ts +9 -0
  127. package/src/utils/useLocale/useLocale.cypress.spec.tsx +38 -0
  128. package/src/utils/useLocaleDateTimeFormat/index.ts +4 -2
  129. package/src/utils/usePluralizedTranslation.ts +2 -3
  130. package/src/Graph/common/timeSeries/index.test.ts-E +0 -622
  131. package/src/components/CrudPage/Actions/Actions.styles.ts +0 -16
  132. package/src/components/CrudPage/Actions/Actions.tsx +0 -24
  133. package/src/components/CrudPage/Actions/AddButton.tsx +0 -23
  134. package/src/components/CrudPage/Actions/Filters.tsx +0 -25
  135. package/src/components/CrudPage/Actions/Search.tsx +0 -31
  136. package/src/components/CrudPage/Actions/useSearch.tsx +0 -24
  137. package/src/components/CrudPage/Columns/Actions.tsx +0 -88
  138. package/src/components/CrudPage/CrudPage.cypress.spec.tsx +0 -559
  139. package/src/components/CrudPage/CrudPage.stories.tsx +0 -278
  140. package/src/components/CrudPage/CrudPageRoot.tsx +0 -142
  141. package/src/components/CrudPage/DeleteModal.tsx +0 -77
  142. package/src/components/CrudPage/Form/AddModal.tsx +0 -35
  143. package/src/components/CrudPage/Form/Buttons.tsx +0 -98
  144. package/src/components/CrudPage/Form/UpdateModal.tsx +0 -60
  145. package/src/components/CrudPage/Listing.tsx +0 -63
  146. package/src/components/CrudPage/atoms.ts +0 -30
  147. package/src/components/CrudPage/hooks/useDeleteItem.ts +0 -53
  148. package/src/components/CrudPage/hooks/useGetItem.ts +0 -36
  149. package/src/components/CrudPage/hooks/useGetItems.ts +0 -67
  150. package/src/components/CrudPage/hooks/useListingQueryKey.ts +0 -31
  151. package/src/components/CrudPage/index.tsx +0 -7
  152. package/src/components/CrudPage/models.ts +0 -118
  153. package/src/components/CrudPage/utils.ts +0 -4
@@ -55,21 +55,43 @@ describe('Bar stack', () => {
55
55
  it('adjusts size based on the provided width and height', () => {
56
56
  initialize({ displayLegend: false, height: '300px', width: '300px' });
57
57
 
58
- cy.findByTestId('barStack').should('have.css', 'height', '270px');
58
+ cy.get('.visx-bar-rounded')
59
+ .eq(0)
60
+ .should(
61
+ 'have.attr',
62
+ 'd',
63
+ 'M8,95.18828451882847 h193 h8v8 v138.81171548117152 a8,8 0 0 1 -8,8 h-193 a8,8 0 0 1 -8,-8 v-138.81171548117152 v-8h8z'
64
+ );
59
65
 
60
66
  cy.makeSnapshot();
61
67
  });
62
68
 
63
69
  it('renders as a horizontal bar when variant is set to "horizontal"', () => {
64
70
  initialize({ variant: 'horizontal' });
65
- cy.get('[data-variant="horizontal"]').should('exist');
71
+
72
+ cy.get('.visx-bar-rounded')
73
+ .eq(0)
74
+ .should(
75
+ 'have.attr',
76
+ 'd',
77
+ 'M8,0 h231.69874476987445 h8v8 v295 v8h-8 h-231.69874476987445 a8,8 0 0 1 -8,-8 v-295 a8,8 0 0 1 8,-8z'
78
+ );
79
+ cy.get('[data-is-vertical="false"]').should('be.visible');
66
80
 
67
81
  cy.makeSnapshot();
68
82
  });
69
83
 
70
84
  it('renders as a vertical bar when variant is set to "vertical"', () => {
71
85
  initialize({ variant: 'vertical' });
72
- cy.get('[data-variant="vertical"]').should('exist');
86
+
87
+ cy.get('.visx-bar-rounded')
88
+ .eq(0)
89
+ .should(
90
+ 'have.attr',
91
+ 'd',
92
+ 'M8,133.26359832635984 h293 h8v8 v200.73640167364016 a8,8 0 0 1 -8,8 h-293 a8,8 0 0 1 -8,-8 v-200.73640167364016 v-8h8z'
93
+ );
94
+ cy.get('[data-is-vertical="true"]').should('be.visible');
73
95
 
74
96
  cy.makeSnapshot();
75
97
  });
@@ -77,6 +99,14 @@ describe('Bar stack', () => {
77
99
  it('displays tooltip with correct information on hover', () => {
78
100
  initialize({ TooltipContent });
79
101
 
102
+ cy.get('.visx-bar-rounded')
103
+ .eq(0)
104
+ .should(
105
+ 'have.attr',
106
+ 'd',
107
+ 'M8,133.26359832635984 h293 h8v8 v200.73640167364016 a8,8 0 0 1 -8,8 h-293 a8,8 0 0 1 -8,-8 v-200.73640167364016 v-8h8z'
108
+ );
109
+
80
110
  defaultData.forEach(({ label, value }) => {
81
111
  cy.findByTestId(label).trigger('mouseover', { force: true });
82
112
 
@@ -90,6 +120,14 @@ describe('Bar stack', () => {
90
120
 
91
121
  it('conditionally displays values on rects based on displayValues prop', () => {
92
122
  initialize({ displayValues: true });
123
+
124
+ cy.get('.visx-bar-rounded')
125
+ .eq(0)
126
+ .should(
127
+ 'have.attr',
128
+ 'd',
129
+ 'M8,133.26359832635984 h293 h8v8 v200.73640167364016 a8,8 0 0 1 -8,8 h-293 a8,8 0 0 1 -8,-8 v-200.73640167364016 v-8h8z'
130
+ );
93
131
  defaultData.forEach(({ value }, index) => {
94
132
  cy.findAllByTestId('value')
95
133
  .eq(index)
@@ -106,6 +144,14 @@ describe('Bar stack', () => {
106
144
 
107
145
  it('displays values on rects in percentage unit when displayValues is set to true and unit to percentage', () => {
108
146
  initialize({ displayValues: true, unit: 'percentage' });
147
+
148
+ cy.get('.visx-bar-rounded')
149
+ .eq(0)
150
+ .should(
151
+ 'have.attr',
152
+ 'd',
153
+ 'M8,133.26359832635984 h293 h8v8 v200.73640167364016 a8,8 0 0 1 -8,8 h-293 a8,8 0 0 1 -8,-8 v-200.73640167364016 v-8h8z'
154
+ );
109
155
  defaultData.forEach(({ value }, index) => {
110
156
  cy.findAllByTestId('value')
111
157
  .eq(index)
@@ -118,21 +164,53 @@ describe('Bar stack', () => {
118
164
  });
119
165
 
120
166
  it('displays Legend component based on displayLegend prop', () => {
121
- initialize({ displayLegend: true });
122
- cy.findByTestId('Legend').should('be.visible');
123
-
124
167
  initialize({ displayLegend: false });
168
+
169
+ cy.get('.visx-bar-rounded')
170
+ .eq(0)
171
+ .should(
172
+ 'have.attr',
173
+ 'd',
174
+ 'M8,133.26359832635984 h293 h8v8 v200.73640167364016 a8,8 0 0 1 -8,8 h-293 a8,8 0 0 1 -8,-8 v-200.73640167364016 v-8h8z'
175
+ );
176
+ cy.findByTestId('Ok').should('be.visible');
125
177
  cy.findByTestId('Legend').should('not.exist');
126
178
 
127
179
  cy.makeSnapshot();
128
180
  });
129
181
 
130
- it('displays the title when the title is giving', () => {
182
+ it('displays the title when the title is given', () => {
131
183
  initialize({ title: 'host' });
184
+
185
+ cy.get('.visx-bar-rounded')
186
+ .eq(0)
187
+ .should(
188
+ 'have.attr',
189
+ 'd',
190
+ 'M8,133.26359832635984 h293 h8v8 v200.73640167364016 a8,8 0 0 1 -8,8 h-293 a8,8 0 0 1 -8,-8 v-200.73640167364016 v-8h8z'
191
+ );
192
+ cy.findByTestId('Ok').should('be.visible');
132
193
  cy.findByTestId('Title').should('be.visible');
133
194
 
134
- initialize({});
135
- cy.findByTestId('Title').should('not.exist');
195
+ cy.makeSnapshot();
196
+ });
197
+
198
+ it('displays the bars within a small display', () => {
199
+ initialize({
200
+ width: '120px',
201
+ height: '89px',
202
+ title: 'host',
203
+ displayLegend: true
204
+ });
205
+
206
+ cy.get('.visx-bar-rounded')
207
+ .eq(0)
208
+ .should(
209
+ 'have.attr',
210
+ 'd',
211
+ 'M8,20.941422594142264 h94 h8v8 v18.058577405857733 a8,8 0 0 1 -8,8 h-94 a8,8 0 0 1 -8,-8 v-18.058577405857733 v-8h8z'
212
+ );
213
+ cy.findByTestId('Ok').should('be.visible');
136
214
 
137
215
  cy.makeSnapshot();
138
216
  });
@@ -1,6 +1,6 @@
1
1
  import { Meta, StoryObj } from '@storybook/react';
2
2
 
3
- import ResponsiveBarStack from './ResponsiveBarStack';
3
+ import ResponsiveBarStack from './BarStack';
4
4
  import { BarType } from './models';
5
5
 
6
6
  import { BarStack } from '.';
@@ -47,11 +47,19 @@ const TooltipContent = ({ label, color, value }: BarType): JSX.Element => {
47
47
  };
48
48
 
49
49
  const Template = (args): JSX.Element => {
50
- return <ResponsiveBarStack height={300} width={500} {...args} />;
50
+ return (
51
+ <div style={{ width: '400px', height: '400px' }}>
52
+ <ResponsiveBarStack {...args} />
53
+ </div>
54
+ );
51
55
  };
52
56
 
53
57
  const SmallTemplate = (args): JSX.Element => {
54
- return <ResponsiveBarStack height={120} width={120} {...args} />;
58
+ return (
59
+ <div style={{ width: '150px', height: '90px' }}>
60
+ <ResponsiveBarStack {...args} />
61
+ </div>
62
+ );
55
63
  };
56
64
 
57
65
  export const Vertical: Story = {
@@ -111,7 +119,8 @@ export const Horizontal: Story = {
111
119
  data,
112
120
  displayValues: true,
113
121
  title: 'hosts',
114
- variant: 'horizontal'
122
+ variant: 'horizontal',
123
+ legendDirection: 'row'
115
124
  },
116
125
  render: Template
117
126
  };
@@ -1,41 +1,65 @@
1
1
  import { makeStyles } from 'tss-react/mui';
2
+ import { legendMaxHeight, legendMaxWidth } from './constants';
2
3
 
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
- },
4
+ export const useStyles = makeStyles()({
10
5
  container: {
11
- alignItems: 'center',
12
- display: 'flex',
13
- gap: theme.spacing(1.5),
14
- justifyContent: 'center'
6
+ display: 'grid',
7
+ '&[data-has-title="false"]': {
8
+ gridTemplateRows: 'auto'
9
+ },
10
+ '&[data-title-variant="xs"]': {
11
+ gridTemplateRows: '40px auto'
12
+ },
13
+ '&[data-title-variant="sm"]': {
14
+ gridTemplateRows: '20px auto'
15
+ },
16
+ '&[data-title-variant="md"]': {
17
+ gridTemplateRows: '36px auto',
18
+ textOverflow: 'clip',
19
+ overflow: 'hidden'
20
+ },
21
+ height: '100%'
15
22
  },
16
- smallTitle: {
17
- fontSize: theme.typography.body1.fontSize
18
- },
19
- svgContainer: {
20
- alignItems: 'center',
21
- backgroundColor: theme.palette.background.panelGroups,
22
- borderRadius: theme.spacing(1.25),
23
- display: 'flex',
24
- justifyContent: 'center',
25
- padding: theme.spacing(1)
26
- },
27
- svgWrapper: {
28
- alignItems: 'center',
29
- display: 'flex',
30
- flexDirection: 'column',
31
- gap: theme.spacing(1),
32
- justifyContent: 'center'
23
+ clippedTitle: {
24
+ textOverflow: 'clip',
25
+ overflow: 'hidden'
26
+ }
27
+ });
28
+
29
+ export const useGraphAndLegendStyles = makeStyles()((theme) => ({
30
+ graphAndLegend: {
31
+ height: '100%',
32
+ display: 'grid',
33
+ '&[data-is-vertical="true"][data-display-legend="false"]': {
34
+ gridTemplateColumns: '1fr'
35
+ },
36
+ '&[data-is-vertical="true"][data-display-legend="true"]': {
37
+ gridTemplateColumns: `1fr ${legendMaxWidth}px`,
38
+ gap: theme.spacing(0.5)
39
+ },
40
+ '&[data-display-legend="false"][data-is-vertical="false"]': {
41
+ gridTemplateRows: '1fr'
42
+ },
43
+ '&[data-display-legend="true"][data-is-vertical="false"]': {
44
+ gridTemplateRows: `1fr ${legendMaxHeight}px`,
45
+ gap: theme.spacing(0.5)
46
+ }
33
47
  },
34
- title: {
35
- fontSize: theme.typography.h6.fontSize,
36
- fontWeight: theme.typography.fontWeightMedium,
37
- margin: 0,
48
+ legend: {
49
+ '&[data-is-vertical="false"]': {
50
+ overflowY: 'auto'
51
+ },
52
+ '&[data-is-vertical="true"]': {
53
+ alignSelf: 'center'
54
+ }
55
+ }
56
+ }));
57
+
58
+ export const useGraphStyles = makeStyles()((theme) => ({
59
+ tooltip: {
60
+ backgroundColor: theme.palette.background.paper,
61
+ color: theme.palette.text.primary,
38
62
  padding: 0,
39
- textAlign: 'center'
63
+ boxShadow: theme.shadows[3]
40
64
  }
41
65
  }));
@@ -0,0 +1,173 @@
1
+ import {
2
+ BarRounded,
3
+ BarStackHorizontal,
4
+ BarStack as BarStackVertical
5
+ } from '@visx/shape';
6
+ import { Text } from '@visx/text';
7
+ import { equals, props } from 'ramda';
8
+ import { memo, useMemo } from 'react';
9
+ import { Tooltip } from '../../components';
10
+ import { getValueByUnit } from '../common/utils';
11
+ import { useGraphStyles } from './BarStack.styles';
12
+ import { BarStackProps } from './models';
13
+ import { useGraphAndLegend } from './useGraphAndLegend';
14
+
15
+ interface Props
16
+ extends Pick<
17
+ BarStackProps,
18
+ | 'data'
19
+ | 'displayValues'
20
+ | 'onSingleBarClick'
21
+ | 'unit'
22
+ | 'TooltipContent'
23
+ | 'tooltipProps'
24
+ > {
25
+ width: number;
26
+ height: number;
27
+ isVerticalBar: boolean;
28
+ colorScale;
29
+ total: number;
30
+ }
31
+
32
+ const Graph = ({
33
+ width,
34
+ height,
35
+ isVerticalBar,
36
+ colorScale,
37
+ data,
38
+ total,
39
+ unit,
40
+ displayValues,
41
+ onSingleBarClick,
42
+ tooltipProps,
43
+ TooltipContent
44
+ }: Props): JSX.Element => {
45
+ const { classes } = useGraphStyles();
46
+
47
+ const BarStackComponent = useMemo(
48
+ () => (isVerticalBar ? BarStackVertical : BarStackHorizontal),
49
+ [isVerticalBar]
50
+ );
51
+
52
+ const normalizedHeight = useMemo(() => height - 10, [height]);
53
+
54
+ const { barStackData, xScale, yScale, keys } = useGraphAndLegend({
55
+ data,
56
+ width,
57
+ height: normalizedHeight,
58
+ isVerticalBar,
59
+ total
60
+ });
61
+
62
+ return (
63
+ <svg width="100%" height={normalizedHeight}>
64
+ <BarStackComponent
65
+ color={colorScale}
66
+ data={[barStackData]}
67
+ keys={keys}
68
+ {...(isVerticalBar ? { x: () => undefined } : { y: () => undefined })}
69
+ xScale={xScale}
70
+ yScale={yScale}
71
+ >
72
+ {(barStacks) =>
73
+ barStacks.map((barStack, index) =>
74
+ barStack.bars.map((bar) => {
75
+ const isFirstBar = equals(index, 0);
76
+ const isLastBar = equals(index, barStacks.length - 1);
77
+ const fitsInBar = isVerticalBar
78
+ ? bar.height >= 18
79
+ : (equals(unit, 'number') && bar.width > 15) ||
80
+ (equals(unit, 'percentage') && bar.width > 35);
81
+
82
+ const textX = bar.x + bar.width / 2;
83
+ const textY = bar.y + bar.height / 2;
84
+
85
+ const click = onSingleBarClick
86
+ ? (e: MouseEvent): void => {
87
+ if (!equals(e.button, 0)) {
88
+ return;
89
+ }
90
+ onSingleBarClick(bar);
91
+ }
92
+ : undefined;
93
+
94
+ return (
95
+ <Tooltip
96
+ followCursor={false}
97
+ classes={classes}
98
+ key={`bar-stack-${barStack.index}-${bar.index}`}
99
+ label={
100
+ TooltipContent && (
101
+ <TooltipContent
102
+ color={bar.color}
103
+ label={bar.key}
104
+ total={total}
105
+ value={barStack.bars[0].bar.data[barStack.key]}
106
+ {...tooltipProps}
107
+ />
108
+ )
109
+ }
110
+ position={isVerticalBar ? 'right' : 'bottom'}
111
+ >
112
+ <g data-testid={bar.key} key={bar.key}>
113
+ <BarRounded
114
+ radius={8}
115
+ cursor={onSingleBarClick ? 'pointer' : 'default'}
116
+ fill={bar.color}
117
+ height={bar.height}
118
+ key={`bar-stack-${barStack.index}-${bar.index}`}
119
+ width={isVerticalBar ? bar.width - 10 : bar.width}
120
+ x={bar.x}
121
+ y={bar.y}
122
+ left={!isVerticalBar && isFirstBar}
123
+ right={!isVerticalBar && isLastBar}
124
+ bottom={isVerticalBar && isFirstBar}
125
+ top={isVerticalBar && isLastBar}
126
+ onMouseDown={click}
127
+ />
128
+ {displayValues && fitsInBar && (
129
+ <Text
130
+ cursor={onSingleBarClick ? 'pointer' : 'default'}
131
+ data-testid="value"
132
+ fill="#000"
133
+ fontSize={12}
134
+ fontWeight={600}
135
+ textAnchor="middle"
136
+ verticalAnchor="middle"
137
+ x={textX}
138
+ y={textY}
139
+ onMouseUp={click}
140
+ >
141
+ {getValueByUnit({
142
+ total,
143
+ unit: unit || 'number',
144
+ value: barStack.bars[0].bar.data[barStack.key]
145
+ })}
146
+ </Text>
147
+ )}
148
+ </g>
149
+ </Tooltip>
150
+ );
151
+ })
152
+ )
153
+ }
154
+ </BarStackComponent>
155
+ </svg>
156
+ );
157
+ };
158
+
159
+ const propsToMemoize = [
160
+ 'width',
161
+ 'height',
162
+ 'isVerticalBar',
163
+ 'colorScale',
164
+ 'data',
165
+ 'total',
166
+ 'unit',
167
+ 'displayValues',
168
+ 'tooltipProps'
169
+ ];
170
+
171
+ export default memo(Graph, (prevProps, nextProps) =>
172
+ equals(props(propsToMemoize, prevProps), props(propsToMemoize, nextProps))
173
+ );
@@ -0,0 +1,117 @@
1
+ import { equals, props } from 'ramda';
2
+ import { memo, useMemo } from 'react';
3
+ import { useGraphAndLegendStyles } from './BarStack.styles';
4
+ import Graph from './Graph';
5
+ import { gap, legendMaxHeight, legendMaxWidth } from './constants';
6
+ import { BarStackProps } from './models';
7
+
8
+ interface Props
9
+ extends Pick<
10
+ BarStackProps,
11
+ | 'data'
12
+ | 'displayValues'
13
+ | 'onSingleBarClick'
14
+ | 'unit'
15
+ | 'TooltipContent'
16
+ | 'tooltipProps'
17
+ > {
18
+ isVerticalBar: boolean;
19
+ legend: JSX.Element;
20
+ displayLegend: boolean;
21
+ height: number;
22
+ width: number;
23
+ colorScale;
24
+ total: number;
25
+ }
26
+
27
+ const GraphAndLegend = ({
28
+ isVerticalBar,
29
+ legend,
30
+ displayLegend,
31
+ height,
32
+ width,
33
+ colorScale,
34
+ total,
35
+ unit,
36
+ data,
37
+ displayValues,
38
+ onSingleBarClick,
39
+ tooltipProps,
40
+ TooltipContent
41
+ }: Props): JSX.Element => {
42
+ const { classes } = useGraphAndLegendStyles();
43
+
44
+ const isSmall = useMemo(
45
+ () =>
46
+ (!isVerticalBar && Math.floor(height) <= 80) ||
47
+ (isVerticalBar && Math.floor(width) <= 150),
48
+ [isVerticalBar, height, width]
49
+ );
50
+
51
+ const mustDisplayLegend = useMemo(
52
+ () => displayLegend && !isSmall,
53
+ [isSmall, displayLegend]
54
+ );
55
+
56
+ const graphWidth = useMemo(
57
+ () =>
58
+ isVerticalBar ? width - (isSmall ? 0 : legendMaxWidth - gap) : width,
59
+ [width, isVerticalBar, isSmall]
60
+ );
61
+
62
+ const graphHeight = useMemo(
63
+ () =>
64
+ isVerticalBar ? height : height - (isSmall ? 0 : legendMaxHeight - gap),
65
+ [height, isVerticalBar, isSmall]
66
+ );
67
+
68
+ return (
69
+ <div
70
+ className={classes.graphAndLegend}
71
+ data-display-legend={mustDisplayLegend}
72
+ data-is-vertical={isVerticalBar}
73
+ style={{ height }}
74
+ >
75
+ <Graph
76
+ isVerticalBar={isVerticalBar}
77
+ data={data}
78
+ width={graphWidth}
79
+ height={graphHeight}
80
+ colorScale={colorScale}
81
+ unit={unit}
82
+ total={total}
83
+ displayValues={displayValues}
84
+ onSingleBarClick={onSingleBarClick}
85
+ tooltipProps={tooltipProps}
86
+ TooltipContent={TooltipContent}
87
+ />
88
+ {mustDisplayLegend && (
89
+ <div
90
+ className={classes.legend}
91
+ data-is-vertical={isVerticalBar}
92
+ data-testid="Legend"
93
+ >
94
+ {legend}
95
+ </div>
96
+ )}
97
+ </div>
98
+ );
99
+ };
100
+
101
+ const propsToMemoize = [
102
+ 'width',
103
+ 'height',
104
+ 'isVerticalBar',
105
+ 'colorScale',
106
+ 'data',
107
+ 'total',
108
+ 'unit',
109
+ 'displayValues',
110
+ 'tooltipProps',
111
+ 'displayLegend',
112
+ 'legend'
113
+ ];
114
+
115
+ export default memo(GraphAndLegend, (prevProps, nextProps) =>
116
+ equals(props(propsToMemoize, prevProps), props(propsToMemoize, nextProps))
117
+ );