@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
@@ -28,14 +28,17 @@ const getRowData = (fieldName, data, axis) => {
28
28
  const getXRowData = (data, xAxis) => getRowData('x', data, xAxis);
29
29
  const getYRowData = (data, yAxis) => getRowData('y', data, yAxis);
30
30
  const getMeasureValue = (data, xAxis, yAxis) => {
31
- var _a, _b;
31
+ var _a, _b, _c, _d;
32
32
  if (data.every((item) => ['pie', 'treemap', 'waterfall', 'sankey'].includes(item.series.type))) {
33
33
  return null;
34
34
  }
35
+ if (data.some((item) => item.series.type === 'radar')) {
36
+ return (_b = (_a = data[0].category) === null || _a === void 0 ? void 0 : _a.key) !== null && _b !== void 0 ? _b : null;
37
+ }
35
38
  if (data.some((item) => item.series.type === 'bar-y')) {
36
- return getYRowData((_a = data[0]) === null || _a === void 0 ? void 0 : _a.data, yAxis);
39
+ return getYRowData((_c = data[0]) === null || _c === void 0 ? void 0 : _c.data, yAxis);
37
40
  }
38
- return getXRowData((_b = data[0]) === null || _b === void 0 ? void 0 : _b.data, xAxis);
41
+ return getXRowData((_d = data[0]) === null || _d === void 0 ? void 0 : _d.data, xAxis);
39
42
  };
40
43
  export const DefaultContent = ({ hovered, xAxis, yAxis }) => {
41
44
  const measureValue = getMeasureValue(hovered, xAxis, yAxis);
@@ -109,6 +112,18 @@ export const DefaultContent = ({ hovered, xAxis, yAxis }) => {
109
112
  ": ",
110
113
  value)));
111
114
  }
115
+ case 'radar': {
116
+ const radarSeries = series;
117
+ const seriesData = data;
118
+ const value = (React.createElement(React.Fragment, null,
119
+ React.createElement("span", null,
120
+ radarSeries.name || radarSeries.id,
121
+ "\u00A0"),
122
+ React.createElement("span", null, seriesData.value)));
123
+ return (React.createElement("div", { key: id, className: b('content-row') },
124
+ React.createElement("div", { className: b('color'), style: { backgroundColor: color } }),
125
+ React.createElement("div", null, closest ? React.createElement("b", null, value) : React.createElement("span", null, value))));
126
+ }
112
127
  default: {
113
128
  return null;
114
129
  }
@@ -89,6 +89,18 @@ export const seriesOptionsDefaults = {
89
89
  },
90
90
  },
91
91
  },
