@gravity-ui/chartkit 4.6.0 → 4.7.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/plugins/d3/examples/bar-x/Basic.d.ts +4 -0
- package/build/plugins/d3/examples/bar-x/Basic.js +78 -0
- package/build/plugins/d3/examples/bar-x/GroupedColumns.d.ts +2 -0
- package/build/plugins/d3/examples/bar-x/GroupedColumns.js +44 -0
- package/build/plugins/d3/examples/bar-x/StackedColumns.d.ts +2 -0
- package/build/plugins/d3/examples/bar-x/StackedColumns.js +45 -0
- package/build/plugins/d3/examples/nintendoGames.d.ts +62 -0
- package/build/plugins/d3/examples/nintendoGames.js +12037 -0
- package/build/plugins/d3/examples/pie/Basic.d.ts +2 -0
- package/build/plugins/d3/examples/pie/Basic.js +30 -0
- package/build/plugins/d3/examples/pie/Donut.d.ts +2 -0
- package/build/plugins/d3/examples/pie/Donut.js +31 -0
- package/build/plugins/d3/examples/scatter/Basic.d.ts +2 -0
- package/build/plugins/d3/examples/scatter/Basic.js +66 -0
- package/build/plugins/d3/renderer/D3Widget.js +11 -1
- package/build/plugins/d3/renderer/components/AxisX.d.ts +1 -2
- package/build/plugins/d3/renderer/components/AxisX.js +8 -21
- package/build/plugins/d3/renderer/components/AxisY.js +50 -18
- package/build/plugins/d3/renderer/components/Chart.js +25 -17
- package/build/plugins/d3/renderer/components/Legend.js +20 -22
- package/build/plugins/d3/renderer/components/Title.js +1 -1
- package/build/plugins/d3/renderer/components/Tooltip/DefaultContent.d.ts +2 -2
- package/build/plugins/d3/renderer/components/Tooltip/DefaultContent.js +8 -0
- package/build/plugins/d3/renderer/components/Tooltip/TooltipTriggerArea.d.ts +14 -0
- package/build/plugins/d3/renderer/components/Tooltip/TooltipTriggerArea.js +70 -0
- package/build/plugins/d3/renderer/components/Tooltip/index.d.ts +5 -3
- package/build/plugins/d3/renderer/components/Tooltip/index.js +4 -2
- package/build/plugins/d3/renderer/components/styles.css +3 -0
- package/build/plugins/d3/renderer/constants/defaults/axis.d.ts +9 -0
- package/build/plugins/d3/renderer/constants/defaults/axis.js +6 -0
- package/build/plugins/d3/renderer/constants/defaults/index.d.ts +1 -0
- package/build/plugins/d3/renderer/constants/defaults/index.js +1 -0
- package/build/plugins/d3/renderer/constants/defaults/series-options.d.ts +11 -0
- package/build/plugins/d3/renderer/constants/defaults/series-options.js +41 -0
- package/build/plugins/d3/renderer/constants/index.d.ts +0 -1
- package/build/plugins/d3/renderer/constants/index.js +0 -1
- package/build/plugins/d3/renderer/d3-dispatcher.d.ts +1 -0
- package/build/plugins/d3/renderer/d3-dispatcher.js +4 -0
- package/build/plugins/d3/renderer/hooks/index.d.ts +0 -1
- package/build/plugins/d3/renderer/hooks/index.js +0 -1
- package/build/plugins/d3/renderer/hooks/useAxisScales/index.d.ts +6 -5
- package/build/plugins/d3/renderer/hooks/useAxisScales/index.js +2 -1
- package/build/plugins/d3/renderer/hooks/useChartDimensions/index.js +8 -41
- package/build/plugins/d3/renderer/hooks/useChartDimensions/utils.d.ts +3 -0
- package/build/plugins/d3/renderer/hooks/useChartDimensions/utils.js +17 -4
- package/build/plugins/d3/renderer/hooks/useChartOptions/chart.d.ts +2 -4
- package/build/plugins/d3/renderer/hooks/useChartOptions/chart.js +4 -23
- package/build/plugins/d3/renderer/hooks/useChartOptions/index.d.ts +1 -1
- package/build/plugins/d3/renderer/hooks/useChartOptions/index.js +2 -10
- package/build/plugins/d3/renderer/hooks/useChartOptions/title.d.ts +1 -1
- package/build/plugins/d3/renderer/hooks/useChartOptions/title.js +1 -0
- package/build/plugins/d3/renderer/hooks/useChartOptions/types.d.ts +8 -5
- package/build/plugins/d3/renderer/hooks/useChartOptions/x-axis.d.ts +5 -3
- package/build/plugins/d3/renderer/hooks/useChartOptions/x-axis.js +61 -6
- package/build/plugins/d3/renderer/hooks/useChartOptions/y-axis.d.ts +1 -1
- package/build/plugins/d3/renderer/hooks/useChartOptions/y-axis.js +20 -12
- package/build/plugins/d3/renderer/hooks/useSeries/index.d.ts +1 -0
- package/build/plugins/d3/renderer/hooks/useSeries/index.js +8 -2
- package/build/plugins/d3/renderer/hooks/useSeries/prepare-legend.js +8 -3
- package/build/plugins/d3/renderer/hooks/useSeries/prepare-options.d.ts +3 -0
- package/build/plugins/d3/renderer/hooks/useSeries/prepare-options.js +5 -0
- package/build/plugins/d3/renderer/hooks/useSeries/prepareSeries.js +6 -3
- package/build/plugins/d3/renderer/hooks/useSeries/types.d.ts +5 -1
- package/build/plugins/d3/renderer/hooks/useShapes/bar-x/index.d.ts +12 -0
- package/build/plugins/d3/renderer/hooks/useShapes/bar-x/index.js +91 -0
- package/build/plugins/d3/renderer/hooks/useShapes/bar-x/prepare-data.d.ts +19 -0
- package/build/plugins/d3/renderer/hooks/useShapes/{bar-x.js → bar-x/prepare-data.js} +9 -88
- package/build/plugins/d3/renderer/hooks/useShapes/index.d.ts +13 -10
- package/build/plugins/d3/renderer/hooks/useShapes/index.js +28 -13
- package/build/plugins/d3/renderer/hooks/useShapes/pie.d.ts +6 -4
- package/build/plugins/d3/renderer/hooks/useShapes/pie.js +98 -20
- package/build/plugins/d3/renderer/hooks/useShapes/scatter/index.d.ts +15 -0
- package/build/plugins/d3/renderer/hooks/useShapes/scatter/index.js +89 -0
- package/build/plugins/d3/renderer/hooks/useShapes/scatter/prepare-data.d.ts +19 -0
- package/build/plugins/d3/renderer/hooks/useShapes/scatter/prepare-data.js +55 -0
- package/build/plugins/d3/renderer/hooks/useShapes/styles.css +1 -9
- package/build/plugins/d3/renderer/hooks/useTooltip/index.d.ts +6 -6
- package/build/plugins/d3/renderer/hooks/useTooltip/index.js +15 -17
- package/build/plugins/d3/renderer/hooks/useTooltip/types.d.ts +0 -6
- package/build/plugins/d3/renderer/utils/axis-generators/bottom.d.ts +3 -1
- package/build/plugins/d3/renderer/utils/axis-generators/bottom.js +77 -38
- package/build/plugins/d3/renderer/utils/axis.js +0 -6
- package/build/plugins/d3/renderer/utils/index.d.ts +5 -0
- package/build/plugins/d3/renderer/utils/index.js +13 -8
- package/build/plugins/d3/renderer/utils/math.d.ts +2 -0
- package/build/plugins/d3/renderer/utils/math.js +8 -0
- package/build/plugins/d3/renderer/utils/text.d.ts +6 -6
- package/build/plugins/d3/renderer/utils/text.js +25 -15
- package/build/types/widget-data/axis.d.ts +10 -0
- package/build/types/widget-data/series.d.ts +51 -0
- package/build/types/widget-data/tooltip.d.ts +18 -7
- package/package.json +2 -2
- package/build/plugins/d3/renderer/hooks/useChartEvents/index.d.ts +0 -5
- package/build/plugins/d3/renderer/hooks/useChartEvents/index.js +0 -15
- package/build/plugins/d3/renderer/hooks/useShapes/bar-x.d.ts +0 -21
- package/build/plugins/d3/renderer/hooks/useShapes/defaults.d.ts +0 -5
- package/build/plugins/d3/renderer/hooks/useShapes/defaults.js +0 -5
- package/build/plugins/d3/renderer/hooks/useShapes/scatter.d.ts +0 -19
- package/build/plugins/d3/renderer/hooks/useShapes/scatter.js +0 -89
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import
|
|
2
|
+
import get from 'lodash/get';
|
|
3
|
+
import kebabCase from 'lodash/kebabCase';
|
|
4
|
+
import { arc, color, pie, pointer, select } from 'd3';
|
|
3
5
|
import { block } from '../../../../../utils/cn';
|
|
4
|
-
import { calculateNumericProperty, getHorisontalSvgTextHeight } from '../../utils';
|
|
6
|
+
import { calculateNumericProperty, extractD3DataFromNode, getHorisontalSvgTextHeight, isNodeContainsD3Data, } from '../../utils';
|
|
5
7
|
const b = block('d3-pie');
|
|
8
|
+
const preparePieData = (series) => {
|
|
9
|
+
return series.map((s) => ({ series: s, data: s.data }));
|
|
10
|
+
};
|
|
6
11
|
const getCenter = (boundsWidth, boundsHeight, center) => {
|
|
7
12
|
var _a, _b;
|
|
8
13
|
const defaultX = boundsWidth * 0.5;
|
|
@@ -15,17 +20,29 @@ const getCenter = (boundsWidth, boundsHeight, center) => {
|
|
|
15
20
|
const resultY = (_b = calculateNumericProperty({ value: y, base: boundsHeight })) !== null && _b !== void 0 ? _b : defaultY;
|
|
16
21
|
return [resultX, resultY];
|
|
17
22
|
};
|
|
23
|
+
const getOpacity = (args) => {
|
|
24
|
+
const { data, hoveredData, opacity } = args;
|
|
25
|
+
if (data.series.id !== (hoveredData === null || hoveredData === void 0 ? void 0 : hoveredData.series.id)) {
|
|
26
|
+
return opacity || null;
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
};
|
|
30
|
+
const isNodeContainsPieData = (node) => {
|
|
31
|
+
return isNodeContainsD3Data(node);
|
|
32
|
+
};
|
|
18
33
|
export function PieSeriesComponent(args) {
|
|
19
34
|
var _a;
|
|
20
|
-
const { boundsWidth, boundsHeight,
|
|
35
|
+
const { boundsWidth, boundsHeight, dispatcher, top, left, series, seriesOptions, svgContainer } = args;
|
|
21
36
|
const ref = React.useRef(null);
|
|
22
37
|
const [x, y] = getCenter(boundsWidth, boundsHeight, (_a = series[0]) === null || _a === void 0 ? void 0 : _a.center);
|
|
23
38
|
React.useEffect(() => {
|
|
24
39
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
25
40
|
if (!ref.current) {
|
|
26
|
-
return;
|
|
41
|
+
return () => { };
|
|
27
42
|
}
|
|
28
43
|
const svgElement = select(ref.current);
|
|
44
|
+
const hoverOptions = get(seriesOptions, 'pie.states.hover');
|
|
45
|
+
const inactiveOptions = get(seriesOptions, 'pie.states.inactive');
|
|
29
46
|
const isLabelsEnabled = (_b = (_a = series[0]) === null || _a === void 0 ? void 0 : _a.dataLabels) === null || _b === void 0 ? void 0 : _b.enabled;
|
|
30
47
|
let radiusRelatedToChart = Math.min(boundsWidth, boundsHeight) / 2;
|
|
31
48
|
if (isLabelsEnabled) {
|
|
@@ -39,41 +56,46 @@ export function PieSeriesComponent(args) {
|
|
|
39
56
|
radius *= 0.9;
|
|
40
57
|
}
|
|
41
58
|
const innerRadius = (_f = calculateNumericProperty({ value: series[0].innerRadius, base: radius })) !== null && _f !== void 0 ? _f : 0;
|
|
42
|
-
const
|
|
43
|
-
const
|
|
59
|
+
const preparedData = preparePieData(series);
|
|
60
|
+
const pieGenerator = pie().value((d) => d.data.value);
|
|
61
|
+
const visibleData = preparedData.filter((d) => d.series.visible);
|
|
44
62
|
const dataReady = pieGenerator(visibleData);
|
|
45
63
|
const arcGenerator = arc()
|
|
46
64
|
.innerRadius(innerRadius)
|
|
47
65
|
.outerRadius(radius)
|
|
48
|
-
.cornerRadius((d) => d.data.borderRadius);
|
|
66
|
+
.cornerRadius((d) => d.data.series.borderRadius);
|
|
49
67
|
svgElement.selectAll('*').remove();
|
|
50
|
-
svgElement
|
|
51
|
-
.selectAll('
|
|
68
|
+
const segmentSelection = svgElement
|
|
69
|
+
.selectAll('segments')
|
|
52
70
|
.data(dataReady)
|
|
53
71
|
.enter()
|
|
54
72
|
.append('path')
|
|
55
73
|
.attr('d', arcGenerator)
|
|
56
74
|
.attr('class', b('segment'))
|
|
57
|
-
.attr('fill', (d) => d.data.color
|
|
75
|
+
.attr('fill', (d) => d.data.series.color)
|
|
58
76
|
.style('stroke', ((_g = series[0]) === null || _g === void 0 ? void 0 : _g.borderColor) || '')
|
|
59
77
|
.style('stroke-width', (_j = (_h = series[0]) === null || _h === void 0 ? void 0 : _h.borderWidth) !== null && _j !== void 0 ? _j : 1);
|
|
78
|
+
let polylineSelection;
|
|
79
|
+
let labelSelection;
|
|
60
80
|
if ((_l = (_k = series[0]) === null || _k === void 0 ? void 0 : _k.dataLabels) === null || _l === void 0 ? void 0 : _l.enabled) {
|
|
61
81
|
const labelHeight = getHorisontalSvgTextHeight({ text: 'tmp' });
|
|
62
82
|
const outerArc = arc()
|
|
63
83
|
.innerRadius(labelsArcRadius)
|
|
64
84
|
.outerRadius(labelsArcRadius);
|
|
85
|
+
const polylineArc = arc()
|
|
86
|
+
.innerRadius(radius)
|
|
87
|
+
.outerRadius(radius);
|
|
65
88
|
// Add the polylines between chart and labels
|
|
66
|
-
svgElement
|
|
67
|
-
.selectAll('
|
|
89
|
+
polylineSelection = svgElement
|
|
90
|
+
.selectAll('polylines')
|
|
68
91
|
.data(dataReady)
|
|
69
92
|
.enter()
|
|
70
93
|
.append('polyline')
|
|
71
|
-
.attr('stroke', (d) => d.data.color || '')
|
|
72
|
-
.style('fill', 'none')
|
|
94
|
+
.attr('stroke', (d) => d.data.series.color || '')
|
|
73
95
|
.attr('stroke-width', 1)
|
|
74
96
|
.attr('points', (d) => {
|
|
75
97
|
// Line insertion in the slice
|
|
76
|
-
const posA =
|
|
98
|
+
const posA = polylineArc.centroid(d);
|
|
77
99
|
// Line break: we use the other arc generator that has been built only for that
|
|
78
100
|
const posB = outerArc.centroid(d);
|
|
79
101
|
const posC = outerArc.centroid(d);
|
|
@@ -101,13 +123,15 @@ export function PieSeriesComponent(args) {
|
|
|
101
123
|
}
|
|
102
124
|
}
|
|
103
125
|
return result.join(' ');
|
|
104
|
-
})
|
|
126
|
+
})
|
|
127
|
+
.attr('pointer-events', 'none')
|
|
128
|
+
.style('fill', 'none');
|
|
105
129
|
// Add the polylines between chart and labels
|
|
106
|
-
svgElement
|
|
107
|
-
.selectAll('
|
|
130
|
+
labelSelection = svgElement
|
|
131
|
+
.selectAll('labels')
|
|
108
132
|
.data(dataReady)
|
|
109
133
|
.join('text')
|
|
110
|
-
.text((d) => d.data.label || d.value)
|
|
134
|
+
.text((d) => d.data.series.label || d.value)
|
|
111
135
|
.attr('class', b('label'))
|
|
112
136
|
.attr('transform', (d) => {
|
|
113
137
|
const pos = outerArc.centroid(d);
|
|
@@ -116,11 +140,65 @@ export function PieSeriesComponent(args) {
|
|
|
116
140
|
pos[1] += labelHeight / 4;
|
|
117
141
|
return `translate(${pos})`;
|
|
118
142
|
})
|
|
143
|
+
.attr('pointer-events', 'none')
|
|
119
144
|
.style('text-anchor', (d) => {
|
|
120
145
|
const midangle = d.startAngle + (d.endAngle - d.startAngle) / 2;
|
|
121
146
|
return midangle < Math.PI ? 'start' : 'end';
|
|
122
147
|
});
|
|
123
148
|
}
|
|
124
|
-
|
|
149
|
+
svgElement
|
|
150
|
+
.on('mousemove', (e) => {
|
|
151
|
+
const segment = e.target;
|
|
152
|
+
if (!isNodeContainsPieData(segment)) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const [pointerX, pointerY] = pointer(e, svgContainer);
|
|
156
|
+
const segmentData = extractD3DataFromNode(segment).data;
|
|
157
|
+
dispatcher.call('hover-shape', {}, [segmentData], [pointerX - left, pointerY - top]);
|
|
158
|
+
})
|
|
159
|
+
.on('mouseleave', () => {
|
|
160
|
+
dispatcher.call('hover-shape', {}, undefined);
|
|
161
|
+
});
|
|
162
|
+
const eventName = `hover-shape.pie-${kebabCase(preparedData[0].series.id)}`;
|
|
163
|
+
dispatcher.on(eventName, (datas) => {
|
|
164
|
+
const data = datas === null || datas === void 0 ? void 0 : datas[0];
|
|
165
|
+
const hoverEnabled = hoverOptions === null || hoverOptions === void 0 ? void 0 : hoverOptions.enabled;
|
|
166
|
+
const inactiveEnabled = inactiveOptions === null || inactiveOptions === void 0 ? void 0 : inactiveOptions.enabled;
|
|
167
|
+
if (hoverEnabled && data) {
|
|
168
|
+
segmentSelection.attr('fill', (d) => {
|
|
169
|
+
var _a;
|
|
170
|
+
const fillColor = d.data.series.color;
|
|
171
|
+
if (d.data.series.id === data.series.id) {
|
|
172
|
+
return (((_a = color(fillColor)) === null || _a === void 0 ? void 0 : _a.brighter(hoverOptions === null || hoverOptions === void 0 ? void 0 : hoverOptions.brightness).toString()) ||
|
|
173
|
+
fillColor);
|
|
174
|
+
}
|
|
175
|
+
return fillColor;
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
else if (hoverEnabled) {
|
|
179
|
+
segmentSelection.attr('fill', (d) => d.data.series.color);
|
|
180
|
+
}
|
|
181
|
+
if (inactiveEnabled && data) {
|
|
182
|
+
const opacity = inactiveOptions === null || inactiveOptions === void 0 ? void 0 : inactiveOptions.opacity;
|
|
183
|
+
segmentSelection.attr('opacity', (d) => {
|
|
184
|
+
return getOpacity({ data: d.data, hoveredData: data, opacity });
|
|
185
|
+
});
|
|
186
|
+
polylineSelection === null || polylineSelection === void 0 ? void 0 : polylineSelection.attr('opacity', (d) => {
|
|
187
|
+
return getOpacity({ data: d.data, hoveredData: data, opacity });
|
|
188
|
+
});
|
|
189
|
+
labelSelection === null || labelSelection === void 0 ? void 0 : labelSelection.attr('opacity', (d) => {
|
|
190
|
+
return getOpacity({ data: d.data, hoveredData: data, opacity });
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
else if (inactiveEnabled) {
|
|
194
|
+
segmentSelection.attr('opacity', null);
|
|
195
|
+
polylineSelection === null || polylineSelection === void 0 ? void 0 : polylineSelection.attr('opacity', null);
|
|
196
|
+
labelSelection === null || labelSelection === void 0 ? void 0 : labelSelection.attr('opacity', null);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
return () => {
|
|
200
|
+
dispatcher.on(eventName, null);
|
|
201
|
+
};
|
|
202
|
+
}, [boundsWidth, boundsHeight, dispatcher, top, left, series, seriesOptions, svgContainer]);
|
|
125
203
|
return React.createElement("g", { ref: ref, className: b(), transform: `translate(${x}, ${y})` });
|
|
126
204
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { Dispatch } from 'd3';
|
|
3
|
+
import { PreparedSeriesOptions } from '../../useSeries/types';
|
|
4
|
+
import type { PreparedScatterData } from './prepare-data';
|
|
5
|
+
export { prepareScatterData } from './prepare-data';
|
|
6
|
+
export type { PreparedScatterData } from './prepare-data';
|
|
7
|
+
type ScatterSeriesShapeProps = {
|
|
8
|
+
dispatcher: Dispatch<object>;
|
|
9
|
+
top: number;
|
|
10
|
+
left: number;
|
|
11
|
+
preparedData: PreparedScatterData[];
|
|
12
|
+
seriesOptions: PreparedSeriesOptions;
|
|
13
|
+
svgContainer: SVGSVGElement | null;
|
|
14
|
+
};
|
|
15
|
+
export declare function ScatterSeriesShape(props: ScatterSeriesShapeProps): React.JSX.Element;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import get from 'lodash/get';
|
|
3
|
+
import { color, pointer, select } from 'd3';
|
|
4
|
+
import { block } from '../../../../../../utils/cn';
|
|
5
|
+
import { extractD3DataFromNode, isNodeContainsD3Data } from '../../../utils';
|
|
6
|
+
export { prepareScatterData } from './prepare-data';
|
|
7
|
+
const b = block('d3-scatter');
|
|
8
|
+
const DEFAULT_SCATTER_POINT_RADIUS = 4;
|
|
9
|
+
const EMPTY_SELECTION = null;
|
|
10
|
+
const key = (d) => d.id || -1;
|
|
11
|
+
const isNodeContainsScatterData = (node) => {
|
|
12
|
+
return isNodeContainsD3Data(node);
|
|
13
|
+
};
|
|
14
|
+
export function ScatterSeriesShape(props) {
|
|
15
|
+
const { dispatcher, top, left, preparedData, seriesOptions, svgContainer } = props;
|
|
16
|
+
const ref = React.useRef(null);
|
|
17
|
+
React.useEffect(() => {
|
|
18
|
+
if (!ref.current) {
|
|
19
|
+
return () => { };
|
|
20
|
+
}
|
|
21
|
+
const svgElement = select(ref.current);
|
|
22
|
+
const hoverOptions = get(seriesOptions, 'scatter.states.hover');
|
|
23
|
+
const inactiveOptions = get(seriesOptions, 'scatter.states.inactive');
|
|
24
|
+
const selection = svgElement
|
|
25
|
+
.selectAll(`circle`)
|
|
26
|
+
.data(preparedData, key)
|
|
27
|
+
.join((enter) => enter.append('circle').attr('class', b('point')), (update) => update, (exit) => exit.remove())
|
|
28
|
+
.attr('fill', (d) => d.data.color || d.series.color || '')
|
|
29
|
+
.attr('r', (d) => d.data.radius || DEFAULT_SCATTER_POINT_RADIUS)
|
|
30
|
+
.attr('cx', (d) => d.cx)
|
|
31
|
+
.attr('cy', (d) => d.cy);
|
|
32
|
+
svgElement
|
|
33
|
+
.on('mousemove', (e) => {
|
|
34
|
+
const point = e.target;
|
|
35
|
+
if (!isNodeContainsScatterData(point)) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const [pointerX, pointerY] = pointer(e, svgContainer);
|
|
39
|
+
const segmentData = extractD3DataFromNode(point);
|
|
40
|
+
dispatcher.call('hover-shape', {}, [segmentData], [pointerX - left, pointerY - top]);
|
|
41
|
+
})
|
|
42
|
+
.on('mouseleave', () => {
|
|
43
|
+
dispatcher.call('hover-shape', {}, undefined);
|
|
44
|
+
});
|
|
45
|
+
const hoverEnabled = hoverOptions === null || hoverOptions === void 0 ? void 0 : hoverOptions.enabled;
|
|
46
|
+
const inactiveEnabled = inactiveOptions === null || inactiveOptions === void 0 ? void 0 : inactiveOptions.enabled;
|
|
47
|
+
dispatcher.on('hover-shape.scatter', (data) => {
|
|
48
|
+
const selectedPoint = data === null || data === void 0 ? void 0 : data[0];
|
|
49
|
+
const updates = [];
|
|
50
|
+
preparedData.forEach((p) => {
|
|
51
|
+
const hovered = Boolean(hoverEnabled &&
|
|
52
|
+
selectedPoint &&
|
|
53
|
+
p.cx === selectedPoint.cx &&
|
|
54
|
+
p.cy === selectedPoint.cy);
|
|
55
|
+
if (p.hovered !== hovered) {
|
|
56
|
+
p.hovered = hovered;
|
|
57
|
+
updates.push(p);
|
|
58
|
+
}
|
|
59
|
+
const active = Boolean(!inactiveEnabled || !selectedPoint || selectedPoint.series.id === p.series.id);
|
|
60
|
+
if (p.active !== active) {
|
|
61
|
+
p.active = active;
|
|
62
|
+
updates.push(p);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
selection.data(updates, key).join(() => EMPTY_SELECTION, (update) => {
|
|
66
|
+
update
|
|
67
|
+
.attr('fill', (d) => {
|
|
68
|
+
var _a;
|
|
69
|
+
const initialColor = d.data.color || d.series.color || '';
|
|
70
|
+
if (d.hovered) {
|
|
71
|
+
return (((_a = color(initialColor)) === null || _a === void 0 ? void 0 : _a.brighter(hoverOptions === null || hoverOptions === void 0 ? void 0 : hoverOptions.brightness).toString()) || initialColor);
|
|
72
|
+
}
|
|
73
|
+
return initialColor;
|
|
74
|
+
})
|
|
75
|
+
.attr('opacity', function (d) {
|
|
76
|
+
if (!d.active) {
|
|
77
|
+
return (inactiveOptions === null || inactiveOptions === void 0 ? void 0 : inactiveOptions.opacity) || null;
|
|
78
|
+
}
|
|
79
|
+
return null;
|
|
80
|
+
});
|
|
81
|
+
return update;
|
|
82
|
+
}, (exit) => exit);
|
|
83
|
+
});
|
|
84
|
+
return () => {
|
|
85
|
+
dispatcher.on('hover-shape.scatter', null);
|
|
86
|
+
};
|
|
87
|
+
}, [dispatcher, top, left, preparedData, seriesOptions, svgContainer]);
|
|
88
|
+
return React.createElement("g", { ref: ref, className: b() });
|
|
89
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { TooltipDataChunkScatter } from '../../../../../../types/widget-data';
|
|
2
|
+
import type { ChartScale } from '../../useAxisScales';
|
|
3
|
+
import type { PreparedAxis } from '../../useChartOptions/types';
|
|
4
|
+
import { PreparedScatterSeries } from '../../useSeries/types';
|
|
5
|
+
export type PreparedScatterData = Omit<TooltipDataChunkScatter, 'series'> & {
|
|
6
|
+
cx: number;
|
|
7
|
+
cy: number;
|
|
8
|
+
series: PreparedScatterSeries;
|
|
9
|
+
hovered: boolean;
|
|
10
|
+
active: boolean;
|
|
11
|
+
id: number;
|
|
12
|
+
};
|
|
13
|
+
export declare const prepareScatterData: (args: {
|
|
14
|
+
series: PreparedScatterSeries[];
|
|
15
|
+
xAxis: PreparedAxis;
|
|
16
|
+
xScale: ChartScale;
|
|
17
|
+
yAxis: PreparedAxis;
|
|
18
|
+
yScale: ChartScale;
|
|
19
|
+
}) => PreparedScatterData[];
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import get from 'lodash/get';
|
|
2
|
+
import { getDataCategoryValue } from '../../../utils';
|
|
3
|
+
const getCxAttr = (args) => {
|
|
4
|
+
const { point, xAxis, xScale } = args;
|
|
5
|
+
let cx;
|
|
6
|
+
if (xAxis.type === 'category') {
|
|
7
|
+
const xBandScale = xScale;
|
|
8
|
+
const categories = get(xAxis, 'categories', []);
|
|
9
|
+
const dataCategory = getDataCategoryValue({ axisDirection: 'x', categories, data: point });
|
|
10
|
+
cx = (xBandScale(dataCategory) || 0) + xBandScale.step() / 2;
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
const xLinearScale = xScale;
|
|
14
|
+
cx = xLinearScale(point.x);
|
|
15
|
+
}
|
|
16
|
+
return cx;
|
|
17
|
+
};
|
|
18
|
+
const getCyAttr = (args) => {
|
|
19
|
+
const { point, yAxis, yScale } = args;
|
|
20
|
+
let cy;
|
|
21
|
+
if (yAxis.type === 'category') {
|
|
22
|
+
const yBandScale = yScale;
|
|
23
|
+
const categories = get(yAxis, 'categories', []);
|
|
24
|
+
const dataCategory = getDataCategoryValue({ axisDirection: 'y', categories, data: point });
|
|
25
|
+
cy = (yBandScale(dataCategory) || 0) + yBandScale.step() / 2;
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
const yLinearScale = yScale;
|
|
29
|
+
cy = yLinearScale(point.y);
|
|
30
|
+
}
|
|
31
|
+
return cy;
|
|
32
|
+
};
|
|
33
|
+
const getFilteredLinearScatterData = (data) => {
|
|
34
|
+
return data.filter((d) => typeof d.x === 'number' && typeof d.y === 'number');
|
|
35
|
+
};
|
|
36
|
+
export const prepareScatterData = (args) => {
|
|
37
|
+
const { series, xAxis, xScale, yAxis, yScale } = args;
|
|
38
|
+
return series.reduce((acc, s) => {
|
|
39
|
+
const filteredData = xAxis.type === 'category' || yAxis.type === 'category'
|
|
40
|
+
? s.data
|
|
41
|
+
: getFilteredLinearScatterData(s.data);
|
|
42
|
+
filteredData.forEach((d) => {
|
|
43
|
+
acc.push({
|
|
44
|
+
data: d,
|
|
45
|
+
series: s,
|
|
46
|
+
cx: getCxAttr({ point: d, xAxis, xScale }),
|
|
47
|
+
cy: getCyAttr({ point: d, yAxis, yScale }),
|
|
48
|
+
hovered: false,
|
|
49
|
+
active: true,
|
|
50
|
+
id: acc.length - 1,
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
return acc;
|
|
54
|
+
}, []);
|
|
55
|
+
};
|
|
@@ -2,15 +2,6 @@
|
|
|
2
2
|
stroke-width: 1px;
|
|
3
3
|
}
|
|
4
4
|
|
|
5
|
-
.chartkit-d3_hovered .chartkit-d3-scatter__point {
|
|
6
|
-
opacity: 0.5;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
.chartkit-d3-scatter__point:hover {
|
|
10
|
-
stroke: #fff;
|
|
11
|
-
opacity: 1;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
5
|
.chartkit-d3-pie__segment {
|
|
15
6
|
stroke: var(--g-color-base-background);
|
|
16
7
|
}
|
|
@@ -23,4 +14,5 @@
|
|
|
23
14
|
|
|
24
15
|
.chartkit-d3-bar-x__label {
|
|
25
16
|
fill: var(--g-color-text-complementary);
|
|
17
|
+
user-select: none;
|
|
26
18
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Dispatch } from 'd3';
|
|
2
|
+
import type { TooltipDataChunk } from '../../../../../types/widget-data';
|
|
2
3
|
import { PreparedTooltip } from '../useChartOptions/types';
|
|
3
|
-
import type { PointerPosition
|
|
4
|
+
import type { PointerPosition } from './types';
|
|
4
5
|
type Args = {
|
|
6
|
+
dispatcher: Dispatch<object>;
|
|
5
7
|
tooltip: PreparedTooltip;
|
|
6
8
|
};
|
|
7
|
-
export declare const useTooltip: ({ tooltip }: Args) => {
|
|
8
|
-
hovered:
|
|
9
|
+
export declare const useTooltip: ({ dispatcher, tooltip }: Args) => {
|
|
10
|
+
hovered: TooltipDataChunk<any>[] | undefined;
|
|
9
11
|
pointerPosition: PointerPosition | undefined;
|
|
10
|
-
handleSeriesMouseMove: OnSeriesMouseMove | undefined;
|
|
11
|
-
handleSeriesMouseLeave: OnSeriesMouseLeave | undefined;
|
|
12
12
|
};
|
|
13
13
|
export {};
|
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
export const useTooltip = ({ tooltip }) => {
|
|
3
|
-
const [hovered,
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
handleSeriesMouseLeave: tooltip.enabled ? handleSeriesMouseLeave : undefined,
|
|
18
|
-
};
|
|
2
|
+
export const useTooltip = ({ dispatcher, tooltip }) => {
|
|
3
|
+
const [{ hovered, pointerPosition }, setTooltipState] = React.useState({});
|
|
4
|
+
React.useEffect(() => {
|
|
5
|
+
if (tooltip === null || tooltip === void 0 ? void 0 : tooltip.enabled) {
|
|
6
|
+
dispatcher.on('hover-shape.tooltip', (nextHovered, nextPointerPosition) => {
|
|
7
|
+
setTooltipState({ hovered: nextHovered, pointerPosition: nextPointerPosition });
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
return () => {
|
|
11
|
+
if (tooltip === null || tooltip === void 0 ? void 0 : tooltip.enabled) {
|
|
12
|
+
dispatcher.on('hover-shape.tooltip', null);
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
}, [dispatcher, tooltip]);
|
|
16
|
+
return { hovered, pointerPosition };
|
|
19
17
|
};
|
|
@@ -1,7 +1 @@
|
|
|
1
|
-
import type { TooltipHoveredData } from '../../../../../types/widget-data';
|
|
2
1
|
export type PointerPosition = [number, number];
|
|
3
|
-
export type OnSeriesMouseMove = (args: {
|
|
4
|
-
hovered: TooltipHoveredData;
|
|
5
|
-
pointerPosition?: PointerPosition;
|
|
6
|
-
}) => void;
|
|
7
|
-
export type OnSeriesMouseLeave = () => void;
|
|
@@ -9,8 +9,10 @@ type AxisBottomArgs = {
|
|
|
9
9
|
labelsPaddings?: number;
|
|
10
10
|
labelsMargin?: number;
|
|
11
11
|
labelsStyle?: BaseTextStyle;
|
|
12
|
+
labelsMaxWidth?: number;
|
|
13
|
+
labelsLineHeight: number;
|
|
12
14
|
size: number;
|
|
13
|
-
|
|
15
|
+
rotation: number;
|
|
14
16
|
};
|
|
15
17
|
domain: {
|
|
16
18
|
size: number;
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { select } from 'd3';
|
|
1
2
|
import { getXAxisItems, getXAxisOffset, getXTickPosition } from '../axis';
|
|
2
|
-
import {
|
|
3
|
+
import { getLabelsMaxHeight, setEllipsisForOverflowText } from '../text';
|
|
4
|
+
import { calculateCos, calculateSin } from '../math';
|
|
3
5
|
function addDomain(selection, options) {
|
|
4
6
|
const { size, color } = options;
|
|
5
7
|
selection
|
|
@@ -12,13 +14,27 @@ function addDomain(selection, options) {
|
|
|
12
14
|
.attr('d', `M0,0V0H${size}`);
|
|
13
15
|
}
|
|
14
16
|
export function axisBottom(args) {
|
|
15
|
-
const { scale, ticks: { labelFormat, labelsPaddings = 0, labelsMargin = 0, labelsStyle, size: tickSize, count: ticksCount, maxTickCount,
|
|
17
|
+
const { scale, ticks: { labelFormat, labelsPaddings = 0, labelsMargin = 0, labelsMaxWidth = Infinity, labelsStyle, labelsLineHeight, size: tickSize, count: ticksCount, maxTickCount, rotation, }, domain: { size: domainSize, color: domainColor }, } = args;
|
|
16
18
|
const offset = getXAxisOffset();
|
|
17
|
-
const spacing = Math.max(tickSize, 0) + labelsMargin;
|
|
18
19
|
const position = getXTickPosition({ scale, offset });
|
|
19
20
|
const values = getXAxisItems({ scale, count: ticksCount, maxCount: maxTickCount });
|
|
21
|
+
const labelHeight = getLabelsMaxHeight({
|
|
22
|
+
labels: values,
|
|
23
|
+
style: { 'font-size': (labelsStyle === null || labelsStyle === void 0 ? void 0 : labelsStyle.fontSize) || '' },
|
|
24
|
+
});
|
|
20
25
|
return function (selection) {
|
|
21
26
|
var _a, _b;
|
|
27
|
+
const x = ((_b = (_a = selection.node()) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect()) === null || _b === void 0 ? void 0 : _b.x) || 0;
|
|
28
|
+
const right = x + domainSize;
|
|
29
|
+
let transform = `translate(0, ${labelHeight + labelsMargin}px)`;
|
|
30
|
+
if (rotation) {
|
|
31
|
+
const labelsOffsetTop = labelHeight * calculateCos(rotation) + labelsMargin;
|
|
32
|
+
let labelsOffsetLeft = calculateSin(rotation) * labelHeight;
|
|
33
|
+
if (Math.abs(rotation) % 360 === 90) {
|
|
34
|
+
labelsOffsetLeft += ((rotation > 0 ? -1 : 1) * labelHeight) / 2;
|
|
35
|
+
}
|
|
36
|
+
transform = `translate(${-labelsOffsetLeft}px, ${labelsOffsetTop}px) rotate(${rotation}deg)`;
|
|
37
|
+
}
|
|
22
38
|
selection
|
|
23
39
|
.selectAll('.tick')
|
|
24
40
|
.data(values)
|
|
@@ -27,49 +43,72 @@ export function axisBottom(args) {
|
|
|
27
43
|
const tick = el.append('g').attr('class', 'tick');
|
|
28
44
|
tick.append('line').attr('stroke', 'currentColor').attr('y2', tickSize);
|
|
29
45
|
tick.append('text')
|
|
46
|
+
.text(labelFormat)
|
|
30
47
|
.attr('fill', 'currentColor')
|
|
31
|
-
.attr('
|
|
32
|
-
|
|
33
|
-
|
|
48
|
+
.attr('text-anchor', () => {
|
|
49
|
+
if (rotation) {
|
|
50
|
+
return rotation > 0 ? 'start' : 'end';
|
|
51
|
+
}
|
|
52
|
+
return 'middle';
|
|
53
|
+
})
|
|
54
|
+
.style('transform', transform)
|
|
55
|
+
.style('alignment-baseline', 'after-edge');
|
|
34
56
|
return tick;
|
|
35
57
|
})
|
|
36
58
|
.attr('transform', function (d) {
|
|
37
59
|
return `translate(${position(d) + offset},0)`;
|
|
38
60
|
});
|
|
61
|
+
// Remove tick that has the same x coordinate like domain
|
|
62
|
+
selection
|
|
63
|
+
.select('.tick')
|
|
64
|
+
.filter((d) => {
|
|
65
|
+
return position(d) === 0;
|
|
66
|
+
})
|
|
67
|
+
.select('line')
|
|
68
|
+
.remove();
|
|
39
69
|
const labels = selection.selectAll('.tick text');
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
labels
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
70
|
+
// FIXME: handle rotated overlapping labels (with a smarter approach)
|
|
71
|
+
if (rotation) {
|
|
72
|
+
const maxWidth = labelsMaxWidth * calculateCos(rotation) + labelsLineHeight * calculateSin(rotation);
|
|
73
|
+
labels.each(function () {
|
|
74
|
+
setEllipsisForOverflowText(select(this), maxWidth);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
// remove overlapping labels
|
|
79
|
+
let elementX = 0;
|
|
80
|
+
selection
|
|
81
|
+
.selectAll('.tick')
|
|
82
|
+
.filter(function () {
|
|
83
|
+
const node = this;
|
|
84
|
+
const r = node.getBoundingClientRect();
|
|
85
|
+
if (r.left < elementX) {
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
elementX = r.right + labelsPaddings;
|
|
89
|
+
return false;
|
|
90
|
+
})
|
|
91
|
+
.remove();
|
|
92
|
+
// add an ellipsis to the labels that go beyond the boundaries of the chart
|
|
93
|
+
labels.each(function (_d, i, nodes) {
|
|
94
|
+
if (i === nodes.length - 1) {
|
|
95
|
+
const currentElement = this;
|
|
96
|
+
const prevElement = nodes[i - 1];
|
|
97
|
+
const text = select(currentElement);
|
|
98
|
+
const currentElementPosition = currentElement.getBoundingClientRect();
|
|
99
|
+
const prevElementPosition = prevElement === null || prevElement === void 0 ? void 0 : prevElement.getBoundingClientRect();
|
|
100
|
+
const lackingSpace = Math.max(0, currentElementPosition.right - right);
|
|
101
|
+
if (lackingSpace) {
|
|
102
|
+
const remainSpace = right - ((prevElementPosition === null || prevElementPosition === void 0 ? void 0 : prevElementPosition.right) || 0) - labelsPaddings;
|
|
103
|
+
const translateX = currentElementPosition.width / 2 - lackingSpace;
|
|
104
|
+
text.attr('text-anchor', 'end').attr('transform', `translate(${translateX},0)`);
|
|
105
|
+
setEllipsisForOverflowText(text, remainSpace);
|
|
65
106
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
})
|
|
69
|
-
.remove();
|
|
70
|
-
}
|
|
107
|
+
}
|
|
108
|
+
});
|
|
71
109
|
}
|
|
72
|
-
selection
|
|
73
|
-
|
|
110
|
+
selection
|
|
111
|
+
.call(addDomain, { size: domainSize, color: domainColor })
|
|
112
|
+
.style('font-size', (labelsStyle === null || labelsStyle === void 0 ? void 0 : labelsStyle.fontSize) || '');
|
|
74
113
|
};
|
|
75
114
|
}
|
|
@@ -30,17 +30,11 @@ export function getXTickPosition({ scale, offset }) {
|
|
|
30
30
|
return isBandScale(scale) ? center(scale.copy(), offset) : number(scale.copy());
|
|
31
31
|
}
|
|
32
32
|
export function getXAxisItems({ scale, count, maxCount, }) {
|
|
33
|
-
const offset = getXAxisOffset();
|
|
34
33
|
let values = getScaleTicks(scale, count);
|
|
35
|
-
const position = getXTickPosition({ scale, offset });
|
|
36
34
|
if (values.length > maxCount) {
|
|
37
35
|
const step = Math.ceil(values.length / maxCount);
|
|
38
36
|
values = values.filter((_, i) => i % step === 0);
|
|
39
37
|
}
|
|
40
|
-
// Remove tick that has the same x coordinate like domain
|
|
41
|
-
if (values.length && position(values[0]) === 0) {
|
|
42
|
-
values = values.slice(1);
|
|
43
|
-
}
|
|
44
38
|
return values;
|
|
45
39
|
}
|
|
46
40
|
export function getMaxTickCount({ axis, width }) {
|