@gravity-ui/charts 1.10.1 → 1.11.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.
Files changed (85) hide show
  1. package/dist/cjs/components/ChartInner/index.js +8 -3
  2. package/dist/cjs/components/ChartInner/useChartInnerHandlers.d.ts +2 -1
  3. package/dist/cjs/components/ChartInner/useChartInnerHandlers.js +1 -5
  4. package/dist/cjs/components/ChartInner/useChartInnerProps.d.ts +2 -0
  5. package/dist/cjs/components/ChartInner/useChartInnerProps.js +9 -3
  6. package/dist/cjs/components/ChartInner/utils.d.ts +1 -1
  7. package/dist/cjs/components/ChartInner/utils.js +3 -3
  8. package/dist/cjs/components/Legend/index.js +3 -0
  9. package/dist/cjs/hooks/hooks-utils/zoom.d.ts +1 -1
  10. package/dist/cjs/hooks/hooks-utils/zoom.js +2 -2
  11. package/dist/cjs/hooks/useAxisScales/index.js +49 -18
  12. package/dist/cjs/hooks/useChartOptions/x-axis.js +3 -14
  13. package/dist/cjs/hooks/useChartOptions/y-axis.js +5 -24
  14. package/dist/cjs/hooks/useShapes/area/index.d.ts +1 -0
  15. package/dist/cjs/hooks/useShapes/area/index.js +13 -9
  16. package/dist/cjs/hooks/useShapes/area/prepare-data.d.ts +1 -0
  17. package/dist/cjs/hooks/useShapes/area/prepare-data.js +2 -1
  18. package/dist/cjs/hooks/useShapes/area/types.d.ts +1 -0
  19. package/dist/cjs/hooks/useShapes/bar-x/index.d.ts +1 -0
  20. package/dist/cjs/hooks/useShapes/bar-x/index.js +2 -2
  21. package/dist/cjs/hooks/useShapes/bar-y/index.d.ts +1 -0
  22. package/dist/cjs/hooks/useShapes/bar-y/index.js +2 -2
  23. package/dist/cjs/hooks/useShapes/index.d.ts +2 -0
  24. package/dist/cjs/hooks/useShapes/index.js +11 -6
  25. package/dist/cjs/hooks/useShapes/line/index.d.ts +1 -0
  26. package/dist/cjs/hooks/useShapes/line/index.js +16 -12
  27. package/dist/cjs/hooks/useShapes/line/prepare-data.d.ts +1 -0
  28. package/dist/cjs/hooks/useShapes/line/prepare-data.js +2 -1
  29. package/dist/cjs/hooks/useShapes/line/types.d.ts +1 -0
  30. package/dist/cjs/hooks/useShapes/marker.js +6 -0
  31. package/dist/cjs/hooks/useShapes/scatter/prepare-data.d.ts +1 -0
  32. package/dist/cjs/hooks/useShapes/scatter/prepare-data.js +6 -3
  33. package/dist/cjs/hooks/useShapes/scatter/types.d.ts +1 -0
  34. package/dist/cjs/hooks/useShapes/waterfall/index.d.ts +1 -0
  35. package/dist/cjs/hooks/useShapes/waterfall/index.js +2 -2
  36. package/dist/cjs/hooks/useZoom/index.js +1 -1
  37. package/dist/cjs/hooks/useZoom/utils.d.ts +1 -1
  38. package/dist/cjs/hooks/useZoom/utils.js +3 -3
  39. package/dist/cjs/types/chart/axis.d.ts +4 -5
  40. package/dist/cjs/utils/chart/axis-generators/bottom.js +3 -1
  41. package/dist/cjs/utils/chart/index.d.ts +2 -0
  42. package/dist/cjs/utils/chart/index.js +31 -0
  43. package/dist/esm/components/ChartInner/index.js +8 -3
  44. package/dist/esm/components/ChartInner/useChartInnerHandlers.d.ts +2 -1
  45. package/dist/esm/components/ChartInner/useChartInnerHandlers.js +1 -5
  46. package/dist/esm/components/ChartInner/useChartInnerProps.d.ts +2 -0
  47. package/dist/esm/components/ChartInner/useChartInnerProps.js +9 -3
  48. package/dist/esm/components/ChartInner/utils.d.ts +1 -1
  49. package/dist/esm/components/ChartInner/utils.js +3 -3
  50. package/dist/esm/components/Legend/index.js +3 -0
  51. package/dist/esm/hooks/hooks-utils/zoom.d.ts +1 -1
  52. package/dist/esm/hooks/hooks-utils/zoom.js +2 -2
  53. package/dist/esm/hooks/useAxisScales/index.js +49 -18
  54. package/dist/esm/hooks/useChartOptions/x-axis.js +3 -14
  55. package/dist/esm/hooks/useChartOptions/y-axis.js +5 -24
  56. package/dist/esm/hooks/useShapes/area/index.d.ts +1 -0
  57. package/dist/esm/hooks/useShapes/area/index.js +13 -9
  58. package/dist/esm/hooks/useShapes/area/prepare-data.d.ts +1 -0
  59. package/dist/esm/hooks/useShapes/area/prepare-data.js +2 -1
  60. package/dist/esm/hooks/useShapes/area/types.d.ts +1 -0
  61. package/dist/esm/hooks/useShapes/bar-x/index.d.ts +1 -0
  62. package/dist/esm/hooks/useShapes/bar-x/index.js +2 -2
  63. package/dist/esm/hooks/useShapes/bar-y/index.d.ts +1 -0
  64. package/dist/esm/hooks/useShapes/bar-y/index.js +2 -2
  65. package/dist/esm/hooks/useShapes/index.d.ts +2 -0
  66. package/dist/esm/hooks/useShapes/index.js +11 -6
  67. package/dist/esm/hooks/useShapes/line/index.d.ts +1 -0
  68. package/dist/esm/hooks/useShapes/line/index.js +16 -12
  69. package/dist/esm/hooks/useShapes/line/prepare-data.d.ts +1 -0
  70. package/dist/esm/hooks/useShapes/line/prepare-data.js +2 -1
  71. package/dist/esm/hooks/useShapes/line/types.d.ts +1 -0
  72. package/dist/esm/hooks/useShapes/marker.js +6 -0
  73. package/dist/esm/hooks/useShapes/scatter/prepare-data.d.ts +1 -0
  74. package/dist/esm/hooks/useShapes/scatter/prepare-data.js +6 -3
  75. package/dist/esm/hooks/useShapes/scatter/types.d.ts +1 -0
  76. package/dist/esm/hooks/useShapes/waterfall/index.d.ts +1 -0
  77. package/dist/esm/hooks/useShapes/waterfall/index.js +2 -2
  78. package/dist/esm/hooks/useZoom/index.js +1 -1
  79. package/dist/esm/hooks/useZoom/utils.d.ts +1 -1
  80. package/dist/esm/hooks/useZoom/utils.js +3 -3
  81. package/dist/esm/types/chart/axis.d.ts +4 -5
  82. package/dist/esm/utils/chart/axis-generators/bottom.js +3 -1
  83. package/dist/esm/utils/chart/index.d.ts +2 -0
  84. package/dist/esm/utils/chart/index.js +31 -0
  85. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import { ArrowRotateLeft } from '@gravity-ui/icons';
