@gravity-ui/charts 1.34.7 → 1.34.9
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/dist/cjs/components/AxisX/AxisX.js +0 -1
- package/dist/cjs/components/AxisX/prepare-axis-data.js +4 -23
- package/dist/cjs/components/AxisX/types.d.ts +1 -9
- package/dist/cjs/components/AxisY/prepare-axis-title.js +3 -34
- package/dist/cjs/components/AxisY/types.d.ts +1 -9
- package/dist/cjs/components/ChartInner/index.js +6 -6
- package/dist/cjs/components/ChartInner/useChartInnerProps.d.ts +0 -1
- package/dist/cjs/components/ChartInner/useChartInnerProps.js +16 -6
- package/dist/cjs/components/ChartInner/useChartInnerState.d.ts +2 -3
- package/dist/cjs/components/ChartInner/useChartInnerState.js +14 -22
- package/dist/cjs/components/RangeSlider/index.d.ts +0 -1
- package/dist/cjs/components/RangeSlider/index.js +1 -8
- package/dist/cjs/components/types/index.d.ts +9 -0
- package/dist/cjs/components/types/index.js +1 -0
- package/dist/cjs/components/utils/axis-title.d.ts +6 -0
- package/dist/cjs/components/utils/axis-title.js +39 -0
- package/dist/cjs/components/{utils.d.ts → utils/index.d.ts} +3 -3
- package/dist/cjs/components/{utils.js → utils/index.js} +1 -1
- package/dist/cjs/hooks/useAxisScales/index.js +3 -1
- package/dist/cjs/hooks/useAxisScales/x-scale.d.ts +0 -6
- package/dist/cjs/hooks/useAxisScales/x-scale.js +12 -35
- package/dist/cjs/hooks/useAxisScales/y-scale.js +2 -7
- package/dist/cjs/hooks/useRangeSlider/types.d.ts +1 -1
- package/dist/cjs/hooks/useSeries/prepare-legend.js +2 -1
- package/dist/cjs/hooks/useShapes/area/index.js +13 -4
- package/dist/cjs/hooks/useShapes/area/prepare-data.js +3 -2
- package/dist/cjs/hooks/useShapes/bar-x/index.js +13 -4
- package/dist/cjs/hooks/useShapes/bar-x/prepare-data.js +15 -6
- package/dist/cjs/hooks/useShapes/line/index.js +13 -4
- package/dist/cjs/hooks/useShapes/line/prepare-data.js +4 -2
- package/dist/cjs/hooks/useShapes/waterfall/index.js +13 -4
- package/dist/cjs/utils/chart/get-closest-data.js +39 -35
- package/dist/cjs/utils/chart/zoom.d.ts +2 -1
- package/dist/cjs/utils/chart/zoom.js +9 -0
- package/dist/esm/components/AxisX/AxisX.js +0 -1
- package/dist/esm/components/AxisX/prepare-axis-data.js +4 -23
- package/dist/esm/components/AxisX/types.d.ts +1 -9
- package/dist/esm/components/AxisY/prepare-axis-title.js +3 -34
- package/dist/esm/components/AxisY/types.d.ts +1 -9
- package/dist/esm/components/ChartInner/index.js +6 -6
- package/dist/esm/components/ChartInner/useChartInnerProps.d.ts +0 -1
- package/dist/esm/components/ChartInner/useChartInnerProps.js +16 -6
- package/dist/esm/components/ChartInner/useChartInnerState.d.ts +2 -3
- package/dist/esm/components/ChartInner/useChartInnerState.js +14 -22
- package/dist/esm/components/RangeSlider/index.d.ts +0 -1
- package/dist/esm/components/RangeSlider/index.js +1 -8
- package/dist/esm/components/types/index.d.ts +9 -0
- package/dist/esm/components/types/index.js +1 -0
- package/dist/esm/components/utils/axis-title.d.ts +6 -0
- package/dist/esm/components/utils/axis-title.js +39 -0
- package/dist/esm/components/{utils.d.ts → utils/index.d.ts} +3 -3
- package/dist/esm/components/{utils.js → utils/index.js} +1 -1
- package/dist/esm/hooks/useAxisScales/index.js +3 -1
- package/dist/esm/hooks/useAxisScales/x-scale.d.ts +0 -6
- package/dist/esm/hooks/useAxisScales/x-scale.js +12 -35
- package/dist/esm/hooks/useAxisScales/y-scale.js +2 -7
- package/dist/esm/hooks/useRangeSlider/types.d.ts +1 -1
- package/dist/esm/hooks/useSeries/prepare-legend.js +2 -1
- package/dist/esm/hooks/useShapes/area/index.js +13 -4
- package/dist/esm/hooks/useShapes/area/prepare-data.js +3 -2
- package/dist/esm/hooks/useShapes/bar-x/index.js +13 -4
- package/dist/esm/hooks/useShapes/bar-x/prepare-data.js +15 -6
- package/dist/esm/hooks/useShapes/line/index.js +13 -4
- package/dist/esm/hooks/useShapes/line/prepare-data.js +4 -2
- package/dist/esm/hooks/useShapes/waterfall/index.js +13 -4
- package/dist/esm/utils/chart/get-closest-data.js +39 -35
- package/dist/esm/utils/chart/zoom.d.ts +2 -1
- package/dist/esm/utils/chart/zoom.js +9 -0
- package/package.json +1 -1
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { Dispatch } from 'd3';
|
|
3
|
-
import type {
|
|
3
|
+
import type { PreparedRangeSlider, PreparedTooltip, RangeSliderState, ZoomState } from '../../hooks';
|
|
4
4
|
type Props = {
|
|
5
5
|
dispatcher: Dispatch<object>;
|
|
6
|
-
preparedChart: PreparedChart;
|
|
7
6
|
preparedRangeSlider: PreparedRangeSlider;
|
|
8
7
|
tooltip?: PreparedTooltip;
|
|
9
8
|
};
|
|
@@ -14,7 +13,7 @@ export declare function useChartInnerState(props: Props): {
|
|
|
14
13
|
tooltipPinned: boolean;
|
|
15
14
|
togglePinTooltip: ((value: boolean, event: React.MouseEvent) => void) | undefined;
|
|
16
15
|
unpinTooltip: (() => void) | undefined;
|
|
17
|
-
updateRangeSliderState: (nextRangeSliderState?: RangeSliderState
|
|
16
|
+
updateRangeSliderState: (nextRangeSliderState?: RangeSliderState) => void;
|
|
18
17
|
updateZoomState: (nextZoomState: Partial<ZoomState>) => void;
|
|
19
18
|
zoomState: Partial<ZoomState>;
|
|
20
19
|
};
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import isEqual from 'lodash/isEqual';
|
|
3
|
-
import { ZOOM_TYPE } from '../../constants';
|
|
4
3
|
import { EventType, isMacintosh } from '../../utils';
|
|
5
|
-
const RANGE_SLIDER_SYNC_ZOOM_TYPES = [ZOOM_TYPE.X, ZOOM_TYPE.XY];
|
|
6
4
|
export function useChartInnerState(props) {
|
|
7
5
|
var _a, _b;
|
|
8
|
-
const { dispatcher,
|
|
6
|
+
const { dispatcher, preparedRangeSlider, tooltip } = props;
|
|
9
7
|
const [tooltipPinned, setTooltipPinned] = React.useState(false);
|
|
10
8
|
const [zoomState, setZoomState] = React.useState({});
|
|
11
9
|
const [rangeSliderState, setRangeSliderState] = React.useState();
|
|
@@ -13,7 +11,6 @@ export function useChartInnerState(props) {
|
|
|
13
11
|
const tooltipEnabled = tooltip === null || tooltip === void 0 ? void 0 : tooltip.enabled;
|
|
14
12
|
const tooltipPinEnabled = (_a = tooltip === null || tooltip === void 0 ? void 0 : tooltip.pin) === null || _a === void 0 ? void 0 : _a.enabled;
|
|
15
13
|
const modifierKey = (_b = tooltip === null || tooltip === void 0 ? void 0 : tooltip.pin) === null || _b === void 0 ? void 0 : _b.modifierKey;
|
|
16
|
-
const rangeSliderEnabled = preparedRangeSlider.enabled;
|
|
17
14
|
const togglePinTooltip = React.useCallback((value, event) => {
|
|
18
15
|
let resultValue = value;
|
|
19
16
|
if (value && modifierKey) {
|
|
@@ -36,16 +33,17 @@ export function useChartInnerState(props) {
|
|
|
36
33
|
const updateZoomState = React.useCallback((nextZoomState) => {
|
|
37
34
|
if (!isEqual(zoomState, nextZoomState)) {
|
|
38
35
|
setZoomState(nextZoomState);
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
36
|
+
// One-way sync: zoom → range slider. On full reset clear the slider state;
|
|
37
|
+
// on x-zoom change sync slider to the zoomed x range.
|
|
38
|
+
if (Object.keys(nextZoomState).length === 0) {
|
|
39
|
+
setRangeSliderState(undefined);
|
|
40
|
+
}
|
|
41
|
+
else if (nextZoomState.x !== undefined) {
|
|
42
|
+
setRangeSliderState({ min: nextZoomState.x[0], max: nextZoomState.x[1] });
|
|
45
43
|
}
|
|
46
44
|
}
|
|
47
|
-
}, [
|
|
48
|
-
const updateRangeSliderState = React.useCallback((nextRangeSliderState
|
|
45
|
+
}, [zoomState]);
|
|
46
|
+
const updateRangeSliderState = React.useCallback((nextRangeSliderState) => {
|
|
49
47
|
if (!isEqual(rangeSliderState, nextRangeSliderState)) {
|
|
50
48
|
setRangeSliderState(nextRangeSliderState
|
|
51
49
|
? {
|
|
@@ -53,17 +51,11 @@ export function useChartInnerState(props) {
|
|
|
53
51
|
min: nextRangeSliderState.min,
|
|
54
52
|
}
|
|
55
53
|
: undefined);
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
Object.keys(zoomState || {}).length > 0 &&
|
|
60
|
-
RANGE_SLIDER_SYNC_ZOOM_TYPES.includes(preparedChart.zoom.type)) {
|
|
61
|
-
setZoomState({
|
|
62
|
-
x: [nextRangeSliderState.min, nextRangeSliderState.max],
|
|
63
|
-
});
|
|
64
|
-
}
|
|
54
|
+
// Moving the slider clears zoom so the slider is always the sole source of truth.
|
|
55
|
+
// Zoom stays active only until the user explicitly touches the slider.
|
|
56
|
+
setZoomState({});
|
|
65
57
|
}
|
|
66
|
-
}, [
|
|
58
|
+
}, [rangeSliderState]);
|
|
67
59
|
return {
|
|
68
60
|
initialized,
|
|
69
61
|
rangeSliderState,
|
|
@@ -2,7 +2,6 @@ import React from 'react';
|
|
|
2
2
|
import type { RangeSliderProps } from '../../hooks';
|
|
3
3
|
import './styles.css';
|
|
4
4
|
export interface RangeSliderHandle {
|
|
5
|
-
getDomain: () => [number, number] | undefined;
|
|
6
5
|
resetState: () => void;
|
|
7
6
|
}
|
|
8
7
|
export declare const RangeSlider: React.MemoExoticComponent<React.ForwardRefExoticComponent<RangeSliderProps & React.RefAttributes<RangeSliderHandle>>>;
|
|
@@ -20,24 +20,17 @@ function RangeSliderComponent(props, forwardedRef) {
|
|
|
20
20
|
* in that case, the extra computations simply don't happen.
|
|
21
21
|
*
|
|
22
22
|
* Methods:
|
|
23
|
-
* - getDomain: returns the current X-axis domain (for synchronization with zoom)
|
|
24
23
|
* - resetState: resets the slider state to its initial value (these calculations
|
|
25
24
|
* are only possible within the component since it has access to the prepared series data)
|
|
26
25
|
*/
|
|
27
26
|
React.useImperativeHandle(forwardedRef, () => ({
|
|
28
|
-
getDomain: () => {
|
|
29
|
-
if (!xScale || isBandScale(xScale)) {
|
|
30
|
-
return undefined;
|
|
31
|
-
}
|
|
32
|
-
return xScale.domain().map(Number);
|
|
33
|
-
},
|
|
34
27
|
resetState: () => {
|
|
35
28
|
if (xScale && !isBandScale(xScale)) {
|
|
36
29
|
const initialRangeSliderState = getInitialRangeSliderState({
|
|
37
30
|
xScale,
|
|
38
31
|
defaultRange,
|
|
39
32
|
});
|
|
40
|
-
onUpdate(initialRangeSliderState
|
|
33
|
+
onUpdate(initialRangeSliderState);
|
|
41
34
|
}
|
|
42
35
|
},
|
|
43
36
|
}), [defaultRange, onUpdate, xScale]);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { getTextSizeFn, getTextWithElipsis, wrapText } from '../../utils';
|
|
2
|
+
export async function getMultilineTitleContentRows({ axis, titleMaxWidth, }) {
|
|
3
|
+
const titleContent = [];
|
|
4
|
+
const getTitleTextSize = getTextSizeFn({ style: axis.title.style });
|
|
5
|
+
let titleTextRows = await wrapText({
|
|
6
|
+
text: axis.title.text,
|
|
7
|
+
style: axis.title.style,
|
|
8
|
+
width: titleMaxWidth,
|
|
9
|
+
getTextSize: getTitleTextSize,
|
|
10
|
+
});
|
|
11
|
+
titleTextRows = titleTextRows.reduce((acc, row, index) => {
|
|
12
|
+
if (index < axis.title.maxRowCount) {
|
|
13
|
+
acc.push(row);
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
acc[axis.title.maxRowCount - 1].text += row.text;
|
|
17
|
+
}
|
|
18
|
+
return acc;
|
|
19
|
+
}, []);
|
|
20
|
+
for (let i = 0; i < titleTextRows.length; i++) {
|
|
21
|
+
const textRow = titleTextRows[i];
|
|
22
|
+
let textRowContent = textRow.text.trim();
|
|
23
|
+
if (i === titleTextRows.length - 1) {
|
|
24
|
+
textRowContent = await getTextWithElipsis({
|
|
25
|
+
text: textRowContent,
|
|
26
|
+
maxWidth: titleMaxWidth,
|
|
27
|
+
getTextWidth: async (s) => (await getTitleTextSize(s)).width,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
const textRowSize = await getTitleTextSize(textRowContent);
|
|
31
|
+
titleContent.push({
|
|
32
|
+
text: textRowContent,
|
|
33
|
+
x: 0,
|
|
34
|
+
y: textRow.y,
|
|
35
|
+
size: textRowSize,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return titleContent;
|
|
39
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { DashStyle } from '
|
|
2
|
-
import type { ChartScaleLinear, ChartScaleTime } from '
|
|
3
|
-
import type { ChartAxisRangeSlider } from '
|
|
1
|
+
import type { DashStyle } from '../../constants';
|
|
2
|
+
import type { ChartScaleLinear, ChartScaleTime } from '../../hooks';
|
|
3
|
+
import type { ChartAxisRangeSlider } from '../../types';
|
|
4
4
|
export declare function getInitialRangeSliderState(args: {
|
|
5
5
|
xScale: ChartScaleLinear | ChartScaleTime;
|
|
6
6
|
defaultRange?: ChartAxisRangeSlider['defaultRange'];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { duration } from '@gravity-ui/date-utils';
|
|
2
2
|
import { line as lineGenerator } from 'd3';
|
|
3
3
|
import { select } from 'd3-selection';
|
|
4
|
-
import { getLineDashArray, isTimeScale } from '
|
|
4
|
+
import { getLineDashArray, isTimeScale } from '../../utils';
|
|
5
5
|
export function getInitialRangeSliderState(args) {
|
|
6
6
|
const { defaultRange, xScale } = args;
|
|
7
7
|
let minRange;
|
|
@@ -9,7 +9,9 @@ export { createXScale } from './x-scale';
|
|
|
9
9
|
export { createYScale } from './y-scale';
|
|
10
10
|
const createScales = (args) => {
|
|
11
11
|
const { boundsWidth, boundsHeight, isRangeSlider, rangeSliderState, series, split, xAxis, yAxis, zoomState, } = args;
|
|
12
|
-
|
|
12
|
+
// For range slider: always use all series regardless of visibility so the slider domain
|
|
13
|
+
// stays stable when the user hides/shows series via the legend.
|
|
14
|
+
let visibleSeries = isRangeSlider ? series : getOnlyVisibleSeries(series);
|
|
13
15
|
// Reassign to all series in case of all series unselected,
|
|
14
16
|
// otherwise we will get an empty space without grid
|
|
15
17
|
visibleSeries = visibleSeries.length === 0 ? series : visibleSeries;
|
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
import type { PreparedAxis, PreparedSeries, RangeSliderState } from '../../hooks';
|
|
2
2
|
import type { ChartAxis, ChartSeries } from '../../types';
|
|
3
|
-
export declare function getXMaxDomainResult(args: {
|
|
4
|
-
xMaxDomain: number;
|
|
5
|
-
xMaxProps?: number;
|
|
6
|
-
xMaxRangeSlider?: number;
|
|
7
|
-
xMaxZoom?: number;
|
|
8
|
-
}): number;
|
|
9
3
|
export declare function createXScale(args: {
|
|
10
4
|
axis: PreparedAxis | ChartAxis;
|
|
11
5
|
boundsWidth: number;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { extent, scaleBand, scaleLinear, scaleLog, scaleUtc } from 'd3';
|
|
2
2
|
import get from 'lodash/get';
|
|
3
3
|
import { DEFAULT_AXIS_TYPE, SERIES_TYPE } from '../../constants';
|
|
4
|
-
import {
|
|
4
|
+
import { getDefaultMaxXAxisValue, getDefaultMinXAxisValue, getDomainDataXBySeries, getEffectiveXRange, } from '../../utils';
|
|
5
5
|
import { getBandSize } from '../utils/get-band-size';
|
|
6
6
|
import { checkIsPointDomain, filterCategoriesByVisibleSeries, getMinMaxPropsOrState, hasOnlyMarkerSeries, validateArrayData, } from './utils';
|
|
7
7
|
const X_AXIS_ZOOM_PADDING = 0.02;
|
|
@@ -27,23 +27,6 @@ function isSeriesWithXAxisOffset(series) {
|
|
|
27
27
|
const types = [SERIES_TYPE.Heatmap, SERIES_TYPE.BarX];
|
|
28
28
|
return series.some((s) => types.includes(s.type));
|
|
29
29
|
}
|
|
30
|
-
export function getXMaxDomainResult(args) {
|
|
31
|
-
const { xMaxDomain, xMaxProps, xMaxRangeSlider, xMaxZoom } = args;
|
|
32
|
-
let xMaxDomainResult = xMaxDomain;
|
|
33
|
-
// When xMaxRangeSlider is provided, we use it directly without considering xMaxDomain.
|
|
34
|
-
// This is intentional: the range slider needs to display the chart's maxPadding area,
|
|
35
|
-
// which would be clipped if we constrained it to xMaxDomain.
|
|
36
|
-
if (typeof xMaxRangeSlider === 'number') {
|
|
37
|
-
xMaxDomainResult = xMaxRangeSlider;
|
|
38
|
-
}
|
|
39
|
-
else if (typeof xMaxZoom === 'number' && xMaxZoom < xMaxDomain) {
|
|
40
|
-
xMaxDomainResult = xMaxZoom;
|
|
41
|
-
}
|
|
42
|
-
else if (typeof xMaxProps === 'number' && xMaxProps < xMaxDomain) {
|
|
43
|
-
xMaxDomainResult = xMaxProps;
|
|
44
|
-
}
|
|
45
|
-
return xMaxDomainResult;
|
|
46
|
-
}
|
|
47
30
|
function getXScaleRange({ boundsWidth, hasZoomX }) {
|
|
48
31
|
const xAxisZoomPadding = boundsWidth * X_AXIS_ZOOM_PADDING;
|
|
49
32
|
const xRange = [0, boundsWidth];
|
|
@@ -54,22 +37,17 @@ function getXScaleRange({ boundsWidth, hasZoomX }) {
|
|
|
54
37
|
// eslint-disable-next-line complexity
|
|
55
38
|
export function createXScale(args) {
|
|
56
39
|
const { axis, boundsWidth, series, rangeSliderState, zoomStateX } = args;
|
|
40
|
+
const effectiveX = getEffectiveXRange(zoomStateX, rangeSliderState);
|
|
41
|
+
const effectiveXMin = effectiveX === null || effectiveX === void 0 ? void 0 : effectiveX[0];
|
|
42
|
+
const effectiveXMax = effectiveX === null || effectiveX === void 0 ? void 0 : effectiveX[1];
|
|
57
43
|
const [xMinPropsOrState, xMaxPropsOrState] = getMinMaxPropsOrState({
|
|
58
44
|
axis,
|
|
59
|
-
maxValues: [
|
|
60
|
-
minValues: [
|
|
45
|
+
maxValues: [effectiveXMax],
|
|
46
|
+
minValues: [effectiveXMin],
|
|
61
47
|
});
|
|
62
48
|
const xType = get(axis, 'type', DEFAULT_AXIS_TYPE);
|
|
63
49
|
const hasZoomX = Boolean(zoomStateX);
|
|
64
|
-
|
|
65
|
-
if (rangeSliderState && xCategories) {
|
|
66
|
-
xCategories = getAxisCategories({
|
|
67
|
-
categories: xCategories,
|
|
68
|
-
min: rangeSliderState.min,
|
|
69
|
-
max: rangeSliderState.max,
|
|
70
|
-
order: axis.order,
|
|
71
|
-
});
|
|
72
|
-
}
|
|
50
|
+
const xCategories = get(axis, 'categories');
|
|
73
51
|
const maxPadding = get(axis, 'maxPadding', 0);
|
|
74
52
|
const xAxisMaxPadding = boundsWidth * maxPadding + calculateXAxisPadding(series);
|
|
75
53
|
const range = getXScaleRange({
|
|
@@ -193,12 +171,11 @@ export function createXScale(args) {
|
|
|
193
171
|
!isPointDomain
|
|
194
172
|
? xMinPropsOrState
|
|
195
173
|
: xMinTimestamp;
|
|
196
|
-
const xMax =
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
});
|
|
174
|
+
const xMax = typeof xMaxPropsOrState === 'number' &&
|
|
175
|
+
xMaxPropsOrState < xMaxTimestamp &&
|
|
176
|
+
!isPointDomain
|
|
177
|
+
? xMaxPropsOrState
|
|
178
|
+
: xMaxTimestamp;
|
|
202
179
|
domain = [xMin, xMax];
|
|
203
180
|
const scale = scaleUtc().domain(domain).range(range);
|
|
204
181
|
let offsetMin = 0;
|
|
@@ -141,14 +141,9 @@ export function createYScale(args) {
|
|
|
141
141
|
}
|
|
142
142
|
if (hasNumberAndNullValues) {
|
|
143
143
|
const [yMinDomain, yMaxDomain] = extent(domain);
|
|
144
|
-
const
|
|
145
|
-
? checkIsPointDomain([yMinDomain, yMaxDomain])
|
|
146
|
-
: false;
|
|
147
|
-
const yMin = typeof yMinPropsOrState === 'number' && !isPointDomain
|
|
148
|
-
? yMinPropsOrState
|
|
149
|
-
: yMinDomain;
|
|
144
|
+
const yMin = typeof yMinPropsOrState === 'number' ? yMinPropsOrState : yMinDomain;
|
|
150
145
|
let yMax;
|
|
151
|
-
if (typeof yMaxPropsOrState === 'number'
|
|
146
|
+
if (typeof yMaxPropsOrState === 'number') {
|
|
152
147
|
yMax = yMaxPropsOrState;
|
|
153
148
|
}
|
|
154
149
|
else {
|
|
@@ -13,7 +13,7 @@ export interface RangeSliderProps {
|
|
|
13
13
|
boundsWidth: number;
|
|
14
14
|
height: number;
|
|
15
15
|
htmlLayout: HTMLElement | null;
|
|
16
|
-
onUpdate: (nextRangeSliderState?: RangeSliderState
|
|
16
|
+
onUpdate: (nextRangeSliderState?: RangeSliderState) => void;
|
|
17
17
|
preparedChart: PreparedChart;
|
|
18
18
|
preparedLegend: PreparedLegend | null;
|
|
19
19
|
preparedRangeSlider: PreparedRangeSlider;
|
|
@@ -8,7 +8,8 @@ import { getDefaultColorStops, getDomainForContinuousColorScale, getLabelsSize }
|
|
|
8
8
|
export async function getPreparedLegend(args) {
|
|
9
9
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
10
10
|
const { legend, series } = args;
|
|
11
|
-
const
|
|
11
|
+
const seriesWithEnabledLegend = series.filter((s) => { var _a; return ((_a = s.legend) === null || _a === void 0 ? void 0 : _a.enabled) !== false; });
|
|
12
|
+
const enabled = Boolean(typeof (legend === null || legend === void 0 ? void 0 : legend.enabled) === 'boolean' ? legend === null || legend === void 0 ? void 0 : legend.enabled : seriesWithEnabledLegend.length > 1);
|
|
12
13
|
const defaultItemStyle = clone(legendDefaults.itemStyle);
|
|
13
14
|
const itemStyle = get(legend, 'itemStyle');
|
|
14
15
|
const computedItemStyle = merge(defaultItemStyle, itemStyle);
|
|
@@ -11,8 +11,10 @@ export const AreaSeriesShapes = (args) => {
|
|
|
11
11
|
const hoveredDataRef = React.useRef(null);
|
|
12
12
|
const plotRef = React.useRef(null);
|
|
13
13
|
const markersRef = React.useRef(null);
|
|
14
|
+
const allowOverlapDataLabels = React.useMemo(() => {
|
|
15
|
+
return preparedData.some((d) => d === null || d === void 0 ? void 0 : d.series.dataLabels.allowOverlap);
|
|
16
|
+
}, [preparedData]);
|
|
14
17
|
React.useEffect(() => {
|
|
15
|
-
var _a;
|
|
16
18
|
if (!plotRef.current || !markersRef.current) {
|
|
17
19
|
return () => { };
|
|
18
20
|
}
|
|
@@ -55,7 +57,7 @@ export const AreaSeriesShapes = (args) => {
|
|
|
55
57
|
let dataLabels = preparedData.reduce((acc, d) => {
|
|
56
58
|
return acc.concat(d.labels);
|
|
57
59
|
}, []);
|
|
58
|
-
if (!
|
|
60
|
+
if (!allowOverlapDataLabels) {
|
|
59
61
|
dataLabels = filterOverlappingLabels(dataLabels);
|
|
60
62
|
}
|
|
61
63
|
const labelsSelection = plotSvgElement
|
|
@@ -147,9 +149,16 @@ export const AreaSeriesShapes = (args) => {
|
|
|
147
149
|
return () => {
|
|
148
150
|
dispatcher === null || dispatcher === void 0 ? void 0 : dispatcher.on('hover-shape.area', null);
|
|
149
151
|
};
|
|
150
|
-
}, [dispatcher, preparedData, seriesOptions]);
|
|
152
|
+
}, [allowOverlapDataLabels, dispatcher, preparedData, seriesOptions]);
|
|
153
|
+
const htmlLayerData = React.useMemo(() => {
|
|
154
|
+
const items = preparedData.map((d) => d === null || d === void 0 ? void 0 : d.htmlElements).flat();
|
|
155
|
+
if (allowOverlapDataLabels) {
|
|
156
|
+
return { htmlElements: items };
|
|
157
|
+
}
|
|
158
|
+
return { htmlElements: filterOverlappingLabels(items) };
|
|
159
|
+
}, [allowOverlapDataLabels, preparedData]);
|
|
151
160
|
return (React.createElement(React.Fragment, null,
|
|
152
161
|
React.createElement("g", { ref: plotRef, className: b(), clipPath: `url(#${clipPathId})` }),
|
|
153
162
|
React.createElement("g", { ref: markersRef }),
|
|
154
|
-
React.createElement(HtmlLayer, { preparedData:
|
|
163
|
+
React.createElement(HtmlLayer, { preparedData: htmlLayerData, htmlLayout: htmlLayout })));
|
|
155
164
|
};
|
|
@@ -27,14 +27,14 @@ function getXValues(series, xAxis, xScale) {
|
|
|
27
27
|
}
|
|
28
28
|
return Array.from(xValues);
|
|
29
29
|
}
|
|
30
|
-
async function prepareDataLabels({ series, points, xMax, yAxisTop, }) {
|
|
30
|
+
async function prepareDataLabels({ series, points, xMax, yAxisTop, isOutsideBounds, }) {
|
|
31
31
|
var _a;
|
|
32
32
|
const svgLabels = [];
|
|
33
33
|
const htmlLabels = [];
|
|
34
34
|
const getTextSize = getTextSizeFn({ style: series.dataLabels.style });
|
|
35
35
|
for (let pointsIndex = 0; pointsIndex < points.length; pointsIndex++) {
|
|
36
36
|
const point = points[pointsIndex];
|
|
37
|
-
if (point.y === null) {
|
|
37
|
+
if (point.y === null || isOutsideBounds(point.x, point.y)) {
|
|
38
38
|
continue;
|
|
39
39
|
}
|
|
40
40
|
const text = getFormattedValue(Object.assign({ value: (_a = point.data.label) !== null && _a !== void 0 ? _a : point.data.y }, series.dataLabels));
|
|
@@ -281,6 +281,7 @@ export const prepareAreaData = async (args) => {
|
|
|
281
281
|
points: item.points,
|
|
282
282
|
xMax,
|
|
283
283
|
yAxisTop: itemYAxisTop,
|
|
284
|
+
isOutsideBounds,
|
|
284
285
|
});
|
|
285
286
|
item.labels.push(...labelsData.svgLabels);
|
|
286
287
|
item.htmlElements.push(...labelsData.htmlLabels);
|
|
@@ -11,8 +11,10 @@ export const BarXSeriesShapes = (args) => {
|
|
|
11
11
|
const { dispatcher, preparedData, seriesOptions, htmlLayout, clipPathId } = args;
|
|
12
12
|
const hoveredDataRef = React.useRef(null);
|
|
13
13
|
const ref = React.useRef(null);
|
|
14
|
+
const allowOverlapDataLabels = React.useMemo(() => {
|
|
15
|
+
return preparedData.some((d) => d === null || d === void 0 ? void 0 : d.series.dataLabels.allowOverlap);
|
|
16
|
+
}, [preparedData]);
|
|
14
17
|
React.useEffect(() => {
|
|
15
|
-
var _a;
|
|
16
18
|
if (!ref.current) {
|
|
17
19
|
return () => { };
|
|
18
20
|
}
|
|
@@ -46,7 +48,7 @@ export const BarXSeriesShapes = (args) => {
|
|
|
46
48
|
.attr('opacity', (d) => d.opacity)
|
|
47
49
|
.attr('cursor', (d) => d.series.cursor);
|
|
48
50
|
let dataLabels = preparedData.map((d) => d.label).filter(Boolean);
|
|
49
|
-
if (!
|
|
51
|
+
if (!allowOverlapDataLabels) {
|
|
50
52
|
dataLabels = filterOverlappingLabels(dataLabels);
|
|
51
53
|
}
|
|
52
54
|
const labelSelection = svgElement
|
|
@@ -108,8 +110,15 @@ export const BarXSeriesShapes = (args) => {
|
|
|
108
110
|
return () => {
|
|
109
111
|
dispatcher === null || dispatcher === void 0 ? void 0 : dispatcher.on('hover-shape.bar-x', null);
|
|
110
112
|
};
|
|
111
|
-
}, [dispatcher, preparedData, seriesOptions]);
|
|
113
|
+
}, [allowOverlapDataLabels, dispatcher, preparedData, seriesOptions]);
|
|
114
|
+
const htmlLayerData = React.useMemo(() => {
|
|
115
|
+
const items = preparedData.map((d) => d === null || d === void 0 ? void 0 : d.htmlElements).flat();
|
|
116
|
+
if (allowOverlapDataLabels) {
|
|
117
|
+
return { htmlElements: items };
|
|
118
|
+
}
|
|
119
|
+
return { htmlElements: filterOverlappingLabels(items) };
|
|
120
|
+
}, [allowOverlapDataLabels, preparedData]);
|
|
112
121
|
return (React.createElement(React.Fragment, null,
|
|
113
122
|
React.createElement("g", { ref: ref, className: b(), clipPath: `url(#${clipPathId})` }),
|
|
114
|
-
React.createElement(HtmlLayer, { preparedData:
|
|
123
|
+
React.createElement(HtmlLayer, { preparedData: htmlLayerData, htmlLayout: htmlLayout })));
|
|
115
124
|
};
|
|
@@ -5,7 +5,7 @@ import { getFormattedValue } from '../../../utils/chart/format';
|
|
|
5
5
|
import { getSeriesStackId } from '../../useSeries/utils';
|
|
6
6
|
import { getBarXLayout } from '../../utils/bar-x';
|
|
7
7
|
const isSeriesDataValid = (d) => d.y !== null;
|
|
8
|
-
async function getLabelData(d) {
|
|
8
|
+
async function getLabelData(d, xMax) {
|
|
9
9
|
var _a;
|
|
10
10
|
if (!d.series.dataLabels.enabled) {
|
|
11
11
|
return undefined;
|
|
@@ -22,10 +22,10 @@ async function getLabelData(d) {
|
|
|
22
22
|
if (d.series.dataLabels.inside) {
|
|
23
23
|
y = d.y + d.height / 2;
|
|
24
24
|
}
|
|
25
|
-
const
|
|
25
|
+
const centerX = Math.min(xMax - width / 2, Math.max(width / 2, d.x + d.width / 2));
|
|
26
26
|
return {
|
|
27
27
|
text,
|
|
28
|
-
x: html ?
|
|
28
|
+
x: html ? centerX - width / 2 : centerX,
|
|
29
29
|
y: html ? y - height : y,
|
|
30
30
|
style,
|
|
31
31
|
size: { width, height },
|
|
@@ -35,7 +35,7 @@ async function getLabelData(d) {
|
|
|
35
35
|
}
|
|
36
36
|
// eslint-disable-next-line complexity
|
|
37
37
|
export const prepareBarXData = async (args) => {
|
|
38
|
-
var _a, _b, _c, _d;
|
|
38
|
+
var _a, _b, _c, _d, _e;
|
|
39
39
|
const { series, seriesOptions, xAxis, xScale, yAxis, yScale, boundsHeight: plotHeight, split, isRangeSlider, } = args;
|
|
40
40
|
const stackGap = seriesOptions['bar-x'].stackGap;
|
|
41
41
|
const categories = (_a = xAxis === null || xAxis === void 0 ? void 0 : xAxis.categories) !== null && _a !== void 0 ? _a : [];
|
|
@@ -176,10 +176,19 @@ export const prepareBarXData = async (args) => {
|
|
|
176
176
|
}
|
|
177
177
|
}
|
|
178
178
|
}
|
|
179
|
+
const [_xMin, xRangeMax] = xScale.range();
|
|
180
|
+
const xMax = xRangeMax;
|
|
179
181
|
for (let i = 0; i < result.length; i++) {
|
|
180
182
|
const barData = result[i];
|
|
181
|
-
|
|
182
|
-
|
|
183
|
+
const isBarOutsideBounds = barData.x + barData.width <= 0 ||
|
|
184
|
+
barData.x >= xMax ||
|
|
185
|
+
barData.y + barData.height <= 0 ||
|
|
186
|
+
barData.y >= plotHeight;
|
|
187
|
+
const isZeroValue = ((_e = barData.data.y) !== null && _e !== void 0 ? _e : 0) === 0;
|
|
188
|
+
if (barData.series.dataLabels.enabled &&
|
|
189
|
+
!isRangeSlider &&
|
|
190
|
+
(!isBarOutsideBounds || isZeroValue)) {
|
|
191
|
+
const label = await getLabelData(barData, xMax);
|
|
183
192
|
if (barData.series.dataLabels.html && label) {
|
|
184
193
|
barData.htmlElements.push({
|
|
185
194
|
x: label.x,
|
|
@@ -11,8 +11,10 @@ export const LineSeriesShapes = (args) => {
|
|
|
11
11
|
const hoveredDataRef = React.useRef(null);
|
|
12
12
|
const plotRef = React.useRef(null);
|
|
13
13
|
const markersRef = React.useRef(null);
|
|
14
|
+
const allowOverlapDataLabels = React.useMemo(() => {
|
|
15
|
+
return preparedData.some((d) => d === null || d === void 0 ? void 0 : d.series.dataLabels.allowOverlap);
|
|
16
|
+
}, [preparedData]);
|
|
14
17
|
React.useEffect(() => {
|
|
15
|
-
var _a;
|
|
16
18
|
if (!plotRef.current || !markersRef.current) {
|
|
17
19
|
return () => { };
|
|
18
20
|
}
|
|
@@ -42,7 +44,7 @@ export const LineSeriesShapes = (args) => {
|
|
|
42
44
|
let dataLabels = preparedData.reduce((acc, d) => {
|
|
43
45
|
return acc.concat(d.labels);
|
|
44
46
|
}, []);
|
|
45
|
-
if (!
|
|
47
|
+
if (!allowOverlapDataLabels) {
|
|
46
48
|
dataLabels = filterOverlappingLabels(dataLabels);
|
|
47
49
|
}
|
|
48
50
|
const labelsSelection = plotSvgElement
|
|
@@ -133,9 +135,16 @@ export const LineSeriesShapes = (args) => {
|
|
|
133
135
|
return () => {
|
|
134
136
|
dispatcher === null || dispatcher === void 0 ? void 0 : dispatcher.on('hover-shape.line', null);
|
|
135
137
|
};
|
|
136
|
-
}, [dispatcher, preparedData, seriesOptions]);
|
|
138
|
+
}, [allowOverlapDataLabels, dispatcher, preparedData, seriesOptions]);
|
|
139
|
+
const htmlLayerData = React.useMemo(() => {
|
|
140
|
+
const items = preparedData.map((d) => d === null || d === void 0 ? void 0 : d.htmlElements).flat();
|
|
141
|
+
if (allowOverlapDataLabels) {
|
|
142
|
+
return { htmlElements: items };
|
|
143
|
+
}
|
|
144
|
+
return { htmlElements: filterOverlappingLabels(items) };
|
|
145
|
+
}, [allowOverlapDataLabels, preparedData]);
|
|
137
146
|
return (React.createElement(React.Fragment, null,
|
|
138
147
|
React.createElement("g", { ref: plotRef, className: b(), clipPath: `url(#${clipPathId})` }),
|
|
139
148
|
React.createElement("g", { ref: markersRef }),
|
|
140
|
-
React.createElement(HtmlLayer, { preparedData:
|
|
149
|
+
React.createElement(HtmlLayer, { preparedData: htmlLayerData, htmlLayout: htmlLayout })));
|
|
141
150
|
};
|
|
@@ -49,7 +49,7 @@ export const prepareLineData = async (args) => {
|
|
|
49
49
|
if (s.dataLabels.enabled && !isRangeSlider) {
|
|
50
50
|
if (s.dataLabels.html) {
|
|
51
51
|
const list = await Promise.all(points.reduce((result, p) => {
|
|
52
|
-
if (p.y === null) {
|
|
52
|
+
if (p.y === null || p.x === null || isOutsideBounds(p.x, p.y)) {
|
|
53
53
|
return result;
|
|
54
54
|
}
|
|
55
55
|
result.push(getHtmlLabel(p, s, xMax));
|
|
@@ -61,7 +61,9 @@ export const prepareLineData = async (args) => {
|
|
|
61
61
|
const getTextSize = getTextSizeFn({ style: s.dataLabels.style });
|
|
62
62
|
for (let index = 0; index < points.length; index++) {
|
|
63
63
|
const point = points[index];
|
|
64
|
-
if (point.y !== null &&
|
|
64
|
+
if (point.y !== null &&
|
|
65
|
+
point.x !== null &&
|
|
66
|
+
!isOutsideBounds(point.x, point.y)) {
|
|
65
67
|
const labelValue = (_b = point.data.label) !== null && _b !== void 0 ? _b : point.data.y;
|
|
66
68
|
const text = getFormattedValue(Object.assign({ value: labelValue }, s.dataLabels));
|
|
67
69
|
const labelSize = await getTextSize(text);
|
|
@@ -12,8 +12,10 @@ export const WaterfallSeriesShapes = (args) => {
|
|
|
12
12
|
const hoveredDataRef = React.useRef(null);
|
|
13
13
|
const ref = React.useRef(null);
|
|
14
14
|
const connectorSelector = `.${b('connector')}`;
|
|
15
|
+
const allowOverlapDataLabels = React.useMemo(() => {
|
|
16
|
+
return preparedData.some((d) => d === null || d === void 0 ? void 0 : d.series.dataLabels.allowOverlap);
|
|
17
|
+
}, [preparedData]);
|
|
15
18
|
React.useEffect(() => {
|
|
16
|
-
var _a;
|
|
17
19
|
if (!ref.current) {
|
|
18
20
|
return () => { };
|
|
19
21
|
}
|
|
@@ -34,7 +36,7 @@ export const WaterfallSeriesShapes = (args) => {
|
|
|
34
36
|
.attr('opacity', (d) => d.opacity)
|
|
35
37
|
.attr('cursor', (d) => d.series.cursor);
|
|
36
38
|
let dataLabels = preparedData.map((d) => d.label).filter(Boolean);
|
|
37
|
-
if (!
|
|
39
|
+
if (!allowOverlapDataLabels) {
|
|
38
40
|
dataLabels = filterOverlappingLabels(dataLabels);
|
|
39
41
|
}
|
|
40
42
|
const labelSelection = svgElement
|
|
@@ -125,8 +127,15 @@ export const WaterfallSeriesShapes = (args) => {
|
|
|
125
127
|
return () => {
|
|
126
128
|
dispatcher === null || dispatcher === void 0 ? void 0 : dispatcher.on('hover-shape.waterfall', null);
|
|
127
129
|
};
|
|
128
|
-
}, [connectorSelector, dispatcher, preparedData, seriesOptions]);
|
|
130
|
+
}, [allowOverlapDataLabels, connectorSelector, dispatcher, preparedData, seriesOptions]);
|
|
131
|
+
const htmlLayerData = React.useMemo(() => {
|
|
132
|
+
const items = preparedData.map((d) => d === null || d === void 0 ? void 0 : d.htmlElements).flat();
|
|
133
|
+
if (allowOverlapDataLabels) {
|
|
134
|
+
return { htmlElements: items };
|
|
135
|
+
}
|
|
136
|
+
return { htmlElements: filterOverlappingLabels(items) };
|
|
137
|
+
}, [allowOverlapDataLabels, preparedData]);
|
|
129
138
|
return (React.createElement(React.Fragment, null,
|
|
130
139
|
React.createElement("g", { ref: ref, className: b(), clipPath: `url(#${clipPathId})` }),
|
|
131
|
-
React.createElement(HtmlLayer, { preparedData:
|
|
140
|
+
React.createElement(HtmlLayer, { preparedData: htmlLayerData, htmlLayout: htmlLayout })));
|
|
132
141
|
};
|