@gravity-ui/chartkit 4.6.1 → 4.7.1
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/components/AxisX.js +6 -7
- package/build/plugins/d3/renderer/components/AxisY.js +50 -18
- package/build/plugins/d3/renderer/components/Chart.js +24 -16
- 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} +7 -87
- 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 +32 -22
- 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 +24 -14
- 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,6 +1,7 @@
|
|
|
1
1
|
import { select } from 'd3';
|
|
2
2
|
import { getXAxisItems, getXAxisOffset, getXTickPosition } from '../axis';
|
|
3
|
-
import {
|
|
3
|
+
import { getLabelsMaxHeight, setEllipsisForOverflowText } from '../text';
|
|
4
|
+
import { calculateCos, calculateSin } from '../math';
|
|
4
5
|
function addDomain(selection, options) {
|
|
5
6
|
const { size, color } = options;
|
|
6
7
|
selection
|
|
@@ -13,15 +14,27 @@ function addDomain(selection, options) {
|
|
|
13
14
|
.attr('d', `M0,0V0H${size}`);
|
|
14
15
|
}
|
|
15
16
|
export function axisBottom(args) {
|
|
16
|
-
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;
|
|
17
18
|
const offset = getXAxisOffset();
|
|
18
|
-
const spacing = Math.max(tickSize, 0) + labelsMargin;
|
|
19
19
|
const position = getXTickPosition({ scale, offset });
|
|
20
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
|
+
});
|
|
21
25
|
return function (selection) {
|
|
22
|
-
var _a, _b
|
|
26
|
+
var _a, _b;
|
|
23
27
|
const x = ((_b = (_a = selection.node()) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect()) === null || _b === void 0 ? void 0 : _b.x) || 0;
|
|
24
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
|
+
}
|
|
25
38
|
selection
|
|
26
39
|
.selectAll('.tick')
|
|
27
40
|
.data(values)
|
|
@@ -30,10 +43,16 @@ export function axisBottom(args) {
|
|
|
30
43
|
const tick = el.append('g').attr('class', 'tick');
|
|
31
44
|
tick.append('line').attr('stroke', 'currentColor').attr('y2', tickSize);
|
|
32
45
|
tick.append('text')
|
|
46
|
+
.text(labelFormat)
|
|
33
47
|
.attr('fill', 'currentColor')
|
|
34
|
-
.attr('
|
|
35
|
-
|
|
36
|
-
|
|
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');
|
|
37
56
|
return tick;
|
|
38
57
|
})
|
|
39
58
|
.attr('transform', function (d) {
|
|
@@ -48,20 +67,12 @@ export function axisBottom(args) {
|
|
|
48
67
|
.select('line')
|
|
49
68
|
.remove();
|
|
50
69
|
const labels = selection.selectAll('.tick text');
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
labels
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
});
|
|
58
|
-
const rotationAngle = overlapping && autoRotation ? '-45' : undefined;
|
|
59
|
-
if (rotationAngle) {
|
|
60
|
-
const labelHeight = (_e = (_c = labelNodes[0]) === null || _c === void 0 ? void 0 : _c.getBoundingClientRect()) === null || _e === void 0 ? void 0 : _e.height;
|
|
61
|
-
const labelOffset = (labelHeight / 2 + labelsMargin) / 2;
|
|
62
|
-
labels
|
|
63
|
-
.attr('text-anchor', 'end')
|
|
64
|
-
.attr('transform', `rotate(${rotationAngle}) translate(-${labelOffset}, -${labelOffset})`);
|
|
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
|
+
});
|
|
65
76
|
}
|
|
66
77
|
else {
|
|
67
78
|
// remove overlapping labels
|
|
@@ -98,7 +109,6 @@ export function axisBottom(args) {
|
|
|
98
109
|
}
|
|
99
110
|
selection
|
|
100
111
|
.call(addDomain, { size: domainSize, color: domainColor })
|
|
101
|
-
.attr('text-anchor', 'middle')
|
|
102
112
|
.style('font-size', (labelsStyle === null || labelsStyle === void 0 ? void 0 : labelsStyle.fontSize) || '');
|
|
103
113
|
};
|
|
104
114
|
}
|
|
@@ -6,6 +6,9 @@ export * from './text';
|
|
|
6
6
|
export * from './time';
|
|
7
7
|
export * from './axis';
|
|
8
8
|
export type AxisDirection = 'x' | 'y';
|
|
9
|
+
export type NodeWithD3Data<T = unknown> = Element & {
|
|
10
|
+
__data__: T;
|
|
11
|
+
};
|
|
9
12
|
type UnknownSeries = {
|
|
10
13
|
type: ChartKitWidgetSeries['type'];
|
|
11
14
|
data: unknown;
|
|
@@ -68,3 +71,5 @@ export declare const getDataCategoryValue: (args: {
|
|
|
68
71
|
data: ChartKitWidgetSeriesData;
|
|
69
72
|
}) => string;
|
|
70
73
|
export declare function getClosestPointsRange(axis: PreparedAxis, points: AxisDomain[]): number | undefined;
|
|
74
|
+
export declare const isNodeContainsD3Data: (node?: Element | null) => node is NodeWithD3Data<unknown>;
|
|
75
|
+
export declare const extractD3DataFromNode: <T extends unknown>(node: NodeWithD3Data<T>) => T;
|
|
@@ -120,18 +120,16 @@ export const formatAxisTickLabel = (args) => {
|
|
|
120
120
|
* @return {number} The height of the text element.
|
|
121
121
|
*/
|
|
122
122
|
export const getHorisontalSvgTextHeight = (args) => {
|
|
123
|
+
var _a;
|
|
123
124
|
const { text, style } = args;
|
|
124
|
-
const
|
|
125
|
+
const container = select(document.body).append('svg');
|
|
126
|
+
const textSelection = container.append('text').text(text);
|
|
125
127
|
const fontSize = get(style, 'fontSize', DEFAULT_AXIS_LABEL_FONT_SIZE);
|
|
126
|
-
let height = 0;
|
|
127
128
|
if (fontSize) {
|
|
128
|
-
textSelection.style('font-size', fontSize);
|
|
129
|
+
textSelection.style('font-size', fontSize).style('alignment-baseline', 'after-edge');
|
|
129
130
|
}
|
|
130
|
-
textSelection
|
|
131
|
-
|
|
132
|
-
height = this.getBoundingClientRect().height;
|
|
133
|
-
})
|
|
134
|
-
.remove();
|
|
131
|
+
const height = ((_a = textSelection.node()) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect().height) || 0;
|
|
132
|
+
container.remove();
|
|
135
133
|
return height;
|
|
136
134
|
};
|
|
137
135
|
const extractCategoryValue = (args) => {
|
|
@@ -163,3 +161,10 @@ export function getClosestPointsRange(axis, points) {
|
|
|
163
161
|
}
|
|
164
162
|
return points[1] - points[0];
|
|
165
163
|
}
|
|
164
|
+
// https://d3js.org/d3-selection/joining#selection_data
|
|
165
|
+
export const isNodeContainsD3Data = (node) => {
|
|
166
|
+
return Boolean(node && '__data__' in node);
|
|
167
|
+
};
|
|
168
|
+
export const extractD3DataFromNode = (node) => {
|
|
169
|
+
return node.__data__;
|
|
170
|
+
};
|
|
@@ -21,3 +21,5 @@ export declare const calculateNumericProperty: (args: {
|
|
|
21
21
|
value?: string | number | null;
|
|
22
22
|
base?: number;
|
|
23
23
|
}) => number | undefined;
|
|
24
|
+
export declare function calculateCos(deg: number, precision?: number): number;
|
|
25
|
+
export declare function calculateSin(deg: number, precision?: number): number;
|
|
@@ -41,3 +41,11 @@ export const calculateNumericProperty = (args) => {
|
|
|
41
41
|
}
|
|
42
42
|
return value;
|
|
43
43
|
};
|
|
44
|
+
export function calculateCos(deg, precision = 2) {
|
|
45
|
+
const factor = Math.pow(10, precision);
|
|
46
|
+
return Math.floor(Math.cos((Math.PI / 180) * deg) * factor) / factor;
|
|
47
|
+
}
|
|
48
|
+
export function calculateSin(deg, precision = 2) {
|
|
49
|
+
const factor = Math.pow(10, precision);
|
|
50
|
+
return Math.floor(Math.sin((Math.PI / 180) * deg) * factor) / factor;
|
|
51
|
+
}
|