92
+ radar: {
93
+ states: {
94
+ hover: {
95
+ enabled: true,
96
+ brightness: 0.3,
97
+ },
98
+ inactive: {
99
+ enabled: true,
100
+ opacity: 0.5,
101
+ },
102
+ },
103
+ },
92
104
  waterfall: {
93
105
  barMaxWidth: 50,
94
106
  barPadding: 0.1,
@@ -10,6 +10,7 @@ export declare const SeriesType: {
10
10
  readonly Treemap: "treemap";
11
11
  readonly Waterfall: "waterfall";
12
12
  readonly Sankey: "sankey";
13
+ readonly Radar: "radar";
13
14
  };
14
15
  export declare enum DashStyle {
15
16
  Dash = "Dash",
@@ -10,6 +10,7 @@ export const SeriesType = {
10
10
  Treemap: 'treemap',
11
11
  Waterfall: 'waterfall',
12
12
  Sankey: 'sankey',
13
+ Radar: 'radar',
13
14
  };
14
15
  export var DashStyle;
15
16
  (function (DashStyle) {
@@ -0,0 +1,16 @@
1
+ import type { ChartSeriesOptions, RadarSeries } from '../../types';
2
+ import type { PreparedLegend, PreparedRadarSeries } from './types';
3
+ type PrepareRadarSeriesArgs = {
4
+ series: RadarSeries[];
5
+ seriesOptions?: ChartSeriesOptions;
6
+ legend: PreparedLegend;
7
+ };
8
+ export declare const DEFAULT_MARKER: {
9
+ enabled: boolean;
10
+ radius: number;
11
+ symbol: `${import("../../constants").SymbolType}`;
12
+ borderColor: string;
13
+ borderWidth: number;
14
+ };
15
+ export declare function prepareRadarSeries(args: PrepareRadarSeriesArgs): PreparedRadarSeries[];
16
+ export {};
@@ -0,0 +1,63 @@
1
+ import { scaleOrdinal } from 'd3';
2
+ import get from 'lodash/get';
3
+ import merge from 'lodash/merge';
4
+ import { DEFAULT_PALETTE } from '../../constants';
5
+ import { getUniqId } from '../../utils';
6
+ import { DEFAULT_DATALABELS_PADDING, DEFAULT_DATALABELS_STYLE, DEFAULT_HALO_OPTIONS, DEFAULT_POINT_MARKER_OPTIONS, } from './constants';
7
+ import { prepareLegendSymbol } from './utils';
8
+ export const DEFAULT_MARKER = Object.assign(Object.assign({}, DEFAULT_POINT_MARKER_OPTIONS), { enabled: true, radius: 2 });
9
+ function prepareMarker(series, seriesOptions) {
10
+ var _a;
11
+ const seriesHoverState = get(seriesOptions, 'radar.states.hover');
12
+ const markerNormalState = Object.assign({}, DEFAULT_MARKER, (_a = seriesOptions === null || seriesOptions === void 0 ? void 0 : seriesOptions.radar) === null || _a === void 0 ? void 0 : _a.marker, series.marker);
13
+ const hoveredMarkerDefaultOptions = {
14
+ enabled: true,
15
+ radius: markerNormalState.radius + 1,
16
+ borderWidth: 1,
17
+ borderColor: '#ffffff',
18
+ halo: DEFAULT_HALO_OPTIONS,
19
+ };
20
+ return {
21
+ states: {
22
+ normal: markerNormalState,
23
+ hover: merge(hoveredMarkerDefaultOptions, seriesHoverState === null || seriesHoverState === void 0 ? void 0 : seriesHoverState.marker),
24
+ },
25
+ };
26
+ }
27
+ export function prepareRadarSeries(args) {
28
+ var _a, _b;
29
+ const { series: radarSeries, seriesOptions, legend } = args;
30
+ const colorScale = scaleOrdinal(radarSeries.map((s, index) => { var _a; return (_a = s.name) !== null && _a !== void 0 ? _a : `Series ${index + 1}`; }), DEFAULT_PALETTE);
31
+ const categories = (_b = (_a = radarSeries.find((s) => s.categories)) === null || _a === void 0 ? void 0 : _a.categories) !== null && _b !== void 0 ? _b : [];
32
+ return radarSeries.map((series, index) => {
33
+ var _a, _b, _c, _d, _e;
34
+ const name = (_a = series.name) !== null && _a !== void 0 ? _a : `Series ${index + 1}`;
35
+ const color = (_b = series.color) !== null && _b !== void 0 ? _b : colorScale(name);
36
+ const preparedSeries = {
37
+ type: 'radar',
38
+ data: series.data,
39
+ categories,
40
+ id: getUniqId(),
41
+ name,
42
+ color,
43
+ visible: typeof series.visible === 'boolean' ? series.visible : true,
44
+ legend: {
45
+ enabled: get(series, 'legend.enabled', legend.enabled),
46
+ symbol: prepareLegendSymbol(series),
47
+ },
48
+ borderColor: series.borderColor || color,
49
+ borderWidth: (_c = series.borderWidth) !== null && _c !== void 0 ? _c : 1,
50
+ fillOpacity: (_d = series.fillOpacity) !== null && _d !== void 0 ? _d : 0.25,
51
+ dataLabels: {
52
+ enabled: get(series, 'dataLabels.enabled', true),
53
+ style: Object.assign({}, DEFAULT_DATALABELS_STYLE, (_e = series.dataLabels) === null || _e === void 0 ? void 0 : _e.style),
54
+ padding: get(series, 'dataLabels.padding', DEFAULT_DATALABELS_PADDING),
55
+ allowOverlap: get(series, 'dataLabels.allowOverlap', false),
56
+ html: get(series, 'dataLabels.html', false),
57
+ },
58
+ cursor: get(series, 'cursor', null),
59
+ marker: prepareMarker(series, seriesOptions),
60
+ };
61
+ return preparedSeries;
62
+ });
63
+ }
@@ -4,6 +4,7 @@ import { prepareBarXSeries } from './prepare-bar-x';
4
4
  import { prepareBarYSeries } from './prepare-bar-y';
5
5
  import { prepareLineSeries } from './prepare-line';
6
6
  import { preparePieSeries } from './prepare-pie';
7
+ import { prepareRadarSeries } from './prepare-radar';
7
8
  import { prepareSankeySeries } from './prepare-sankey';
8
9
  import { prepareScatterSeries } from './prepare-scatter';
9
10
  import { prepareTreemap } from './prepare-treemap';
@@ -75,6 +76,13 @@ export function prepareSeries(args) {
75
76
  legend,
76
77
  });
77
78
  }
79
+ case 'radar': {
80
+ return prepareRadarSeries({
81
+ series: series,
82
+ seriesOptions,
83
+ legend,
84
+ });
85
+ }
78
86
  default: {
79
87
  throw new ChartError({
80
88
  message: `Series type "${type}" does not support data preparation for series that do not support the presence of axes`,
@@ -1,5 +1,5 @@
1
1
  import type { DashStyle, LayoutAlgorithm, LineCap, SeriesOptionsDefaults, SymbolType } from '../../constants';
2
- import type { AreaSeries, AreaSeriesData, BarXSeries, BarXSeriesData, BarYSeries, BarYSeriesData, BaseTextStyle, ChartLegend, ConnectorCurve, ConnectorShape, LineSeries, LineSeriesData, PathLegendSymbolOptions, PieSeries, PieSeriesData, RectLegendSymbolOptions, SankeySeries, SankeySeriesData, ScatterSeries, ScatterSeriesData, SymbolLegendSymbolOptions, TreemapSeries, TreemapSeriesData, WaterfallSeries, WaterfallSeriesData } from '../../types';
2
+ import type { AreaSeries, AreaSeriesData, BarXSeries, BarXSeriesData, BarYSeries, BarYSeriesData, BaseTextStyle, ChartLegend, ConnectorCurve, ConnectorShape, LineSeries, LineSeriesData, PathLegendSymbolOptions, PieSeries, PieSeriesData, RadarSeries, RadarSeriesCategory, RadarSeriesData, RectLegendSymbolOptions, SankeySeries, SankeySeriesData, ScatterSeries, ScatterSeriesData, SymbolLegendSymbolOptions, TreemapSeries, TreemapSeriesData, WaterfallSeries, WaterfallSeriesData } from '../../types';
3
3
  export type RectLegendSymbol = {
4
4
  shape: 'rect';
5
5
  } & Required<RectLegendSymbolOptions>;
@@ -258,7 +258,40 @@ export type PreparedSankeySeries = {
258
258
  style: BaseTextStyle;
259
259
  };
260
260
  } & BasePreparedSeries & Omit<SankeySeries, keyof BasePreparedSeries>;
261
- export type PreparedSeries = PreparedScatterSeries | PreparedBarXSeries | PreparedBarYSeries | PreparedPieSeries | PreparedLineSeries | PreparedAreaSeries | PreparedTreemapSeries | PreparedWaterfallSeries | PreparedSankeySeries;
261
+ export type PreparedRadarSeries = {
262
+ type: RadarSeries['type'];
263
+ data: RadarSeriesData[];
264
+ categories: RadarSeriesCategory[];
265
+ borderColor: string;
266
+ borderWidth: number;
267
+ fillOpacity: number;
268
+ dataLabels: {
269
+ enabled: boolean;
270
+ style: BaseTextStyle;
271
+ padding: number;
272
+ allowOverlap: boolean;
273
+ html: boolean;
274
+ };
275
+ marker: {
276
+ states: {
277
+ normal: {
278
+ symbol: `${SymbolType}`;
279
+ enabled: boolean;
280
+ radius: number;
281
+ borderWidth: number;
282
+ borderColor: string;
283
+ };
284
+ hover: {
285
+ enabled: boolean;
286
+ radius: number;
287
+ borderWidth: number;
288
+ borderColor: string;
289
+ halo: PreparedHaloOptions;
290
+ };
291
+ };
292
+ };
293
+ } & BasePreparedSeries;
294
+ export type PreparedSeries = PreparedScatterSeries | PreparedBarXSeries | PreparedBarYSeries | PreparedPieSeries | PreparedLineSeries | PreparedAreaSeries | PreparedTreemapSeries | PreparedWaterfallSeries | PreparedSankeySeries | PreparedRadarSeries;
262
295
  export type PreparedSeriesOptions = SeriesOptionsDefaults;
263
296
  export type StackedSeries = BarXSeries | AreaSeries | BarYSeries;
264
297
  export {};
@@ -8,13 +8,14 @@ import type { PreparedBarXData } from './bar-x';
8
8
  import type { PreparedBarYData } from './bar-y/types';
9
9
  import type { PreparedLineData } from './line/types';
10
10
  import type { PreparedPieData } from './pie/types';
11
+ import type { PreparedRadarData } from './radar/types';
11
12
  import type { PreparedSankeyData } from './sankey/types';
12
13
  import type { PreparedScatterData } from './scatter/types';
13
14
  export type { PreparedBarXData } from './bar-x';
14
15
  export type { PreparedScatterData } from './scatter/types';
15
16
  import type { PreparedWaterfallData } from './waterfall';
16
17
  import './styles.css';
17
- export type ShapeData = PreparedBarXData | PreparedBarYData | PreparedScatterData | PreparedLineData | PreparedPieData | PreparedAreaData | PreparedWaterfallData | PreparedSankeyData;
18
+ export type ShapeData = PreparedBarXData | PreparedBarYData | PreparedScatterData | PreparedLineData | PreparedPieData | PreparedAreaData | PreparedWaterfallData | PreparedSankeyData | PreparedRadarData;
18
19
  type Args = {
19
20
  boundsWidth: number;
20
21
  boundsHeight: number;
@@ -10,6 +10,8 @@ import { LineSeriesShapes } from './line';
10
10
  import { prepareLineData } from './line/prepare-data';
11
11
  import { PieSeriesShapes } from './pie';
12
12
  import { preparePieData } from './pie/prepare-data';
13
+ import { RadarSeriesShapes } from './radar';
14
+ import { prepareRadarData } from './radar/prepare-data';
13
15
  import { SankeySeriesShape } from './sankey';
14
16
  import { prepareSankeyData } from './sankey/prepare-data';
15
17
  import { ScatterSeriesShape, prepareScatterData } from './scatter';
@@ -149,6 +151,16 @@ export const useShapes = (args) => {
149
151
  shapesData.push(preparedData);
150
152
  break;
151
153
  }
154
+ case 'radar': {
155
+ const preparedData = prepareRadarData({
156
+ series: chartSeries,
157
+ boundsWidth,
158
+ boundsHeight,
159
+ });
160
+ acc.push(React.createElement(RadarSeriesShapes, { key: "radar", dispatcher: dispatcher, series: preparedData, seriesOptions: seriesOptions, htmlLayout: htmlLayout }));
161
+ shapesData.push(...preparedData);
162
+ break;
163
+ }
152
164
  default: {
153
165
  throw new ChartError({
154
166
  message: `The display method is not defined for a series with type "${seriesType}"`,
@@ -2,9 +2,10 @@ import type { BaseType, Selection } from 'd3';
2
2
  import { SymbolType } from '../../constants';
3
3
  import type { MarkerData as AreaMarkerData } from './area/types';
4
4
  import type { MarkerData as LineMarkerData } from './line/types';
5
+ import type { RadarMarkerData } from './radar/types';
5
6
  import type { MarkerData as ScatterMarkerData } from './scatter/types';
6
- type MarkerData = LineMarkerData | AreaMarkerData | ScatterMarkerData;
7
- export declare function renderMarker<T extends MarkerData>(selection: Selection<BaseType | SVGGElement, T, SVGGElement, unknown>): Selection<BaseType | SVGGElement, T, SVGGElement, unknown>;
7
+ type MarkerData = LineMarkerData | AreaMarkerData | ScatterMarkerData | RadarMarkerData;
8
+ export declare function renderMarker<T extends MarkerData>(selection: Selection<BaseType, T, BaseType, unknown>): Selection<BaseType, T, BaseType, unknown>;
8
9
  export declare function getMarkerVisibility(d: MarkerData): "" | "hidden";
9
10
  export declare function getMarkerHaloVisibility(d: MarkerData): "" | "hidden";
10
11
  export declare function setMarker<T extends BaseType, D extends MarkerData>(selection: Selection<T, D, BaseType | null, unknown>, state: 'normal' | 'hover'): void;
@@ -0,0 +1,12 @@
1
+ import React from 'react';
2
+ import type { Dispatch } from 'd3';
3
+ import type { PreparedSeriesOptions } from '../../useSeries/types';
4
+ import type { PreparedRadarData } from './types';
5
+ type PrepareRadarSeriesArgs = {
6
+ dispatcher: Dispatch<object>;
7
+ series: PreparedRadarData[];
8
+ seriesOptions: PreparedSeriesOptions;
9
+ htmlLayout: HTMLElement | null;
10
+ };
11
+ export declare function RadarSeriesShapes(args: PrepareRadarSeriesArgs): React.JSX.Element;
12
+ export {};
@@ -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 {};