@gravity-ui/charts 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/components/Tooltip/DefaultContent.js +18 -3
- package/dist/cjs/constants/defaults/series-options.js +12 -0
- package/dist/cjs/constants/index.d.ts +1 -0
- package/dist/cjs/constants/index.js +1 -0
- package/dist/cjs/hooks/useSeries/prepare-radar.d.ts +16 -0
- package/dist/cjs/hooks/useSeries/prepare-radar.js +63 -0
- package/dist/cjs/hooks/useSeries/prepareSeries.js +8 -0
- package/dist/cjs/hooks/useSeries/types.d.ts +35 -2
- package/dist/cjs/hooks/useShapes/index.d.ts +2 -1
- package/dist/cjs/hooks/useShapes/index.js +12 -0
- package/dist/cjs/hooks/useShapes/marker.d.ts +3 -2
- package/dist/cjs/hooks/useShapes/radar/index.d.ts +12 -0
- package/dist/cjs/hooks/useShapes/radar/index.js +136 -0
- package/dist/cjs/hooks/useShapes/radar/prepare-data.d.ts +9 -0
- package/dist/cjs/hooks/useShapes/radar/prepare-data.js +155 -0
- package/dist/cjs/hooks/useShapes/radar/types.d.ts +58 -0
- package/dist/cjs/hooks/useShapes/radar/types.js +1 -0
- package/dist/cjs/hooks/useShapes/styles.css +4 -0
- package/dist/cjs/types/chart/radar.d.ts +50 -0
- package/dist/cjs/types/chart/radar.js +1 -0
- package/dist/cjs/types/chart/series.d.ts +17 -2
- package/dist/cjs/types/chart/tooltip.d.ts +8 -1
- package/dist/cjs/types/index.d.ts +1 -0
- package/dist/cjs/types/index.js +1 -0
- package/dist/cjs/utils/chart/get-closest-data.js +39 -2
- package/dist/cjs/utils/chart/index.js +1 -1
- package/dist/esm/components/Tooltip/DefaultContent.js +18 -3
- package/dist/esm/constants/defaults/series-options.js +12 -0
- package/dist/esm/constants/index.d.ts +1 -0
- package/dist/esm/constants/index.js +1 -0
- package/dist/esm/hooks/useSeries/prepare-radar.d.ts +16 -0
- package/dist/esm/hooks/useSeries/prepare-radar.js +63 -0
- package/dist/esm/hooks/useSeries/prepareSeries.js +8 -0
- package/dist/esm/hooks/useSeries/types.d.ts +35 -2
- package/dist/esm/hooks/useShapes/index.d.ts +2 -1
- package/dist/esm/hooks/useShapes/index.js +12 -0
- package/dist/esm/hooks/useShapes/marker.d.ts +3 -2
- package/dist/esm/hooks/useShapes/radar/index.d.ts +12 -0
- package/dist/esm/hooks/useShapes/radar/index.js +136 -0
- package/dist/esm/hooks/useShapes/radar/prepare-data.d.ts +9 -0
- package/dist/esm/hooks/useShapes/radar/prepare-data.js +155 -0
- package/dist/esm/hooks/useShapes/radar/types.d.ts +58 -0
- package/dist/esm/hooks/useShapes/radar/types.js +1 -0
- package/dist/esm/hooks/useShapes/styles.css +4 -0
- package/dist/esm/types/chart/radar.d.ts +50 -0
- package/dist/esm/types/chart/radar.js +1 -0
- package/dist/esm/types/chart/series.d.ts +17 -2
- package/dist/esm/types/chart/tooltip.d.ts +8 -1
- package/dist/esm/types/index.d.ts +1 -0
- package/dist/esm/types/index.js +1 -0
- package/dist/esm/utils/chart/get-closest-data.js +39 -2
- package/dist/esm/utils/chart/index.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { color, line, select } from 'd3';
|
|
3
|
+
import get from 'lodash/get';
|
|
4
|
+
import { block } from '../../../utils';
|
|
5
|
+
import { HtmlLayer } from '../HtmlLayer';
|
|
6
|
+
import { getMarkerHaloVisibility, getMarkerVisibility, renderMarker, selectMarkerHalo, selectMarkerSymbol, setMarker, } from '../marker';
|
|
7
|
+
import { setActiveState } from '../utils';
|
|
8
|
+
const b = block('radar');
|
|
9
|
+
export function RadarSeriesShapes(args) {
|
|
10
|
+
const { dispatcher, series: preparedData, seriesOptions, htmlLayout } = args;
|
|
11
|
+
const ref = React.useRef(null);
|
|
12
|
+
React.useEffect(() => {
|
|
13
|
+
if (!ref.current) {
|
|
14
|
+
return () => { };
|
|
15
|
+
}
|
|
16
|
+
const svgElement = select(ref.current);
|
|
17
|
+
svgElement.selectAll('*').remove();
|
|
18
|
+
const areaSelector = `.${b('area')}`;
|
|
19
|
+
const radarSelection = svgElement
|
|
20
|
+
.selectAll('radar')
|
|
21
|
+
.data(preparedData)
|
|
22
|
+
.join('g')
|
|
23
|
+
.attr('id', (radarData) => radarData.id)
|
|
24
|
+
.attr('class', b('item'))
|
|
25
|
+
.attr('cursor', (radarData) => radarData.cursor);
|
|
26
|
+
// render axes
|
|
27
|
+
radarSelection
|
|
28
|
+
.selectAll(`.${b('axis')}`)
|
|
29
|
+
.data((radarData) => radarData.axes)
|
|
30
|
+
.join('line')
|
|
31
|
+
.attr('class', b('axis'))
|
|
32
|
+
.attr('x1', (d) => d.radar.center[0])
|
|
33
|
+
.attr('y1', (d) => d.radar.center[1])
|
|
34
|
+
.attr('x2', (d) => d.point[0])
|
|
35
|
+
.attr('y2', (d) => d.point[1])
|
|
36
|
+
.attr('stroke', (d) => d.strokeColor)
|
|
37
|
+
.attr('stroke-width', (d) => d.strokeWidth);
|
|
38
|
+
// render grid lines
|
|
39
|
+
radarSelection
|
|
40
|
+
.selectAll(`.${b('grid')}`)
|
|
41
|
+
.data((radarData) => radarData.grid)
|
|
42
|
+
.join('path')
|
|
43
|
+
.attr('class', b('grid'))
|
|
44
|
+
.attr('d', (d) => `${line()(d.path)} Z`)
|
|
45
|
+
.attr('fill', 'none')
|
|
46
|
+
.attr('stroke', (d) => d.strokeColor)
|
|
47
|
+
.attr('stroke-width', (d) => d.strokeWidth);
|
|
48
|
+
// render radar area
|
|
49
|
+
const shapesSelection = radarSelection
|
|
50
|
+
.selectAll(areaSelector)
|
|
51
|
+
.data((radarData) => radarData.shapes)
|
|
52
|
+
.join('g')
|
|
53
|
+
.attr('class', b('area'));
|
|
54
|
+
shapesSelection
|
|
55
|
+
.append('path')
|
|
56
|
+
.attr('d', (d) => d.path)
|
|
57
|
+
.attr('fill', (d) => d.color)
|
|
58
|
+
.attr('fill-opacity', (d) => d.fillOpacity)
|
|
59
|
+
.attr('stroke', (d) => d.borderColor)
|
|
60
|
+
.attr('stroke-width', (d) => d.borderWidth);
|
|
61
|
+
// render markers
|
|
62
|
+
const markerSelection = shapesSelection
|
|
63
|
+
.selectAll('marker')
|
|
64
|
+
.data((radarData) => radarData.points)
|
|
65
|
+
.join('g')
|
|
66
|
+
.call(renderMarker);
|
|
67
|
+
// Render labels
|
|
68
|
+
radarSelection
|
|
69
|
+
.selectAll('text')
|
|
70
|
+
.data((radarData) => radarData.labels)
|
|
71
|
+
.join('text')
|
|
72
|
+
.text((d) => d.text)
|
|
73
|
+
.attr('class', b('label'))
|
|
74
|
+
.attr('x', (d) => d.x)
|
|
75
|
+
.attr('y', (d) => d.y)
|
|
76
|
+
.attr('text-anchor', (d) => d.textAnchor)
|
|
77
|
+
.style('font-size', (d) => d.style.fontSize)
|
|
78
|
+
.style('font-weight', (d) => d.style.fontWeight || null)
|
|
79
|
+
.style('fill', (d) => d.style.fontColor || null);
|
|
80
|
+
// Handle hover events
|
|
81
|
+
const eventName = `hover-shape.radar`;
|
|
82
|
+
const hoverOptions = get(seriesOptions, 'radar.states.hover');
|
|
83
|
+
const inactiveOptions = get(seriesOptions, 'radar.states.inactive');
|
|
84
|
+
dispatcher.on(eventName, (data) => {
|
|
85
|
+
const closest = data === null || data === void 0 ? void 0 : data.find((d) => d.closest);
|
|
86
|
+
const selectedSeries = closest === null || closest === void 0 ? void 0 : closest.series;
|
|
87
|
+
const selectedSeriesData = closest === null || closest === void 0 ? void 0 : closest.data;
|
|
88
|
+
const selectedSeriesId = selectedSeries === null || selectedSeries === void 0 ? void 0 : selectedSeries.id;
|
|
89
|
+
const hoverEnabled = hoverOptions === null || hoverOptions === void 0 ? void 0 : hoverOptions.enabled;
|
|
90
|
+
const inactiveEnabled = inactiveOptions === null || inactiveOptions === void 0 ? void 0 : inactiveOptions.enabled;
|
|
91
|
+
shapesSelection.datum((d, i, elements) => {
|
|
92
|
+
var _a;
|
|
93
|
+
const hovered = Boolean(hoverEnabled && ((_a = d.series) === null || _a === void 0 ? void 0 : _a.id) === selectedSeriesId);
|
|
94
|
+
if (d.hovered !== hovered) {
|
|
95
|
+
d.hovered = hovered;
|
|
96
|
+
select(elements[i]).attr('fill', () => {
|
|
97
|
+
var _a;
|
|
98
|
+
const initialColor = d.color;
|
|
99
|
+
if (d.hovered) {
|
|
100
|
+
return (((_a = color(initialColor)) === null || _a === void 0 ? void 0 : _a.brighter(hoverOptions === null || hoverOptions === void 0 ? void 0 : hoverOptions.brightness).toString()) || initialColor);
|
|
101
|
+
}
|
|
102
|
+
return initialColor;
|
|
103
|
+
});
|
|
104
|
+
if (hovered) {
|
|
105
|
+
select(elements[i]).raise();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
setActiveState({
|
|
109
|
+
element: elements[i],
|
|
110
|
+
state: inactiveOptions,
|
|
111
|
+
active: Boolean(!inactiveEnabled || !selectedSeriesId || selectedSeriesId === d.series.id),
|
|
112
|
+
datum: d,
|
|
113
|
+
});
|
|
114
|
+
markerSelection.datum((markerData, index, markers) => {
|
|
115
|
+
const hoveredState = Boolean(hoverEnabled && markerData.data === selectedSeriesData);
|
|
116
|
+
if (markerData.hovered !== hoveredState) {
|
|
117
|
+
markerData.hovered = hoveredState;
|
|
118
|
+
const elementSelection = select(markers[index]);
|
|
119
|
+
elementSelection.attr('visibility', getMarkerVisibility(markerData));
|
|
120
|
+
selectMarkerHalo(elementSelection).attr('visibility', getMarkerHaloVisibility);
|
|
121
|
+
selectMarkerSymbol(elementSelection).call(setMarker, hoveredState ? 'hover' : 'normal');
|
|
122
|
+
}
|
|
123
|
+
return markerData;
|
|
124
|
+
});
|
|
125
|
+
return d;
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
return () => {
|
|
129
|
+
dispatcher.on(eventName, null);
|
|
130
|
+
};
|
|
131
|
+
}, [dispatcher, preparedData, seriesOptions]);
|
|
132
|
+
const htmlElements = preparedData.map((d) => d.htmlLabels).flat();
|
|
133
|
+
return (React.createElement(React.Fragment, null,
|
|
134
|
+
React.createElement("g", { ref: ref, className: b() }),
|
|
135
|
+
React.createElement(HtmlLayer, { preparedData: { htmlElements }, htmlLayout: htmlLayout })));
|
|
136
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { PreparedRadarSeries } from '../../useSeries/types';
|
|
2
|
+
import type { PreparedRadarData } from './types';
|
|
3
|
+
type Args = {
|
|
4
|
+
series: PreparedRadarSeries[];
|
|
5
|
+
boundsWidth: number;
|
|
6
|
+
boundsHeight: number;
|
|
7
|
+
};
|
|
8
|
+
export declare function prepareRadarData(args: Args): PreparedRadarData[];
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { curveLinearClosed, line, range, scaleLinear } from 'd3';
|
|
2
|
+
import { getLabelsSize } from '../../../utils';
|
|
3
|
+
export function prepareRadarData(args) {
|
|
4
|
+
const { series: preparedSeries, boundsWidth, boundsHeight } = args;
|
|
5
|
+
const maxRadius = Math.min(boundsWidth, boundsHeight) / 2;
|
|
6
|
+
const center = [boundsWidth / 2, boundsHeight / 2];
|
|
7
|
+
const result = [];
|
|
8
|
+
const gridStepsCount = 5;
|
|
9
|
+
const radius = maxRadius * 0.8; // Leave some space for labels
|
|
10
|
+
// Create scale for values
|
|
11
|
+
const valueScale = scaleLinear()
|
|
12
|
+
.domain([0, Math.max(...preparedSeries.map((s) => s.data.map((d) => d.value)).flat())])
|
|
13
|
+
.range([0, radius]);
|
|
14
|
+
const [, finalRadius] = valueScale.range();
|
|
15
|
+
const data = {
|
|
16
|
+
type: 'radar',
|
|
17
|
+
id: preparedSeries[0].id,
|
|
18
|
+
center,
|
|
19
|
+
radius: finalRadius,
|
|
20
|
+
shapes: [],
|
|
21
|
+
labels: [],
|
|
22
|
+
axes: [],
|
|
23
|
+
htmlLabels: [],
|
|
24
|
+
grid: [],
|
|
25
|
+
cursor: preparedSeries[0].cursor,
|
|
26
|
+
};
|
|
27
|
+
const categories = preparedSeries[0].categories;
|
|
28
|
+
const axisStrokeColor = 'var(--g-color-line-generic)';
|
|
29
|
+
const axisStrokeWidth = 1;
|
|
30
|
+
// Create axes based on categories
|
|
31
|
+
const axesCount = categories.length;
|
|
32
|
+
const angleStep = (2 * Math.PI) / axesCount;
|
|
33
|
+
data.axes = categories.map((_category, index) => {
|
|
34
|
+
const angle = index * angleStep - Math.PI / 2; // Start from top (negative PI/2)
|
|
35
|
+
return {
|
|
36
|
+
point: [
|
|
37
|
+
center[0] + Math.cos(angle) * data.radius,
|
|
38
|
+
center[1] + Math.sin(angle) * data.radius,
|
|
39
|
+
],
|
|
40
|
+
radar: data,
|
|
41
|
+
strokeColor: axisStrokeColor,
|
|
42
|
+
strokeWidth: axisStrokeWidth,
|
|
43
|
+
angle,
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
const gridStepInc = data.radius / gridStepsCount;
|
|
47
|
+
const gridSteps = range(gridStepInc, data.radius + gridStepInc, gridStepInc);
|
|
48
|
+
gridSteps.forEach((gridStep) => {
|
|
49
|
+
const gridLines = {
|
|
50
|
+
path: [],
|
|
51
|
+
strokeColor: axisStrokeColor,
|
|
52
|
+
strokeWidth: axisStrokeWidth,
|
|
53
|
+
};
|
|
54
|
+
categories.forEach((_category, index) => {
|
|
55
|
+
const angle = index * angleStep - Math.PI / 2; // Start from top (negative PI/2)
|
|
56
|
+
gridLines.path.push([
|
|
57
|
+
center[0] + Math.cos(angle) * gridStep,
|
|
58
|
+
center[1] + Math.sin(angle) * gridStep,
|
|
59
|
+
]);
|
|
60
|
+
});
|
|
61
|
+
data.grid.push(gridLines);
|
|
62
|
+
});
|
|
63
|
+
const radarAreaLine = line().curve(curveLinearClosed);
|
|
64
|
+
preparedSeries.forEach((series) => {
|
|
65
|
+
var _a;
|
|
66
|
+
const { dataLabels } = series;
|
|
67
|
+
const markers = [];
|
|
68
|
+
categories.forEach((category, index) => {
|
|
69
|
+
var _a;
|
|
70
|
+
const dataItem = series.data[index];
|
|
71
|
+
const angle = index * angleStep - Math.PI / 2; // Start from top (negative PI/2)
|
|
72
|
+
const pointValueScale = scaleLinear()
|
|
73
|
+
.domain([
|
|
74
|
+
0,
|
|
75
|
+
(_a = category.maxValue) !== null && _a !== void 0 ? _a : Math.max(...preparedSeries.map((s) => s.data[index].value)),
|
|
76
|
+
])
|
|
77
|
+
.range([0, radius]);
|
|
78
|
+
const pointRadius = pointValueScale(dataItem.value);
|
|
79
|
+
const x = center[0] + Math.cos(angle) * pointRadius;
|
|
80
|
+
const y = center[1] + Math.sin(angle) * pointRadius;
|
|
81
|
+
markers.push({
|
|
82
|
+
point: {
|
|
83
|
+
x,
|
|
84
|
+
y,
|
|
85
|
+
series,
|
|
86
|
+
data: dataItem,
|
|
87
|
+
},
|
|
88
|
+
index,
|
|
89
|
+
position: [x, y],
|
|
90
|
+
color: series.color,
|
|
91
|
+
opacity: 1,
|
|
92
|
+
radius: 2,
|
|
93
|
+
data: dataItem,
|
|
94
|
+
series: series,
|
|
95
|
+
hovered: false,
|
|
96
|
+
active: false,
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
data.shapes.push({
|
|
100
|
+
borderWidth: series.borderWidth,
|
|
101
|
+
borderColor: series.borderColor,
|
|
102
|
+
fillOpacity: series.fillOpacity,
|
|
103
|
+
points: markers,
|
|
104
|
+
path: (_a = radarAreaLine(markers.map((p) => p.position))) !== null && _a !== void 0 ? _a : '',
|
|
105
|
+
series: series,
|
|
106
|
+
color: series.color,
|
|
107
|
+
hovered: false,
|
|
108
|
+
active: true,
|
|
109
|
+
});
|
|
110
|
+
// Create labels if enabled
|
|
111
|
+
if (dataLabels.enabled) {
|
|
112
|
+
const { style } = dataLabels;
|
|
113
|
+
const shouldUseHtml = dataLabels.html;
|
|
114
|
+
data.labels = categories.map((category, index) => {
|
|
115
|
+
const text = category.key;
|
|
116
|
+
const labelSize = getLabelsSize({ labels: [text], style });
|
|
117
|
+
const angle = index * angleStep - Math.PI / 2;
|
|
118
|
+
// Position label slightly outside the point
|
|
119
|
+
const labelRadius = data.radius + 10;
|
|
120
|
+
let x = center[0] + Math.cos(angle) * labelRadius;
|
|
121
|
+
let y = center[1] + Math.sin(angle) * labelRadius;
|
|
122
|
+
if (shouldUseHtml) {
|
|
123
|
+
x = x < center[0] ? x - labelSize.maxWidth : x;
|
|
124
|
+
y = y - labelSize.maxHeight;
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
y = y < center[1] ? y - labelSize.maxHeight : y;
|
|
128
|
+
}
|
|
129
|
+
x = Math.max(-boundsWidth / 2, x);
|
|
130
|
+
return {
|
|
131
|
+
text,
|
|
132
|
+
x,
|
|
133
|
+
y,
|
|
134
|
+
style,
|
|
135
|
+
size: { width: labelSize.maxWidth, height: labelSize.maxHeight },
|
|
136
|
+
maxWidth: labelSize.maxWidth,
|
|
137
|
+
textAnchor: angle > Math.PI / 2 && angle < (3 * Math.PI) / 2 ? 'end' : 'start',
|
|
138
|
+
series: { id: series.id },
|
|
139
|
+
};
|
|
140
|
+
});
|
|
141
|
+
// Create HTML labels if needed
|
|
142
|
+
if (dataLabels.html) {
|
|
143
|
+
data.htmlLabels = data.labels.map((label) => ({
|
|
144
|
+
x: label.x,
|
|
145
|
+
y: label.y,
|
|
146
|
+
content: label.text,
|
|
147
|
+
size: label.size,
|
|
148
|
+
}));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return data;
|
|
152
|
+
});
|
|
153
|
+
result.push(data);
|
|
154
|
+
return result;
|
|
155
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { HtmlItem, LabelData, RadarSeriesData } from '../../../types';
|
|
2
|
+
import type { PreparedRadarSeries } from '../../useSeries/types';
|
|
3
|
+
export type RadarLabelData = LabelData & {
|
|
4
|
+
maxWidth: number;
|
|
5
|
+
};
|
|
6
|
+
export type RadarAxisData = {
|
|
7
|
+
point: [number, number];
|
|
8
|
+
angle: number;
|
|
9
|
+
strokeColor: string;
|
|
10
|
+
strokeWidth: number;
|
|
11
|
+
radar: PreparedRadarData;
|
|
12
|
+
};
|
|
13
|
+
export type RadarGridData = {
|
|
14
|
+
path: [number, number][];
|
|
15
|
+
strokeColor: string;
|
|
16
|
+
strokeWidth: number;
|
|
17
|
+
};
|
|
18
|
+
export type PointData = {
|
|
19
|
+
x: number;
|
|
20
|
+
y: number;
|
|
21
|
+
data: RadarSeriesData;
|
|
22
|
+
series: PreparedRadarSeries;
|
|
23
|
+
};
|
|
24
|
+
export type RadarMarkerData = {
|
|
25
|
+
point: PointData;
|
|
26
|
+
radius: number;
|
|
27
|
+
position: [number, number];
|
|
28
|
+
index: number;
|
|
29
|
+
color: string;
|
|
30
|
+
opacity: number;
|
|
31
|
+
data: RadarSeriesData;
|
|
32
|
+
series: PreparedRadarSeries;
|
|
33
|
+
hovered: boolean;
|
|
34
|
+
active: boolean;
|
|
35
|
+
};
|
|
36
|
+
export type RadarShapeData = {
|
|
37
|
+
points: RadarMarkerData[];
|
|
38
|
+
path: string;
|
|
39
|
+
color: string;
|
|
40
|
+
series: PreparedRadarSeries;
|
|
41
|
+
hovered: boolean;
|
|
42
|
+
active: boolean;
|
|
43
|
+
borderColor: string;
|
|
44
|
+
borderWidth: number;
|
|
45
|
+
fillOpacity: number;
|
|
46
|
+
};
|
|
47
|
+
export type PreparedRadarData = {
|
|
48
|
+
type: 'radar';
|
|
49
|
+
id: string;
|
|
50
|
+
shapes: RadarShapeData[];
|
|
51
|
+
labels: RadarLabelData[];
|
|
52
|
+
axes: RadarAxisData[];
|
|
53
|
+
grid: RadarGridData[];
|
|
54
|
+
center: [number, number];
|
|
55
|
+
radius: number;
|
|
56
|
+
htmlLabels: HtmlItem[];
|
|
57
|
+
cursor: string | null;
|
|
58
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { SeriesType } from '../../constants';
|
|
2
|
+
import type { MeaningfulAny } from '../misc';
|
|
3
|
+
import type { BaseSeries, BaseSeriesData } from './base';
|
|
4
|
+
import type { ChartLegend, RectLegendSymbolOptions } from './legend';
|
|
5
|
+
import type { PointMarkerOptions } from './marker';
|
|
6
|
+
export interface RadarSeriesData<T = MeaningfulAny> extends BaseSeriesData<T> {
|
|
7
|
+
/** The value of the radar point. */
|
|
8
|
+
value: number;
|
|
9
|
+
}
|
|
10
|
+
export interface RadarSeriesCategory {
|
|
11
|
+
/** The categories for the radar chart. */
|
|
12
|
+
key: string;
|
|
13
|
+
/** Maximum value for current key. */
|
|
14
|
+
maxValue?: number;
|
|
15
|
+
}
|
|
16
|
+
export type RadarMarkerSymbol = 'circle' | 'square';
|
|
17
|
+
export interface RadarMarkerOptions extends PointMarkerOptions {
|
|
18
|
+
symbol?: RadarMarkerSymbol;
|
|
19
|
+
}
|
|
20
|
+
export interface RadarSeries<T = MeaningfulAny> extends BaseSeries {
|
|
21
|
+
type: typeof SeriesType.Radar;
|
|
22
|
+
/** The categories for the radar chart. */
|
|
23
|
+
categories?: RadarSeriesCategory[];
|
|
24
|
+
data: RadarSeriesData<T>[];
|
|
25
|
+
/** The name of the radar series. */
|
|
26
|
+
name?: string;
|
|
27
|
+
/** The color of the radar series. */
|
|
28
|
+
color?: string;
|
|
29
|
+
/**
|
|
30
|
+
* The color of the border surrounding the radar area.
|
|
31
|
+
* @default `--g-color-base-background` from @gravity-ui/uikit.
|
|
32
|
+
*/
|
|
33
|
+
borderColor?: string;
|
|
34
|
+
/**
|
|
35
|
+
* The width of the border surrounding the radar area.
|
|
36
|
+
* @default 1
|
|
37
|
+
*/
|
|
38
|
+
borderWidth?: number;
|
|
39
|
+
/**
|
|
40
|
+
* The opacity of the radar area fill.
|
|
41
|
+
* @default 0.5
|
|
42
|
+
*/
|
|
43
|
+
fillOpacity?: number;
|
|
44
|
+
/** Individual series legend options. Has higher priority than legend options in widget data */
|
|
45
|
+
legend?: ChartLegend & {
|
|
46
|
+
symbol?: RectLegendSymbolOptions;
|
|
47
|
+
};
|
|
48
|
+
/** Options for the point markers of line in radar series */
|
|
49
|
+
marker?: RadarMarkerOptions;
|
|
50
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -8,12 +8,13 @@ import type { Halo } from './halo';
|
|
|
8
8
|
import type { LineSeries, LineSeriesData } from './line';
|
|
9
9
|
import type { PointMarkerOptions } from './marker';
|
|
10
10
|
import type { PieSeries, PieSeriesData } from './pie';
|
|
11
|
+
import type { RadarSeries, RadarSeriesData } from './radar';
|
|
11
12
|
import type { SankeySeries, SankeySeriesData } from './sankey';
|
|
12
13
|
import type { ScatterSeries, ScatterSeriesData } from './scatter';
|
|
13
14
|
import type { TreemapSeries, TreemapSeriesData } from './treemap';
|
|
14
15
|
import type { WaterfallSeries, WaterfallSeriesData } from './waterfall';
|
|
15
|
-
export type ChartSeries<T = MeaningfulAny> = ScatterSeries<T> | PieSeries<T> | BarXSeries<T> | BarYSeries<T> | LineSeries<T> | AreaSeries<T> | TreemapSeries<T> | WaterfallSeries<T> | SankeySeries<T>;
|
|
16
|
-
export type ChartSeriesData<T = MeaningfulAny> = ScatterSeriesData<T> | PieSeriesData<T> | BarXSeriesData<T> | BarYSeriesData<T> | LineSeriesData<T> | AreaSeriesData<T> | TreemapSeriesData<T> | WaterfallSeriesData<T> | SankeySeriesData<T>;
|
|
16
|
+
export type ChartSeries<T = MeaningfulAny> = ScatterSeries<T> | PieSeries<T> | BarXSeries<T> | BarYSeries<T> | LineSeries<T> | AreaSeries<T> | TreemapSeries<T> | WaterfallSeries<T> | SankeySeries<T> | RadarSeries<T>;
|
|
17
|
+
export type ChartSeriesData<T = MeaningfulAny> = ScatterSeriesData<T> | PieSeriesData<T> | BarXSeriesData<T> | BarYSeriesData<T> | LineSeriesData<T> | AreaSeriesData<T> | TreemapSeriesData<T> | WaterfallSeriesData<T> | SankeySeriesData<T> | RadarSeriesData<T>;
|
|
17
18
|
export interface DataLabelRendererData<T = MeaningfulAny> {
|
|
18
19
|
data: ChartSeriesData<T>;
|
|
19
20
|
}
|
|
@@ -233,4 +234,18 @@ export interface ChartSeriesOptions {
|
|
|
233
234
|
inactive?: BasicInactiveState;
|
|
234
235
|
};
|
|
235
236
|
};
|
|
237
|
+
radar?: {
|
|
238
|
+
/** Options for the series states that provide additional styling information to the series. */
|
|
239
|
+
states?: {
|
|
240
|
+
hover?: BasicHoverState & {
|
|
241
|
+
marker?: PointMarkerOptions & {
|
|
242
|
+
/** Options for the halo appearing around the hovered point */
|
|
243
|
+
halo?: Halo;
|
|
244
|
+
};
|
|
245
|
+
};
|
|
246
|
+
inactive?: BasicInactiveState;
|
|
247
|
+
};
|
|
248
|
+
/** Options for the point markers of radar series */
|
|
249
|
+
marker?: PointMarkerOptions;
|
|
250
|
+
};
|
|
236
251
|
}
|
|
@@ -5,6 +5,7 @@ import type { BarXSeries, BarXSeriesData } from './bar-x';
|
|
|
5
5
|
import type { BarYSeries, BarYSeriesData } from './bar-y';
|
|
6
6
|
import type { LineSeries, LineSeriesData } from './line';
|
|
7
7
|
import type { PieSeries, PieSeriesData } from './pie';
|
|
8
|
+
import type { RadarSeries, RadarSeriesCategory, RadarSeriesData } from './radar';
|
|
8
9
|
import type { SankeySeries, SankeySeriesData } from './sankey';
|
|
9
10
|
import type { ScatterSeries, ScatterSeriesData } from './scatter';
|
|
10
11
|
import type { TreemapSeries, TreemapSeriesData } from './treemap';
|
|
@@ -62,7 +63,13 @@ export interface TooltipDataChunkWaterfall<T = MeaningfulAny> {
|
|
|
62
63
|
data: WaterfallSeriesData<T>;
|
|
63
64
|
series: WaterfallSeries<T>;
|
|
64
65
|
}
|
|
65
|
-
export
|
|
66
|
+
export interface TooltipDataChunkRadar<T = MeaningfulAny> {
|
|
67
|
+
data: RadarSeriesData<T>;
|
|
68
|
+
series: RadarSeries<T>;
|
|
69
|
+
category?: RadarSeriesCategory;
|
|
70
|
+
closest: boolean;
|
|
71
|
+
}
|
|
72
|
+
export type TooltipDataChunk<T = MeaningfulAny> = (TooltipDataChunkBarX<T> | TooltipDataChunkBarY<T> | TooltipDataChunkPie<T> | TooltipDataChunkScatter<T> | TooltipDataChunkLine<T> | TooltipDataChunkArea<T> | TooltipDataChunkTreemap<T> | TooltipDataChunkSankey<T> | TooltipDataChunkWaterfall<T> | TooltipDataChunkRadar<T>) & {
|
|
66
73
|
closest?: boolean;
|
|
67
74
|
};
|
|
68
75
|
export interface ChartTooltipRendererArgs<T = MeaningfulAny> {
|
|
@@ -26,6 +26,7 @@ export * from './chart/halo';
|
|
|
26
26
|
export * from './chart/treemap';
|
|
27
27
|
export * from './chart/waterfall';
|
|
28
28
|
export * from './chart/sankey';
|
|
29
|
+
export * from './chart/radar';
|
|
29
30
|
export interface ChartData<T = MeaningfulAny> {
|
|
30
31
|
/**
|
|
31
32
|
* General options for the chart.
|
package/dist/esm/types/index.js
CHANGED
|
@@ -32,13 +32,16 @@ function getClosestPointsByXValue(x, y, points) {
|
|
|
32
32
|
}));
|
|
33
33
|
}
|
|
34
34
|
function getSeriesType(shapeData) {
|
|
35
|
-
return get(shapeData, 'series.type') ||
|
|
35
|
+
return (get(shapeData, 'series.type') ||
|
|
36
|
+
get(shapeData, 'point.series.type') ||
|
|
37
|
+
get(shapeData, 'type'));
|
|
36
38
|
}
|
|
37
39
|
export function getClosestPoints(args) {
|
|
38
40
|
const { position, shapesData, boundsHeight, boundsWidth } = args;
|
|
39
41
|
const [pointerX, pointerY] = position;
|
|
40
42
|
const result = [];
|
|
41
43
|
const groups = groupBy(shapesData, getSeriesType);
|
|
44
|
+
// eslint-disable-next-line complexity
|
|
42
45
|
Object.entries(groups).forEach(([seriesType, list]) => {
|
|
43
46
|
var _a, _b;
|
|
44
47
|
switch (seriesType) {
|
|
@@ -140,7 +143,7 @@ export function getClosestPoints(args) {
|
|
|
140
143
|
const y = pointerY - center[1];
|
|
141
144
|
let angle = Math.atan2(y, x) + 0.5 * Math.PI;
|
|
142
145
|
angle = angle < 0 ? Math.PI * 2 + angle : angle;
|
|
143
|
-
const polarRadius =
|
|
146
|
+
const polarRadius = getRadius({ center, pointer: [pointerX, pointerY] });
|
|
144
147
|
return (angle >= p.startAngle && angle <= p.endAngle && polarRadius < p.data.radius);
|
|
145
148
|
});
|
|
146
149
|
if (closestPoint) {
|
|
@@ -188,6 +191,33 @@ export function getClosestPoints(args) {
|
|
|
188
191
|
}
|
|
189
192
|
break;
|
|
190
193
|
}
|
|
194
|
+
case 'radar': {
|
|
195
|
+
const [radarData] = list;
|
|
196
|
+
const radius = getRadius({ center: radarData.center, pointer: [pointerX, pointerY] });
|
|
197
|
+
if (radius <= radarData.radius) {
|
|
198
|
+
const radarShapes = radarData.shapes.filter((shape) => isInsidePath({
|
|
199
|
+
path: shape.path,
|
|
200
|
+
point: [pointerX, pointerY],
|
|
201
|
+
width: boundsWidth,
|
|
202
|
+
height: boundsHeight,
|
|
203
|
+
strokeWidth: shape.borderWidth,
|
|
204
|
+
}));
|
|
205
|
+
const points = radarShapes.map((shape) => shape.points).flat();
|
|
206
|
+
const delaunayX = Delaunay.from(points, (d) => d.position[0], (d) => d.position[1]);
|
|
207
|
+
const closestPoint = points[delaunayX.find(pointerX, pointerY)];
|
|
208
|
+
if (closestPoint) {
|
|
209
|
+
radarData.shapes.forEach((shape) => {
|
|
210
|
+
result.push({
|
|
211
|
+
data: shape.points[closestPoint.index].data,
|
|
212
|
+
series: shape.series,
|
|
213
|
+
category: shape.series.categories[closestPoint.index],
|
|
214
|
+
closest: shape.series === closestPoint.series,
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
191
221
|
}
|
|
192
222
|
});
|
|
193
223
|
return result;
|
|
@@ -206,3 +236,10 @@ function isInsidePath(args) {
|
|
|
206
236
|
}
|
|
207
237
|
return null;
|
|
208
238
|
}
|
|
239
|
+
function getRadius(args) {
|
|
240
|
+
const { pointer, center } = args;
|
|
241
|
+
const x = pointer[0] - center[0];
|
|
242
|
+
const y = pointer[1] - center[1];
|
|
243
|
+
const polarRadius = Math.sqrt(x * x + y * y);
|
|
244
|
+
return polarRadius;
|
|
245
|
+
}
|
|
@@ -15,7 +15,7 @@ export * from './legend';
|
|
|
15
15
|
export * from './symbol';
|
|
16
16
|
export * from './series';
|
|
17
17
|
export * from './color';
|
|
18
|
-
const CHARTS_WITHOUT_AXIS = ['pie', 'treemap', 'sankey'];
|
|
18
|
+
const CHARTS_WITHOUT_AXIS = ['pie', 'treemap', 'sankey', 'radar'];
|
|
19
19
|
export const CHART_SERIES_WITH_VOLUME_ON_Y_AXIS = [
|
|
20
20
|
'bar-x',
|
|
21
21
|
'area',
|