@gravity-ui/chartkit 4.13.0 → 4.14.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/build/i18n/keysets/en.json +6 -1
- package/build/i18n/keysets/ru.json +6 -1
- package/build/libs/chartkit-error/chartkit-error.d.ts +1 -0
- package/build/libs/chartkit-error/chartkit-error.js +1 -0
- package/build/plugins/d3/examples/area/Basic.d.ts +2 -0
- package/build/plugins/d3/examples/area/Basic.js +35 -0
- package/build/plugins/d3/examples/area/StackedArea.d.ts +2 -0
- package/build/plugins/d3/examples/area/StackedArea.js +48 -0
- package/build/plugins/d3/examples/bar-x/Basic.js +11 -5
- package/build/plugins/d3/renderer/D3Widget.js +27 -23
- package/build/plugins/d3/renderer/components/Tooltip/DefaultContent.js +1 -0
- package/build/plugins/d3/renderer/components/Tooltip/TooltipTriggerArea.js +1 -1
- package/build/plugins/d3/renderer/constants/defaults/axis.d.ts +2 -0
- package/build/plugins/d3/renderer/constants/defaults/axis.js +1 -0
- package/build/plugins/d3/renderer/constants/defaults/series-options.js +16 -4
- package/build/plugins/d3/renderer/hooks/useAxisScales/index.js +3 -2
- package/build/plugins/d3/renderer/hooks/useChartOptions/y-axis.js +2 -1
- package/build/plugins/d3/renderer/hooks/useSeries/constants.d.ts +3 -1
- package/build/plugins/d3/renderer/hooks/useSeries/constants.js +5 -0
- package/build/plugins/d3/renderer/hooks/useSeries/prepare-area.d.ts +19 -0
- package/build/plugins/d3/renderer/hooks/useSeries/prepare-area.js +66 -0
- package/build/plugins/d3/renderer/hooks/useSeries/prepare-bar-x.js +2 -7
- package/build/plugins/d3/renderer/hooks/useSeries/prepare-bar-y.js +2 -7
- package/build/plugins/d3/renderer/hooks/useSeries/prepare-line-series.js +2 -6
- package/build/plugins/d3/renderer/hooks/useSeries/prepareSeries.js +9 -0
- package/build/plugins/d3/renderer/hooks/useSeries/types.d.ts +39 -2
- package/build/plugins/d3/renderer/hooks/useSeries/utils.d.ts +2 -1
- package/build/plugins/d3/renderer/hooks/useSeries/utils.js +10 -0
- package/build/plugins/d3/renderer/hooks/useShapes/area/index.d.ts +11 -0
- package/build/plugins/d3/renderer/hooks/useShapes/area/index.js +194 -0
- package/build/plugins/d3/renderer/hooks/useShapes/area/prepare-data.d.ts +11 -0
- package/build/plugins/d3/renderer/hooks/useShapes/area/prepare-data.js +114 -0
- package/build/plugins/d3/renderer/hooks/useShapes/area/types.d.ts +27 -0
- package/build/plugins/d3/renderer/hooks/useShapes/area/types.js +1 -0
- package/build/plugins/d3/renderer/hooks/useShapes/index.d.ts +2 -1
- package/build/plugins/d3/renderer/hooks/useShapes/index.js +16 -0
- package/build/plugins/d3/renderer/utils/index.d.ts +1 -1
- package/build/plugins/d3/renderer/utils/index.js +16 -9
- package/build/plugins/d3/renderer/validation/__mocks__/index.d.ts +3 -0
- package/build/plugins/d3/renderer/validation/__mocks__/index.js +44 -0
- package/build/plugins/d3/renderer/validation/index.d.ts +2 -0
- package/build/plugins/d3/renderer/validation/index.js +145 -0
- package/build/types/widget-data/area.d.ts +57 -0
- package/build/types/widget-data/area.js +1 -0
- package/build/types/widget-data/index.d.ts +1 -0
- package/build/types/widget-data/index.js +1 -0
- package/build/types/widget-data/line.d.ts +3 -3
- package/build/types/widget-data/marker.d.ts +8 -0
- package/build/types/widget-data/series.d.ts +24 -8
- package/build/types/widget-data/tooltip.d.ts +10 -1
- package/package.json +2 -2
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import memoize from 'lodash/memoize';
|
|
1
2
|
import { DEFAULT_LEGEND_SYMBOL_PADDING, DEFAULT_LEGEND_SYMBOL_SIZE } from './constants';
|
|
3
|
+
import { getRandomCKId } from '../../../../../utils';
|
|
2
4
|
export const getActiveLegendItems = (series) => {
|
|
3
5
|
return series.reduce((acc, s) => {
|
|
4
6
|
if (s.legend.enabled && s.visible) {
|
|
@@ -22,3 +24,11 @@ export function prepareLegendSymbol(series) {
|
|
|
22
24
|
padding: (symbolOptions === null || symbolOptions === void 0 ? void 0 : symbolOptions.padding) || DEFAULT_LEGEND_SYMBOL_PADDING,
|
|
23
25
|
};
|
|
24
26
|
}
|
|
27
|
+
const getCommonStackId = memoize(getRandomCKId);
|
|
28
|
+
export function getSeriesStackId(series) {
|
|
29
|
+
let stackId = series.stackId;
|
|
30
|
+
if (!stackId) {
|
|
31
|
+
stackId = series.stacking === 'normal' ? getCommonStackId() : getRandomCKId();
|
|
32
|
+
}
|
|
33
|
+
return stackId;
|
|
34
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { Dispatch } from 'd3';
|
|
3
|
+
import type { PreparedSeriesOptions } from '../../useSeries/types';
|
|
4
|
+
import type { PreparedAreaData } from './types';
|
|
5
|
+
type Args = {
|
|
6
|
+
dispatcher: Dispatch<object>;
|
|
7
|
+
preparedData: PreparedAreaData[];
|
|
8
|
+
seriesOptions: PreparedSeriesOptions;
|
|
9
|
+
};
|
|
10
|
+
export declare const AreaSeriesShapes: (args: Args) => React.JSX.Element;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { color, line as lineGenerator, area as areaGenerator, select, symbol, symbolCircle, symbolSquare, } from 'd3';
|
|
3
|
+
import get from 'lodash/get';
|
|
4
|
+
import { block } from '../../../../../../utils/cn';
|
|
5
|
+
import { filterOverlappingLabels } from '../../../utils';
|
|
6
|
+
import { setActiveState } from '../utils';
|
|
7
|
+
const b = block('d3-area');
|
|
8
|
+
function setMarker(selection, state) {
|
|
9
|
+
selection
|
|
10
|
+
.attr('d', (d) => {
|
|
11
|
+
const radius = d.point.series.marker.states[state].radius +
|
|
12
|
+
d.point.series.marker.states[state].borderWidth;
|
|
13
|
+
return getMarkerSymbol(d.point.series.marker.states.normal.symbol, radius);
|
|
14
|
+
})
|
|
15
|
+
.attr('stroke-width', (d) => d.point.series.marker.states[state].borderWidth)
|
|
16
|
+
.attr('stroke', (d) => d.point.series.marker.states[state].borderColor);
|
|
17
|
+
}
|
|
18
|
+
function getMarkerSymbol(type, radius) {
|
|
19
|
+
switch (type) {
|
|
20
|
+
case 'square': {
|
|
21
|
+
const size = Math.pow(radius, 2) * Math.PI;
|
|
22
|
+
return symbol(symbolSquare, size)();
|
|
23
|
+
}
|
|
24
|
+
case 'circle':
|
|
25
|
+
default: {
|
|
26
|
+
const size = Math.pow(radius, 2) * Math.PI;
|
|
27
|
+
return symbol(symbolCircle, size)();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const getMarkerVisibility = (d) => {
|
|
32
|
+
const markerStates = d.point.series.marker.states;
|
|
33
|
+
const enabled = (markerStates.hover.enabled && d.hovered) || markerStates.normal.enabled;
|
|
34
|
+
return enabled ? '' : 'hidden';
|
|
35
|
+
};
|
|
36
|
+
const getMarkerHaloVisibility = (d) => {
|
|
37
|
+
const markerStates = d.point.series.marker.states;
|
|
38
|
+
const enabled = markerStates.hover.halo.enabled && d.hovered;
|
|
39
|
+
return enabled ? '' : 'hidden';
|
|
40
|
+
};
|
|
41
|
+
export const AreaSeriesShapes = (args) => {
|
|
42
|
+
const { dispatcher, preparedData, seriesOptions } = args;
|
|
43
|
+
const ref = React.useRef(null);
|
|
44
|
+
React.useEffect(() => {
|
|
45
|
+
var _a;
|
|
46
|
+
if (!ref.current) {
|
|
47
|
+
return () => { };
|
|
48
|
+
}
|
|
49
|
+
const svgElement = select(ref.current);
|
|
50
|
+
const hoverOptions = get(seriesOptions, 'area.states.hover');
|
|
51
|
+
const inactiveOptions = get(seriesOptions, 'area.states.inactive');
|
|
52
|
+
const line = lineGenerator()
|
|
53
|
+
.x((d) => d.x)
|
|
54
|
+
.y((d) => d.y);
|
|
55
|
+
svgElement.selectAll('*').remove();
|
|
56
|
+
const shapeSelection = svgElement
|
|
57
|
+
.selectAll('shape')
|
|
58
|
+
.data(preparedData)
|
|
59
|
+
.join('g')
|
|
60
|
+
.attr('class', b('series'));
|
|
61
|
+
shapeSelection
|
|
62
|
+
.append('path')
|
|
63
|
+
.attr('class', b('line'))
|
|
64
|
+
.attr('d', (d) => line(d.points))
|
|
65
|
+
.attr('fill', 'none')
|
|
66
|
+
.attr('stroke', (d) => d.color)
|
|
67
|
+
.attr('stroke-width', (d) => d.width)
|
|
68
|
+
.attr('stroke-linejoin', 'round')
|
|
69
|
+
.attr('stroke-linecap', 'round');
|
|
70
|
+
const area = areaGenerator()
|
|
71
|
+
.x((d) => d.x)
|
|
72
|
+
.y0((d) => d.y0)
|
|
73
|
+
.y1((d) => d.y);
|
|
74
|
+
shapeSelection
|
|
75
|
+
.append('path')
|
|
76
|
+
.attr('class', b('region'))
|
|
77
|
+
.attr('d', (d) => area(d.points))
|
|
78
|
+
.attr('fill', (d) => d.color)
|
|
79
|
+
.attr('opacity', (d) => d.opacity);
|
|
80
|
+
let dataLabels = preparedData.reduce((acc, d) => {
|
|
81
|
+
return acc.concat(d.labels);
|
|
82
|
+
}, []);
|
|
83
|
+
if (!((_a = preparedData[0]) === null || _a === void 0 ? void 0 : _a.series.dataLabels.allowOverlap)) {
|
|
84
|
+
dataLabels = filterOverlappingLabels(dataLabels);
|
|
85
|
+
}
|
|
86
|
+
const labelsSelection = svgElement
|
|
87
|
+
.selectAll('text')
|
|
88
|
+
.data(dataLabels)
|
|
89
|
+
.join('text')
|
|
90
|
+
.text((d) => d.text)
|
|
91
|
+
.attr('class', b('label'))
|
|
92
|
+
.attr('x', (d) => d.x)
|
|
93
|
+
.attr('y', (d) => d.y)
|
|
94
|
+
.attr('text-anchor', (d) => d.textAnchor)
|
|
95
|
+
.style('font-size', (d) => d.style.fontSize)
|
|
96
|
+
.style('font-weight', (d) => d.style.fontWeight || null)
|
|
97
|
+
.style('fill', (d) => d.style.fontColor || null);
|
|
98
|
+
const markers = preparedData.reduce((acc, d) => acc.concat(d.markers), []);
|
|
99
|
+
const markerSelection = svgElement
|
|
100
|
+
.selectAll('marker')
|
|
101
|
+
.data(markers)
|
|
102
|
+
.join('g')
|
|
103
|
+
.attr('class', b('marker'))
|
|
104
|
+
.attr('visibility', getMarkerVisibility)
|
|
105
|
+
.attr('transform', (d) => {
|
|
106
|
+
return `translate(${d.point.x},${d.point.y})`;
|
|
107
|
+
});
|
|
108
|
+
markerSelection
|
|
109
|
+
.append('path')
|
|
110
|
+
.attr('class', b('marker-halo'))
|
|
111
|
+
.attr('d', (d) => {
|
|
112
|
+
const type = d.point.series.marker.states.normal.symbol;
|
|
113
|
+
const radius = d.point.series.marker.states.hover.halo.radius;
|
|
114
|
+
return getMarkerSymbol(type, radius);
|
|
115
|
+
})
|
|
116
|
+
.attr('fill', (d) => d.point.series.color)
|
|
117
|
+
.attr('opacity', (d) => d.point.series.marker.states.hover.halo.opacity)
|
|
118
|
+
.attr('z-index', -1)
|
|
119
|
+
.attr('visibility', getMarkerHaloVisibility);
|
|
120
|
+
markerSelection
|
|
121
|
+
.append('path')
|
|
122
|
+
.attr('class', b('marker-symbol'))
|
|
123
|
+
.call(setMarker, 'normal')
|
|
124
|
+
.attr('fill', (d) => d.point.series.color);
|
|
125
|
+
const hoverEnabled = hoverOptions === null || hoverOptions === void 0 ? void 0 : hoverOptions.enabled;
|
|
126
|
+
const inactiveEnabled = inactiveOptions === null || inactiveOptions === void 0 ? void 0 : inactiveOptions.enabled;
|
|
127
|
+
dispatcher.on('hover-shape.area', (data) => {
|
|
128
|
+
var _a;
|
|
129
|
+
const selected = data === null || data === void 0 ? void 0 : data.find((d) => d.series.type === 'area');
|
|
130
|
+
const selectedDataItem = selected === null || selected === void 0 ? void 0 : selected.data;
|
|
131
|
+
const selectedSeriesId = (_a = selected === null || selected === void 0 ? void 0 : selected.series) === null || _a === void 0 ? void 0 : _a.id;
|
|
132
|
+
shapeSelection.datum((d, index, list) => {
|
|
133
|
+
var _a;
|
|
134
|
+
const elementSelection = select(list[index]);
|
|
135
|
+
const hovered = Boolean(hoverEnabled && d.id === selectedSeriesId);
|
|
136
|
+
if (d.hovered !== hovered) {
|
|
137
|
+
d.hovered = hovered;
|
|
138
|
+
let strokeColor = d.color || '';
|
|
139
|
+
if (d.hovered) {
|
|
140
|
+
strokeColor =
|
|
141
|
+
((_a = color(strokeColor)) === null || _a === void 0 ? void 0 : _a.brighter(hoverOptions === null || hoverOptions === void 0 ? void 0 : hoverOptions.brightness).toString()) ||
|
|
142
|
+
strokeColor;
|
|
143
|
+
}
|
|
144
|
+
elementSelection.selectAll(`.${b('line')}`).attr('stroke', strokeColor);
|
|
145
|
+
elementSelection.selectAll(`.${b('region')}`).attr('fill', strokeColor);
|
|
146
|
+
}
|
|
147
|
+
return setActiveState({
|
|
148
|
+
element: list[index],
|
|
149
|
+
state: inactiveOptions,
|
|
150
|
+
active: Boolean(!inactiveEnabled || !selectedSeriesId || selectedSeriesId === d.id),
|
|
151
|
+
datum: d,
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
labelsSelection.datum((d, index, list) => {
|
|
155
|
+
return setActiveState({
|
|
156
|
+
element: list[index],
|
|
157
|
+
state: inactiveOptions,
|
|
158
|
+
active: Boolean(!inactiveEnabled || !selectedSeriesId || selectedSeriesId === d.series.id),
|
|
159
|
+
datum: d,
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
markerSelection.datum((d, index, list) => {
|
|
163
|
+
const elementSelection = select(list[index]);
|
|
164
|
+
const hovered = Boolean(hoverEnabled && d.point.data === selectedDataItem);
|
|
165
|
+
if (d.hovered !== hovered) {
|
|
166
|
+
d.hovered = hovered;
|
|
167
|
+
elementSelection.attr('visibility', getMarkerVisibility(d));
|
|
168
|
+
elementSelection
|
|
169
|
+
.select(`.${b('marker-halo')}`)
|
|
170
|
+
.attr('visibility', getMarkerHaloVisibility);
|
|
171
|
+
elementSelection
|
|
172
|
+
.select(`.${b('marker-symbol')}`)
|
|
173
|
+
.call(setMarker, hovered ? 'hover' : 'normal');
|
|
174
|
+
}
|
|
175
|
+
if (d.point.series.marker.states.normal.enabled) {
|
|
176
|
+
const isActive = Boolean(!inactiveEnabled ||
|
|
177
|
+
!selectedSeriesId ||
|
|
178
|
+
selectedSeriesId === d.point.series.id);
|
|
179
|
+
setActiveState({
|
|
180
|
+
element: list[index],
|
|
181
|
+
state: inactiveOptions,
|
|
182
|
+
active: isActive,
|
|
183
|
+
datum: d,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
return d;
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
return () => {
|
|
190
|
+
dispatcher.on('hover-shape.area', null);
|
|
191
|
+
};
|
|
192
|
+
}, [dispatcher, preparedData, seriesOptions]);
|
|
193
|
+
return React.createElement("g", { ref: ref, className: b() });
|
|
194
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { PreparedAreaSeries } from '../../useSeries/types';
|
|
2
|
+
import type { PreparedAxis } from '../../useChartOptions/types';
|
|
3
|
+
import type { ChartScale } from '../../useAxisScales';
|
|
4
|
+
import type { PreparedAreaData } from './types';
|
|
5
|
+
export declare const prepareAreaData: (args: {
|
|
6
|
+
series: PreparedAreaSeries[];
|
|
7
|
+
xAxis: PreparedAxis;
|
|
8
|
+
xScale: ChartScale;
|
|
9
|
+
yAxis: PreparedAxis[];
|
|
10
|
+
yScale: ChartScale;
|
|
11
|
+
}) => PreparedAreaData[];
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { group, sort } from 'd3';
|
|
2
|
+
import { getXValue, getYValue } from '../utils';
|
|
3
|
+
import { getLabelsSize, getLeftPosition } from '../../../utils';
|
|
4
|
+
function getLabelData(point, series, xMax) {
|
|
5
|
+
const text = String(point.data.label || point.data.y);
|
|
6
|
+
const style = series.dataLabels.style;
|
|
7
|
+
const size = getLabelsSize({ labels: [text], style });
|
|
8
|
+
const labelData = {
|
|
9
|
+
text,
|
|
10
|
+
x: point.x,
|
|
11
|
+
y: point.y - series.dataLabels.padding,
|
|
12
|
+
style,
|
|
13
|
+
size: { width: size.maxWidth, height: size.maxHeight },
|
|
14
|
+
textAnchor: 'middle',
|
|
15
|
+
series: series,
|
|
16
|
+
active: true,
|
|
17
|
+
};
|
|
18
|
+
const left = getLeftPosition(labelData);
|
|
19
|
+
if (left < 0) {
|
|
20
|
+
labelData.x = labelData.x + Math.abs(left);
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
const right = left + labelData.size.width;
|
|
24
|
+
if (right > xMax) {
|
|
25
|
+
labelData.x = labelData.x - xMax - right;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return labelData;
|
|
29
|
+
}
|
|
30
|
+
function getXValues(series, xAxis, xScale) {
|
|
31
|
+
const xValues = series.reduce((acc, s) => {
|
|
32
|
+
s.data.forEach((d) => {
|
|
33
|
+
const key = String(d.x);
|
|
34
|
+
if (!acc.has(key)) {
|
|
35
|
+
acc.set(key, getXValue({ point: d, xAxis, xScale }));
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
return acc;
|
|
39
|
+
}, new Map());
|
|
40
|
+
if (xAxis.type === 'category') {
|
|
41
|
+
return (xAxis.categories || []).reduce((acc, category) => {
|
|
42
|
+
const xValue = xValues.get(category);
|
|
43
|
+
if (typeof xValue === 'number') {
|
|
44
|
+
acc.push([category, xValue]);
|
|
45
|
+
}
|
|
46
|
+
return acc;
|
|
47
|
+
}, []);
|
|
48
|
+
}
|
|
49
|
+
return sort(Array.from(xValues), ([_x, xValue]) => xValue);
|
|
50
|
+
}
|
|
51
|
+
export const prepareAreaData = (args) => {
|
|
52
|
+
const { series, xAxis, xScale, yScale } = args;
|
|
53
|
+
const yAxis = args.yAxis[0];
|
|
54
|
+
const [_xMin, xRangeMax] = xScale.range();
|
|
55
|
+
const xMax = xRangeMax / (1 - xAxis.maxPadding);
|
|
56
|
+
const [yMin, _yMax] = yScale.range();
|
|
57
|
+
return Array.from(group(series, (s) => s.stackId)).reduce((result, [_stackId, seriesStack]) => {
|
|
58
|
+
const xValues = getXValues(seriesStack, xAxis, xScale);
|
|
59
|
+
const accumulatedYValues = new Map();
|
|
60
|
+
xValues.forEach(([key]) => {
|
|
61
|
+
accumulatedYValues.set(key, 0);
|
|
62
|
+
});
|
|
63
|
+
const seriesStackData = seriesStack.reduce((acc, s) => {
|
|
64
|
+
const seriesData = s.data.reduce((m, d) => {
|
|
65
|
+
return m.set(String(d.x), d);
|
|
66
|
+
}, new Map());
|
|
67
|
+
const points = xValues.reduce((pointsAcc, [x, xValue]) => {
|
|
68
|
+
const accumulatedYValue = accumulatedYValues.get(x) || 0;
|
|
69
|
+
const d = seriesData.get(x) ||
|
|
70
|
+
{
|
|
71
|
+
x,
|
|
72
|
+
// FIXME: think about how to break the series into separate areas(null Y values)
|
|
73
|
+
y: 0,
|
|
74
|
+
};
|
|
75
|
+
const yValue = getYValue({ point: d, yAxis, yScale }) - accumulatedYValue;
|
|
76
|
+
accumulatedYValues.set(x, yMin - yValue);
|
|
77
|
+
pointsAcc.push({
|
|
78
|
+
y0: yMin - accumulatedYValue,
|
|
79
|
+
x: xValue,
|
|
80
|
+
y: yValue,
|
|
81
|
+
data: d,
|
|
82
|
+
series: s,
|
|
83
|
+
});
|
|
84
|
+
return pointsAcc;
|
|
85
|
+
}, []);
|
|
86
|
+
let labels = [];
|
|
87
|
+
if (s.dataLabels.enabled) {
|
|
88
|
+
labels = points.map((p) => getLabelData(p, s, xMax));
|
|
89
|
+
}
|
|
90
|
+
let markers = [];
|
|
91
|
+
if (s.marker.states.normal.enabled || s.marker.states.hover.enabled) {
|
|
92
|
+
markers = points.map((p) => ({
|
|
93
|
+
point: p,
|
|
94
|
+
active: true,
|
|
95
|
+
hovered: false,
|
|
96
|
+
}));
|
|
97
|
+
}
|
|
98
|
+
acc.push({
|
|
99
|
+
points,
|
|
100
|
+
markers,
|
|
101
|
+
labels,
|
|
102
|
+
color: s.color,
|
|
103
|
+
opacity: s.opacity,
|
|
104
|
+
width: s.lineWidth,
|
|
105
|
+
series: s,
|
|
106
|
+
hovered: false,
|
|
107
|
+
active: true,
|
|
108
|
+
id: s.id,
|
|
109
|
+
});
|
|
110
|
+
return acc;
|
|
111
|
+
}, []);
|
|
112
|
+
return result.concat(seriesStackData);
|
|
113
|
+
}, []);
|
|
114
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { PreparedAreaSeries } from '../../useSeries/types';
|
|
2
|
+
import { AreaSeriesData } from '../../../../../../types';
|
|
3
|
+
import { LabelData } from '../../../types';
|
|
4
|
+
export type PointData = {
|
|
5
|
+
y0: number;
|
|
6
|
+
x: number;
|
|
7
|
+
y: number;
|
|
8
|
+
data: AreaSeriesData;
|
|
9
|
+
series: PreparedAreaSeries;
|
|
10
|
+
};
|
|
11
|
+
export type MarkerData = {
|
|
12
|
+
point: PointData;
|
|
13
|
+
active: boolean;
|
|
14
|
+
hovered: boolean;
|
|
15
|
+
};
|
|
16
|
+
export type PreparedAreaData = {
|
|
17
|
+
id: string;
|
|
18
|
+
points: PointData[];
|
|
19
|
+
markers: MarkerData[];
|
|
20
|
+
color: string;
|
|
21
|
+
opacity: number;
|
|
22
|
+
width: number;
|
|
23
|
+
series: PreparedAreaSeries;
|
|
24
|
+
hovered: boolean;
|
|
25
|
+
active: boolean;
|
|
26
|
+
labels: LabelData[];
|
|
27
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -10,8 +10,9 @@ import type { PreparedLineData } from './line/types';
|
|
|
10
10
|
import type { PreparedBarYData } from './bar-y/types';
|
|
11
11
|
export type { PreparedBarXData } from './bar-x';
|
|
12
12
|
export type { PreparedScatterData } from './scatter';
|
|
13
|
+
import type { PreparedAreaData } from './area/types';
|
|
13
14
|
import './styles.css';
|
|
14
|
-
export type ShapeData = PreparedBarXData | PreparedBarYData | PreparedScatterData | PreparedLineData | PreparedPieData;
|
|
15
|
+
export type ShapeData = PreparedBarXData | PreparedBarYData | PreparedScatterData | PreparedLineData | PreparedPieData | PreparedAreaData;
|
|
15
16
|
type Args = {
|
|
16
17
|
boundsWidth: number;
|
|
17
18
|
boundsHeight: number;
|
|
@@ -8,6 +8,8 @@ import { preparePieData } from './pie/prepare-data';
|
|
|
8
8
|
import { prepareLineData } from './line/prepare-data';
|
|
9
9
|
import { LineSeriesShapes } from './line';
|
|
10
10
|
import { BarYSeriesShapes, prepareBarYData } from './bar-y';
|
|
11
|
+
import { AreaSeriesShapes } from './area';
|
|
12
|
+
import { prepareAreaData } from './area/prepare-data';
|
|
11
13
|
import './styles.css';
|
|
12
14
|
export const useShapes = (args) => {
|
|
13
15
|
const { boundsWidth, boundsHeight, dispatcher, series, seriesOptions, xAxis, xScale, yAxis, yScale, svgContainer, } = args;
|
|
@@ -62,6 +64,20 @@ export const useShapes = (args) => {
|
|
|
62
64
|
}
|
|
63
65
|
break;
|
|
64
66
|
}
|
|
67
|
+
case 'area': {
|
|
68
|
+
if (xScale && yScale) {
|
|
69
|
+
const preparedData = prepareAreaData({
|
|
70
|
+
series: chartSeries,
|
|
71
|
+
xAxis,
|
|
72
|
+
xScale,
|
|
73
|
+
yAxis,
|
|
74
|
+
yScale,
|
|
75
|
+
});
|
|
76
|
+
acc.push(React.createElement(AreaSeriesShapes, { key: "area", dispatcher: dispatcher, seriesOptions: seriesOptions, preparedData: preparedData }));
|
|
77
|
+
shapesData.push(...preparedData);
|
|
78
|
+
}
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
65
81
|
case 'scatter': {
|
|
66
82
|
if (xScale && yScale) {
|
|
67
83
|
const preparedData = prepareScatterData({
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AxisDomain } from 'd3';
|
|
2
|
-
import type { BaseTextStyle, ChartKitWidgetSeries, ChartKitWidgetSeriesData } from '../../../../types
|
|
2
|
+
import type { BaseTextStyle, ChartKitWidgetSeries, ChartKitWidgetSeriesData } from '../../../../types';
|
|
3
3
|
import { PreparedAxis } from '../hooks';
|
|
4
4
|
export * from './math';
|
|
5
5
|
export * from './text';
|
|
@@ -6,6 +6,7 @@ import { formatNumber } from '../../../shared';
|
|
|
6
6
|
import { DEFAULT_AXIS_LABEL_FONT_SIZE } from '../constants';
|
|
7
7
|
import { getNumberUnitRate } from '../../../shared/format-number/format-number';
|
|
8
8
|
import { getDefaultDateFormat } from './time';
|
|
9
|
+
import { getSeriesStackId } from '../hooks/useSeries/utils';
|
|
9
10
|
export * from './math';
|
|
10
11
|
export * from './text';
|
|
11
12
|
export * from './time';
|
|
@@ -42,20 +43,26 @@ export const getDomainDataYBySeries = (series) => {
|
|
|
42
43
|
const groupedSeries = group(series, (item) => item.type);
|
|
43
44
|
return Array.from(groupedSeries).reduce((acc, [type, seriesList]) => {
|
|
44
45
|
switch (type) {
|
|
46
|
+
case 'area':
|
|
45
47
|
case 'bar-x': {
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
Array.from(stackedSeries).forEach(([, stack]) => {
|
|
48
|
+
const stackedSeries = group(seriesList, getSeriesStackId);
|
|
49
|
+
Array.from(stackedSeries).forEach(([_stackId, seriesStack]) => {
|
|
49
50
|
const values = {};
|
|
50
|
-
|
|
51
|
+
seriesStack.forEach((singleSeries) => {
|
|
52
|
+
const data = new Map();
|
|
51
53
|
singleSeries.data.forEach((point) => {
|
|
52
|
-
const key = String(point.x
|
|
53
|
-
|
|
54
|
-
values[key] = 0;
|
|
55
|
-
}
|
|
54
|
+
const key = String(point.x);
|
|
55
|
+
let value = 0;
|
|
56
56
|
if (point.y && typeof point.y === 'number') {
|
|
57
|
-
|
|
57
|
+
value = point.y;
|
|
58
|
+
}
|
|
59
|
+
if (data.has(key)) {
|
|
60
|
+
value = Math.max(value, data.get(key));
|
|
58
61
|
}
|
|
62
|
+
data.set(key, value);
|
|
63
|
+
});
|
|
64
|
+
Array.from(data).forEach(([key, value]) => {
|
|
65
|
+
values[key] = (values[key] || 0) + value;
|
|
59
66
|
});
|
|
60
67
|
});
|
|
61
68
|
acc.push(...Object.values(values));
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export const XY_SERIES = {
|
|
2
|
+
INVALID_CATEGORY_X: {
|
|
3
|
+
series: {
|
|
4
|
+
data: [{ type: 'scatter', data: [{ x: undefined, y: 1 }], name: 'Series' }],
|
|
5
|
+
},
|
|
6
|
+
xAxis: { type: 'category' },
|
|
7
|
+
},
|
|
8
|
+
INVALID_CATEGORY_Y: {
|
|
9
|
+
series: {
|
|
10
|
+
data: [{ type: 'scatter', data: [{ x: 1, y: undefined }], name: 'Series' }],
|
|
11
|
+
},
|
|
12
|
+
yAxis: [{ type: 'category' }],
|
|
13
|
+
},
|
|
14
|
+
INVALID_DATETIME_X: {
|
|
15
|
+
series: {
|
|
16
|
+
data: [{ type: 'scatter', data: [{ x: undefined, y: 1 }], name: 'Series' }],
|
|
17
|
+
},
|
|
18
|
+
xAxis: { type: 'datetime' },
|
|
19
|
+
},
|
|
20
|
+
INVALID_DATETIME_Y: {
|
|
21
|
+
series: {
|
|
22
|
+
data: [{ type: 'scatter', data: [{ x: undefined, y: 1 }], name: 'Series' }],
|
|
23
|
+
},
|
|
24
|
+
yAxis: [{ type: 'datetime' }],
|
|
25
|
+
},
|
|
26
|
+
INVALID_LINEAR_X: {
|
|
27
|
+
series: {
|
|
28
|
+
data: [{ type: 'scatter', data: [{ x: 'str', y: 1 }], name: 'Series' }],
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
INVALID_LINEAR_Y: {
|
|
32
|
+
series: {
|
|
33
|
+
data: [{ type: 'scatter', data: [{ x: 1, y: 'str' }], name: 'Series' }],
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
export const PIE_SERIES = {
|
|
38
|
+
INVALID_VALUE: {
|
|
39
|
+
series: {
|
|
40
|
+
// @ts-expect-error
|
|
41
|
+
data: [{ type: 'pie', data: [{ value: undefined, name: 'Series' }] }],
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
};
|