3
- import { Button, ButtonIcon } from '@gravity-ui/uikit';
3
+ import { Button, ButtonIcon, useUniqId } from '@gravity-ui/uikit';
4
4
  import { useCrosshair } from '../../hooks';
5
5
  import { EventType, block, getDispatcher } from '../../utils';
6
6
  import { AxisX, AxisY } from '../Axis';
@@ -22,8 +22,9 @@ export const ChartInner = (props) => {
22
22
  const plotBeforeRef = React.useRef(null);
23
23
  const plotAfterRef = React.useRef(null);
24
24
  const dispatcher = React.useMemo(() => getDispatcher(), []);
25
- const { boundsHeight, boundsOffsetLeft, boundsOffsetTop, boundsWidth, handleLegendItemClick, handleZoomReset, legendConfig, legendItems, preparedSeries, preparedSplit, preparedLegend, prevHeight, prevWidth, shapes, shapesData, title, tooltip, xAxis, xScale, yAxis, yScale, svgXPos, svgBottomPos, } = useChartInnerProps(Object.assign(Object.assign({}, props), { dispatcher,
26
- htmlLayout, svgContainer: svgRef.current, plotNode: plotRef.current }));
25
+ const clipPathId = useUniqId();
26
+ const { boundsHeight, boundsOffsetLeft, boundsOffsetTop, boundsWidth, handleLegendItemClick, handleZoomReset, isOutsideBounds, legendConfig, legendItems, preparedSeries, preparedSplit, preparedLegend, prevHeight, prevWidth, shapes, shapesData, title, tooltip, xAxis, xScale, yAxis, yScale, svgXPos, svgBottomPos, } = useChartInnerProps(Object.assign(Object.assign({}, props), { dispatcher,
27
+ htmlLayout, svgContainer: svgRef.current, plotNode: plotRef.current, clipPathId }));
27
28
  const { tooltipPinned, togglePinTooltip, unpinTooltip } = useChartInnerState({
28
29
  dispatcher,
29
30
  tooltip,
@@ -42,6 +43,7 @@ export const ChartInner = (props) => {
42
43
  xAxis,
43
44
  yAxis,
44
45
  tooltipThrottle: tooltip.throttle,
46
+ isOutsideBounds,
45
47
  });
46
48
  const clickHandler = (_b = (_a = data.chart) === null || _a === void 0 ? void 0 : _a.events) === null || _b === void 0 ? void 0 : _b.click;
47
49
  const pointerMoveHandler = (_d = (_c = data.chart) === null || _c === void 0 ? void 0 : _c.events) === null || _d === void 0 ? void 0 : _d.pointermove;
@@ -83,6 +85,9 @@ export const ChartInner = (props) => {
83
85
  // We use onPointerMove here because onMouseMove works incorrectly when the zoom setting is enabled:
84
86
  // when starting to select an area, the tooltip remains in the position where the selection began
85
87
  onPointerMove: throttledHandleMouseMove, onMouseLeave: handleMouseLeave, onTouchStart: throttledHandleTouchMove, onTouchMove: throttledHandleTouchMove, onClick: handleChartClick },
88
+ React.createElement("defs", null,
89
+ React.createElement("clipPath", { id: clipPathId },
90
+ React.createElement("rect", { x: 0, y: 0, width: boundsWidth, height: boundsHeight }))),
86
91
  title && React.createElement(Title, Object.assign({}, title, { chartWidth: width })),
87
92
  React.createElement("g", { transform: `translate(0, ${boundsOffsetTop})` }, preparedSplit.plots.map((plot, index) => {
88
93
  return React.createElement(PlotTitle, { key: `plot-${index}`, title: plot.title });
@@ -1,4 +1,4 @@
1
- import React from 'react';
1
+ import type React from 'react';
2
2
  import type { Dispatch } from 'd3';
3
3
  import type { PreparedAxis, ShapeData } from '../../hooks';
4
4
  import type { useChartInnerState } from './useChartInnerState';
@@ -17,6 +17,7 @@ type Props = {
17
17
  xAxis: PreparedAxis | null;
18
18
  yAxis: PreparedAxis[];
19
19
  tooltipThrottle: number;
20
+ isOutsideBounds: (x: number, y: number) => boolean;
20
21
  };
21
22
  export declare function useChartInnerHandlers(props: Props): {
22
23
  handleChartClick: (event: React.MouseEvent<SVGSVGElement>) => void;
@@ -1,14 +1,10 @@
1
- import React from 'react';
2
1
  import { pointer } from 'd3';
3
2
  import throttle from 'lodash/throttle';
4
3
  import { IS_TOUCH_ENABLED } from '../../constants';
5
4
  import { EventType } from '../../utils';
6
5
  import { getClosestPoints } from '../../utils/chart/get-closest-data';
7
6
  export function useChartInnerHandlers(props) {
8
- const { boundsHeight, boundsOffsetLeft, boundsOffsetTop, boundsWidth, dispatcher, shapesData, svgContainer, togglePinTooltip, tooltipPinned, unpinTooltip, xAxis, yAxis, tooltipThrottle, } = props;
9
- const isOutsideBounds = React.useCallback((x, y) => {
10
- return x < 0 || x > boundsWidth || y < 0 || y > boundsHeight;
11
- }, [boundsHeight, boundsWidth]);
7
+ const { boundsHeight, boundsOffsetLeft, boundsOffsetTop, boundsWidth, dispatcher, shapesData, svgContainer, togglePinTooltip, tooltipPinned, unpinTooltip, xAxis, yAxis, tooltipThrottle, isOutsideBounds, } = props;
12
8
  const handleMove = ([pointerX, pointerY], event) => {
13
9
  if (tooltipPinned) {
14
10
  return;
@@ -7,6 +7,7 @@ type Props = ChartInnerProps & {
7
7
  htmlLayout: HTMLElement | null;
8
8
  svgContainer: SVGGElement | null;
9
9
  plotNode: SVGGElement | null;
10
+ clipPathId: string;
10
11
  };
11
12
  export declare function useChartInnerProps(props: Props): {
12
13
  svgBottomPos: number | undefined;
@@ -17,6 +18,7 @@ export declare function useChartInnerProps(props: Props): {
17
18
  boundsWidth: number;
18
19
  handleLegendItemClick: import("../../hooks").OnLegendItemClick;
19
20
  handleZoomReset: (() => void) | undefined;
21
+ isOutsideBounds: (x: number, y: number) => boolean;
20
22
  legendConfig: {
21
23
  offset: {
22
24
  left: number;
@@ -8,7 +8,7 @@ import { useZoom } from '../../hooks/useZoom';
8
8
  import { hasAtLeastOneSeriesDataPerPlot } from './utils';
9
9
  export function useChartInnerProps(props) {
10
10
  var _a;
11
- const { width, height, data, dispatcher, htmlLayout, svgContainer, plotNode } = props;
11
+ const { width, height, data, dispatcher, htmlLayout, svgContainer, plotNode, clipPathId } = props;
12
12
  const prevWidth = usePrevious(width);
13
13
  const prevHeight = usePrevious(height);
14
14
  const [zoomState, setZoomState] = React.useState({});
@@ -17,7 +17,7 @@ export function useChartInnerProps(props) {
17
17
  return getZoomedSeriesData({
18
18
  seriesData: data.series.data,
19
19
  xAxis: data.xAxis,
20
- yAxises: data.yAxis,
20
+ yAxes: data.yAxis,
21
21
  zoomState,
22
22
  });
23
23
  }, [data.series.data, data.xAxis, data.yAxis, zoomState]);
@@ -60,6 +60,9 @@ export function useChartInnerProps(props) {
60
60
  yAxis,
61
61
  split: preparedSplit,
62
62
  });
63
+ const isOutsideBounds = React.useCallback((x, y) => {
64
+ return x < 0 || x > boundsWidth || y < 0 || y > boundsHeight;
65
+ }, [boundsHeight, boundsWidth]);
63
66
  const { shapes, shapesData } = useShapes({
64
67
  boundsWidth,
65
68
  boundsHeight,
@@ -72,12 +75,14 @@ export function useChartInnerProps(props) {
72
75
  yScale,
73
76
  split: preparedSplit,
74
77
  htmlLayout,
78
+ clipPathId,
79
+ isOutsideBounds,
75
80
  });
76
81
  const handleAttemptToSetZoomState = React.useCallback((nextZoomState) => {
77
82
  const nextZoomedSeriesData = getZoomedSeriesData({
78
83
  seriesData: zoomedSeriesData,
79
84
  xAxis: data.xAxis,
80
- yAxises: data.yAxis,
85
+ yAxes: data.yAxis,
81
86
  zoomState: nextZoomState,
82
87
  });
83
88
  const hasData = hasAtLeastOneSeriesDataPerPlot(nextZoomedSeriesData, yAxis);
@@ -123,6 +128,7 @@ export function useChartInnerProps(props) {
123
128
  boundsWidth,
124
129
  handleLegendItemClick,
125
130
  handleZoomReset: Object.keys(zoomState).length > 0 ? handleZoomReset : undefined,
131
+ isOutsideBounds,
126
132
  legendConfig,
127
133
  legendItems,
128
134
  preparedLegend,
@@ -1,3 +1,3 @@
1
1
  import type { PreparedAxis } from '../../hooks/useChartOptions/types';
2
2
  import type { ChartSeries } from '../../types';
3
- export declare function hasAtLeastOneSeriesDataPerPlot(seriesData: ChartSeries[], yAxises?: PreparedAxis[]): boolean;
3
+ export declare function hasAtLeastOneSeriesDataPerPlot(seriesData: ChartSeries[], yAxes?: PreparedAxis[]): boolean;
@@ -1,6 +1,6 @@
1
- export function hasAtLeastOneSeriesDataPerPlot(seriesData, yAxises = []) {
1
+ export function hasAtLeastOneSeriesDataPerPlot(seriesData, yAxes = []) {
2
2
  const hasDataMap = new Map();
3
- yAxises.forEach((yAxis) => {
3
+ yAxes.forEach((yAxis) => {
4
4
  var _a;
5
5
  const plotIndex = (_a = yAxis.plotIndex) !== null && _a !== void 0 ? _a : 0;
6
6
  if (!hasDataMap.has(plotIndex)) {
@@ -16,7 +16,7 @@ export function hasAtLeastOneSeriesDataPerPlot(seriesData, yAxises = []) {
16
16
  if ('yAxis' in seriesDataChunk && typeof seriesDataChunk.yAxis === 'number') {
17
17
  yAxisIndex = seriesDataChunk.yAxis;
18
18
  }
19
- const yAxis = yAxises[yAxisIndex];
19
+ const yAxis = yAxes[yAxisIndex];
20
20
  const plotIndex = (_a = yAxis === null || yAxis === void 0 ? void 0 : yAxis.plotIndex) !== null && _a !== void 0 ? _a : 0;
21
21
  if (!hasDataMap.get(plotIndex)) {
22
22
  if (seriesDataChunk.data.length > 0) {
@@ -312,8 +312,11 @@ export const Legend = (props) => {
312
312
  },
313
313
  });
314
314
  const tickTop = legend.title.height + legend.title.margin + rectHeight;
315
+ const legendAxisClassname = b('axis');
316
+ svgElement.selectAll(`.${legendAxisClassname}`).remove();
315
317
  svgElement
316
318
  .append('g')
319
+ .attr('class', legendAxisClassname)
317
320
  .attr('transform', `translate(0, ${tickTop})`)
318
321
  .call(xAxisGenerator);
319
322
  legendWidth = legend.width;
@@ -4,5 +4,5 @@ export declare function getZoomedSeriesData(args: {
4
4
  seriesData: ChartSeries[];
5
5
  zoomState: Partial<ZoomState>;
6
6
  xAxis?: ChartXAxis;
7
- yAxises?: ChartYAxis[];
7
+ yAxes?: ChartYAxis[];
8
8
  }): ChartSeries[];
@@ -40,7 +40,7 @@ function isValueInRange(args) {
40
40
  }
41
41
  }
42
42
  export function getZoomedSeriesData(args) {
43
- const { seriesData, xAxis, yAxises, zoomState } = args;
43
+ const { seriesData, xAxis, yAxes, zoomState } = args;
44
44
  if (Object.keys(zoomState).length < 0) {
45
45
  return seriesData;
46
46
  }
@@ -65,7 +65,7 @@ export function getZoomedSeriesData(args) {
65
65
  const [yMin, yMax] = zoomState.y[yAxisIndex];
66
66
  const y = 'y' in point ? point.y : undefined;
67
67
  inYRange = isValueInRange({
68
- axis: yAxises === null || yAxises === void 0 ? void 0 : yAxises[yAxisIndex],
68
+ axis: yAxes === null || yAxes === void 0 ? void 0 : yAxes[yAxisIndex],
69
69
  value: y,
70
70
  min: yMin,
71
71
  max: yMax,
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  import { extent, scaleBand, scaleLinear, scaleLog, scaleUtc } from 'd3';
3
3
  import get from 'lodash/get';
4
4
  import { DEFAULT_AXIS_TYPE } from '../../constants';
5
- import { CHART_SERIES_WITH_VOLUME_ON_Y_AXIS, getAxisHeight, getDataCategoryValue, getDefaultMaxXAxisValue, getDomainDataXBySeries, getDomainDataYBySeries, getOnlyVisibleSeries, isAxisRelatedSeries, isSeriesWithCategoryValues, } from '../../utils';
5
+ import { CHART_SERIES_WITH_VOLUME_ON_Y_AXIS, getAxisHeight, getDataCategoryValue, getDefaultMaxXAxisValue, getDefaultMinXAxisValue, getDomainDataXBySeries, getDomainDataYBySeries, getOnlyVisibleSeries, isAxisRelatedSeries, isSeriesWithCategoryValues, } from '../../utils';
6
6
  const isNumericalArrayData = (data) => {
7
7
  return data.every((d) => typeof d === 'number' || d === null);
8
8
  };
@@ -20,7 +20,8 @@ const filterCategoriesByVisibleSeries = (args) => {
20
20
  };
21
21
  export function createYScale(axis, series, boundsHeight) {
22
22
  const yType = get(axis, 'type', DEFAULT_AXIS_TYPE);
23
- const yMin = get(axis, 'min');
23
+ const yMinProps = get(axis, 'min');
24
+ const yMaxProps = get(axis, 'max');
24
25
  const yCategories = get(axis, 'categories');
25
26
  const yTimestamps = get(axis, 'timestamps');
26
27
  switch (yType) {
@@ -29,14 +30,18 @@ export function createYScale(axis, series, boundsHeight) {
29
30
  const domain = getDomainDataYBySeries(series);
30
31
  const range = [boundsHeight, boundsHeight * axis.maxPadding];
31
32
  if (isNumericalArrayData(domain)) {
32
- const [domainYMin, domainMax] = extent(domain);
33
- const yMinValue = typeof yMin === 'number' ? yMin : domainYMin;
34
- let yMaxValue = domainMax;
35
- if (series.some((s) => CHART_SERIES_WITH_VOLUME_ON_Y_AXIS.includes(s.type))) {
36
- yMaxValue = Math.max(yMaxValue, 0);
33
+ const [yMinDomain, yMaxDomain] = extent(domain);
34
+ const yMin = typeof yMinProps === 'number' ? yMinProps : yMinDomain;
35
+ let yMax;
36
+ if (typeof yMaxProps === 'number') {
37
+ yMax = yMaxProps;
38
+ }
39
+ else {
40
+ const hasSeriesWithVolumeOnYAxis = series.some((s) => CHART_SERIES_WITH_VOLUME_ON_Y_AXIS.includes(s.type));
41
+ yMax = hasSeriesWithVolumeOnYAxis ? Math.max(yMaxDomain, 0) : yMaxDomain;
37
42
  }
38
43
  const scaleFn = yType === 'logarithmic' ? scaleLog : scaleLinear;
39
- return scaleFn().domain([yMinValue, yMaxValue]).range(range).nice();
44
+ return scaleFn().domain([yMin, yMax]).range(range).nice();
40
45
  }
41
46
  break;
42
47
  }
@@ -54,13 +59,17 @@ export function createYScale(axis, series, boundsHeight) {
54
59
  case 'datetime': {
55
60
  const range = [boundsHeight, boundsHeight * axis.maxPadding];
56
61
  if (yTimestamps) {
57
- const [yMin, yMax] = extent(yTimestamps);
62
+ const [yMinTimestamp, yMaxTimestamp] = extent(yTimestamps);
63
+ const yMin = typeof yMinProps === 'number' ? yMinProps : yMinTimestamp;
64
+ const yMax = typeof yMaxProps === 'number' ? yMaxProps : yMaxTimestamp;
58
65
  return scaleUtc().domain([yMin, yMax]).range(range).nice();
59
66
  }
60
67
  else {
61
68
  const domain = getDomainDataYBySeries(series);
62
69
  if (isNumericalArrayData(domain)) {
63
- const [yMin, yMax] = extent(domain);
70
+ const [yMinTimestamp, yMaxTimestamp] = extent(domain);
71
+ const yMin = typeof yMinProps === 'number' ? yMinProps : yMinTimestamp;
72
+ const yMax = typeof yMaxProps === 'number' ? yMaxProps : yMaxTimestamp;
64
73
  return scaleUtc().domain([yMin, yMax]).range(range).nice();
65
74
  }
66
75
  }
@@ -86,9 +95,10 @@ function calculateXAxisPadding(series) {
86
95
  });
87
96
  return result;
88
97
  }
98
+ // eslint-disable-next-line complexity
89
99
  export function createXScale(axis, series, boundsWidth, hasZoomX) {
90
- const xMin = get(axis, 'min');
91
- const xMax = getDefaultMaxXAxisValue(series);
100
+ const xMinProps = get(axis, 'min');
101
+ const xMaxProps = get(axis, 'max');
92
102
  const xType = get(axis, 'type', DEFAULT_AXIS_TYPE);
93
103
  const xCategories = get(axis, 'categories');
94
104
  const xTimestamps = get(axis, 'timestamps');
@@ -100,11 +110,28 @@ export function createXScale(axis, series, boundsWidth, hasZoomX) {
100
110
  case 'logarithmic': {
101
111
  const domain = getDomainDataXBySeries(series);
102
112
  if (isNumericalArrayData(domain)) {
103
- const [domainXMin, domainXMax] = extent(domain);
104
- const xMinValue = typeof xMin === 'number' ? xMin : domainXMin;
105
- const xMaxValue = typeof xMax === 'number' ? Math.max(xMax, domainXMax) : domainXMax;
113
+ const [xMinDomain, xMaxDomain] = extent(domain);
114
+ let xMin;
115
+ let xMax;
116
+ if (typeof xMinProps === 'number') {
117
+ xMin = xMinProps;
118
+ }
119
+ else {
120
+ const xMinDefault = getDefaultMinXAxisValue(series);
121
+ xMin = xMinDefault !== null && xMinDefault !== void 0 ? xMinDefault : xMinDomain;
122
+ }
123
+ if (typeof xMaxProps === 'number') {
124
+ xMax = xMaxProps;
125
+ }
126
+ else {
127
+ const xMaxDefault = getDefaultMaxXAxisValue(series);
128
+ xMax =
129
+ typeof xMaxDefault === 'number'
130
+ ? Math.max(xMaxDefault, xMaxDomain)
131
+ : xMaxDomain;
132
+ }
106
133
  const scaleFn = xType === 'logarithmic' ? scaleLog : scaleLinear;
107
- const scale = scaleFn().domain([xMinValue, xMaxValue]).range(xRange);
134
+ const scale = scaleFn().domain([xMin, xMax]).range(xRange);
108
135
  if (!hasZoomX) {
109
136
  scale.nice();
110
137
  }
@@ -129,7 +156,9 @@ export function createXScale(axis, series, boundsWidth, hasZoomX) {
129
156
  }
130
157
  case 'datetime': {
131
158
  if (xTimestamps) {
132
- const [xMin, xMax] = extent(xTimestamps);
159
+ const [xMinTimestamp, xMaxTimestamp] = extent(xTimestamps);
160
+ const xMin = typeof xMinProps === 'number' ? xMinProps : xMinTimestamp;
161
+ const xMax = typeof xMaxProps === 'number' ? xMaxProps : xMaxTimestamp;
133
162
  const scale = scaleUtc().domain([xMin, xMax]).range(xRange);
134
163
  if (!hasZoomX) {
135
164
  scale.nice();
@@ -139,7 +168,9 @@ export function createXScale(axis, series, boundsWidth, hasZoomX) {
139
168
  else {
140
169
  const domain = getDomainDataXBySeries(series);
141
170
  if (isNumericalArrayData(domain)) {
142
- const [xMin, xMax] = extent(domain);
171
+ const [xMinTimestamp, xMaxTimestamp] = extent(domain);
172
+ const xMin = typeof xMinProps === 'number' ? xMinProps : xMinTimestamp;
173
+ const xMax = typeof xMaxProps === 'number' ? xMaxProps : xMaxTimestamp;
143
174
  const scale = scaleUtc().domain([xMin, xMax]).range(xRange);
144
175
  if (!hasZoomX) {
145
176
  scale.nice();
@@ -1,6 +1,6 @@
1
1
  import get from 'lodash/get';
2
2
  import { DASH_STYLE, DEFAULT_AXIS_LABEL_FONT_SIZE, axisCrosshairDefaults, axisLabelsDefaults, xAxisTitleDefaults, } from '../../constants';
3
- import { CHART_SERIES_WITH_VOLUME_ON_X_AXIS, calculateCos, formatAxisTickLabel, getClosestPointsRange, getHorisontalSvgTextHeight, getLabelsSize, getMaxTickCount, getTicksCount, getXAxisItems, hasOverlappingLabels, wrapText, } from '../../utils';
3
+ import { calculateCos, formatAxisTickLabel, getClosestPointsRange, getHorisontalSvgTextHeight, getLabelsSize, getMaxTickCount, getTicksCount, getXAxisItems, hasOverlappingLabels, wrapText, } from '../../utils';
4
4
  import { createXScale } from '../useAxisScales';
5
5
  async function getLabelSettings({ axis, seriesData, width, autoRotation = true, }) {
6
6
  const scale = createXScale(axis, seriesData, width);
@@ -36,18 +36,6 @@ async function getLabelSettings({ axis, seriesData, width, autoRotation = true,
36
36
  const maxHeight = rotation ? calculateCos(rotation) * axis.labels.maxWidth : labelsHeight;
37
37
  return { height: Math.min(maxHeight, labelsHeight), rotation };
38
38
  }
39
- function getAxisMin(axis, series) {
40
- const min = axis === null || axis === void 0 ? void 0 : axis.min;
41
- if (typeof min === 'undefined' &&
42
- (series === null || series === void 0 ? void 0 : series.some((s) => CHART_SERIES_WITH_VOLUME_ON_X_AXIS.includes(s.type)))) {
43
- return series.reduce((minValue, s) => {
44
- // @ts-expect-error
45
- const minYValue = s.data.reduce((res, d) => Math.min(res, get(d, 'x', 0)), 0);
46
- return Math.min(minValue, minYValue);
47
- }, 0);
48
- }
49
- return min;
50
- }
51
39
  export const getPreparedXAxis = async ({ xAxis, seriesData, width, }) => {
52
40
  var _a;
53
41
  const titleText = get(xAxis, 'title.text', '');
@@ -92,7 +80,8 @@ export const getPreparedXAxis = async ({ xAxis, seriesData, width, }) => {
92
80
  align: get(xAxis, 'title.align', xAxisTitleDefaults.align),
93
81
  maxRowCount: get(xAxis, 'title.maxRowCount', xAxisTitleDefaults.maxRowCount),
94
82
  },
95
- min: getAxisMin(xAxis, seriesData),
83
+ min: get(xAxis, 'min'),
84
+ max: get(xAxis, 'max'),
96
85
  maxPadding: get(xAxis, 'maxPadding', 0.01),
97
86
  grid: {
98
87
  enabled: get(xAxis, 'grid.enabled', true),
@@ -1,6 +1,6 @@
1
1
  import get from 'lodash/get';
2
2
  import { DASH_STYLE, DEFAULT_AXIS_LABEL_FONT_SIZE, DEFAULT_AXIS_TYPE, axisCrosshairDefaults, axisLabelsDefaults, yAxisTitleDefaults, } from '../../constants';
3
- import { CHART_SERIES_WITH_VOLUME_ON_Y_AXIS, formatAxisTickLabel, getClosestPointsRange, getHorisontalSvgTextHeight, getLabelsSize, getScaleTicks, getWaterfallPointSubtotal, isAxisRelatedSeries, wrapText, } from '../../utils';
3
+ import { formatAxisTickLabel, getClosestPointsRange, getDefaultMinYAxisValue, getHorisontalSvgTextHeight, getLabelsSize, getScaleTicks, isAxisRelatedSeries, wrapText, } from '../../utils';
4
4
  import { createYScale } from '../useAxisScales';
5
5
  const getAxisLabelMaxWidth = async (args) => {
6
6
  const { axis, seriesData } = args;
@@ -23,26 +23,6 @@ const getAxisLabelMaxWidth = async (args) => {
23
23
  });
24
24
  return size.maxWidth;
25
25
  };
26
- function getAxisMin(axis, seriesData) {
27
- const min = axis === null || axis === void 0 ? void 0 : axis.min;
28
- if (typeof min === 'undefined' &&
29
- (seriesData === null || seriesData === void 0 ? void 0 : seriesData.some((s) => CHART_SERIES_WITH_VOLUME_ON_Y_AXIS.includes(s.type)))) {
30
- return seriesData.reduce((minValue, s) => {
31
- switch (s.type) {
32
- case 'waterfall': {
33
- const minSubTotal = s.data.reduce((res, d) => Math.min(res, getWaterfallPointSubtotal(d, s) || 0), 0);
34
- return Math.min(minValue, minSubTotal);
35
- }
36
- default: {
37
- // @ts-expect-error
38
- const minYValue = s.data.reduce((res, d) => Math.min(res, get(d, 'y', 0)), 0);
39
- return Math.min(minValue, minYValue);
40
- }
41
- }
42
- }, 0);
43
- }
44
- return min;
45
- }
46
26
  export const getPreparedYAxis = ({ seriesData, yAxis, height, }) => {
47
27
  const axisByPlot = [];
48
28
  const axisItems = yAxis || [{}];
@@ -51,7 +31,7 @@ export const getPreparedYAxis = ({ seriesData, yAxis, height, }) => {
51
31
  return Promise.resolve([]);
52
32
  }
53
33
  return Promise.all(axisItems.map(async (axisItem) => {
54
- var _a;
34
+ var _a, _b;
55
35
  const plotIndex = get(axisItem, 'plotIndex', 0);
56
36
  const firstPlotAxis = !axisByPlot[plotIndex];
57
37
  if (firstPlotAxis) {
@@ -104,11 +84,12 @@ export const getPreparedYAxis = ({ seriesData, yAxis, height, }) => {
104
84
  align: get(axisItem, 'title.align', yAxisTitleDefaults.align),
105
85
  maxRowCount: titleMaxRowsCount,
106
86
  },
107
- min: getAxisMin(axisItem, seriesData),
87
+ min: (_a = get(axisItem, 'min')) !== null && _a !== void 0 ? _a : getDefaultMinYAxisValue(seriesData),
88
+ max: get(axisItem, 'max'),
108
89
  maxPadding: get(axisItem, 'maxPadding', 0.05),
109
90
  grid: {
110
91
  enabled: get(axisItem, 'grid.enabled', firstPlotAxis ||
111
- (!firstPlotAxis && !((_a = axisByPlot[plotIndex][0].visible) !== null && _a !== void 0 ? _a : true))),
92
+ (!firstPlotAxis && !((_b = axisByPlot[plotIndex][0].visible) !== null && _b !== void 0 ? _b : true))),
112
93
  },
113
94
  ticks: {
114
95
  pixelInterval: get(axisItem, 'ticks.pixelInterval'),
@@ -7,6 +7,7 @@ type Args = {
7
7
  preparedData: PreparedAreaData[];
8
8
  seriesOptions: PreparedSeriesOptions;
9
9
  htmlLayout: HTMLElement | null;
10
+ clipPathId: string;
10
11
  };
11
12
  export declare const AreaSeriesShapes: (args: Args) => React.JSX.Element;
12
13
  export {};
@@ -7,22 +7,25 @@ import { getMarkerHaloVisibility, getMarkerVisibility, renderMarker, selectMarke
7
7
  import { setActiveState } from '../utils';
8
8
  const b = block('area');
9
9
  export const AreaSeriesShapes = (args) => {
10
- const { dispatcher, preparedData, seriesOptions, htmlLayout } = args;
10
+ const { dispatcher, preparedData, seriesOptions, htmlLayout, clipPathId } = args;
11
11
  const hoveredDataRef = React.useRef(null);
12
- const ref = React.useRef(null);
12
+ const plotRef = React.useRef(null);
13
+ const markersRef = React.useRef(null);
13
14
  React.useEffect(() => {
14
15
  var _a;
15
- if (!ref.current) {
16
+ if (!plotRef.current || !markersRef.current) {
16
17
  return () => { };
17
18
  }
18
- const svgElement = select(ref.current);
19
+ const plotSvgElement = select(plotRef.current);
20
+ const markersSvgElement = select(markersRef.current);
19
21
  const hoverOptions = get(seriesOptions, 'area.states.hover');
20
22
  const inactiveOptions = get(seriesOptions, 'area.states.inactive');
21
23
  const line = lineGenerator()
22
24
  .x((d) => d.x)
23
25
  .y((d) => d.y);
24
- svgElement.selectAll('*').remove();
25
- const shapeSelection = svgElement
26
+ plotSvgElement.selectAll('*').remove();
27
+ markersSvgElement.selectAll('*').remove();
28
+ const shapeSelection = plotSvgElement
26
29
  .selectAll('shape')
27
30
  .data(preparedData)
28
31
  .join('g')
@@ -53,7 +56,7 @@ export const AreaSeriesShapes = (args) => {
53
56
  if (!((_a = preparedData[0]) === null || _a === void 0 ? void 0 : _a.series.dataLabels.allowOverlap)) {
54
57
  dataLabels = filterOverlappingLabels(dataLabels);
55
58
  }
56
- const labelsSelection = svgElement
59
+ const labelsSelection = plotSvgElement
57
60
  .selectAll('text')
58
61
  .data(dataLabels)
59
62
  .join('text')
@@ -66,7 +69,7 @@ export const AreaSeriesShapes = (args) => {
66
69
  .style('font-weight', (d) => d.style.fontWeight || null)
67
70
  .style('fill', (d) => d.style.fontColor || null);
68
71
  const markers = preparedData.reduce((acc, d) => acc.concat(d.markers), []);
69
- const markerSelection = svgElement
72
+ const markerSelection = markersSvgElement
70
73
  .selectAll('marker')
71
74
  .data(markers)
72
75
  .join('g')
@@ -144,6 +147,7 @@ export const AreaSeriesShapes = (args) => {
144
147
  };
145
148
  }, [dispatcher, preparedData, seriesOptions]);
146
149
  return (React.createElement(React.Fragment, null,
147
- React.createElement("g", { ref: ref, className: b() }),
150
+ React.createElement("g", { ref: plotRef, className: b(), clipPath: `url(#${clipPathId})` }),
151
+ React.createElement("g", { ref: markersRef }),
148
152
  React.createElement(HtmlLayer, { preparedData: preparedData, htmlLayout: htmlLayout })));
149
153
  };
@@ -9,4 +9,5 @@ export declare const prepareAreaData: (args: {
9
9
  yAxis: PreparedAxis[];
10
10
  yScale: ChartScale[];
11
11
  boundsHeight: number;
12
+ isOutsideBounds: (x: number, y: number) => boolean;
12
13
  }) => Promise<PreparedAreaData[]>;
@@ -53,7 +53,7 @@ function getXValues(series, xAxis, xScale) {
53
53
  return sort(Array.from(xValues), ([_x, xValue]) => xValue);
54
54
  }
55
55
  export const prepareAreaData = async (args) => {
56
- const { series, xAxis, xScale, yAxis, yScale, boundsHeight: plotHeight } = args;
56
+ const { series, xAxis, xScale, yAxis, yScale, boundsHeight: plotHeight, isOutsideBounds } = args;
57
57
  const [_xMin, xRangeMax] = xScale.range();
58
58
  const xMax = xRangeMax / (1 - xAxis.maxPadding);
59
59
  const result = [];
@@ -142,6 +142,7 @@ export const prepareAreaData = async (args) => {
142
142
  point: p,
143
143
  active: true,
144
144
  hovered: false,
145
+ clipped: isOutsideBounds(p.x, p.y),
145
146
  }));
146
147
  }
147
148
  seriesStackData.push({
@@ -12,6 +12,7 @@ export type MarkerData = {
12
12
  point: PointData;
13
13
  active: boolean;
14
14
  hovered: boolean;
15
+ clipped: boolean;
15
16
  };
16
17
  export type PreparedAreaData = {
17
18
  id: string;
@@ -9,5 +9,6 @@ type Args = {
9
9
  preparedData: PreparedBarXData[];
10
10
  seriesOptions: PreparedSeriesOptions;
11
11
  htmlLayout: HTMLElement | null;
12
+ clipPathId: string;
12
13
  };
13
14
  export declare const BarXSeriesShapes: (args: Args) => React.JSX.Element;
@@ -8,7 +8,7 @@ export { prepareBarXData } from './prepare-data';
8
8
  export * from './types';
9
9
  const b = block('bar-x');
10
10
  export const BarXSeriesShapes = (args) => {
11
- const { dispatcher, preparedData, seriesOptions, htmlLayout } = args;
11
+ const { dispatcher, preparedData, seriesOptions, htmlLayout, clipPathId } = args;
12
12
  const hoveredDataRef = React.useRef(null);
13
13
  const ref = React.useRef(null);
14
14
  React.useEffect(() => {
@@ -110,6 +110,6 @@ export const BarXSeriesShapes = (args) => {
110
110
  };
111
111
  }, [dispatcher, preparedData, seriesOptions]);
112
112
  return (React.createElement(React.Fragment, null,
113
- React.createElement("g", { ref: ref, className: b() }),
113
+ React.createElement("g", { ref: ref, className: b(), clipPath: `url(#${clipPathId})` }),
114
114
  React.createElement(HtmlLayer, { preparedData: preparedData, htmlLayout: htmlLayout })));
115
115
  };
@@ -8,5 +8,6 @@ type Args = {
8
8
  preparedData: PreparedBarYData[];
9
9
  seriesOptions: PreparedSeriesOptions;
10
10
  htmlLayout: HTMLElement | null;
11
+ clipPathId: string;
11
12
  };
12
13
  export declare const BarYSeriesShapes: (args: Args) => React.JSX.Element;
@@ -7,7 +7,7 @@ import { getRectPath } from '../utils';
7
7
  export { prepareBarYData } from './prepare-data';
8
8
  const b = block('bar-y');
9
9
  export const BarYSeriesShapes = (args) => {
10
- const { dispatcher, preparedData, seriesOptions, htmlLayout } = args;
10
+ const { dispatcher, preparedData, seriesOptions, htmlLayout, clipPathId } = args;
11
11
  const hoveredDataRef = React.useRef(null);
12
12
  const ref = React.useRef(null);
13
13
  React.useEffect(() => {
@@ -99,6 +99,6 @@ export const BarYSeriesShapes = (args) => {
99
99
  };
100
100
  }, [dispatcher, preparedData, seriesOptions]);
101
101
  return (React.createElement(React.Fragment, null,
102
- React.createElement("g", { ref: ref, className: b() }),
102
+ React.createElement("g", { ref: ref, className: b(), clipPath: `url(#${clipPathId})` }),
103
103
  React.createElement(HtmlLayer, { preparedData: preparedData, htmlLayout: htmlLayout })));
104
104
  };
@@ -28,6 +28,8 @@ type Args = {
28
28
  yScale?: ChartScale[];
29
29
  split: PreparedSplit;
30
30
  htmlLayout: HTMLElement | null;
31
+ clipPathId: string;
32
+ isOutsideBounds: (x: number, y: number) => boolean;
31
33
  };
32
34
  export declare const useShapes: (args: Args) => {
33
35
  shapes: React.ReactElement<any, string | React.JSXElementConstructor<any>>[];