@centreon/ui 25.10.29 → 25.11.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@centreon/ui",
3
- "version": "25.10.29",
3
+ "version": "25.11.1",
4
4
  "description": "Centreon UI Components",
5
5
  "scripts": {
6
6
  "update:deps": "pnpx npm-check-updates -i --format group",
@@ -16,6 +16,7 @@ import { LineChartProps } from '../Chart/models';
16
16
  import useChartData from '../Chart/useChartData';
17
17
  import { LineChartData, Thresholds } from '../common/models';
18
18
 
19
+ import { ReactElement } from 'react';
19
20
  import useResizeObserver from 'use-resize-observer';
20
21
  import ResponsiveBarChart from './ResponsiveBarChart';
21
22
  import { BarStyle } from './models';
@@ -36,6 +37,9 @@ export interface BarChartProps
36
37
  | 'min'
37
38
  | 'max'
38
39
  | 'boundariesUnit'
40
+ | 'timeShiftZones'
41
+ | 'zoomPreview'
42
+ | 'annotationEvent'
39
43
  >
40
44
  > {
41
45
  barStyle?: BarStyle;
@@ -71,8 +75,11 @@ const BarChart = ({
71
75
  skipIntersectionObserver,
72
76
  min,
73
77
  max,
74
- boundariesUnit
75
- }: BarChartProps): JSX.Element => {
78
+ boundariesUnit,
79
+ zoomPreview,
80
+ timeShiftZones,
81
+ annotationEvent
82
+ }: BarChartProps): ReactElement => {
76
83
  const { adjustedData } = useChartData({ data, end, start, min, max });
77
84
  const { ref, width, height: responsiveHeight } = useResizeObserver();
78
85
 
@@ -109,6 +116,11 @@ const BarChart = ({
109
116
  min={min}
110
117
  max={max}
111
118
  boundariesUnit={boundariesUnit}
119
+ zoomPreview={zoomPreview}
120
+ timeShiftZones={timeShiftZones}
121
+ annotationEvent={annotationEvent}
122
+ start={start}
123
+ end={end}
112
124
  />
113
125
  )}
114
126
  </Box>
@@ -1,12 +1,21 @@
1
- import { MutableRefObject, useEffect, useMemo, useRef, useState } from 'react';
1
+ import {
2
+ MutableRefObject,
3
+ ReactElement,
4
+ useEffect,
5
+ useMemo,
6
+ useRef,
7
+ useState
8
+ } from 'react';
2
9
 
3
- import { useAtom } from 'jotai';
10
+ import { useAtom, useAtomValue } from 'jotai';
4
11
  import { equals, flatten, gte, has, isNil, pluck } from 'ramda';
5
12
 
6
13
  import { Skeleton } from '@mui/material';
7
14
 
8
15
  import { Tooltip } from '../../components';
9
16
  import { useDeepCompare } from '../../utils';
17
+ import InteractionWithGraph from '../Chart/InteractiveComponents';
18
+ import { applyingZoomAtomAtom } from '../Chart/InteractiveComponents/ZoomPreview/zoomPreviewAtoms';
10
19
  import { margin } from '../Chart/common';
11
20
  import { Data, LineChartProps } from '../Chart/models';
12
21
  import { useIntersection } from '../Chart/useChartIntersection';
@@ -18,18 +27,29 @@ import Thresholds from '../common/Thresholds/Thresholds';
18
27
  import { Thresholds as ThresholdsModel } from '../common/models';
19
28
  import {
20
29
  getUnits,
30
+ getXScale,
21
31
  getXScaleBand,
22
32
  getYScalePerUnit
23
33
  } from '../common/timeSeries';
24
34
  import { Line } from '../common/timeSeries/models';
25
35
  import { useTooltipStyles } from '../common/useTooltipStyles';
36
+ import { computPixelsToShiftMouse } from '../common/utils';
26
37
  import BarGroup from './BarGroup';
27
38
  import BarChartTooltip from './Tooltip/BarChartTooltip';
28
39
  import { tooltipDataAtom } from './atoms';
29
40
  import { BarStyle } from './models';
30
41
 
31
42
  interface Props
32
- extends Pick<LineChartProps, 'tooltip' | 'legend' | 'axis' | 'header'> {
43
+ extends Pick<
44
+ LineChartProps,
45
+ | 'tooltip'
46
+ | 'legend'
47
+ | 'axis'
48
+ | 'header'
49
+ | 'zoomPreview'
50
+ | 'timeShiftZones'
51
+ | 'annotationEvent'
52
+ > {
33
53
  barStyle: BarStyle;
34
54
  graphData: Data;
35
55
  graphRef: MutableRefObject<HTMLDivElement | null>;
@@ -43,6 +63,8 @@ interface Props
43
63
  min?: number;
44
64
  max?: number;
45
65
  boundariesUnit?: string;
66
+ start: string;
67
+ end: string;
46
68
  }
47
69
 
48
70
  const ResponsiveBarChart = ({
@@ -62,8 +84,13 @@ const ResponsiveBarChart = ({
62
84
  skipIntersectionObserver,
63
85
  min,
64
86
  max,
65
- boundariesUnit
66
- }: Props): JSX.Element => {
87
+ boundariesUnit,
88
+ start,
89
+ end,
90
+ timeShiftZones,
91
+ zoomPreview,
92
+ annotationEvent
93
+ }: Props): ReactElement => {
67
94
  const { title, timeSeries, baseAxis, lines } = graphData || {};
68
95
 
69
96
  const { classes, cx } = useTooltipStyles();
@@ -72,6 +99,7 @@ const ResponsiveBarChart = ({
72
99
  const graphSvgRef = useRef<SVGSVGElement | null>(null);
73
100
 
74
101
  const [tooltipData, setTooltipData] = useAtom(tooltipDataAtom);
102
+ const isApplyingZoom = useAtomValue(applyingZoomAtomAtom);
75
103
 
76
104
  const { isInViewport } = useIntersection({ element: graphRef?.current });
77
105
 
@@ -125,6 +153,15 @@ const ResponsiveBarChart = ({
125
153
  [timeSeries, graphWidth, isHorizontal, graphHeight]
126
154
  );
127
155
 
156
+ const xScaleLinear = useMemo(
157
+ () =>
158
+ getXScale({
159
+ dataTime: timeSeries,
160
+ valueWidth: isHorizontal ? graphWidth : graphHeight - 30
161
+ }),
162
+ [timeSeries, graphWidth, isHorizontal, graphHeight]
163
+ );
164
+
128
165
  const yScalesPerUnit = useMemo(
129
166
  () =>
130
167
  getYScalePerUnit({
@@ -158,6 +195,7 @@ const ResponsiveBarChart = ({
158
195
 
159
196
  const leftScale = yScalesPerUnit[firstUnit];
160
197
  const rightScale = yScalesPerUnit[secondUnit];
198
+ const pixelsToShift = computPixelsToShiftMouse(xScaleLinear);
161
199
 
162
200
  useEffect(
163
201
  () => {
@@ -244,33 +282,97 @@ const ResponsiveBarChart = ({
244
282
  hasSecondUnit={Boolean(secondUnit)}
245
283
  >
246
284
  <>
247
- <BarGroup
248
- barStyle={barStyle}
249
- isTooltipHidden={isTooltipHidden}
250
- lines={displayedLines}
251
- orientation={isHorizontal ? 'horizontal' : 'vertical'}
252
- size={isHorizontal ? graphHeight - margin.top - 5 : graphWidth}
253
- timeSeries={timeSeries}
254
- xScale={xScale}
255
- yScalesPerUnit={yScalesPerUnit}
256
- scaleType={axis?.scale}
257
- />
258
- {thresholds?.enabled && (
259
- <Thresholds
260
- displayedLines={displayedLines}
261
- hideTooltip={() => setTooltipData(null)}
262
- isHorizontal={isHorizontal}
263
- showTooltip={({ tooltipData: thresholdLabel }) =>
264
- setTooltipData({
265
- thresholdLabel
266
- })
267
- }
268
- thresholdUnit={thresholdUnit}
269
- thresholds={thresholds as ThresholdsModel}
270
- width={isHorizontal ? graphWidth : graphHeight - margin.top}
271
- yScalesPerUnit={yScalesPerUnit}
285
+ {isApplyingZoom && (
286
+ <>
287
+ <BarGroup
288
+ barStyle={barStyle}
289
+ isTooltipHidden={isTooltipHidden}
290
+ lines={displayedLines}
291
+ orientation={isHorizontal ? 'horizontal' : 'vertical'}
292
+ size={
293
+ isHorizontal ? graphHeight - margin.top - 5 : graphWidth
294
+ }
295
+ timeSeries={timeSeries}
296
+ xScale={xScale}
297
+ yScalesPerUnit={yScalesPerUnit}
298
+ scaleType={axis?.scale}
299
+ />
300
+ {thresholds?.enabled && (
301
+ <Thresholds
302
+ displayedLines={displayedLines}
303
+ hideTooltip={() => setTooltipData(null)}
304
+ isHorizontal={isHorizontal}
305
+ showTooltip={({ tooltipData: thresholdLabel }) =>
306
+ setTooltipData({
307
+ thresholdLabel
308
+ })
309
+ }
310
+ thresholdUnit={thresholdUnit}
311
+ thresholds={thresholds as ThresholdsModel}
312
+ width={
313
+ isHorizontal ? graphWidth : graphHeight - margin.top
314
+ }
315
+ yScalesPerUnit={yScalesPerUnit}
316
+ />
317
+ )}
318
+ </>
319
+ )}
320
+ {isHorizontal && (
321
+ <InteractionWithGraph
322
+ additionalZoomMargin={pixelsToShift}
323
+ maxLeftAxisCharacters={maxLeftAxisCharacters}
324
+ commonData={{
325
+ graphHeight,
326
+ graphSvgRef,
327
+ graphWidth,
328
+ lines,
329
+ xScale: xScaleLinear,
330
+ timeSeries,
331
+ yScalesPerUnit
332
+ }}
333
+ annotationData={{ ...annotationEvent }}
334
+ zoomData={{ ...zoomPreview }}
335
+ timeShiftZonesData={{
336
+ ...timeShiftZones,
337
+ graphInterval: { start, end }
338
+ }}
272
339
  />
273
340
  )}
341
+ {!isApplyingZoom && (
342
+ <>
343
+ <BarGroup
344
+ barStyle={barStyle}
345
+ isTooltipHidden={isTooltipHidden}
346
+ lines={displayedLines}
347
+ orientation={isHorizontal ? 'horizontal' : 'vertical'}
348
+ size={
349
+ isHorizontal ? graphHeight - margin.top - 5 : graphWidth
350
+ }
351
+ timeSeries={timeSeries}
352
+ xScale={xScale}
353
+ yScalesPerUnit={yScalesPerUnit}
354
+ scaleType={axis?.scale}
355
+ />
356
+ {thresholds?.enabled && (
357
+ <Thresholds
358
+ displayedLines={displayedLines}
359
+ hideTooltip={() => setTooltipData(null)}
360
+ isHorizontal={isHorizontal}
361
+ showTooltip={({ tooltipData: thresholdLabel }) =>
362
+ setTooltipData({
363
+ thresholdLabel
364
+ })
365
+ }
366
+ thresholdUnit={thresholdUnit}
367
+ thresholds={thresholds as ThresholdsModel}
368
+ width={
369
+ isHorizontal ? graphWidth : graphHeight - margin.top
370
+ }
371
+ yScalesPerUnit={yScalesPerUnit}
372
+ />
373
+ )}
374
+ </>
375
+ )}
274
376
  </>
275
377
  </ChartSvgWrapper>
276
378
  </div>
@@ -46,7 +46,7 @@ export const getYAnchorPoint = ({
46
46
  return null;
47
47
  }
48
48
 
49
- return yScale(timeValue[1] as number);
49
+ return yScale(timeValue[0] as number);
50
50
  };
51
51
 
52
52
  const StackedAnchorPoint = ({
@@ -4,18 +4,29 @@ import { alpha, useTheme } from '@mui/system';
4
4
 
5
5
  import Bar from '../Bar';
6
6
 
7
+ import { margin } from '../../common';
7
8
  import { ZoomPreviewData } from './models';
8
9
  import useZoomPreview from './useZoomPreview';
9
10
 
10
11
  const ZoomPreview = (data: ZoomPreviewData): JSX.Element => {
11
12
  const theme = useTheme();
12
13
 
13
- const { graphHeight, xScale, graphWidth, getInterval, ...rest } = data;
14
+ const {
15
+ graphHeight,
16
+ xScale,
17
+ graphWidth,
18
+ getInterval,
19
+ graphSvgRef,
20
+ graphMarginLeft,
21
+ ...rest
22
+ } = data;
14
23
 
15
24
  const { zoomBarWidth, zoomBoundaries } = useZoomPreview({
16
25
  getInterval,
17
26
  graphWidth,
18
- xScale
27
+ xScale,
28
+ graphSvgRef,
29
+ graphMarginLeft
19
30
  });
20
31
 
21
32
  const restData = omit(['enable'], { ...rest });
@@ -24,7 +35,7 @@ const ZoomPreview = (data: ZoomPreviewData): JSX.Element => {
24
35
  <g>
25
36
  <Bar
26
37
  fill={alpha(theme.palette.primary.main, 0.2)}
27
- height={graphHeight}
38
+ height={graphHeight - margin.bottom}
28
39
  stroke={alpha(theme.palette.primary.main, 0.5)}
29
40
  width={zoomBarWidth}
30
41
  x={zoomBoundaries?.start || 0}
@@ -1,9 +1,12 @@
1
1
  import { ScaleTime } from 'd3-scale';
2
2
 
3
+ import { RefObject } from 'react';
3
4
  import { InteractedZone } from '../../models';
4
5
 
5
6
  export interface ZoomPreviewData extends InteractedZone {
6
7
  graphHeight: number;
7
8
  graphWidth: number;
8
9
  xScale: ScaleTime<number, number>;
10
+ graphSvgRef: RefObject<SVGSVGElement | null>;
11
+ graphMarginLeft: number;
9
12
  }
@@ -1,18 +1,15 @@
1
- import { useEffect, useState } from 'react';
1
+ import { RefObject, useEffect, useState } from 'react';
2
2
 
3
3
  import { Event } from '@visx/visx';
4
4
  import { ScaleTime } from 'd3-scale';
5
5
  import { useAtomValue, useSetAtom } from 'jotai';
6
6
  import { equals, gte, isNil, lt } from 'ramda';
7
-
8
- import { margin } from '../../common';
9
7
  import { Interval } from '../../models';
10
8
  import {
11
9
  eventMouseDownAtom,
12
10
  eventMouseUpAtom,
13
11
  mousePositionAtom
14
12
  } from '../interactionWithGraphAtoms';
15
-
16
13
  import { applyingZoomAtomAtom } from './zoomPreviewAtoms';
17
14
 
18
15
  interface Boundaries {
@@ -28,12 +25,16 @@ interface Props {
28
25
  getInterval?: (args: Interval) => void;
29
26
  graphWidth: number;
30
27
  xScale: ScaleTime<number, number>;
28
+ graphSvgRef: RefObject<SVGSVGElement | null>;
29
+ graphMarginLeft: number;
31
30
  }
32
31
 
33
32
  const useZoomPreview = ({
34
33
  xScale,
35
34
  graphWidth,
36
- getInterval
35
+ getInterval,
36
+ graphSvgRef,
37
+ graphMarginLeft
37
38
  }: Props): ZoomPreview => {
38
39
  const [zoomBoundaries, setZoomBoundaries] = useState<Boundaries | null>(null);
39
40
  const eventMouseDown = useAtomValue(eventMouseDownAtom);
@@ -41,16 +42,17 @@ const useZoomPreview = ({
41
42
  const mousePosition = useAtomValue(mousePositionAtom);
42
43
  const setApplyingZoom = useSetAtom(applyingZoomAtomAtom);
43
44
 
44
- const mousePointDown = eventMouseDown
45
- ? Event.localPoint(eventMouseDown)
46
- : null;
45
+ const mousePointDown =
46
+ eventMouseDown && graphSvgRef.current
47
+ ? Event.localPoint(graphSvgRef.current, eventMouseDown)
48
+ : null;
47
49
 
48
50
  const mouseDownPositionX = mousePointDown
49
- ? mousePointDown.x - margin.left
51
+ ? mousePointDown.x - graphMarginLeft
50
52
  : null;
51
53
 
52
54
  const movingMousePositionX = mousePosition
53
- ? mousePosition[0] - margin.left
55
+ ? mousePosition[0] - graphMarginLeft
54
56
  : null;
55
57
 
56
58
  const applyZoom = (): void => {
@@ -1,4 +1,4 @@
1
- import type { MutableRefObject } from 'react';
1
+ import { type MutableRefObject, ReactElement, useMemo } from 'react';
2
2
 
3
3
  import { Event } from '@visx/visx';
4
4
  import type { ScaleLinear, ScaleTime } from 'd3-scale';
@@ -9,9 +9,11 @@ import {
9
9
  find,
10
10
  isEmpty,
11
11
  isNil,
12
+ isNotNil,
12
13
  keys,
13
14
  map,
14
15
  negate,
16
+ omit,
15
17
  pick,
16
18
  pipe,
17
19
  pluck,
@@ -86,6 +88,7 @@ interface Props {
86
88
  };
87
89
  hasSecondUnit?: boolean;
88
90
  maxLeftAxisCharacters: number;
91
+ additionalZoomMargin?: number;
89
92
  }
90
93
 
91
94
  const InteractionWithGraph = ({
@@ -95,8 +98,9 @@ const InteractionWithGraph = ({
95
98
  timeShiftZonesData,
96
99
  transformMatrix,
97
100
  hasSecondUnit,
98
- maxLeftAxisCharacters
99
- }: Props): JSX.Element => {
101
+ maxLeftAxisCharacters,
102
+ additionalZoomMargin = 0
103
+ }: Props): ReactElement => {
100
104
  const { classes } = useStyles();
101
105
 
102
106
  const setEventMouseDown = useSetAtom(eventMouseDownAtom);
@@ -150,6 +154,15 @@ const InteractionWithGraph = ({
150
154
  setEventMouseDown(event);
151
155
  };
152
156
 
157
+ const graphMarginLeft = useMemo(
158
+ () =>
159
+ computeGElementMarginLeft({
160
+ maxCharacters: maxLeftAxisCharacters,
161
+ hasSecondUnit
162
+ }) + additionalZoomMargin,
163
+ [additionalZoomMargin, maxLeftAxisCharacters, hasSecondUnit]
164
+ );
165
+
153
166
  const updateMousePosition = (pointPosition: MousePosition): void => {
154
167
  if (isNil(pointPosition)) {
155
168
  changeMousePosition({
@@ -164,10 +177,7 @@ const InteractionWithGraph = ({
164
177
  timeSeries,
165
178
  x: pointPosition[0] - pixelToShift,
166
179
  xScale,
167
- marginLeft: computeGElementMarginLeft({
168
- maxCharacters: maxLeftAxisCharacters,
169
- hasSecondUnit
170
- })
180
+ marginLeft: graphMarginLeft
171
181
  });
172
182
 
173
183
  if (isNil(timeValue)) {
@@ -212,6 +222,39 @@ const InteractionWithGraph = ({
212
222
  yScalesPerUnit
213
223
  });
214
224
 
225
+ if (isNotNil(lineData?.stackOrder)) {
226
+ const test = Object.entries(omit(['timeTick'], timeValue)).reduce(
227
+ (acc, [key, value]) => {
228
+ const line = getLineForMetric({
229
+ lines,
230
+ metric_id: Number(key)
231
+ });
232
+
233
+ const isBelowStackOrder =
234
+ isNotNil(line?.stackOrder) &&
235
+ (line?.stackOrder as number) >= (lineData.stackOrder as number);
236
+
237
+ if (isBelowStackOrder) {
238
+ return acc + value;
239
+ }
240
+
241
+ return acc;
242
+ },
243
+ 0
244
+ );
245
+
246
+ const y0 = yScale(test);
247
+
248
+ const diffBetweenY0AndPointPosition = Math.abs(
249
+ y0 - margin.top - (graphHeight - pointPosition[1])
250
+ );
251
+
252
+ return {
253
+ ...acc,
254
+ [metricId]: diffBetweenY0AndPointPosition
255
+ };
256
+ }
257
+
215
258
  const y0 = yScale(value);
216
259
 
217
260
  const diffBetweenY0AndPointPosition = Math.abs(
@@ -261,6 +304,8 @@ const InteractionWithGraph = ({
261
304
  graphHeight={graphHeight}
262
305
  graphWidth={graphWidth}
263
306
  xScale={xScale}
307
+ graphSvgRef={graphSvgRef}
308
+ graphMarginLeft={graphMarginLeft}
264
309
  />
265
310
  )}
266
311
  {displayEventAnnotations && (
@@ -107,14 +107,16 @@ const PickersStartEndDate = ({
107
107
  const maxEnd = rangeEndDate?.max;
108
108
  const minEnd = rangeEndDate?.min || startDate;
109
109
 
110
- const isColumn = equals(direction, PickersStartEndDateDirection.column)
110
+ const isColumn = equals(direction, PickersStartEndDateDirection.column);
111
111
 
112
112
  return (
113
113
  <LocalizationProvider
114
114
  adapterLocale={locale.substring(0, 2)}
115
115
  dateAdapter={AdapterDayjs}
116
116
  >
117
- <div className={`flex ${isColumn ? 'flex-col justify-center' : 'flex-row items-center py-2 px-4'} gap-2 ${className}`}>
117
+ <div
118
+ className={`flex ${isColumn ? 'flex-col justify-center' : 'flex-row items-center py-2 px-4'} gap-2 ${className}`}
119
+ >
118
120
  <PickerDateWithLabel
119
121
  changeDate={changeDate}
120
122
  date={startDate}