@gravity-ui/charts 1.43.1 → 1.44.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.
- package/dist/cjs/components/ChartInner/utils/zoom.js +3 -1
- package/dist/cjs/components/Tooltip/DefaultTooltipContent/index.js +31 -6
- package/dist/cjs/components/Tooltip/DefaultTooltipContent/utils.js +4 -5
- package/dist/cjs/core/constants/chart-types.d.ts +1 -0
- package/dist/cjs/core/constants/chart-types.js +1 -0
- package/dist/cjs/core/constants/defaults/series-options.d.ts +5 -1
- package/dist/cjs/core/constants/defaults/series-options.js +13 -0
- package/dist/cjs/core/i18n/keysets/en.json +2 -1
- package/dist/cjs/core/i18n/keysets/ru.json +2 -1
- package/dist/cjs/core/series/prepare-legend.js +2 -2
- package/dist/cjs/core/series/prepare-x-range.d.ts +11 -0
- package/dist/cjs/core/series/prepare-x-range.js +41 -0
- package/dist/cjs/core/series/prepareSeries.js +9 -0
- package/dist/cjs/core/series/types.d.ts +18 -2
- package/dist/cjs/core/types/chart/area.d.ts +2 -1
- package/dist/cjs/core/types/chart/series.d.ts +29 -2
- package/dist/cjs/core/types/chart/tooltip.d.ts +6 -1
- package/dist/cjs/core/types/chart/x-range.d.ts +59 -0
- package/dist/cjs/core/types/chart/x-range.js +1 -0
- package/dist/cjs/core/types/chart/zoom.d.ts +1 -1
- package/dist/cjs/core/types/index.d.ts +1 -0
- package/dist/cjs/core/types/index.js +1 -0
- package/dist/cjs/core/utils/axis/x-axis.js +9 -1
- package/dist/cjs/core/utils/color.js +6 -0
- package/dist/cjs/core/utils/common.js +10 -0
- package/dist/cjs/core/utils/get-closest-data.js +19 -0
- package/dist/cjs/core/validation/index.js +13 -0
- package/dist/cjs/core/zoom/zoom.js +24 -7
- package/dist/cjs/hooks/useShapes/area/prepare-data.js +15 -14
- package/dist/cjs/hooks/useShapes/bar-x/prepare-data.js +22 -9
- package/dist/cjs/hooks/useShapes/index.d.ts +2 -1
- package/dist/cjs/hooks/useShapes/index.js +17 -0
- package/dist/cjs/hooks/useShapes/x-range/index.d.ts +14 -0
- package/dist/cjs/hooks/useShapes/x-range/index.js +115 -0
- package/dist/cjs/hooks/useShapes/x-range/prepare-data.d.ts +15 -0
- package/dist/cjs/hooks/useShapes/x-range/prepare-data.js +147 -0
- package/dist/cjs/hooks/useShapes/x-range/types.d.ts +12 -0
- package/dist/cjs/hooks/useShapes/x-range/types.js +1 -0
- package/dist/esm/components/ChartInner/utils/zoom.js +3 -1
- package/dist/esm/components/Tooltip/DefaultTooltipContent/index.js +31 -6
- package/dist/esm/components/Tooltip/DefaultTooltipContent/utils.js +4 -5
- package/dist/esm/core/constants/chart-types.d.ts +1 -0
- package/dist/esm/core/constants/chart-types.js +1 -0
- package/dist/esm/core/constants/defaults/series-options.d.ts +5 -1
- package/dist/esm/core/constants/defaults/series-options.js +13 -0
- package/dist/esm/core/i18n/keysets/en.json +2 -1
- package/dist/esm/core/i18n/keysets/ru.json +2 -1
- package/dist/esm/core/series/prepare-legend.js +2 -2
- package/dist/esm/core/series/prepare-x-range.d.ts +11 -0
- package/dist/esm/core/series/prepare-x-range.js +41 -0
- package/dist/esm/core/series/prepareSeries.js +9 -0
- package/dist/esm/core/series/types.d.ts +18 -2
- package/dist/esm/core/types/chart/area.d.ts +2 -1
- package/dist/esm/core/types/chart/series.d.ts +29 -2
- package/dist/esm/core/types/chart/tooltip.d.ts +6 -1
- package/dist/esm/core/types/chart/x-range.d.ts +59 -0
- package/dist/esm/core/types/chart/x-range.js +1 -0
- package/dist/esm/core/types/chart/zoom.d.ts +1 -1
- package/dist/esm/core/types/index.d.ts +1 -0
- package/dist/esm/core/types/index.js +1 -0
- package/dist/esm/core/utils/axis/x-axis.js +9 -1
- package/dist/esm/core/utils/color.js +6 -0
- package/dist/esm/core/utils/common.js +10 -0
- package/dist/esm/core/utils/get-closest-data.js +19 -0
- package/dist/esm/core/validation/index.js +13 -0
- package/dist/esm/core/zoom/zoom.js +24 -7
- package/dist/esm/hooks/useShapes/area/prepare-data.js +15 -14
- package/dist/esm/hooks/useShapes/bar-x/prepare-data.js +22 -9
- package/dist/esm/hooks/useShapes/index.d.ts +2 -1
- package/dist/esm/hooks/useShapes/index.js +17 -0
- package/dist/esm/hooks/useShapes/x-range/index.d.ts +14 -0
- package/dist/esm/hooks/useShapes/x-range/index.js +115 -0
- package/dist/esm/hooks/useShapes/x-range/prepare-data.d.ts +15 -0
- package/dist/esm/hooks/useShapes/x-range/prepare-data.js +147 -0
- package/dist/esm/hooks/useShapes/x-range/types.d.ts +12 -0
- package/dist/esm/hooks/useShapes/x-range/types.js +1 -0
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { group, min } from 'd3-array';
|
|
1
|
+
import { group, min, sort } from 'd3-array';
|
|
2
2
|
import isNil from 'lodash/isNil';
|
|
3
3
|
import round from 'lodash/round';
|
|
4
4
|
import { getDataCategoryValue, getLabelsSize, getTextSizeFn } from '../../../core/utils';
|
|
@@ -27,7 +27,7 @@ function getXValues(series, xAxis, xScale) {
|
|
|
27
27
|
return acc;
|
|
28
28
|
}, []);
|
|
29
29
|
}
|
|
30
|
-
return Array.from(xValues);
|
|
30
|
+
return sort(Array.from(xValues), (d) => d[1]);
|
|
31
31
|
}
|
|
32
32
|
async function prepareDataLabels({ series, points, xMax, yAxisTop, isOutsideBounds, }) {
|
|
33
33
|
var _a;
|
|
@@ -166,13 +166,14 @@ export const prepareAreaData = async (args) => {
|
|
|
166
166
|
return m.set(key, d);
|
|
167
167
|
}, new Map());
|
|
168
168
|
const points = xValues.reduce((pointsAcc, [x, xValue], index) => {
|
|
169
|
-
var _a, _b, _c, _d, _e, _f, _g
|
|
170
|
-
const
|
|
169
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
170
|
+
const rawData = seriesData.get(x);
|
|
171
|
+
const d = rawData !== null && rawData !== void 0 ? rawData : {
|
|
171
172
|
x,
|
|
172
173
|
y: 0,
|
|
173
174
|
};
|
|
174
|
-
let yDataValue = (
|
|
175
|
-
if (s.nullMode === 'connect' && yDataValue === null) {
|
|
175
|
+
let yDataValue = (_a = d.y) !== null && _a !== void 0 ? _a : null;
|
|
176
|
+
if (s.nullMode === 'connect' && (yDataValue === null || !rawData)) {
|
|
176
177
|
return pointsAcc;
|
|
177
178
|
}
|
|
178
179
|
if (yDataValue && isPercentStacking) {
|
|
@@ -187,13 +188,13 @@ export const prepareAreaData = async (args) => {
|
|
|
187
188
|
});
|
|
188
189
|
if (typeof yDataValue === 'number' && yValue !== null) {
|
|
189
190
|
yValue = round(yValue, 2);
|
|
190
|
-
const prevPoint = seriesData.get((
|
|
191
|
-
const nextPoint = seriesData.get((
|
|
191
|
+
const prevPoint = seriesData.get((_b = xValues[index - 1]) === null || _b === void 0 ? void 0 : _b[0]);
|
|
192
|
+
const nextPoint = seriesData.get((_c = xValues[index + 1]) === null || _c === void 0 ? void 0 : _c[0]);
|
|
192
193
|
const currentPointStackHeight = Math.abs(yMin - yValue);
|
|
193
194
|
if (yDataValue >= 0) {
|
|
194
195
|
const positiveStackHeights = positiveStackValues.get(x);
|
|
195
|
-
let prevSectionStackHeight = (
|
|
196
|
-
let nextSectionStackHeight = (
|
|
196
|
+
let prevSectionStackHeight = (_d = positiveStackHeights === null || positiveStackHeights === void 0 ? void 0 : positiveStackHeights.prev) !== null && _d !== void 0 ? _d : 0;
|
|
197
|
+
let nextSectionStackHeight = (_e = positiveStackHeights === null || positiveStackHeights === void 0 ? void 0 : positiveStackHeights.next) !== null && _e !== void 0 ? _e : 0;
|
|
197
198
|
const point = {
|
|
198
199
|
y0: yAxisTop + yMin - prevSectionStackHeight,
|
|
199
200
|
x: xValue,
|
|
@@ -219,11 +220,11 @@ export const prepareAreaData = async (args) => {
|
|
|
219
220
|
point2.y = newYValue;
|
|
220
221
|
}
|
|
221
222
|
}
|
|
222
|
-
if ((prevPoint === null || prevPoint === void 0 ? void 0 : prevPoint.y) !== null) {
|
|
223
|
+
if ((prevPoint === null || prevPoint === void 0 ? void 0 : prevPoint.y) !== null || s.nullMode === 'zero') {
|
|
223
224
|
prevSectionStackHeight =
|
|
224
225
|
prevSectionStackHeight + currentPointStackHeight;
|
|
225
226
|
}
|
|
226
|
-
if ((nextPoint === null || nextPoint === void 0 ? void 0 : nextPoint.y) !== null) {
|
|
227
|
+
if ((nextPoint === null || nextPoint === void 0 ? void 0 : nextPoint.y) !== null || s.nullMode === 'zero') {
|
|
227
228
|
nextSectionStackHeight =
|
|
228
229
|
nextSectionStackHeight + currentPointStackHeight;
|
|
229
230
|
}
|
|
@@ -234,8 +235,8 @@ export const prepareAreaData = async (args) => {
|
|
|
234
235
|
}
|
|
235
236
|
else {
|
|
236
237
|
const negativeStackHeights = negativeStackValues.get(x);
|
|
237
|
-
let prevSectionStackHeight = (
|
|
238
|
-
let nextSectionStackHeight = (
|
|
238
|
+
let prevSectionStackHeight = (_f = negativeStackHeights === null || negativeStackHeights === void 0 ? void 0 : negativeStackHeights.prev) !== null && _f !== void 0 ? _f : 0;
|
|
239
|
+
let nextSectionStackHeight = (_g = negativeStackHeights === null || negativeStackHeights === void 0 ? void 0 : negativeStackHeights.next) !== null && _g !== void 0 ? _g : 0;
|
|
239
240
|
pointsAcc.push({
|
|
240
241
|
y0: yAxisTop + yMin + prevSectionStackHeight,
|
|
241
242
|
x: xValue,
|
|
@@ -110,8 +110,8 @@ export const prepareBarXData = async (args) => {
|
|
|
110
110
|
const currentGroupWidth = rectWidth * stacks.length + rectGap * (stacks.length - 1);
|
|
111
111
|
for (let groupItemIndex = 0; groupItemIndex < stacks.length; groupItemIndex++) {
|
|
112
112
|
const yValues = stacks[groupItemIndex];
|
|
113
|
-
let
|
|
114
|
-
let
|
|
113
|
+
let positiveStackSum = 0;
|
|
114
|
+
let negativeStackSum = 0;
|
|
115
115
|
const stackItems = [];
|
|
116
116
|
let sortedData = yValues;
|
|
117
117
|
if (sortKey) {
|
|
@@ -144,7 +144,6 @@ export const prepareBarXData = async (args) => {
|
|
|
144
144
|
}
|
|
145
145
|
const x = xCenter - currentGroupWidth / 2 + (rectWidth + rectGap) * groupItemIndex;
|
|
146
146
|
const yDataValue = ((_d = yValue.data.y) !== null && _d !== void 0 ? _d : 0);
|
|
147
|
-
const y = seriesYScale(yDataValue);
|
|
148
147
|
let base = 0;
|
|
149
148
|
if (seriesYAxis.type === 'logarithmic') {
|
|
150
149
|
const domainData = seriesYScale.domain();
|
|
@@ -155,7 +154,22 @@ export const prepareBarXData = async (args) => {
|
|
|
155
154
|
base = seriesYScale(0);
|
|
156
155
|
}
|
|
157
156
|
const isLastStackItem = yValueIndex === sortedData.length - 1;
|
|
158
|
-
|
|
157
|
+
let height;
|
|
158
|
+
let barPositionY;
|
|
159
|
+
if (yDataValue > 0) {
|
|
160
|
+
const newSum = positiveStackSum + yDataValue;
|
|
161
|
+
const topPixel = seriesYScale(newSum);
|
|
162
|
+
const bottomPixel = positiveStackSum === 0 ? base : seriesYScale(positiveStackSum);
|
|
163
|
+
height = Math.abs(bottomPixel - topPixel);
|
|
164
|
+
barPositionY = yAxisTop + topPixel;
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
const newSum = negativeStackSum + yDataValue;
|
|
168
|
+
const bottomPixel = negativeStackSum === 0 ? base : seriesYScale(negativeStackSum);
|
|
169
|
+
const topPixel = seriesYScale(newSum);
|
|
170
|
+
height = Math.abs(bottomPixel - topPixel);
|
|
171
|
+
barPositionY = yAxisTop + bottomPixel;
|
|
172
|
+
}
|
|
159
173
|
let shapeHeight = height - (stackItems.length ? stackGap : 0);
|
|
160
174
|
if (shapeHeight < 0) {
|
|
161
175
|
shapeHeight = height;
|
|
@@ -165,9 +179,7 @@ export const prepareBarXData = async (args) => {
|
|
|
165
179
|
}
|
|
166
180
|
const barData = {
|
|
167
181
|
x,
|
|
168
|
-
y:
|
|
169
|
-
? yAxisTop + y - positiveStackHeight
|
|
170
|
-
: yAxisTop + base + negativeStackHeight,
|
|
182
|
+
y: barPositionY,
|
|
171
183
|
width: rectWidth,
|
|
172
184
|
height: shapeHeight,
|
|
173
185
|
_height: height,
|
|
@@ -180,14 +192,15 @@ export const prepareBarXData = async (args) => {
|
|
|
180
192
|
};
|
|
181
193
|
stackItems.push(barData);
|
|
182
194
|
if (yDataValue > 0) {
|
|
183
|
-
|
|
195
|
+
positiveStackSum += yDataValue;
|
|
184
196
|
}
|
|
185
197
|
else {
|
|
186
|
-
|
|
198
|
+
negativeStackSum += yDataValue;
|
|
187
199
|
}
|
|
188
200
|
}
|
|
189
201
|
if (series.some((s) => s.stacking === 'percent')) {
|
|
190
202
|
let acc = 0;
|
|
203
|
+
const positiveStackHeight = stackItems.reduce((sum, item) => sum + item._height, 0);
|
|
191
204
|
const ratio = plotHeight / positiveStackHeight;
|
|
192
205
|
stackItems.forEach((item) => {
|
|
193
206
|
item.height = item._height * ratio;
|
|
@@ -19,8 +19,9 @@ import type { PreparedScatterData } from './scatter/types';
|
|
|
19
19
|
export type { PreparedBarXData } from './bar-x';
|
|
20
20
|
export type { PreparedScatterData } from './scatter/types';
|
|
21
21
|
import type { PreparedWaterfallData } from './waterfall';
|
|
22
|
+
import type { PreparedXRangeData } from './x-range';
|
|
22
23
|
import './styles.css';
|
|
23
|
-
export type ShapeData = PreparedBarXData | PreparedBarYData | PreparedScatterData | PreparedLineData | PreparedPieData | PreparedAreaData | PreparedWaterfallData | PreparedSankeyData | PreparedRadarData | PreparedHeatmapData | PreparedFunnelData;
|
|
24
|
+
export type ShapeData = PreparedBarXData | PreparedBarYData | PreparedScatterData | PreparedLineData | PreparedPieData | PreparedAreaData | PreparedWaterfallData | PreparedSankeyData | PreparedRadarData | PreparedHeatmapData | PreparedFunnelData | PreparedXRangeData;
|
|
24
25
|
export type ClipPathBySeriesType = Partial<Record<SeriesType, boolean>>;
|
|
25
26
|
type Args = {
|
|
26
27
|
boundsWidth: number;
|
|
@@ -22,6 +22,7 @@ import { TreemapSeriesShape } from './treemap';
|
|
|
22
22
|
import { prepareTreemapData } from './treemap/prepare-data';
|
|
23
23
|
import { getSeriesClipPathId } from './utils';
|
|
24
24
|
import { WaterfallSeriesShapes, prepareWaterfallData } from './waterfall';
|
|
25
|
+
import { XRangeSeriesShapes, prepareXRangeData } from './x-range';
|
|
25
26
|
import './styles.css';
|
|
26
27
|
function IS_OUTSIDE_BOUNDS() {
|
|
27
28
|
return false;
|
|
@@ -224,6 +225,22 @@ export async function getShapes(args) {
|
|
|
224
225
|
shapesData.splice(index, 0, preparedData);
|
|
225
226
|
break;
|
|
226
227
|
}
|
|
228
|
+
case SERIES_TYPE.XRange: {
|
|
229
|
+
if (xAxis && xScale && (yScale === null || yScale === void 0 ? void 0 : yScale.length)) {
|
|
230
|
+
const preparedData = await prepareXRangeData({
|
|
231
|
+
series: chartSeries,
|
|
232
|
+
xAxis,
|
|
233
|
+
xScale,
|
|
234
|
+
yAxis,
|
|
235
|
+
yScale,
|
|
236
|
+
boundsWidth,
|
|
237
|
+
isRangeSlider,
|
|
238
|
+
});
|
|
239
|
+
shapes[index] = (React.createElement(XRangeSeriesShapes, { key: SERIES_TYPE.XRange, dispatcher: dispatcher, preparedData: preparedData, seriesOptions: seriesOptions, htmlLayout: htmlLayout, clipPathId: clipPathId }));
|
|
240
|
+
shapesData.splice(index, 0, ...preparedData);
|
|
241
|
+
}
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
227
244
|
default: {
|
|
228
245
|
throw new ChartError({
|
|
229
246
|
message: `The display method is not defined for a series with type "${seriesType}"`,
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { Dispatch } from 'd3-dispatch';
|
|
3
|
+
import type { PreparedSeriesOptions } from '../../useSeries/types';
|
|
4
|
+
export { prepareXRangeData } from './prepare-data';
|
|
5
|
+
export type { PreparedXRangeData } from './types';
|
|
6
|
+
import type { PreparedXRangeData } from './types';
|
|
7
|
+
type Args = {
|
|
8
|
+
clipPathId: string;
|
|
9
|
+
htmlLayout: HTMLElement | null;
|
|
10
|
+
preparedData: PreparedXRangeData[];
|
|
11
|
+
seriesOptions: PreparedSeriesOptions;
|
|
12
|
+
dispatcher?: Dispatch<object>;
|
|
13
|
+
};
|
|
14
|
+
export declare function XRangeSeriesShapes(args: Args): React.JSX.Element;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { color } from 'd3-color';
|
|
3
|
+
import { select } from 'd3-selection';
|
|
4
|
+
import get from 'lodash/get';
|
|
5
|
+
import { getLineDashArray } from '../../../core/utils';
|
|
6
|
+
import { block } from '../../../utils';
|
|
7
|
+
import { HtmlLayer } from '../HtmlLayer';
|
|
8
|
+
import { getRectPath } from '../utils';
|
|
9
|
+
export { prepareXRangeData } from './prepare-data';
|
|
10
|
+
const b = block('x-range');
|
|
11
|
+
export function XRangeSeriesShapes(args) {
|
|
12
|
+
const { dispatcher, preparedData, seriesOptions, htmlLayout, clipPathId } = args;
|
|
13
|
+
const hoveredDataRef = React.useRef(null);
|
|
14
|
+
const ref = React.useRef(null);
|
|
15
|
+
React.useEffect(() => {
|
|
16
|
+
var _a;
|
|
17
|
+
if (!ref.current) {
|
|
18
|
+
return () => { };
|
|
19
|
+
}
|
|
20
|
+
const svgElement = select(ref.current);
|
|
21
|
+
svgElement.selectAll('*').remove();
|
|
22
|
+
const segmentSelection = svgElement
|
|
23
|
+
.selectAll(`path.${b('segment')}`)
|
|
24
|
+
.data(preparedData)
|
|
25
|
+
.join('path')
|
|
26
|
+
.attr('d', (d) => {
|
|
27
|
+
const borderRadius = Math.min(d.height / 2, d.width / 2, d.series.borderRadius);
|
|
28
|
+
return getRectPath({
|
|
29
|
+
x: d.x,
|
|
30
|
+
y: d.y,
|
|
31
|
+
width: d.width,
|
|
32
|
+
height: d.height,
|
|
33
|
+
borderRadius,
|
|
34
|
+
}).toString();
|
|
35
|
+
})
|
|
36
|
+
.attr('class', b('segment'))
|
|
37
|
+
.attr('fill', (d) => d.color)
|
|
38
|
+
.attr('opacity', (d) => { var _a; return (_a = d.data.opacity) !== null && _a !== void 0 ? _a : d.series.opacity; })
|
|
39
|
+
.attr('cursor', (d) => d.series.cursor);
|
|
40
|
+
svgElement
|
|
41
|
+
.selectAll(`path.${b('segment-border')}`)
|
|
42
|
+
.data(preparedData.filter((d) => d.series.borderWidth > 0))
|
|
43
|
+
.join('path')
|
|
44
|
+
.attr('d', (d) => {
|
|
45
|
+
const borderRadius = Math.min(d.height / 2, d.width / 2, d.series.borderRadius);
|
|
46
|
+
return getRectPath({
|
|
47
|
+
x: d.x,
|
|
48
|
+
y: d.y,
|
|
49
|
+
width: d.width,
|
|
50
|
+
height: d.height,
|
|
51
|
+
borderRadius,
|
|
52
|
+
}).toString();
|
|
53
|
+
})
|
|
54
|
+
.attr('class', b('segment-border'))
|
|
55
|
+
.attr('fill', 'none')
|
|
56
|
+
.attr('stroke', (d) => d.series.borderColor)
|
|
57
|
+
.attr('stroke-width', (d) => d.series.borderWidth)
|
|
58
|
+
.attr('stroke-dasharray', (d) => getLineDashArray(d.series.borderDashStyle, d.series.borderWidth))
|
|
59
|
+
.attr('opacity', (d) => { var _a; return (_a = d.data.opacity) !== null && _a !== void 0 ? _a : d.series.opacity; })
|
|
60
|
+
.attr('pointer-events', 'none');
|
|
61
|
+
const svgLabels = preparedData.flatMap((d) => d.svgLabels);
|
|
62
|
+
svgElement
|
|
63
|
+
.selectAll(`text.${b('label')}`)
|
|
64
|
+
.data(svgLabels)
|
|
65
|
+
.join('text')
|
|
66
|
+
.attr('class', b('label'))
|
|
67
|
+
.attr('x', (d) => d.x)
|
|
68
|
+
.attr('y', (d) => d.y)
|
|
69
|
+
.attr('text-anchor', (d) => d.textAnchor)
|
|
70
|
+
.attr('dominant-baseline', 'central')
|
|
71
|
+
.attr('pointer-events', 'none')
|
|
72
|
+
.style('font-size', (d) => d.style.fontSize)
|
|
73
|
+
.style('font-weight', (d) => d.style.fontWeight || null)
|
|
74
|
+
.style('fill', (d) => d.style.fontColor || null)
|
|
75
|
+
.html((d) => d.text);
|
|
76
|
+
const hoverOptions = get(seriesOptions, 'x-range.states.hover');
|
|
77
|
+
const inactiveOptions = get(seriesOptions, 'x-range.states.inactive');
|
|
78
|
+
function handleShapeHover(data) {
|
|
79
|
+
hoveredDataRef.current = data;
|
|
80
|
+
if (hoverOptions === null || hoverOptions === void 0 ? void 0 : hoverOptions.enabled) {
|
|
81
|
+
const hoveredSet = new Set(data === null || data === void 0 ? void 0 : data.map((d) => d.data));
|
|
82
|
+
segmentSelection.attr('fill', (d) => {
|
|
83
|
+
var _a;
|
|
84
|
+
const fillColor = d.color;
|
|
85
|
+
if (hoveredSet.has(d.data)) {
|
|
86
|
+
return (((_a = color(fillColor)) === null || _a === void 0 ? void 0 : _a.brighter(hoverOptions.brightness).toString()) ||
|
|
87
|
+
fillColor);
|
|
88
|
+
}
|
|
89
|
+
return fillColor;
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
if (inactiveOptions === null || inactiveOptions === void 0 ? void 0 : inactiveOptions.enabled) {
|
|
93
|
+
const hoveredSeries = data === null || data === void 0 ? void 0 : data.map((d) => d.series.id);
|
|
94
|
+
segmentSelection.attr('opacity', (d) => {
|
|
95
|
+
var _a, _b;
|
|
96
|
+
if ((hoveredSeries === null || hoveredSeries === void 0 ? void 0 : hoveredSeries.length) && !hoveredSeries.includes(d.series.id)) {
|
|
97
|
+
return inactiveOptions.opacity || null;
|
|
98
|
+
}
|
|
99
|
+
return (_b = (_a = d.data.opacity) !== null && _a !== void 0 ? _a : d.series.opacity) !== null && _b !== void 0 ? _b : null;
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (hoveredDataRef.current !== null) {
|
|
104
|
+
handleShapeHover((_a = hoveredDataRef.current) !== null && _a !== void 0 ? _a : undefined);
|
|
105
|
+
}
|
|
106
|
+
dispatcher === null || dispatcher === void 0 ? void 0 : dispatcher.on('hover-shape.x-range', handleShapeHover);
|
|
107
|
+
return () => {
|
|
108
|
+
dispatcher === null || dispatcher === void 0 ? void 0 : dispatcher.on('hover-shape.x-range', null);
|
|
109
|
+
};
|
|
110
|
+
}, [dispatcher, preparedData, seriesOptions]);
|
|
111
|
+
const htmlLayerData = React.useMemo(() => ({ htmlElements: preparedData.flatMap((d) => d.htmlLabels) }), [preparedData]);
|
|
112
|
+
return (React.createElement(React.Fragment, null,
|
|
113
|
+
React.createElement("g", { ref: ref, className: b(), clipPath: `url(#${clipPathId})` }),
|
|
114
|
+
React.createElement(HtmlLayer, { preparedData: htmlLayerData, htmlLayout: htmlLayout })));
|
|
115
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ChartScale } from '../../../core/scales/types';
|
|
2
|
+
import type { PreparedXAxis, PreparedYAxis } from '../../useAxis/types';
|
|
3
|
+
import type { PreparedXRangeSeries } from '../../useSeries/types';
|
|
4
|
+
import type { PreparedXRangeData } from './types';
|
|
5
|
+
type PrepareXRangeDataArgs = {
|
|
6
|
+
series: PreparedXRangeSeries[];
|
|
7
|
+
xAxis: PreparedXAxis;
|
|
8
|
+
xScale: ChartScale;
|
|
9
|
+
yAxis: PreparedYAxis[];
|
|
10
|
+
yScale: (ChartScale | undefined)[];
|
|
11
|
+
boundsWidth?: number;
|
|
12
|
+
isRangeSlider?: boolean;
|
|
13
|
+
};
|
|
14
|
+
export declare function prepareXRangeData(args: PrepareXRangeDataArgs): Promise<PreparedXRangeData[]>;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import get from 'lodash/get';
|
|
2
|
+
import { getDataCategoryValue, getLabelsSize, getTextSizeFn, getTextWithElipsis } from '../../../core/utils';
|
|
3
|
+
import { getFormattedValue } from '../../../core/utils/format';
|
|
4
|
+
import { MIN_BAR_WIDTH } from '../../constants';
|
|
5
|
+
import { getBandSize } from '../../utils/get-band-size';
|
|
6
|
+
const DEFAULT_BAR_PADDING = 0.2;
|
|
7
|
+
export async function prepareXRangeData(args) {
|
|
8
|
+
var _a;
|
|
9
|
+
const { series, xAxis, xScale, yAxis, yScale: [yScale], boundsWidth, isRangeSlider, } = args;
|
|
10
|
+
if (!yScale) {
|
|
11
|
+
return [];
|
|
12
|
+
}
|
|
13
|
+
// Collect unique y-domain values
|
|
14
|
+
const domain = [];
|
|
15
|
+
const seen = new Set();
|
|
16
|
+
const categories = get(yAxis[0], 'categories', []);
|
|
17
|
+
series.forEach((s) => {
|
|
18
|
+
s.data.forEach((d) => {
|
|
19
|
+
const key = yAxis[0].type === 'category'
|
|
20
|
+
? getDataCategoryValue({ axisDirection: 'y', categories, data: d })
|
|
21
|
+
: d.y;
|
|
22
|
+
if (key !== undefined && !seen.has(key)) {
|
|
23
|
+
seen.add(key);
|
|
24
|
+
domain.push(key);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
const bandSize = getBandSize({ domain, scale: yScale });
|
|
29
|
+
const barSize = Math.max(MIN_BAR_WIDTH, bandSize * (1 - DEFAULT_BAR_PADDING));
|
|
30
|
+
const result = [];
|
|
31
|
+
series.forEach((s) => {
|
|
32
|
+
s.data.forEach((d) => {
|
|
33
|
+
let center;
|
|
34
|
+
if (yAxis[0].type === 'category') {
|
|
35
|
+
const bandScale = yScale;
|
|
36
|
+
const yCategory = getDataCategoryValue({ axisDirection: 'y', categories, data: d });
|
|
37
|
+
if (!bandScale.domain().includes(yCategory)) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
center = (bandScale(yCategory) || 0) + bandSize / 2;
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
const linearScale = yScale;
|
|
44
|
+
if (d.y === undefined) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
center = linearScale(Number(d.y));
|
|
48
|
+
}
|
|
49
|
+
let xStart;
|
|
50
|
+
let xEnd;
|
|
51
|
+
if (xAxis.type === 'category') {
|
|
52
|
+
// x-range on a category x-axis is unusual but supported
|
|
53
|
+
const xBandScale = xScale;
|
|
54
|
+
const xCategories = get(xAxis, 'categories', []);
|
|
55
|
+
const startCategory = getDataCategoryValue({
|
|
56
|
+
axisDirection: 'x',
|
|
57
|
+
categories: xCategories,
|
|
58
|
+
data: { x: d.x0 },
|
|
59
|
+
});
|
|
60
|
+
const endCategory = getDataCategoryValue({
|
|
61
|
+
axisDirection: 'x',
|
|
62
|
+
categories: xCategories,
|
|
63
|
+
data: { x: d.x1 },
|
|
64
|
+
});
|
|
65
|
+
xStart = xBandScale(startCategory) || 0;
|
|
66
|
+
xEnd = (xBandScale(endCategory) || 0) + xBandScale.bandwidth();
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
const linearScale = xScale;
|
|
70
|
+
xStart = linearScale(Number(d.x0));
|
|
71
|
+
xEnd = linearScale(Number(d.x1));
|
|
72
|
+
}
|
|
73
|
+
const width = xEnd - xStart;
|
|
74
|
+
if (width <= 0) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
result.push({
|
|
78
|
+
x: xStart,
|
|
79
|
+
y: center - barSize / 2,
|
|
80
|
+
width,
|
|
81
|
+
height: barSize,
|
|
82
|
+
color: d.color || s.color,
|
|
83
|
+
data: d,
|
|
84
|
+
series: s,
|
|
85
|
+
htmlLabels: [],
|
|
86
|
+
svgLabels: [],
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
const textSizeFnCache = new Map();
|
|
91
|
+
for (let i = 0; i < result.length; i++) {
|
|
92
|
+
const item = result[i];
|
|
93
|
+
const { dataLabels } = item.series;
|
|
94
|
+
if (!dataLabels.enabled || item.data.label === null || isRangeSlider) {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const content = getFormattedValue(Object.assign({ value: item.data.label }, dataLabels));
|
|
98
|
+
const visibleStart = Math.max(0, item.x);
|
|
99
|
+
const visibleEnd = boundsWidth === undefined
|
|
100
|
+
? item.x + item.width
|
|
101
|
+
: Math.min(boundsWidth, item.x + item.width);
|
|
102
|
+
const visibleWidth = visibleEnd - visibleStart;
|
|
103
|
+
const visibleCenterX = visibleStart + visibleWidth / 2;
|
|
104
|
+
if (dataLabels.html) {
|
|
105
|
+
const { maxHeight: height, maxWidth: width } = await getLabelsSize({
|
|
106
|
+
labels: [content],
|
|
107
|
+
style: dataLabels.style,
|
|
108
|
+
html: true,
|
|
109
|
+
});
|
|
110
|
+
const htmlItem = {
|
|
111
|
+
content,
|
|
112
|
+
size: { width, height },
|
|
113
|
+
style: dataLabels.style,
|
|
114
|
+
x: visibleCenterX - width / 2,
|
|
115
|
+
y: item.y + item.height / 2 - height / 2,
|
|
116
|
+
};
|
|
117
|
+
item.htmlLabels.push(htmlItem);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
if (!textSizeFnCache.has(dataLabels.style)) {
|
|
121
|
+
textSizeFnCache.set(dataLabels.style, getTextSizeFn({ style: dataLabels.style }));
|
|
122
|
+
}
|
|
123
|
+
const getTextSize = (_a = textSizeFnCache.get(dataLabels.style)) !== null && _a !== void 0 ? _a : getTextSizeFn({ style: dataLabels.style });
|
|
124
|
+
const availableWidth = Math.max(0, visibleWidth - 2 * dataLabels.padding);
|
|
125
|
+
const text = await getTextWithElipsis({
|
|
126
|
+
text: content,
|
|
127
|
+
getTextWidth: (s) => getTextSize(s).then((r) => r.width),
|
|
128
|
+
maxWidth: availableWidth,
|
|
129
|
+
});
|
|
130
|
+
if (!text) {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
const { width, height, hangingOffset } = await getTextSize(text);
|
|
134
|
+
const svgItem = {
|
|
135
|
+
text,
|
|
136
|
+
size: { width, height, hangingOffset },
|
|
137
|
+
style: dataLabels.style,
|
|
138
|
+
textAnchor: 'middle',
|
|
139
|
+
x: visibleCenterX,
|
|
140
|
+
y: item.y + item.height / 2,
|
|
141
|
+
series: item.series,
|
|
142
|
+
};
|
|
143
|
+
item.svgLabels.push(svgItem);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return result;
|
|
147
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { HtmlItem, LabelData, TooltipDataChunkXRange } from '../../../types';
|
|
2
|
+
import type { PreparedXRangeSeries } from '../../useSeries/types';
|
|
3
|
+
export type PreparedXRangeData = Omit<TooltipDataChunkXRange, 'series'> & {
|
|
4
|
+
x: number;
|
|
5
|
+
y: number;
|
|
6
|
+
width: number;
|
|
7
|
+
height: number;
|
|
8
|
+
color: string;
|
|
9
|
+
series: PreparedXRangeSeries;
|
|
10
|
+
svgLabels: LabelData[];
|
|
11
|
+
htmlLabels: HtmlItem[];
|
|
12
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -6,7 +6,8 @@ function mapSeriesTypeToZoomType(seriesType) {
|
|
|
6
6
|
case SERIES_TYPE.Area: {
|
|
7
7
|
return [ZOOM_TYPE.X, ZOOM_TYPE.XY, ZOOM_TYPE.Y];
|
|
8
8
|
}
|
|
9
|
-
case SERIES_TYPE.BarX:
|
|
9
|
+
case SERIES_TYPE.BarX:
|
|
10
|
+
case SERIES_TYPE.XRange: {
|
|
10
11
|
return [ZOOM_TYPE.X];
|
|
11
12
|
}
|
|
12
13
|
case SERIES_TYPE.BarY: {
|
|
@@ -36,6 +37,7 @@ function getDefaultZoomType(seriesType) {
|
|
|
36
37
|
}
|
|
37
38
|
case SERIES_TYPE.Area:
|
|
38
39
|
case SERIES_TYPE.BarX:
|
|
40
|
+
case SERIES_TYPE.XRange:
|
|
39
41
|
case SERIES_TYPE.Line:
|
|
40
42
|
case SERIES_TYPE.Waterfall: {
|
|
41
43
|
return ZOOM_TYPE.X;
|
|
@@ -43,12 +43,15 @@ export const DefaultTooltipContent = ({ hovered, pinned, rowRenderer, totals, va
|
|
|
43
43
|
const colorSymbol = getTooltipRowColorSymbol({ series, color });
|
|
44
44
|
return (React.createElement(Row, { key: id, active: active, color: color, colorSymbol: colorSymbol ? (React.createElement("div", { dangerouslySetInnerHTML: { __html: colorSymbol.outerHTML } })) : undefined, label: React.createElement("span", { dangerouslySetInnerHTML: { __html: name } }), striped: striped, value: formattedValue }));
|
|
45
45
|
};
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
46
|
+
let formattedHeadValue;
|
|
47
|
+
if (measureValue) {
|
|
48
|
+
formattedHeadValue = headerFormat
|
|
49
|
+
? getFormattedValue({
|
|
50
|
+
value: measureValue.value,
|
|
51
|
+
format: headerFormat,
|
|
52
|
+
})
|
|
53
|
+
: measureValue.formattedValue;
|
|
54
|
+
}
|
|
52
55
|
React.useEffect(() => {
|
|
53
56
|
if (!contentRowsRef.current) {
|
|
54
57
|
return;
|
|
@@ -186,6 +189,28 @@ export const DefaultTooltipContent = ({ hovered, pinned, rowRenderer, totals, va
|
|
|
186
189
|
formattedValue,
|
|
187
190
|
});
|
|
188
191
|
}
|
|
192
|
+
case 'x-range': {
|
|
193
|
+
const xRangeData = data;
|
|
194
|
+
const format = rowValueFormat || getDefaultValueFormat({ axis: xAxis });
|
|
195
|
+
const x0Formatted = getFormattedValue({
|
|
196
|
+
value: xRangeData.x0,
|
|
197
|
+
format,
|
|
198
|
+
});
|
|
199
|
+
const x1Formatted = getFormattedValue({
|
|
200
|
+
value: xRangeData.x1,
|
|
201
|
+
format,
|
|
202
|
+
});
|
|
203
|
+
return renderRow({
|
|
204
|
+
id,
|
|
205
|
+
active,
|
|
206
|
+
color,
|
|
207
|
+
name: series.name,
|
|
208
|
+
striped,
|
|
209
|
+
value: hoveredValues[i],
|
|
210
|
+
formattedValue: `${x0Formatted} — ${x1Formatted}`,
|
|
211
|
+
series,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
189
214
|
default: {
|
|
190
215
|
return null;
|
|
191
216
|
}
|
|
@@ -48,7 +48,7 @@ export const getMeasureValue = ({ data, xAxis, yAxis, headerFormat, }) => {
|
|
|
48
48
|
const value = (_b = (_a = data[0].category) === null || _a === void 0 ? void 0 : _a.key) !== null && _b !== void 0 ? _b : null;
|
|
49
49
|
return { value };
|
|
50
50
|
}
|
|
51
|
-
if (data.some((item) => item.series.type
|
|
51
|
+
if (data.some((item) => ['bar-y', 'x-range'].includes(item.series.type))) {
|
|
52
52
|
const value = getYRowData((_c = data[0]) === null || _c === void 0 ? void 0 : _c.data, yAxis);
|
|
53
53
|
const formattedValue = getFormattedValue({
|
|
54
54
|
value: getYRowData((_d = data[0]) === null || _d === void 0 ? void 0 : _d.data, yAxis),
|
|
@@ -72,7 +72,9 @@ export function getHoveredValues(args) {
|
|
|
72
72
|
case 'area':
|
|
73
73
|
case 'line':
|
|
74
74
|
case 'bar-x':
|
|
75
|
-
case '
|
|
75
|
+
case 'waterfall':
|
|
76
|
+
case 'scatter':
|
|
77
|
+
case 'x-range': {
|
|
76
78
|
return getYRowData(data, yAxis);
|
|
77
79
|
}
|
|
78
80
|
case 'bar-y': {
|
|
@@ -90,9 +92,6 @@ export function getHoveredValues(args) {
|
|
|
90
92
|
const { target, data: source } = seriesItem;
|
|
91
93
|
return (_a = source.links.find((d) => d.name === (target === null || target === void 0 ? void 0 : target.name))) === null || _a === void 0 ? void 0 : _a.value;
|
|
92
94
|
}
|
|
93
|
-
case 'waterfall': {
|
|
94
|
-
return getYRowData(data, yAxis);
|
|
95
|
-
}
|
|
96
95
|
default: {
|
|
97
96
|
return undefined;
|
|
98
97
|
}
|
|
@@ -21,7 +21,11 @@ type DefaultWaterfallSeriesOptions = Partial<ChartSeriesOptions['waterfall']> &
|
|
|
21
21
|
barPadding: number;
|
|
22
22
|
};
|
|
23
23
|
};
|
|
24
|
-
export type SeriesOptionsDefaults = Partial<ChartSeriesOptions> & DefaultBarXSeriesOptions & DefaultBarYSeriesOptions & DefaultWaterfallSeriesOptions
|
|
24
|
+
export type SeriesOptionsDefaults = Partial<ChartSeriesOptions> & DefaultBarXSeriesOptions & DefaultBarYSeriesOptions & DefaultWaterfallSeriesOptions & {
|
|
25
|
+
'x-range': {
|
|
26
|
+
borderRadius: number;
|
|
27
|
+
};
|
|
28
|
+
};
|
|
25
29
|
export declare const seriesOptionsDefaults: SeriesOptionsDefaults;
|
|
26
30
|
export declare const seriesRangeSliderOptionsDefaults: Required<ChartSeriesRangeSliderOptions>;
|
|
27
31
|
export {};
|
|
@@ -133,6 +133,19 @@ export const seriesOptionsDefaults = {
|
|
|
133
133
|
},
|
|
134
134
|
},
|
|
135
135
|
},
|
|
136
|
+
'x-range': {
|
|
137
|
+
borderRadius: 0,
|
|
138
|
+
states: {
|
|
139
|
+
hover: {
|
|
140
|
+
enabled: true,
|
|
141
|
+
brightness: 0.3,
|
|
142
|
+
},
|
|
143
|
+
inactive: {
|
|
144
|
+
enabled: false,
|
|
145
|
+
opacity: 0.5,
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
},
|
|
136
149
|
};
|
|
137
150
|
export const seriesRangeSliderOptionsDefaults = {
|
|
138
151
|
visible: true,
|