@centreon/ui 25.10.20 → 25.10.22
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 +1 -1
- package/src/Form/Inputs/Grid.tsx +3 -2
- package/src/Form/Section/navigateToSection.ts +6 -6
- package/src/Graph/BarChart/BarChart.cypress.spec.tsx +19 -0
- package/src/Graph/BarChart/BarChart.stories.tsx +51 -1
- package/src/Graph/BarChart/BarChart.tsx +1 -1
- package/src/Graph/BarChart/BarGroup.tsx +22 -32
- package/src/Graph/BarChart/MemoizedGroup.tsx +8 -11
- package/src/Graph/BarChart/ResponsiveBarChart.tsx +2 -1
- package/src/Graph/Chart/BasicComponents/Lines/StackedLines/useStackedLines.ts +18 -45
- package/src/Graph/Chart/BasicComponents/Lines/index.tsx +32 -26
- package/src/Graph/Chart/Chart.cypress.spec.tsx +24 -5
- package/src/Graph/Chart/Chart.stories.tsx +43 -1
- package/src/Graph/Chart/Chart.tsx +3 -2
- package/src/Graph/Chart/Legend/index.tsx +26 -2
- package/src/Graph/Chart/models.ts +6 -1
- package/src/Graph/Chart/useChartData.ts +1 -1
- package/src/Graph/common/BaseChart/BaseChart.tsx +6 -1
- package/src/Graph/common/timeSeries/index.test.ts +20 -0
- package/src/Graph/common/timeSeries/index.ts +121 -11
- package/src/Graph/common/timeSeries/models.ts +4 -2
- package/src/Graph/common/utils.ts +10 -4
- package/src/Graph/mockedData/dataWithMissingPoint.json +74 -0
- package/src/Graph/mockedData/pingServiceWithStackedKeys.json +205 -0
- package/src/InputField/Select/index.tsx +1 -2
- package/src/Module/index.tsx +8 -2
- package/src/ThemeProvider/index.tsx +30 -21
- package/src/ThemeProvider/tailwindcss.css +10 -10
- package/src/components/Layout/PageLayout/PageLayout.tsx +9 -3
- package/src/components/Layout/PageLayout/PageLayoutActions.tsx +5 -3
- package/src/components/Layout/PageLayout/PageLayoutBody.tsx +5 -3
- package/src/components/Layout/PageLayout/PageLayoutHeader.tsx +5 -3
- package/src/components/Modal/Modal.styles.ts +1 -2
- package/src/components/Modal/ModalHeader.tsx +5 -1
package/package.json
CHANGED
package/src/Form/Inputs/Grid.tsx
CHANGED
|
@@ -21,8 +21,9 @@ const Grid = ({
|
|
|
21
21
|
<div
|
|
22
22
|
className={`${className} grid gap-3`}
|
|
23
23
|
style={{
|
|
24
|
-
gridTemplateColumns:
|
|
25
|
-
|
|
24
|
+
gridTemplateColumns: className
|
|
25
|
+
? grid?.gridTemplateColumns || undefined
|
|
26
|
+
: grid?.gridTemplateColumns ||
|
|
26
27
|
`repeat(${grid?.columns.length || 1}, 1fr)`,
|
|
27
28
|
alignItems: grid?.alignItems || 'flex-start'
|
|
28
29
|
}}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
export const useNavigateToSection = () => {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
return (sectionName: string) => {
|
|
3
|
+
const section = document.querySelector(
|
|
4
|
+
`[data-section-group-form-id="${sectionName}"]`
|
|
5
|
+
);
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
section?.scrollIntoView({ behavior: 'smooth' });
|
|
8
|
+
};
|
|
9
9
|
};
|
|
@@ -4,10 +4,12 @@ import { useAtomValue } from 'jotai';
|
|
|
4
4
|
|
|
5
5
|
import { userAtom } from '@centreon/ui-context';
|
|
6
6
|
|
|
7
|
+
import dataMissingPoint from '../mockedData/dataWithMissingPoint.json';
|
|
7
8
|
import dataLastWeek from '../mockedData/lastWeek.json';
|
|
8
9
|
import dataPingService from '../mockedData/pingService.json';
|
|
9
10
|
import dataPingServiceMixedStacked from '../mockedData/pingServiceMixedStacked.json';
|
|
10
11
|
import dataPingServiceStacked from '../mockedData/pingServiceStacked.json';
|
|
12
|
+
import dataPingServiceLinesStackKeys from '../mockedData/pingServiceWithStackedKeys.json';
|
|
11
13
|
|
|
12
14
|
import BarChart, { BarChartProps } from './BarChart';
|
|
13
15
|
|
|
@@ -312,4 +314,21 @@ describe('Bar chart', () => {
|
|
|
312
314
|
cy.contains('1 s').should('be.visible');
|
|
313
315
|
cy.contains('1%').should('be.visible');
|
|
314
316
|
});
|
|
317
|
+
|
|
318
|
+
it('displays the stacked bar chart correctly when a point is missing compare to the time serie', () => {
|
|
319
|
+
initialize({ data: dataMissingPoint });
|
|
320
|
+
|
|
321
|
+
cy.findByTestId('stacked-bar-2-0-139').should('be.visible');
|
|
322
|
+
|
|
323
|
+
cy.makeSnapshot();
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
it('displays the stacked bar chart with bars stacked together', () => {
|
|
327
|
+
initialize({ data: dataPingServiceLinesStackKeys });
|
|
328
|
+
|
|
329
|
+
cy.findByTestId('stacked-bar-3-0-0.05336').should('be.visible');
|
|
330
|
+
cy.findByTestId('stacked-bar-4-0-0.06684').should('be.visible');
|
|
331
|
+
|
|
332
|
+
cy.makeSnapshot();
|
|
333
|
+
});
|
|
315
334
|
});
|
|
@@ -5,7 +5,10 @@ import { LineChartData } from '../common/models';
|
|
|
5
5
|
import dataPingService from '../mockedData/pingService.json';
|
|
6
6
|
import dataPingServiceMixedStacked from '../mockedData/pingServiceMixedStacked.json';
|
|
7
7
|
import dataPingServiceStacked from '../mockedData/pingServiceStacked.json';
|
|
8
|
+
import dataPingServiceStackeKey from '../mockedData/pingServiceWithStackedKeys.json';
|
|
8
9
|
|
|
10
|
+
import { ClickAwayListener } from '@mui/material';
|
|
11
|
+
import { useState } from 'react';
|
|
9
12
|
import BarChart from './BarChart';
|
|
10
13
|
|
|
11
14
|
const meta: Meta<typeof BarChart> = {
|
|
@@ -259,4 +262,51 @@ export const mixedStackedMinMax: Story = {
|
|
|
259
262
|
max: 20
|
|
260
263
|
},
|
|
261
264
|
render: Template
|
|
262
|
-
};
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
const LegendSecondaryClick = (args) => {
|
|
268
|
+
const [position, setPosition] = useState<Array<[number, number]> | null>(
|
|
269
|
+
null
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
return (
|
|
273
|
+
<>
|
|
274
|
+
<Template
|
|
275
|
+
{...args}
|
|
276
|
+
legend={{
|
|
277
|
+
secondaryClick: ({ position }) => setPosition(position)
|
|
278
|
+
}}
|
|
279
|
+
/>
|
|
280
|
+
{position && (
|
|
281
|
+
<ClickAwayListener onClickAway={() => setPosition(null)}>
|
|
282
|
+
<div
|
|
283
|
+
className="absolute py-1 px-2 rounded-sm bg-background-widget shadow-md"
|
|
284
|
+
style={{ left: position?.[0], top: position?.[1] }}
|
|
285
|
+
open={Boolean(position)}
|
|
286
|
+
onClose={() => setPosition(null)}
|
|
287
|
+
>
|
|
288
|
+
menu
|
|
289
|
+
</div>
|
|
290
|
+
</ClickAwayListener>
|
|
291
|
+
)}
|
|
292
|
+
</>
|
|
293
|
+
);
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
export const withLegendSecondaryClick: Story = {
|
|
297
|
+
args: defaultArgs,
|
|
298
|
+
render: (args) => (
|
|
299
|
+
<LegendSecondaryClick
|
|
300
|
+
{...args}
|
|
301
|
+
data={dataPingService as unknown as LineChartData}
|
|
302
|
+
/>
|
|
303
|
+
)
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
export const stackKey: Story = {
|
|
307
|
+
args: {
|
|
308
|
+
...defaultArgs,
|
|
309
|
+
data: dataPingServiceStackeKey
|
|
310
|
+
},
|
|
311
|
+
render: Template
|
|
312
|
+
};
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { scaleBand, scaleOrdinal } from '@visx/scale';
|
|
2
2
|
import { BarGroupHorizontal, BarGroup as VisxBarGroup } from '@visx/shape';
|
|
3
3
|
import { ScaleLinear } from 'd3-scale';
|
|
4
|
-
import { difference, equals, keys, omit, pick
|
|
4
|
+
import { difference, equals, keys, omit, pick } from 'ramda';
|
|
5
5
|
import { memo, useMemo } from 'react';
|
|
6
6
|
|
|
7
7
|
import { useDeepMemo } from '../../utils';
|
|
8
8
|
import {
|
|
9
9
|
getSortedStackedLines,
|
|
10
|
+
getStackedLinesTimeSeriesPerStackAndUnit,
|
|
10
11
|
getTime,
|
|
11
12
|
getTimeSeriesForLines,
|
|
12
13
|
getUnits
|
|
@@ -54,41 +55,18 @@ const BarGroup = ({
|
|
|
54
55
|
);
|
|
55
56
|
|
|
56
57
|
const stackedLines = getSortedStackedLines(lines);
|
|
57
|
-
const stackedUnits = uniq(pluck('unit', stackedLines));
|
|
58
58
|
const notStackedLines = difference(lines, stackedLines);
|
|
59
|
-
|
|
60
|
-
const stackedKeys = stackedUnits.reduce(
|
|
61
|
-
(acc, unit) => ({
|
|
62
|
-
...acc,
|
|
63
|
-
[`stacked-${unit}`]: null
|
|
64
|
-
}),
|
|
65
|
-
{}
|
|
66
|
-
);
|
|
67
|
-
const stackedLinesTimeSeriesPerUnit = stackedUnits.reduce(
|
|
68
|
-
(acc, stackedUnit) => {
|
|
69
|
-
const relatedLines = stackedLines.filter(({ unit }) =>
|
|
70
|
-
equals(unit, stackedUnit)
|
|
71
|
-
);
|
|
72
|
-
|
|
73
|
-
return {
|
|
74
|
-
...acc,
|
|
75
|
-
[stackedUnit]: {
|
|
76
|
-
lines: relatedLines,
|
|
77
|
-
timeSeries: getTimeSeriesForLines({
|
|
78
|
-
lines: relatedLines,
|
|
79
|
-
timeSeries
|
|
80
|
-
})
|
|
81
|
-
}
|
|
82
|
-
};
|
|
83
|
-
},
|
|
84
|
-
{}
|
|
85
|
-
);
|
|
86
|
-
|
|
87
59
|
const notStackedTimeSeries = getTimeSeriesForLines({
|
|
88
60
|
lines: notStackedLines,
|
|
89
61
|
timeSeries
|
|
90
62
|
});
|
|
91
63
|
|
|
64
|
+
const { stackedLinesTimeSeriesPerStackKeyAndUnit, stackedKeys } = useMemo(
|
|
65
|
+
() =>
|
|
66
|
+
getStackedLinesTimeSeriesPerStackAndUnit({ stackedLines, timeSeries }),
|
|
67
|
+
[stackedLines, timeSeries]
|
|
68
|
+
);
|
|
69
|
+
|
|
92
70
|
const normalizedTimeSeries = notStackedTimeSeries.map((timeSerie) => ({
|
|
93
71
|
...timeSerie,
|
|
94
72
|
...stackedKeys
|
|
@@ -98,6 +76,16 @@ const BarGroup = ({
|
|
|
98
76
|
deps: [normalizedTimeSeries],
|
|
99
77
|
variable: keys(omit(['timeTick'], normalizedTimeSeries[0]))
|
|
100
78
|
});
|
|
79
|
+
const sortedLineKeys = lineKeys.sort((lineKeyA: string, lineKeyB: string) => {
|
|
80
|
+
if (lineKeyA.startsWith('stacked-') && !lineKeyB.startsWith('stacked-')) {
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const lineKeysA = lineKeyA.split('-');
|
|
85
|
+
const lineKeysB = lineKeyB.split('-');
|
|
86
|
+
|
|
87
|
+
return lineKeysA[2] === '' && lineKeysB[2] !== '';
|
|
88
|
+
});
|
|
101
89
|
const colors = useDeepMemo({
|
|
102
90
|
deps: [lineKeys, lines],
|
|
103
91
|
variable: lineKeys.map((key) => {
|
|
@@ -154,7 +142,7 @@ const BarGroup = ({
|
|
|
154
142
|
color={colorScale}
|
|
155
143
|
data={normalizedTimeSeries}
|
|
156
144
|
height={size}
|
|
157
|
-
keys={
|
|
145
|
+
keys={sortedLineKeys}
|
|
158
146
|
{...barComponentBaseProps}
|
|
159
147
|
>
|
|
160
148
|
{(barGroups) =>
|
|
@@ -164,7 +152,9 @@ const BarGroup = ({
|
|
|
164
152
|
key={`bar-group-${barGroup.index}-${barGroup.x0}`}
|
|
165
153
|
barGroup={barGroup}
|
|
166
154
|
barStyle={barStyle}
|
|
167
|
-
|
|
155
|
+
stackedLinesTimeSeriesPerStackKeyAndUnit={
|
|
156
|
+
stackedLinesTimeSeriesPerStackKeyAndUnit
|
|
157
|
+
}
|
|
168
158
|
notStackedTimeSeries={notStackedTimeSeries}
|
|
169
159
|
notStackedLines={notStackedLines}
|
|
170
160
|
isTooltipHidden={isTooltipHidden}
|
|
@@ -12,7 +12,7 @@ interface Props {
|
|
|
12
12
|
isTooltipHidden: boolean;
|
|
13
13
|
barStyle: BarStyle;
|
|
14
14
|
yScalesPerUnit: Record<string, ScaleLinear<number, number>>;
|
|
15
|
-
|
|
15
|
+
stackedLinesTimeSeriesPerStackKeyAndUnit: Record<
|
|
16
16
|
string,
|
|
17
17
|
{ lines: Array<Line>; timeSeries: Array<TimeValue> }
|
|
18
18
|
>;
|
|
@@ -25,7 +25,7 @@ interface Props {
|
|
|
25
25
|
|
|
26
26
|
const MemoizedGroup = ({
|
|
27
27
|
barGroup,
|
|
28
|
-
|
|
28
|
+
stackedLinesTimeSeriesPerStackKeyAndUnit,
|
|
29
29
|
notStackedLines,
|
|
30
30
|
notStackedTimeSeries,
|
|
31
31
|
isHorizontal,
|
|
@@ -38,9 +38,7 @@ const MemoizedGroup = ({
|
|
|
38
38
|
const hasEmptyValues = barGroup.bars.every(({ key, value }) => {
|
|
39
39
|
if (key.startsWith('stacked-')) {
|
|
40
40
|
const timeValueBar =
|
|
41
|
-
|
|
42
|
-
barIndex
|
|
43
|
-
];
|
|
41
|
+
stackedLinesTimeSeriesPerStackKeyAndUnit[key].timeSeries[barIndex];
|
|
44
42
|
|
|
45
43
|
return Object.values(omit(['timeTick'], timeValueBar)).every(
|
|
46
44
|
(value) => !value
|
|
@@ -59,13 +57,12 @@ const MemoizedGroup = ({
|
|
|
59
57
|
{barGroup.bars.map((bar) => {
|
|
60
58
|
const isStackedBar = bar.key.startsWith('stacked-');
|
|
61
59
|
const linesBar = isStackedBar
|
|
62
|
-
?
|
|
60
|
+
? stackedLinesTimeSeriesPerStackKeyAndUnit[bar.key].lines
|
|
63
61
|
: (notStackedLines.find(({ metric_id }) =>
|
|
64
62
|
equals(metric_id, Number(bar.key))
|
|
65
63
|
) as Line);
|
|
66
64
|
const timeSeriesBar = isStackedBar
|
|
67
|
-
?
|
|
68
|
-
.timeSeries
|
|
65
|
+
? stackedLinesTimeSeriesPerStackKeyAndUnit[bar.key].timeSeries
|
|
69
66
|
: notStackedTimeSeries.map((timeSerie) => ({
|
|
70
67
|
timeTick: timeSerie.timeTick,
|
|
71
68
|
[bar.key]: timeSerie[Number(bar.key)]
|
|
@@ -82,7 +79,7 @@ const MemoizedGroup = ({
|
|
|
82
79
|
isTooltipHidden={isTooltipHidden}
|
|
83
80
|
lines={linesBar as Array<Line>}
|
|
84
81
|
timeSeries={timeSeriesBar}
|
|
85
|
-
yScale={yScalesPerUnit[bar.key.
|
|
82
|
+
yScale={yScalesPerUnit[bar.key.split('-')[1] || undefined]}
|
|
86
83
|
neutralValue={neutralValue}
|
|
87
84
|
/>
|
|
88
85
|
) : (
|
|
@@ -110,8 +107,8 @@ export default memo(
|
|
|
110
107
|
(prevProps, nextProps) =>
|
|
111
108
|
equals(prevProps.barGroup, nextProps.barGroup) &&
|
|
112
109
|
equals(
|
|
113
|
-
prevProps.
|
|
114
|
-
nextProps.
|
|
110
|
+
prevProps.stackedLinesTimeSeriesPerStackKeyAndUnit,
|
|
111
|
+
nextProps.stackedLinesTimeSeriesPerStackKeyAndUnit
|
|
115
112
|
) &&
|
|
116
113
|
equals(prevProps.notStackedLines, nextProps.notStackedLines) &&
|
|
117
114
|
equals(prevProps.notStackedTimeSeries, nextProps.notStackedTimeSeries) &&
|
|
@@ -196,7 +196,8 @@ const ResponsiveBarChart = ({
|
|
|
196
196
|
displayLegend,
|
|
197
197
|
mode: legend?.mode,
|
|
198
198
|
placement: legend?.placement,
|
|
199
|
-
renderExtraComponent: legend?.renderExtraComponent
|
|
199
|
+
renderExtraComponent: legend?.renderExtraComponent,
|
|
200
|
+
secondaryClick: legend?.secondaryClick
|
|
200
201
|
}}
|
|
201
202
|
legendRef={legendRef}
|
|
202
203
|
limitLegend={limitLegend}
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import { equals, pluck, uniq } from 'ramda';
|
|
2
|
-
|
|
3
1
|
import {
|
|
4
2
|
getInvertedStackedLines,
|
|
5
3
|
getNotInvertedStackedLines,
|
|
6
|
-
|
|
4
|
+
getStackedLinesTimeSeriesPerStackAndUnit
|
|
7
5
|
} from '../../../../common/timeSeries';
|
|
8
6
|
import { LinesData } from '../models';
|
|
9
7
|
|
|
@@ -14,53 +12,28 @@ interface StackedLinesState {
|
|
|
14
12
|
|
|
15
13
|
const useStackedLines = ({ lines, timeSeries }): StackedLinesState => {
|
|
16
14
|
const regularStackedLines = getNotInvertedStackedLines(lines);
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
return {
|
|
25
|
-
...acc,
|
|
26
|
-
[stackedUnit]: {
|
|
27
|
-
lines: relatedLines,
|
|
28
|
-
timeSeries: getTimeSeriesForLines({
|
|
29
|
-
lines: relatedLines,
|
|
30
|
-
timeSeries
|
|
31
|
-
})
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
},
|
|
35
|
-
{}
|
|
36
|
-
);
|
|
15
|
+
const {
|
|
16
|
+
stackedLinesTimeSeriesPerStackKeyAndUnit:
|
|
17
|
+
regularStackedLinesTimeSeriesPerStackKeyAndUnit
|
|
18
|
+
} = getStackedLinesTimeSeriesPerStackAndUnit({
|
|
19
|
+
stackedLines: regularStackedLines,
|
|
20
|
+
timeSeries
|
|
21
|
+
});
|
|
37
22
|
|
|
38
23
|
const invertedStackedLines = getInvertedStackedLines(lines);
|
|
39
|
-
const invertedStackedUnits = uniq(pluck('unit', invertedStackedLines));
|
|
40
|
-
const invertedStackedLinesTimeSeriesPerUnit = invertedStackedUnits.reduce(
|
|
41
|
-
(acc, stackedUnit) => {
|
|
42
|
-
const relatedLines = invertedStackedLines.filter(({ unit }) =>
|
|
43
|
-
equals(unit, stackedUnit)
|
|
44
|
-
);
|
|
45
24
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
})
|
|
55
|
-
}
|
|
56
|
-
};
|
|
57
|
-
},
|
|
58
|
-
{}
|
|
59
|
-
);
|
|
25
|
+
const {
|
|
26
|
+
stackedLinesTimeSeriesPerStackKeyAndUnit:
|
|
27
|
+
invertedStackedLinesTimeSeriesPerStackKeyAndUnit
|
|
28
|
+
} = getStackedLinesTimeSeriesPerStackAndUnit({
|
|
29
|
+
stackedLines: invertedStackedLines,
|
|
30
|
+
timeSeries,
|
|
31
|
+
invert: true
|
|
32
|
+
});
|
|
60
33
|
|
|
61
34
|
return {
|
|
62
|
-
invertedStackedLinesData:
|
|
63
|
-
stackedLinesData:
|
|
35
|
+
invertedStackedLinesData: invertedStackedLinesTimeSeriesPerStackKeyAndUnit,
|
|
36
|
+
stackedLinesData: regularStackedLinesTimeSeriesPerStackKeyAndUnit
|
|
64
37
|
};
|
|
65
38
|
};
|
|
66
39
|
|
|
@@ -101,34 +101,40 @@ const Lines = ({
|
|
|
101
101
|
{(areaStackedLines?.display ?? true) && (
|
|
102
102
|
<>
|
|
103
103
|
{Object.entries(stackedLinesData).map(
|
|
104
|
-
([
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
104
|
+
([stackedKey, { lines, timeSeries: stackedTimeSeries }]) => {
|
|
105
|
+
const [, unit] = stackedKey.split('-');
|
|
106
|
+
return (
|
|
107
|
+
<StackedLines
|
|
108
|
+
lineStyle={lineStyle}
|
|
109
|
+
key={`stacked-${unit}`}
|
|
110
|
+
lines={lines}
|
|
111
|
+
timeSeries={stackedTimeSeries}
|
|
112
|
+
yScale={yScalesPerUnit[unit || undefined]}
|
|
113
|
+
{...commonStackedLinesProps}
|
|
114
|
+
/>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
114
117
|
)}
|
|
115
118
|
{Object.entries(invertedStackedLinesData).map(
|
|
116
|
-
([
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
119
|
+
([stackedKey, { lines, timeSeries: stackedTimeSeries }]) => {
|
|
120
|
+
const [, unit] = stackedKey.split('-');
|
|
121
|
+
return (
|
|
122
|
+
<StackedLines
|
|
123
|
+
lineStyle={lineStyle}
|
|
124
|
+
key={`invert-stacked-${unit}`}
|
|
125
|
+
lines={lines}
|
|
126
|
+
timeSeries={stackedTimeSeries}
|
|
127
|
+
yScale={getYScale({
|
|
128
|
+
invert: '1',
|
|
129
|
+
scale,
|
|
130
|
+
scaleLogarithmicBase,
|
|
131
|
+
unit: unit || undefined,
|
|
132
|
+
yScalesPerUnit
|
|
133
|
+
})}
|
|
134
|
+
{...commonStackedLinesProps}
|
|
135
|
+
/>
|
|
136
|
+
);
|
|
137
|
+
}
|
|
132
138
|
)}
|
|
133
139
|
</>
|
|
134
140
|
)}
|
|
@@ -180,7 +180,7 @@ describe('Line chart', () => {
|
|
|
180
180
|
cy.contains('06/18/2023').should('be.visible');
|
|
181
181
|
|
|
182
182
|
cy.contains('0.4 s').should('be.visible');
|
|
183
|
-
|
|
183
|
+
cy.contains('75.64%').should('be.visible');
|
|
184
184
|
|
|
185
185
|
cy.makeSnapshot();
|
|
186
186
|
});
|
|
@@ -532,7 +532,7 @@ describe('Line chart', () => {
|
|
|
532
532
|
|
|
533
533
|
checkGraphWidth();
|
|
534
534
|
cy.contains(':00 AM').should('be.visible');
|
|
535
|
-
cy.get('circle[cx="
|
|
535
|
+
cy.get('circle[cx="248.33333333333334"]').should('be.visible');
|
|
536
536
|
cy.get('circle[cy="251.79089393069725"]').should('be.visible');
|
|
537
537
|
|
|
538
538
|
cy.makeSnapshot();
|
|
@@ -591,7 +591,7 @@ describe('Line chart', () => {
|
|
|
591
591
|
cy.get('path.visx-area-closed')
|
|
592
592
|
.should('have.attr', 'stroke-dasharray')
|
|
593
593
|
.and('equals', '5 4');
|
|
594
|
-
cy.get('circle[cx="33.
|
|
594
|
+
cy.get('circle[cx="33.11111111111111"]').should('be.visible');
|
|
595
595
|
|
|
596
596
|
cy.makeSnapshot();
|
|
597
597
|
});
|
|
@@ -746,9 +746,11 @@ describe('Lines and bars', () => {
|
|
|
746
746
|
|
|
747
747
|
checkGraphWidth();
|
|
748
748
|
|
|
749
|
-
cy.get(
|
|
749
|
+
cy.get(
|
|
750
|
+
'path[d="M7.501377410468319,350.5553648585503 h56.51239669421488 h1v1 v23.44463514144968 a1,1 0 0 1 -1,1 h-56.51239669421488 a1,1 0 0 1 -1,-1 v-23.44463514144968 v-1h1z"]'
|
|
750
751
|
).should('be.visible');
|
|
751
|
-
cy.get(
|
|
752
|
+
cy.get(
|
|
753
|
+
'path[d="M24.05509641873278,201.58170928199803 h23.404958677685954 a17.553719008264462,17.553719008264462 0 0 1 17.553719008264462,17.553719008264462 v113.86621756002336 v17.553719008264462h-17.553719008264462 h-23.404958677685954 h-17.553719008264462v-17.553719008264462 v-113.86621756002336 a17.553719008264462,17.553719008264462 0 0 1 17.553719008264462,-17.553719008264462z"]'
|
|
752
754
|
).should('be.visible');
|
|
753
755
|
|
|
754
756
|
cy.makeSnapshot();
|
|
@@ -814,4 +816,21 @@ describe('Lines and bars', () => {
|
|
|
814
816
|
|
|
815
817
|
cy.makeSnapshot();
|
|
816
818
|
});
|
|
819
|
+
|
|
820
|
+
it('calls the secondary function when a metric is clicked in the legend', () => {
|
|
821
|
+
const secondaryClick = cy.stub().as('secondaryClick');
|
|
822
|
+
initialize({
|
|
823
|
+
data: dataPingServiceLines,
|
|
824
|
+
legend: {
|
|
825
|
+
mode: 'grid',
|
|
826
|
+
placement: 'bottom',
|
|
827
|
+
secondaryClick
|
|
828
|
+
}
|
|
829
|
+
});
|
|
830
|
+
|
|
831
|
+
checkGraphWidth();
|
|
832
|
+
|
|
833
|
+
cy.contains('Packet Loss').rightclick();
|
|
834
|
+
cy.get('@secondaryClick').should('have.been.called');
|
|
835
|
+
});
|
|
817
836
|
});
|
|
@@ -2,7 +2,7 @@ import { useEffect, useState } from 'react';
|
|
|
2
2
|
|
|
3
3
|
import { Meta, StoryObj } from '@storybook/react';
|
|
4
4
|
|
|
5
|
-
import { Button } from '@mui/material';
|
|
5
|
+
import { Button, Menu } from '@mui/material';
|
|
6
6
|
import ButtonGroup from '@mui/material/ButtonGroup';
|
|
7
7
|
import Switch from '@mui/material/Switch';
|
|
8
8
|
import Tooltip from '@mui/material/Tooltip';
|
|
@@ -26,6 +26,7 @@ import dataPingService from '../mockedData/pingService.json';
|
|
|
26
26
|
import dataPingServiceLinesBars from '../mockedData/pingServiceLinesBars.json';
|
|
27
27
|
import dataPingServiceLinesBarsMixed from '../mockedData/pingServiceLinesBarsMixed.json';
|
|
28
28
|
import dataPingServiceLinesBarsStacked from '../mockedData/pingServiceLinesBarsStacked.json';
|
|
29
|
+
import dataPingServiceLinesStackKeys from '../mockedData/pingServiceWithStackedKeys.json';
|
|
29
30
|
import dataZoomPreview from '../mockedData/zoomPreview.json';
|
|
30
31
|
|
|
31
32
|
import { dateTimeFormat } from './common';
|
|
@@ -758,3 +759,44 @@ export const linesAndBarsMinMaxForUnit: Story = {
|
|
|
758
759
|
/>
|
|
759
760
|
)
|
|
760
761
|
};
|
|
762
|
+
|
|
763
|
+
const LegendSecondaryClick = (args) => {
|
|
764
|
+
const [anchor, setAnchor] = useState<EventTarget | null>(null);
|
|
765
|
+
|
|
766
|
+
return (
|
|
767
|
+
<>
|
|
768
|
+
<WrapperChart
|
|
769
|
+
{...args}
|
|
770
|
+
legend={{
|
|
771
|
+
secondaryClick: ({ element }) => setAnchor(element)
|
|
772
|
+
}}
|
|
773
|
+
/>
|
|
774
|
+
<Menu
|
|
775
|
+
anchorEl={anchor}
|
|
776
|
+
open={Boolean(anchor)}
|
|
777
|
+
onClose={() => setAnchor(null)}
|
|
778
|
+
>
|
|
779
|
+
menu
|
|
780
|
+
</Menu>
|
|
781
|
+
</>
|
|
782
|
+
);
|
|
783
|
+
};
|
|
784
|
+
|
|
785
|
+
export const withLegendSecondaryClick: Story = {
|
|
786
|
+
argTypes,
|
|
787
|
+
args: argumentsData,
|
|
788
|
+
render: (args) => (
|
|
789
|
+
<LegendSecondaryClick
|
|
790
|
+
{...args}
|
|
791
|
+
data={dataPingService as unknown as LineChartData}
|
|
792
|
+
/>
|
|
793
|
+
)
|
|
794
|
+
};
|
|
795
|
+
|
|
796
|
+
export const stackedKey: Story = {
|
|
797
|
+
argTypes,
|
|
798
|
+
args: {
|
|
799
|
+
...argumentsData,
|
|
800
|
+
data: dataPingServiceLinesStackKeys
|
|
801
|
+
}
|
|
802
|
+
};
|
|
@@ -277,7 +277,8 @@ const Chart = ({
|
|
|
277
277
|
legendHeight: legend?.height,
|
|
278
278
|
mode: legend?.mode,
|
|
279
279
|
placement: legend?.placement,
|
|
280
|
-
renderExtraComponent: legend?.renderExtraComponent
|
|
280
|
+
renderExtraComponent: legend?.renderExtraComponent,
|
|
281
|
+
secondaryClick: legend?.secondaryClick
|
|
281
282
|
}}
|
|
282
283
|
legendRef={legendRef}
|
|
283
284
|
limitLegend={limitLegend}
|
|
@@ -394,4 +395,4 @@ const Chart = ({
|
|
|
394
395
|
);
|
|
395
396
|
};
|
|
396
397
|
|
|
397
|
-
export default Chart;
|
|
398
|
+
export default Chart;
|
|
@@ -26,6 +26,11 @@ interface Props extends Pick<LegendModel, 'placement' | 'mode'> {
|
|
|
26
26
|
setLinesGraph: Dispatch<SetStateAction<Array<Line> | null>>;
|
|
27
27
|
shouldDisplayLegendInCompactMode: boolean;
|
|
28
28
|
toggable?: boolean;
|
|
29
|
+
secondaryClick?: (props: {
|
|
30
|
+
element: EventTarget | null;
|
|
31
|
+
metricId: number | string;
|
|
32
|
+
position: [number, number];
|
|
33
|
+
}) => void;
|
|
29
34
|
}
|
|
30
35
|
|
|
31
36
|
const MainLegend = ({
|
|
@@ -38,7 +43,8 @@ const MainLegend = ({
|
|
|
38
43
|
shouldDisplayLegendInCompactMode,
|
|
39
44
|
placement,
|
|
40
45
|
height,
|
|
41
|
-
mode
|
|
46
|
+
mode,
|
|
47
|
+
secondaryClick
|
|
42
48
|
}: Props): JSX.Element => {
|
|
43
49
|
const { classes, cx } = useStyles({
|
|
44
50
|
limitLegendRows: Boolean(limitLegend),
|
|
@@ -65,7 +71,24 @@ const MainLegend = ({
|
|
|
65
71
|
value
|
|
66
72
|
}) || 'N/A';
|
|
67
73
|
|
|
68
|
-
const
|
|
74
|
+
const contextMenuClick =
|
|
75
|
+
(metricId: number) =>
|
|
76
|
+
(event: MouseEvent): void => {
|
|
77
|
+
if (!secondaryClick) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
event.preventDefault();
|
|
81
|
+
secondaryClick({
|
|
82
|
+
element: event.target,
|
|
83
|
+
metricId,
|
|
84
|
+
position: [event.pageX, event.pageY]
|
|
85
|
+
});
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const selectMetric = ({
|
|
89
|
+
event,
|
|
90
|
+
metric_id
|
|
91
|
+
}: { event: MouseEvent; metric_id: number }): void => {
|
|
69
92
|
if (!toggable) {
|
|
70
93
|
return;
|
|
71
94
|
}
|
|
@@ -127,6 +150,7 @@ const MainLegend = ({
|
|
|
127
150
|
onClick={(event): void => selectMetric({ event, metric_id })}
|
|
128
151
|
onMouseEnter={(): void => highlightLine(metric_id)}
|
|
129
152
|
onMouseLeave={(): void => clearHighlight()}
|
|
153
|
+
onContextMenu={contextMenuClick(metric_id)}
|
|
130
154
|
>
|
|
131
155
|
<LegendHeader
|
|
132
156
|
color={markerColor}
|