@gravity-ui/chartkit 4.13.0 → 4.15.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/constants/index.d.ts +1 -0
- package/build/constants/index.js +1 -0
- package/build/constants/widget-data.d.ts +27 -0
- package/build/constants/widget-data.js +29 -0
- 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/ExampleWrapper.d.ts +7 -0
- package/build/plugins/d3/examples/ExampleWrapper.js +5 -0
- package/build/plugins/d3/examples/area/Basic.d.ts +2 -0
- package/build/plugins/d3/examples/area/Basic.js +34 -0
- package/build/plugins/d3/examples/area/StackedArea.d.ts +2 -0
- package/build/plugins/d3/examples/area/StackedArea.js +47 -0
- package/build/plugins/d3/examples/bar-x/Basic.js +19 -9
- package/build/plugins/d3/examples/bar-x/DataLabels.js +4 -2
- package/build/plugins/d3/examples/bar-x/GroupedColumns.js +4 -2
- package/build/plugins/d3/examples/bar-x/StackedColumns.js +4 -2
- package/build/plugins/d3/examples/bar-y/Basic.js +4 -2
- package/build/plugins/d3/examples/bar-y/GroupedColumns.js +4 -2
- package/build/plugins/d3/examples/bar-y/StackedColumns.js +4 -2
- package/build/plugins/d3/examples/combined/LineAndBarX.js +5 -3
- package/build/plugins/d3/examples/line/Basic.js +4 -2
- package/build/plugins/d3/examples/line/DataLabels.js +4 -2
- package/build/plugins/d3/examples/line/LineWithMarkers.js +4 -2
- package/build/plugins/d3/examples/line/Shapes.d.ts +2 -0
- package/build/plugins/d3/examples/line/Shapes.js +93 -0
- package/build/plugins/d3/examples/pie/Basic.js +4 -2
- package/build/plugins/d3/examples/pie/Donut.js +4 -2
- package/build/plugins/d3/examples/scatter/Basic.js +4 -2
- package/build/plugins/d3/renderer/D3Widget.js +27 -23
- package/build/plugins/d3/renderer/components/Legend.js +4 -0
- 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 +2 -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.d.ts +2 -0
- package/build/plugins/d3/renderer/hooks/useSeries/prepare-line-series.js +13 -6
- package/build/plugins/d3/renderer/hooks/useSeries/prepare-pie.d.ts +2 -1
- package/build/plugins/d3/renderer/hooks/useSeries/prepare-pie.js +11 -1
- package/build/plugins/d3/renderer/hooks/useSeries/prepareSeries.js +10 -1
- package/build/plugins/d3/renderer/hooks/useSeries/types.d.ts +50 -7
- 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 +137 -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/hooks/useShapes/line/index.js +9 -65
- package/build/plugins/d3/renderer/hooks/useShapes/line/prepare-data.js +5 -2
- package/build/plugins/d3/renderer/hooks/useShapes/line/types.d.ts +3 -0
- package/build/plugins/d3/renderer/hooks/useShapes/marker.d.ts +12 -0
- package/build/plugins/d3/renderer/hooks/useShapes/marker.js +70 -0
- package/build/plugins/d3/renderer/hooks/useShapes/pie/index.d.ts +3 -2
- package/build/plugins/d3/renderer/hooks/useShapes/pie/index.js +28 -0
- package/build/plugins/d3/renderer/hooks/useShapes/pie/prepare-data.js +9 -3
- package/build/plugins/d3/renderer/hooks/useShapes/pie/types.d.ts +5 -0
- package/build/plugins/d3/renderer/hooks/useShapes/scatter/index.js +1 -1
- package/build/plugins/d3/renderer/hooks/useShapes/utils.d.ts +2 -0
- package/build/plugins/d3/renderer/hooks/useShapes/utils.js +17 -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 +139 -0
- package/build/plugins/highcharts/renderer/helpers/config/config.js +0 -3
- package/build/types/widget-data/area.d.ts +58 -0
- package/build/types/widget-data/area.js +1 -0
- package/build/types/widget-data/bar-x.d.ts +2 -1
- package/build/types/widget-data/bar-y.d.ts +2 -1
- package/build/types/widget-data/halo.d.ts +9 -0
- package/build/types/widget-data/halo.js +1 -0
- package/build/types/widget-data/index.d.ts +2 -0
- package/build/types/widget-data/index.js +2 -0
- package/build/types/widget-data/line.d.ts +9 -4
- package/build/types/widget-data/pie.d.ts +2 -1
- package/build/types/widget-data/scatter.d.ts +2 -1
- package/build/types/widget-data/series.d.ts +40 -9
- package/build/types/widget-data/tooltip.d.ts +10 -1
- package/package.json +7 -2
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { color, line as lineGenerator, area as areaGenerator, select } 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
|
+
import { getMarkerHaloVisibility, getMarkerVisibility, renderMarker, selectMarkerHalo, selectMarkerSymbol, setMarker, } from '../marker';
|
|
8
|
+
const b = block('d3-area');
|
|
9
|
+
export const AreaSeriesShapes = (args) => {
|
|
10
|
+
const { dispatcher, preparedData, seriesOptions } = args;
|
|
11
|
+
const ref = React.useRef(null);
|
|
12
|
+
React.useEffect(() => {
|
|
13
|
+
var _a;
|
|
14
|
+
if (!ref.current) {
|
|
15
|
+
return () => { };
|
|
16
|
+
}
|
|
17
|
+
const svgElement = select(ref.current);
|
|
18
|
+
const hoverOptions = get(seriesOptions, 'area.states.hover');
|
|
19
|
+
const inactiveOptions = get(seriesOptions, 'area.states.inactive');
|
|
20
|
+
const line = lineGenerator()
|
|
21
|
+
.x((d) => d.x)
|
|
22
|
+
.y((d) => d.y);
|
|
23
|
+
svgElement.selectAll('*').remove();
|
|
24
|
+
const shapeSelection = svgElement
|
|
25
|
+
.selectAll('shape')
|
|
26
|
+
.data(preparedData)
|
|
27
|
+
.join('g')
|
|
28
|
+
.attr('class', b('series'));
|
|
29
|
+
shapeSelection
|
|
30
|
+
.append('path')
|
|
31
|
+
.attr('class', b('line'))
|
|
32
|
+
.attr('d', (d) => line(d.points))
|
|
33
|
+
.attr('fill', 'none')
|
|
34
|
+
.attr('stroke', (d) => d.color)
|
|
35
|
+
.attr('stroke-width', (d) => d.width)
|
|
36
|
+
.attr('stroke-linejoin', 'round')
|
|
37
|
+
.attr('stroke-linecap', 'round');
|
|
38
|
+
const area = areaGenerator()
|
|
39
|
+
.x((d) => d.x)
|
|
40
|
+
.y0((d) => d.y0)
|
|
41
|
+
.y1((d) => d.y);
|
|
42
|
+
shapeSelection
|
|
43
|
+
.append('path')
|
|
44
|
+
.attr('class', b('region'))
|
|
45
|
+
.attr('d', (d) => area(d.points))
|
|
46
|
+
.attr('fill', (d) => d.color)
|
|
47
|
+
.attr('opacity', (d) => d.opacity);
|
|
48
|
+
let dataLabels = preparedData.reduce((acc, d) => {
|
|
49
|
+
return acc.concat(d.labels);
|
|
50
|
+
}, []);
|
|
51
|
+
if (!((_a = preparedData[0]) === null || _a === void 0 ? void 0 : _a.series.dataLabels.allowOverlap)) {
|
|
52
|
+
dataLabels = filterOverlappingLabels(dataLabels);
|
|
53
|
+
}
|
|
54
|
+
const labelsSelection = svgElement
|
|
55
|
+
.selectAll('text')
|
|
56
|
+
.data(dataLabels)
|
|
57
|
+
.join('text')
|
|
58
|
+
.text((d) => d.text)
|
|
59
|
+
.attr('class', b('label'))
|
|
60
|
+
.attr('x', (d) => d.x)
|
|
61
|
+
.attr('y', (d) => d.y)
|
|
62
|
+
.attr('text-anchor', (d) => d.textAnchor)
|
|
63
|
+
.style('font-size', (d) => d.style.fontSize)
|
|
64
|
+
.style('font-weight', (d) => d.style.fontWeight || null)
|
|
65
|
+
.style('fill', (d) => d.style.fontColor || null);
|
|
66
|
+
const markers = preparedData.reduce((acc, d) => acc.concat(d.markers), []);
|
|
67
|
+
const markerSelection = svgElement
|
|
68
|
+
.selectAll('marker')
|
|
69
|
+
.data(markers)
|
|
70
|
+
.join('g')
|
|
71
|
+
.call(renderMarker);
|
|
72
|
+
const hoverEnabled = hoverOptions === null || hoverOptions === void 0 ? void 0 : hoverOptions.enabled;
|
|
73
|
+
const inactiveEnabled = inactiveOptions === null || inactiveOptions === void 0 ? void 0 : inactiveOptions.enabled;
|
|
74
|
+
dispatcher.on('hover-shape.area', (data) => {
|
|
75
|
+
var _a;
|
|
76
|
+
const selected = data === null || data === void 0 ? void 0 : data.find((d) => d.series.type === 'area');
|
|
77
|
+
const selectedDataItem = selected === null || selected === void 0 ? void 0 : selected.data;
|
|
78
|
+
const selectedSeriesId = (_a = selected === null || selected === void 0 ? void 0 : selected.series) === null || _a === void 0 ? void 0 : _a.id;
|
|
79
|
+
shapeSelection.datum((d, index, list) => {
|
|
80
|
+
var _a;
|
|
81
|
+
const elementSelection = select(list[index]);
|
|
82
|
+
const hovered = Boolean(hoverEnabled && d.id === selectedSeriesId);
|
|
83
|
+
if (d.hovered !== hovered) {
|
|
84
|
+
d.hovered = hovered;
|
|
85
|
+
let strokeColor = d.color || '';
|
|
86
|
+
if (d.hovered) {
|
|
87
|
+
strokeColor =
|
|
88
|
+
((_a = color(strokeColor)) === null || _a === void 0 ? void 0 : _a.brighter(hoverOptions === null || hoverOptions === void 0 ? void 0 : hoverOptions.brightness).toString()) ||
|
|
89
|
+
strokeColor;
|
|
90
|
+
}
|
|
91
|
+
elementSelection.selectAll(`.${b('line')}`).attr('stroke', strokeColor);
|
|
92
|
+
elementSelection.selectAll(`.${b('region')}`).attr('fill', strokeColor);
|
|
93
|
+
}
|
|
94
|
+
return setActiveState({
|
|
95
|
+
element: list[index],
|
|
96
|
+
state: inactiveOptions,
|
|
97
|
+
active: Boolean(!inactiveEnabled || !selectedSeriesId || selectedSeriesId === d.id),
|
|
98
|
+
datum: d,
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
labelsSelection.datum((d, index, list) => {
|
|
102
|
+
return setActiveState({
|
|
103
|
+
element: list[index],
|
|
104
|
+
state: inactiveOptions,
|
|
105
|
+
active: Boolean(!inactiveEnabled || !selectedSeriesId || selectedSeriesId === d.series.id),
|
|
106
|
+
datum: d,
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
markerSelection.datum((d, index, list) => {
|
|
110
|
+
const elementSelection = select(list[index]);
|
|
111
|
+
const hovered = Boolean(hoverEnabled && d.point.data === selectedDataItem);
|
|
112
|
+
if (d.hovered !== hovered) {
|
|
113
|
+
d.hovered = hovered;
|
|
114
|
+
elementSelection.attr('visibility', getMarkerVisibility(d));
|
|
115
|
+
selectMarkerHalo(elementSelection).attr('visibility', getMarkerHaloVisibility);
|
|
116
|
+
selectMarkerSymbol(elementSelection).call(setMarker, hovered ? 'hover' : 'normal');
|
|
117
|
+
}
|
|
118
|
+
if (d.point.series.marker.states.normal.enabled) {
|
|
119
|
+
const isActive = Boolean(!inactiveEnabled ||
|
|
120
|
+
!selectedSeriesId ||
|
|
121
|
+
selectedSeriesId === d.point.series.id);
|
|
122
|
+
setActiveState({
|
|
123
|
+
element: list[index],
|
|
124
|
+
state: inactiveOptions,
|
|
125
|
+
active: isActive,
|
|
126
|
+
datum: d,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
return d;
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
return () => {
|
|
133
|
+
dispatcher.on('hover-shape.area', null);
|
|
134
|
+
};
|
|
135
|
+
}, [dispatcher, preparedData, seriesOptions]);
|
|
136
|
+
return React.createElement("g", { ref: ref, className: b() });
|
|
137
|
+
};
|
|
@@ -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,43 +1,11 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { color, line as lineGenerator, select
|
|
2
|
+
import { color, line as lineGenerator, select } from 'd3';
|
|
3
3
|
import get from 'lodash/get';
|
|
4
4
|
import { block } from '../../../../../../utils/cn';
|
|
5
5
|
import { filterOverlappingLabels } from '../../../utils';
|
|
6
|
-
import { setActiveState } from '../utils';
|
|
6
|
+
import { getLineDashArray, setActiveState } from '../utils';
|
|
7
|
+
import { getMarkerHaloVisibility, getMarkerVisibility, renderMarker, selectMarkerHalo, selectMarkerSymbol, setMarker, } from '../marker';
|
|
7
8
|
const b = block('d3-line');
|
|
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
9
|
export const LineSeriesShapes = (args) => {
|
|
42
10
|
const { dispatcher, preparedData, seriesOptions } = args;
|
|
43
11
|
const ref = React.useRef(null);
|
|
@@ -61,8 +29,9 @@ export const LineSeriesShapes = (args) => {
|
|
|
61
29
|
.attr('fill', 'none')
|
|
62
30
|
.attr('stroke', (d) => d.color)
|
|
63
31
|
.attr('stroke-width', (d) => d.width)
|
|
64
|
-
.attr('stroke-linejoin',
|
|
65
|
-
.attr('stroke-linecap',
|
|
32
|
+
.attr('stroke-linejoin', (d) => d.linecap)
|
|
33
|
+
.attr('stroke-linecap', (d) => d.linecap)
|
|
34
|
+
.attr('stroke-dasharray', (d) => getLineDashArray(d.dashStyle, d.width));
|
|
66
35
|
let dataLabels = preparedData.reduce((acc, d) => {
|
|
67
36
|
return acc.concat(d.labels);
|
|
68
37
|
}, []);
|
|
@@ -86,28 +55,7 @@ export const LineSeriesShapes = (args) => {
|
|
|
86
55
|
.selectAll('marker')
|
|
87
56
|
.data(markers)
|
|
88
57
|
.join('g')
|
|
89
|
-
.
|
|
90
|
-
.attr('visibility', getMarkerVisibility)
|
|
91
|
-
.attr('transform', (d) => {
|
|
92
|
-
return `translate(${d.point.x},${d.point.y})`;
|
|
93
|
-
});
|
|
94
|
-
markerSelection
|
|
95
|
-
.append('path')
|
|
96
|
-
.attr('class', b('marker-halo'))
|
|
97
|
-
.attr('d', (d) => {
|
|
98
|
-
const type = d.point.series.marker.states.normal.symbol;
|
|
99
|
-
const radius = d.point.series.marker.states.hover.halo.radius;
|
|
100
|
-
return getMarkerSymbol(type, radius);
|
|
101
|
-
})
|
|
102
|
-
.attr('fill', (d) => d.point.series.color)
|
|
103
|
-
.attr('opacity', (d) => d.point.series.marker.states.hover.halo.opacity)
|
|
104
|
-
.attr('z-index', -1)
|
|
105
|
-
.attr('visibility', getMarkerHaloVisibility);
|
|
106
|
-
markerSelection
|
|
107
|
-
.append('path')
|
|
108
|
-
.attr('class', b('marker-symbol'))
|
|
109
|
-
.call(setMarker, 'normal')
|
|
110
|
-
.attr('fill', (d) => d.point.series.color);
|
|
58
|
+
.call(renderMarker);
|
|
111
59
|
const hoverEnabled = hoverOptions === null || hoverOptions === void 0 ? void 0 : hoverOptions.enabled;
|
|
112
60
|
const inactiveEnabled = inactiveOptions === null || inactiveOptions === void 0 ? void 0 : inactiveOptions.enabled;
|
|
113
61
|
dispatcher.on('hover-shape.line', (data) => {
|
|
@@ -150,12 +98,8 @@ export const LineSeriesShapes = (args) => {
|
|
|
150
98
|
if (d.hovered !== hovered) {
|
|
151
99
|
d.hovered = hovered;
|
|
152
100
|
elementSelection.attr('visibility', getMarkerVisibility(d));
|
|
153
|
-
elementSelection
|
|
154
|
-
|
|
155
|
-
.attr('visibility', getMarkerHaloVisibility);
|
|
156
|
-
elementSelection
|
|
157
|
-
.select(`.${b('marker-symbol')}`)
|
|
158
|
-
.call(setMarker, hovered ? 'hover' : 'normal');
|
|
101
|
+
selectMarkerHalo(elementSelection).attr('visibility', getMarkerHaloVisibility);
|
|
102
|
+
selectMarkerSymbol(elementSelection).call(setMarker, hovered ? 'hover' : 'normal');
|
|
159
103
|
}
|
|
160
104
|
if (d.point.series.marker.states.normal.enabled) {
|
|
161
105
|
const isActive = Boolean(!inactiveEnabled ||
|
|
@@ -51,7 +51,7 @@ export const prepareLineData = (args) => {
|
|
|
51
51
|
hovered: false,
|
|
52
52
|
}));
|
|
53
53
|
}
|
|
54
|
-
|
|
54
|
+
const result = {
|
|
55
55
|
points,
|
|
56
56
|
markers,
|
|
57
57
|
labels,
|
|
@@ -61,7 +61,10 @@ export const prepareLineData = (args) => {
|
|
|
61
61
|
hovered: false,
|
|
62
62
|
active: true,
|
|
63
63
|
id: s.id,
|
|
64
|
-
|
|
64
|
+
dashStyle: s.dashStyle,
|
|
65
|
+
linecap: s.linecap,
|
|
66
|
+
};
|
|
67
|
+
acc.push(result);
|
|
65
68
|
return acc;
|
|
66
69
|
}, []);
|
|
67
70
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { PreparedLineSeries } from '../../useSeries/types';
|
|
2
2
|
import { LineSeriesData } from '../../../../../../types';
|
|
3
3
|
import { LabelData } from '../../../types';
|
|
4
|
+
import { DashStyle, LineCap } from '../../../../../../constants';
|
|
4
5
|
export type PointData = {
|
|
5
6
|
x: number;
|
|
6
7
|
y: number;
|
|
@@ -22,4 +23,6 @@ export type PreparedLineData = {
|
|
|
22
23
|
hovered: boolean;
|
|
23
24
|
active: boolean;
|
|
24
25
|
labels: LabelData[];
|
|
26
|
+
dashStyle: DashStyle;
|
|
27
|
+
linecap: LineCap;
|
|
25
28
|
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { BaseType, Selection } from 'd3';
|
|
2
|
+
import { MarkerData as LineMarkerData } from './line/types';
|
|
3
|
+
import { MarkerData as AreaMarkerData } from './area/types';
|
|
4
|
+
type MarkerData = LineMarkerData | AreaMarkerData;
|
|
5
|
+
export declare function renderMarker<T extends MarkerData>(selection: Selection<BaseType | SVGGElement, T, SVGGElement, unknown>): Selection<SVGGElement | BaseType, T, SVGGElement, unknown>;
|
|
6
|
+
export declare function getMarkerVisibility(d: MarkerData): "" | "hidden";
|
|
7
|
+
export declare function getMarkerHaloVisibility(d: MarkerData): "" | "hidden";
|
|
8
|
+
export declare function setMarker<T extends BaseType, D extends MarkerData>(selection: Selection<T, D, BaseType | null, unknown>, state: 'normal' | 'hover'): void;
|
|
9
|
+
export declare function getMarkerSymbol(type: string, radius: number): string | null;
|
|
10
|
+
export declare function selectMarkerHalo<T>(parentSelection: Selection<BaseType, T, null, undefined>): Selection<BaseType, T, null, undefined>;
|
|
11
|
+
export declare function selectMarkerSymbol<T>(parentSelection: Selection<BaseType, T, null, undefined>): Selection<BaseType, T, null, undefined>;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { symbol, symbolCircle, symbolSquare } from 'd3';
|
|
2
|
+
import { block } from '../../../../../utils/cn';
|
|
3
|
+
const b = block('d3-marker');
|
|
4
|
+
const haloClassName = b('halo');
|
|
5
|
+
const symbolClassName = b('symbol');
|
|
6
|
+
export function renderMarker(selection) {
|
|
7
|
+
const markerSelection = selection
|
|
8
|
+
.attr('class', b('wrapper'))
|
|
9
|
+
.attr('visibility', getMarkerVisibility)
|
|
10
|
+
.attr('transform', (d) => {
|
|
11
|
+
return `translate(${d.point.x},${d.point.y})`;
|
|
12
|
+
});
|
|
13
|
+
markerSelection
|
|
14
|
+
.append('path')
|
|
15
|
+
.attr('class', haloClassName)
|
|
16
|
+
.attr('d', (d) => {
|
|
17
|
+
const type = d.point.series.marker.states.normal.symbol;
|
|
18
|
+
const radius = d.point.series.marker.states.hover.halo.size;
|
|
19
|
+
return getMarkerSymbol(type, radius);
|
|
20
|
+
})
|
|
21
|
+
.attr('fill', (d) => d.point.series.color)
|
|
22
|
+
.attr('opacity', (d) => d.point.series.marker.states.hover.halo.opacity)
|
|
23
|
+
.attr('z-index', -1)
|
|
24
|
+
.attr('visibility', getMarkerHaloVisibility);
|
|
25
|
+
markerSelection
|
|
26
|
+
.append('path')
|
|
27
|
+
.attr('class', symbolClassName)
|
|
28
|
+
.call(setMarker, 'normal')
|
|
29
|
+
.attr('fill', (d) => d.point.series.color);
|
|
30
|
+
return markerSelection;
|
|
31
|
+
}
|
|
32
|
+
export function getMarkerVisibility(d) {
|
|
33
|
+
const markerStates = d.point.series.marker.states;
|
|
34
|
+
const enabled = (markerStates.hover.enabled && d.hovered) || markerStates.normal.enabled;
|
|
35
|
+
return enabled ? '' : 'hidden';
|
|
36
|
+
}
|
|
37
|
+
export function getMarkerHaloVisibility(d) {
|
|
38
|
+
const markerStates = d.point.series.marker.states;
|
|
39
|
+
const enabled = markerStates.hover.halo.enabled && d.hovered;
|
|
40
|
+
return enabled ? '' : 'hidden';
|
|
41
|
+
}
|
|
42
|
+
export function setMarker(selection, state) {
|
|
43
|
+
selection
|
|
44
|
+
.attr('d', (d) => {
|
|
45
|
+
const radius = d.point.series.marker.states[state].radius +
|
|
46
|
+
d.point.series.marker.states[state].borderWidth;
|
|
47
|
+
return getMarkerSymbol(d.point.series.marker.states.normal.symbol, radius);
|
|
48
|
+
})
|
|
49
|
+
.attr('stroke-width', (d) => d.point.series.marker.states[state].borderWidth)
|
|
50
|
+
.attr('stroke', (d) => d.point.series.marker.states[state].borderColor);
|
|
51
|
+
}
|
|
52
|
+
export function getMarkerSymbol(type, radius) {
|
|
53
|
+
switch (type) {
|
|
54
|
+
case 'square': {
|
|
55
|
+
const size = Math.pow(radius, 2) * Math.PI;
|
|
56
|
+
return symbol(symbolSquare, size)();
|
|
57
|
+
}
|
|
58
|
+
case 'circle':
|
|
59
|
+
default: {
|
|
60
|
+
const size = Math.pow(radius, 2) * Math.PI;
|
|
61
|
+
return symbol(symbolCircle, size)();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
export function selectMarkerHalo(parentSelection) {
|
|
66
|
+
return parentSelection.select(`.${haloClassName}`);
|
|
67
|
+
}
|
|
68
|
+
export function selectMarkerSymbol(parentSelection) {
|
|
69
|
+
return parentSelection.select(`.${symbolClassName}`);
|
|
70
|
+
}
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import type { Dispatch } from 'd3';
|
|
2
|
+
import type { Dispatch, PieArcDatum } from 'd3';
|
|
3
3
|
import { PreparedSeriesOptions } from '../../useSeries/types';
|
|
4
|
-
import { PreparedPieData } from './types';
|
|
4
|
+
import { PreparedPieData, SegmentData } from './types';
|
|
5
5
|
type PreparePieSeriesArgs = {
|
|
6
6
|
dispatcher: Dispatch<object>;
|
|
7
7
|
preparedData: PreparedPieData[];
|
|
8
8
|
seriesOptions: PreparedSeriesOptions;
|
|
9
9
|
svgContainer: SVGSVGElement | null;
|
|
10
10
|
};
|
|
11
|
+
export declare function getHaloVisibility(d: PieArcDatum<SegmentData>): "" | "hidden";
|
|
11
12
|
export declare function PieSeriesShapes(args: PreparePieSeriesArgs): React.JSX.Element;
|
|
12
13
|
export {};
|
|
@@ -7,6 +7,10 @@ import { line as lineGenerator } from 'd3-shape';
|
|
|
7
7
|
import { setEllipsisForOverflowTexts } from '../../../utils';
|
|
8
8
|
import { getCurveFactory } from './utils';
|
|
9
9
|
const b = block('d3-pie');
|
|
10
|
+
export function getHaloVisibility(d) {
|
|
11
|
+
const enabled = d.data.pie.halo.enabled && d.data.hovered;
|
|
12
|
+
return enabled ? '' : 'hidden';
|
|
13
|
+
}
|
|
10
14
|
export function PieSeriesShapes(args) {
|
|
11
15
|
const { dispatcher, preparedData, seriesOptions, svgContainer } = args;
|
|
12
16
|
const ref = React.useRef(null);
|
|
@@ -30,6 +34,27 @@ export function PieSeriesShapes(args) {
|
|
|
30
34
|
})
|
|
31
35
|
.style('stroke', (pieData) => pieData.borderColor)
|
|
32
36
|
.style('stroke-width', (pieData) => pieData.borderWidth);
|
|
37
|
+
shapesSelection
|
|
38
|
+
.selectAll('halo')
|
|
39
|
+
.data((pieData) => {
|
|
40
|
+
if (pieData.halo.enabled) {
|
|
41
|
+
return pieData.segments;
|
|
42
|
+
}
|
|
43
|
+
return [];
|
|
44
|
+
})
|
|
45
|
+
.join('path')
|
|
46
|
+
.attr('d', (d) => {
|
|
47
|
+
const arcGenerator = arc()
|
|
48
|
+
.innerRadius(d.data.pie.innerRadius)
|
|
49
|
+
.outerRadius(d.data.pie.radius + d.data.pie.halo.size)
|
|
50
|
+
.cornerRadius(d.data.pie.borderRadius);
|
|
51
|
+
return arcGenerator(d);
|
|
52
|
+
})
|
|
53
|
+
.attr('class', b('halo'))
|
|
54
|
+
.attr('fill', (d) => d.data.color)
|
|
55
|
+
.attr('opacity', (d) => d.data.pie.halo.opacity)
|
|
56
|
+
.attr('z-index', -1)
|
|
57
|
+
.attr('visibility', getHaloVisibility);
|
|
33
58
|
shapesSelection
|
|
34
59
|
.selectAll(segmentSelector)
|
|
35
60
|
.data((pieData) => pieData.segments)
|
|
@@ -108,6 +133,7 @@ export function PieSeriesShapes(args) {
|
|
|
108
133
|
const inactiveEnabled = inactiveOptions === null || inactiveOptions === void 0 ? void 0 : inactiveOptions.enabled;
|
|
109
134
|
shapesSelection.datum((_d, index, list) => {
|
|
110
135
|
const pieSelection = select(list[index]);
|
|
136
|
+
const haloSelection = pieSelection.selectAll(`.${b('halo')}`);
|
|
111
137
|
pieSelection
|
|
112
138
|
.selectAll(segmentSelector)
|
|
113
139
|
.datum((d, i, elements) => {
|
|
@@ -122,6 +148,8 @@ export function PieSeriesShapes(args) {
|
|
|
122
148
|
}
|
|
123
149
|
return initialColor;
|
|
124
150
|
});
|
|
151
|
+
const currentSegmentHalo = haloSelection.nodes()[i];
|
|
152
|
+
select(currentSegmentHalo).attr('visibility', getHaloVisibility);
|
|
125
153
|
}
|
|
126
154
|
setActiveState({
|
|
127
155
|
element: elements[i],
|
|
@@ -15,12 +15,13 @@ const getCenter = (boundsWidth, boundsHeight, center) => {
|
|
|
15
15
|
return [resultX, resultY];
|
|
16
16
|
};
|
|
17
17
|
export function preparePieData(args) {
|
|
18
|
-
const { series:
|
|
18
|
+
const { series: preparedSeries, boundsWidth, boundsHeight } = args;
|
|
19
19
|
const maxRadius = Math.min(boundsWidth, boundsHeight) / 2;
|
|
20
|
-
const groupedPieSeries = group(
|
|
20
|
+
const groupedPieSeries = group(preparedSeries, (pieSeries) => pieSeries.stackId);
|
|
21
21
|
return Array.from(groupedPieSeries).map(([stackId, items]) => {
|
|
22
22
|
var _a, _b, _c;
|
|
23
|
-
const
|
|
23
|
+
const series = items[0];
|
|
24
|
+
const { center, borderWidth, borderColor, borderRadius, radius: seriesRadius, innerRadius: seriesInnerRadius, dataLabels, } = series;
|
|
24
25
|
const radius = (_a = calculateNumericProperty({ value: seriesRadius, base: maxRadius })) !== null && _a !== void 0 ? _a : maxRadius;
|
|
25
26
|
const data = {
|
|
26
27
|
id: stackId,
|
|
@@ -34,6 +35,11 @@ export function preparePieData(args) {
|
|
|
34
35
|
borderRadius,
|
|
35
36
|
series: items[0],
|
|
36
37
|
connectorCurve: dataLabels.connectorCurve,
|
|
38
|
+
halo: {
|
|
39
|
+
enabled: series.states.hover.halo.enabled,
|
|
40
|
+
opacity: series.states.hover.halo.opacity,
|
|
41
|
+
size: series.states.hover.halo.size,
|
|
42
|
+
},
|
|
37
43
|
};
|
|
38
44
|
const segments = items.map((item) => {
|
|
39
45
|
return {
|