@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.
Files changed (53) hide show
  1. package/dist/cjs/components/Tooltip/DefaultContent.js +18 -3
  2. package/dist/cjs/constants/defaults/series-options.js +12 -0
  3. package/dist/cjs/constants/index.d.ts +1 -0
  4. package/dist/cjs/constants/index.js +1 -0
  5. package/dist/cjs/hooks/useSeries/prepare-radar.d.ts +16 -0
  6. package/dist/cjs/hooks/useSeries/prepare-radar.js +63 -0
  7. package/dist/cjs/hooks/useSeries/prepareSeries.js +8 -0
  8. package/dist/cjs/hooks/useSeries/types.d.ts +35 -2
  9. package/dist/cjs/hooks/useShapes/index.d.ts +2 -1
  10. package/dist/cjs/hooks/useShapes/index.js +12 -0
  11. package/dist/cjs/hooks/useShapes/marker.d.ts +3 -2
  12. package/dist/cjs/hooks/useShapes/radar/index.d.ts +12 -0
  13. package/dist/cjs/hooks/useShapes/radar/index.js +136 -0
  14. package/dist/cjs/hooks/useShapes/radar/prepare-data.d.ts +9 -0
  15. package/dist/cjs/hooks/useShapes/radar/prepare-data.js +155 -0
  16. package/dist/cjs/hooks/useShapes/radar/types.d.ts +58 -0
  17. package/dist/cjs/hooks/useShapes/radar/types.js +1 -0
  18. package/dist/cjs/hooks/useShapes/styles.css +4 -0
  19. package/dist/cjs/types/chart/radar.d.ts +50 -0
  20. package/dist/cjs/types/chart/radar.js +1 -0
  21. package/dist/cjs/types/chart/series.d.ts +17 -2
  22. package/dist/cjs/types/chart/tooltip.d.ts +8 -1
  23. package/dist/cjs/types/index.d.ts +1 -0
  24. package/dist/cjs/types/index.js +1 -0
  25. package/dist/cjs/utils/chart/get-closest-data.js +39 -2
  26. package/dist/cjs/utils/chart/index.js +1 -1
  27. package/dist/esm/components/Tooltip/DefaultContent.js +18 -3
  28. package/dist/esm/constants/defaults/series-options.js +12 -0
  29. package/dist/esm/constants/index.d.ts +1 -0
  30. package/dist/esm/constants/index.js +1 -0
  31. package/dist/esm/hooks/useSeries/prepare-radar.d.ts +16 -0
  32. package/dist/esm/hooks/useSeries/prepare-radar.js +63 -0
  33. package/dist/esm/hooks/useSeries/prepareSeries.js +8 -0
  34. package/dist/esm/hooks/useSeries/types.d.ts +35 -2
  35. package/dist/esm/hooks/useShapes/index.d.ts +2 -1
  36. package/dist/esm/hooks/useShapes/index.js +12 -0
  37. package/dist/esm/hooks/useShapes/marker.d.ts +3 -2
  38. package/dist/esm/hooks/useShapes/radar/index.d.ts +12 -0
  39. package/dist/esm/hooks/useShapes/radar/index.js +136 -0
  40. package/dist/esm/hooks/useShapes/radar/prepare-data.d.ts +9 -0
  41. package/dist/esm/hooks/useShapes/radar/prepare-data.js +155 -0
  42. package/dist/esm/hooks/useShapes/radar/types.d.ts +58 -0
  43. package/dist/esm/hooks/useShapes/radar/types.js +1 -0
  44. package/dist/esm/hooks/useShapes/styles.css +4 -0
  45. package/dist/esm/types/chart/radar.d.ts +50 -0
  46. package/dist/esm/types/chart/radar.js +1 -0
  47. package/dist/esm/types/chart/series.d.ts +17 -2
  48. package/dist/esm/types/chart/tooltip.d.ts +8 -1
  49. package/dist/esm/types/index.d.ts +1 -0
  50. package/dist/esm/types/index.js +1 -0
  51. package/dist/esm/utils/chart/get-closest-data.js +39 -2
  52. package/dist/esm/utils/chart/index.js +1 -1
  53. 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 {};
@@ -12,6 +12,10 @@
12
12
  alignment-baseline: before-edge;
13
13
  }
14
14
 
15
+ .gcharts-radar__label {
16
+ alignment-baseline: before-edge;
17
+ }
18
+
15
19
  .gcharts-bar-x__label {
16
20
  user-select: none;
17
21
  fill: var(--g-color-text-complementary);
@@ -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 type TooltipDataChunk<T = MeaningfulAny> = (TooltipDataChunkBarX<T> | TooltipDataChunkBarY<T> | TooltipDataChunkPie<T> | TooltipDataChunkScatter<T> | TooltipDataChunkLine<T> | TooltipDataChunkArea<T> | TooltipDataChunkTreemap<T> | TooltipDataChunkSankey<T> | TooltipDataChunkWaterfall<T>) & {
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.
@@ -18,3 +18,4 @@ export * from './chart/halo';
18
18
  export * from './chart/treemap';
19
19
  export * from './chart/waterfall';
20
20
  export * from './chart/sankey';
21
+ export * from './chart/radar';
@@ -32,13 +32,16 @@ function getClosestPointsByXValue(x, y, points) {
32
32
  }));
33
33
  }
34
34
  function getSeriesType(shapeData) {
35
- return get(shapeData, 'series.type') || get(shapeData, 'point.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 = Math.sqrt(x * x + y * y);
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',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gravity-ui/charts",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "React component used to render charts",
5
5
  "license": "MIT",
6
6
  "main": "dist/cjs/index.js",