@gravity-ui/charts 1.34.7 → 1.34.8
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/hooks/useAxisScales/y-scale.js +2 -7
- 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/esm/hooks/useAxisScales/y-scale.js +2 -7
- 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/package.json +1 -1
|
@@ -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 {
|
|
@@ -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
|
};
|
|
@@ -47,19 +47,53 @@ export function getClosestPoints(args) {
|
|
|
47
47
|
const [pointerX, pointerY] = position;
|
|
48
48
|
const result = [];
|
|
49
49
|
const groups = groupBy(shapesData, getSeriesType);
|
|
50
|
+
const closestPointsByXValue = [];
|
|
50
51
|
// eslint-disable-next-line complexity
|
|
51
52
|
Object.entries(groups).forEach(([seriesType, list]) => {
|
|
52
53
|
var _a, _b, _c, _d, _e;
|
|
53
54
|
switch (seriesType) {
|
|
55
|
+
case 'line': {
|
|
56
|
+
const linePoints = list.reduce((acc, d) => {
|
|
57
|
+
acc.push(...d.points.reduce((accPoints, p) => {
|
|
58
|
+
if (p.y !== null && p.x !== null) {
|
|
59
|
+
accPoints.push({
|
|
60
|
+
data: p.data,
|
|
61
|
+
series: p.series,
|
|
62
|
+
x: p.x,
|
|
63
|
+
y0: p.y,
|
|
64
|
+
y1: p.y,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return accPoints;
|
|
68
|
+
}, []));
|
|
69
|
+
return acc;
|
|
70
|
+
}, []);
|
|
71
|
+
closestPointsByXValue.push(...linePoints);
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
case 'area': {
|
|
75
|
+
const areaPoints = list.reduce((acc, d) => {
|
|
76
|
+
Array.prototype.push.apply(acc, d.points.map((p) => ({
|
|
77
|
+
data: p.data,
|
|
78
|
+
series: p.series,
|
|
79
|
+
x: p.x,
|
|
80
|
+
y0: p.y0,
|
|
81
|
+
y1: p.y,
|
|
82
|
+
})));
|
|
83
|
+
return acc;
|
|
84
|
+
}, []);
|
|
85
|
+
closestPointsByXValue.push(...areaPoints);
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
54
88
|
case 'bar-x': {
|
|
55
|
-
const
|
|
89
|
+
const barXPoints = list.map((d) => ({
|
|
56
90
|
data: d.data,
|
|
57
91
|
series: d.series,
|
|
58
92
|
x: d.x + d.width / 2,
|
|
59
93
|
y0: d.y,
|
|
60
94
|
y1: d.y + d.height,
|
|
61
95
|
}));
|
|
62
|
-
|
|
96
|
+
closestPointsByXValue.push(...barXPoints);
|
|
63
97
|
break;
|
|
64
98
|
}
|
|
65
99
|
case 'waterfall': {
|
|
@@ -74,39 +108,6 @@ export function getClosestPoints(args) {
|
|
|
74
108
|
result.push(...getClosestPointsByXValue(pointerX, pointerY, points));
|
|
75
109
|
break;
|
|
76
110
|
}
|
|
77
|
-
case 'area': {
|
|
78
|
-
const points = list.reduce((acc, d) => {
|
|
79
|
-
Array.prototype.push.apply(acc, d.points.map((p) => ({
|
|
80
|
-
data: p.data,
|
|
81
|
-
series: p.series,
|
|
82
|
-
x: p.x,
|
|
83
|
-
y0: p.y0,
|
|
84
|
-
y1: p.y,
|
|
85
|
-
})));
|
|
86
|
-
return acc;
|
|
87
|
-
}, []);
|
|
88
|
-
result.push(...getClosestPointsByXValue(pointerX, pointerY, points));
|
|
89
|
-
break;
|
|
90
|
-
}
|
|
91
|
-
case 'line': {
|
|
92
|
-
const points = list.reduce((acc, d) => {
|
|
93
|
-
acc.push(...d.points.reduce((accPoints, p) => {
|
|
94
|
-
if (p.y !== null && p.x !== null) {
|
|
95
|
-
accPoints.push({
|
|
96
|
-
data: p.data,
|
|
97
|
-
series: p.series,
|
|
98
|
-
x: p.x,
|
|
99
|
-
y0: p.y,
|
|
100
|
-
y1: p.y,
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
return accPoints;
|
|
104
|
-
}, []));
|
|
105
|
-
return acc;
|
|
106
|
-
}, []);
|
|
107
|
-
result.push(...getClosestPointsByXValue(pointerX, pointerY, points));
|
|
108
|
-
break;
|
|
109
|
-
}
|
|
110
111
|
case 'bar-y': {
|
|
111
112
|
const points = list;
|
|
112
113
|
const sorted = sort(points, (p) => p.y);
|
|
@@ -267,6 +268,9 @@ export function getClosestPoints(args) {
|
|
|
267
268
|
}
|
|
268
269
|
}
|
|
269
270
|
});
|
|
271
|
+
if (closestPointsByXValue.length) {
|
|
272
|
+
result.push(...getClosestPointsByXValue(pointerX, pointerY, closestPointsByXValue));
|
|
273
|
+
}
|
|
270
274
|
return result;
|
|
271
275
|
}
|
|
272
276
|
function isInsidePath(args) {
|
|
@@ -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 {
|
|
@@ -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
|
};
|
|
@@ -47,19 +47,53 @@ export function getClosestPoints(args) {
|
|
|
47
47
|
const [pointerX, pointerY] = position;
|
|
48
48
|
const result = [];
|
|
49
49
|
const groups = groupBy(shapesData, getSeriesType);
|
|
50
|
+
const closestPointsByXValue = [];
|
|
50
51
|
// eslint-disable-next-line complexity
|
|
51
52
|
Object.entries(groups).forEach(([seriesType, list]) => {
|
|
52
53
|
var _a, _b, _c, _d, _e;
|
|
53
54
|
switch (seriesType) {
|
|
55
|
+
case 'line': {
|
|
56
|
+
const linePoints = list.reduce((acc, d) => {
|
|
57
|
+
acc.push(...d.points.reduce((accPoints, p) => {
|
|
58
|
+
if (p.y !== null && p.x !== null) {
|
|
59
|
+
accPoints.push({
|
|
60
|
+
data: p.data,
|
|
61
|
+
series: p.series,
|
|
62
|
+
x: p.x,
|
|
63
|
+
y0: p.y,
|
|
64
|
+
y1: p.y,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return accPoints;
|
|
68
|
+
}, []));
|
|
69
|
+
return acc;
|
|
70
|
+
}, []);
|
|
71
|
+
closestPointsByXValue.push(...linePoints);
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
case 'area': {
|
|
75
|
+
const areaPoints = list.reduce((acc, d) => {
|
|
76
|
+
Array.prototype.push.apply(acc, d.points.map((p) => ({
|
|
77
|
+
data: p.data,
|
|
78
|
+
series: p.series,
|
|
79
|
+
x: p.x,
|
|
80
|
+
y0: p.y0,
|
|
81
|
+
y1: p.y,
|
|
82
|
+
})));
|
|
83
|
+
return acc;
|
|
84
|
+
}, []);
|
|
85
|
+
closestPointsByXValue.push(...areaPoints);
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
54
88
|
case 'bar-x': {
|
|
55
|
-
const
|
|
89
|
+
const barXPoints = list.map((d) => ({
|
|
56
90
|
data: d.data,
|
|
57
91
|
series: d.series,
|
|
58
92
|
x: d.x + d.width / 2,
|
|
59
93
|
y0: d.y,
|
|
60
94
|
y1: d.y + d.height,
|
|
61
95
|
}));
|
|
62
|
-
|
|
96
|
+
closestPointsByXValue.push(...barXPoints);
|
|
63
97
|
break;
|
|
64
98
|
}
|
|
65
99
|
case 'waterfall': {
|
|
@@ -74,39 +108,6 @@ export function getClosestPoints(args) {
|
|
|
74
108
|
result.push(...getClosestPointsByXValue(pointerX, pointerY, points));
|
|
75
109
|
break;
|
|
76
110
|
}
|
|
77
|
-
case 'area': {
|
|
78
|
-
const points = list.reduce((acc, d) => {
|
|
79
|
-
Array.prototype.push.apply(acc, d.points.map((p) => ({
|
|
80
|
-
data: p.data,
|
|
81
|
-
series: p.series,
|
|
82
|
-
x: p.x,
|
|
83
|
-
y0: p.y0,
|
|
84
|
-
y1: p.y,
|
|
85
|
-
})));
|
|
86
|
-
return acc;
|
|
87
|
-
}, []);
|
|
88
|
-
result.push(...getClosestPointsByXValue(pointerX, pointerY, points));
|
|
89
|
-
break;
|
|
90
|
-
}
|
|
91
|
-
case 'line': {
|
|
92
|
-
const points = list.reduce((acc, d) => {
|
|
93
|
-
acc.push(...d.points.reduce((accPoints, p) => {
|
|
94
|
-
if (p.y !== null && p.x !== null) {
|
|
95
|
-
accPoints.push({
|
|
96
|
-
data: p.data,
|
|
97
|
-
series: p.series,
|
|
98
|
-
x: p.x,
|
|
99
|
-
y0: p.y,
|
|
100
|
-
y1: p.y,
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
return accPoints;
|
|
104
|
-
}, []));
|
|
105
|
-
return acc;
|
|
106
|
-
}, []);
|
|
107
|
-
result.push(...getClosestPointsByXValue(pointerX, pointerY, points));
|
|
108
|
-
break;
|
|
109
|
-
}
|
|
110
111
|
case 'bar-y': {
|
|
111
112
|
const points = list;
|
|
112
113
|
const sorted = sort(points, (p) => p.y);
|
|
@@ -267,6 +268,9 @@ export function getClosestPoints(args) {
|
|
|
267
268
|
}
|
|
268
269
|
}
|
|
269
270
|
});
|
|
271
|
+
if (closestPointsByXValue.length) {
|
|
272
|
+
result.push(...getClosestPointsByXValue(pointerX, pointerY, closestPointsByXValue));
|
|
273
|
+
}
|
|
270
274
|
return result;
|
|
271
275
|
}
|
|
272
276
|
function isInsidePath(args) {
|