@gravity-ui/chartkit 4.13.0 → 4.15.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 (94) hide show
  1. package/build/constants/index.d.ts +1 -0
  2. package/build/constants/index.js +1 -0
  3. package/build/constants/widget-data.d.ts +27 -0
  4. package/build/constants/widget-data.js +29 -0
  5. package/build/i18n/keysets/en.json +6 -1
  6. package/build/i18n/keysets/ru.json +6 -1
  7. package/build/libs/chartkit-error/chartkit-error.d.ts +1 -0
  8. package/build/libs/chartkit-error/chartkit-error.js +1 -0
  9. package/build/plugins/d3/examples/ExampleWrapper.d.ts +7 -0
  10. package/build/plugins/d3/examples/ExampleWrapper.js +5 -0
  11. package/build/plugins/d3/examples/area/Basic.d.ts +2 -0
  12. package/build/plugins/d3/examples/area/Basic.js +34 -0
  13. package/build/plugins/d3/examples/area/StackedArea.d.ts +2 -0
  14. package/build/plugins/d3/examples/area/StackedArea.js +47 -0
  15. package/build/plugins/d3/examples/bar-x/Basic.js +19 -9
  16. package/build/plugins/d3/examples/bar-x/DataLabels.js +4 -2
  17. package/build/plugins/d3/examples/bar-x/GroupedColumns.js +4 -2
  18. package/build/plugins/d3/examples/bar-x/StackedColumns.js +4 -2
  19. package/build/plugins/d3/examples/bar-y/Basic.js +4 -2
  20. package/build/plugins/d3/examples/bar-y/GroupedColumns.js +4 -2
  21. package/build/plugins/d3/examples/bar-y/StackedColumns.js +4 -2
  22. package/build/plugins/d3/examples/combined/LineAndBarX.js +5 -3
  23. package/build/plugins/d3/examples/line/Basic.js +4 -2
  24. package/build/plugins/d3/examples/line/DataLabels.js +4 -2
  25. package/build/plugins/d3/examples/line/LineWithMarkers.js +4 -2
  26. package/build/plugins/d3/examples/line/Shapes.d.ts +2 -0
  27. package/build/plugins/d3/examples/line/Shapes.js +93 -0
  28. package/build/plugins/d3/examples/pie/Basic.js +4 -2
  29. package/build/plugins/d3/examples/pie/Donut.js +4 -2
  30. package/build/plugins/d3/examples/scatter/Basic.js +4 -2
  31. package/build/plugins/d3/renderer/D3Widget.js +27 -23
  32. package/build/plugins/d3/renderer/components/Legend.js +4 -0
  33. package/build/plugins/d3/renderer/components/Tooltip/DefaultContent.js +1 -0
  34. package/build/plugins/d3/renderer/components/Tooltip/TooltipTriggerArea.js +1 -1
  35. package/build/plugins/d3/renderer/constants/defaults/axis.d.ts +2 -0
  36. package/build/plugins/d3/renderer/constants/defaults/axis.js +1 -0
  37. package/build/plugins/d3/renderer/constants/defaults/series-options.js +16 -4
  38. package/build/plugins/d3/renderer/hooks/useAxisScales/index.js +3 -2
  39. package/build/plugins/d3/renderer/hooks/useChartOptions/y-axis.js +2 -1
  40. package/build/plugins/d3/renderer/hooks/useSeries/constants.d.ts +2 -1
  41. package/build/plugins/d3/renderer/hooks/useSeries/constants.js +5 -0
  42. package/build/plugins/d3/renderer/hooks/useSeries/prepare-area.d.ts +19 -0
  43. package/build/plugins/d3/renderer/hooks/useSeries/prepare-area.js +66 -0
  44. package/build/plugins/d3/renderer/hooks/useSeries/prepare-bar-x.js +2 -7
  45. package/build/plugins/d3/renderer/hooks/useSeries/prepare-bar-y.js +2 -7
  46. package/build/plugins/d3/renderer/hooks/useSeries/prepare-line-series.d.ts +2 -0
  47. package/build/plugins/d3/renderer/hooks/useSeries/prepare-line-series.js +13 -6
  48. package/build/plugins/d3/renderer/hooks/useSeries/prepare-pie.d.ts +2 -1
  49. package/build/plugins/d3/renderer/hooks/useSeries/prepare-pie.js +11 -1
  50. package/build/plugins/d3/renderer/hooks/useSeries/prepareSeries.js +10 -1
  51. package/build/plugins/d3/renderer/hooks/useSeries/types.d.ts +50 -7
  52. package/build/plugins/d3/renderer/hooks/useSeries/utils.d.ts +2 -1
  53. package/build/plugins/d3/renderer/hooks/useSeries/utils.js +10 -0
  54. package/build/plugins/d3/renderer/hooks/useShapes/area/index.d.ts +11 -0
  55. package/build/plugins/d3/renderer/hooks/useShapes/area/index.js +137 -0
  56. package/build/plugins/d3/renderer/hooks/useShapes/area/prepare-data.d.ts +11 -0
  57. package/build/plugins/d3/renderer/hooks/useShapes/area/prepare-data.js +114 -0
  58. package/build/plugins/d3/renderer/hooks/useShapes/area/types.d.ts +27 -0
  59. package/build/plugins/d3/renderer/hooks/useShapes/area/types.js +1 -0
  60. package/build/plugins/d3/renderer/hooks/useShapes/index.d.ts +2 -1
  61. package/build/plugins/d3/renderer/hooks/useShapes/index.js +16 -0
  62. package/build/plugins/d3/renderer/hooks/useShapes/line/index.js +9 -65
  63. package/build/plugins/d3/renderer/hooks/useShapes/line/prepare-data.js +5 -2
  64. package/build/plugins/d3/renderer/hooks/useShapes/line/types.d.ts +3 -0
  65. package/build/plugins/d3/renderer/hooks/useShapes/marker.d.ts +12 -0
  66. package/build/plugins/d3/renderer/hooks/useShapes/marker.js +70 -0
  67. package/build/plugins/d3/renderer/hooks/useShapes/pie/index.d.ts +3 -2
  68. package/build/plugins/d3/renderer/hooks/useShapes/pie/index.js +28 -0
  69. package/build/plugins/d3/renderer/hooks/useShapes/pie/prepare-data.js +9 -3
  70. package/build/plugins/d3/renderer/hooks/useShapes/pie/types.d.ts +5 -0
  71. package/build/plugins/d3/renderer/hooks/useShapes/scatter/index.js +1 -1
  72. package/build/plugins/d3/renderer/hooks/useShapes/utils.d.ts +2 -0
  73. package/build/plugins/d3/renderer/hooks/useShapes/utils.js +17 -0
  74. package/build/plugins/d3/renderer/utils/index.d.ts +1 -1
  75. package/build/plugins/d3/renderer/utils/index.js +16 -9
  76. package/build/plugins/d3/renderer/validation/__mocks__/index.d.ts +3 -0
  77. package/build/plugins/d3/renderer/validation/__mocks__/index.js +44 -0
  78. package/build/plugins/d3/renderer/validation/index.d.ts +2 -0
  79. package/build/plugins/d3/renderer/validation/index.js +139 -0
  80. package/build/plugins/highcharts/renderer/helpers/config/config.js +0 -3
  81. package/build/types/widget-data/area.d.ts +58 -0
  82. package/build/types/widget-data/area.js +1 -0
  83. package/build/types/widget-data/bar-x.d.ts +2 -1
  84. package/build/types/widget-data/bar-y.d.ts +2 -1
  85. package/build/types/widget-data/halo.d.ts +9 -0
  86. package/build/types/widget-data/halo.js +1 -0
  87. package/build/types/widget-data/index.d.ts +2 -0
  88. package/build/types/widget-data/index.js +2 -0
  89. package/build/types/widget-data/line.d.ts +9 -4
  90. package/build/types/widget-data/pie.d.ts +2 -1
  91. package/build/types/widget-data/scatter.d.ts +2 -1
  92. package/build/types/widget-data/series.d.ts +40 -9
  93. package/build/types/widget-data/tooltip.d.ts +10 -1
  94. package/package.json +7 -2
@@ -0,0 +1,137 @@
1
+ import React from 'react';
2
+ import { color, line as lineGenerator, area as areaGenerator, select } from 'd3';
3
+ import get from 'lodash/get';
4
+ import { block } from '../../../../../../utils/cn';
5
+ import { filterOverlappingLabels } from '../../../utils';
6
+ import { setActiveState } from '../utils';
7
+ import { getMarkerHaloVisibility, getMarkerVisibility, renderMarker, selectMarkerHalo, selectMarkerSymbol, setMarker, } from '../marker';
8
+ const b = block('d3-area');
9
+ export const AreaSeriesShapes = (args) => {
10
+ const { dispatcher, preparedData, seriesOptions } = args;
11
+ const ref = React.useRef(null);
12
+ React.useEffect(() => {
13
+ var _a;
14
+ if (!ref.current) {
15
+ return () => { };
16
+ }
17
+ const svgElement = select(ref.current);
18
+ const hoverOptions = get(seriesOptions, 'area.states.hover');
19
+ const inactiveOptions = get(seriesOptions, 'area.states.inactive');
20
+ const line = lineGenerator()
21
+ .x((d) => d.x)
22
+ .y((d) => d.y);
23
+ svgElement.selectAll('*').remove();
24
+ const shapeSelection = svgElement
25
+ .selectAll('shape')
26
+ .data(preparedData)
27
+ .join('g')
28
+ .attr('class', b('series'));
29
+ shapeSelection
30
+ .append('path')
31
+ .attr('class', b('line'))
32
+ .attr('d', (d) => line(d.points))
33
+ .attr('fill', 'none')
34
+ .attr('stroke', (d) => d.color)
35
+ .attr('stroke-width', (d) => d.width)
36
+ .attr('stroke-linejoin', 'round')
37
+ .attr('stroke-linecap', 'round');
38
+ const area = areaGenerator()
39
+ .x((d) => d.x)
40
+ .y0((d) => d.y0)
41
+ .y1((d) => d.y);
42
+ shapeSelection
43
+ .append('path')
44
+ .attr('class', b('region'))
45
+ .attr('d', (d) => area(d.points))
46
+ .attr('fill', (d) => d.color)
47
+ .attr('opacity', (d) => d.opacity);
48
+ let dataLabels = preparedData.reduce((acc, d) => {
49
+ return acc.concat(d.labels);
50
+ }, []);
51
+ if (!((_a = preparedData[0]) === null || _a === void 0 ? void 0 : _a.series.dataLabels.allowOverlap)) {
52
+ dataLabels = filterOverlappingLabels(dataLabels);
53
+ }
54
+ const labelsSelection = svgElement
55
+ .selectAll('text')
56
+ .data(dataLabels)
57
+ .join('text')
58
+ .text((d) => d.text)
59
+ .attr('class', b('label'))
60
+ .attr('x', (d) => d.x)
61
+ .attr('y', (d) => d.y)
62
+ .attr('text-anchor', (d) => d.textAnchor)
63
+ .style('font-size', (d) => d.style.fontSize)
64
+ .style('font-weight', (d) => d.style.fontWeight || null)
65
+ .style('fill', (d) => d.style.fontColor || null);
66
+ const markers = preparedData.reduce((acc, d) => acc.concat(d.markers), []);
67
+ const markerSelection = svgElement
68
+ .selectAll('marker')
69
+ .data(markers)
70
+ .join('g')
71
+ .call(renderMarker);
72
+ const hoverEnabled = hoverOptions === null || hoverOptions === void 0 ? void 0 : hoverOptions.enabled;
73
+ const inactiveEnabled = inactiveOptions === null || inactiveOptions === void 0 ? void 0 : inactiveOptions.enabled;
74
+ dispatcher.on('hover-shape.area', (data) => {
75
+ var _a;
76
+ const selected = data === null || data === void 0 ? void 0 : data.find((d) => d.series.type === 'area');
77
+ const selectedDataItem = selected === null || selected === void 0 ? void 0 : selected.data;
78
+ const selectedSeriesId = (_a = selected === null || selected === void 0 ? void 0 : selected.series) === null || _a === void 0 ? void 0 : _a.id;
79
+ shapeSelection.datum((d, index, list) => {
80
+ var _a;
81
+ const elementSelection = select(list[index]);
82
+ const hovered = Boolean(hoverEnabled && d.id === selectedSeriesId);
83
+ if (d.hovered !== hovered) {
84
+ d.hovered = hovered;
85
+ let strokeColor = d.color || '';
86
+ if (d.hovered) {
87
+ strokeColor =
88
+ ((_a = color(strokeColor)) === null || _a === void 0 ? void 0 : _a.brighter(hoverOptions === null || hoverOptions === void 0 ? void 0 : hoverOptions.brightness).toString()) ||
89
+ strokeColor;
90
+ }
91
+ elementSelection.selectAll(`.${b('line')}`).attr('stroke', strokeColor);
92
+ elementSelection.selectAll(`.${b('region')}`).attr('fill', strokeColor);
93
+ }
94
+ return setActiveState({
95
+ element: list[index],
96
+ state: inactiveOptions,
97
+ active: Boolean(!inactiveEnabled || !selectedSeriesId || selectedSeriesId === d.id),
98
+ datum: d,
99
+ });
100
+ });
101
+ labelsSelection.datum((d, index, list) => {
102
+ return setActiveState({
103
+ element: list[index],
104
+ state: inactiveOptions,
105
+ active: Boolean(!inactiveEnabled || !selectedSeriesId || selectedSeriesId === d.series.id),
106
+ datum: d,
107
+ });
108
+ });
109
+ markerSelection.datum((d, index, list) => {
110
+ const elementSelection = select(list[index]);
111
+ const hovered = Boolean(hoverEnabled && d.point.data === selectedDataItem);
112
+ if (d.hovered !== hovered) {
113
+ d.hovered = hovered;
114
+ elementSelection.attr('visibility', getMarkerVisibility(d));
115
+ selectMarkerHalo(elementSelection).attr('visibility', getMarkerHaloVisibility);
116
+ selectMarkerSymbol(elementSelection).call(setMarker, hovered ? 'hover' : 'normal');
117
+ }
118
+ if (d.point.series.marker.states.normal.enabled) {
119
+ const isActive = Boolean(!inactiveEnabled ||
120
+ !selectedSeriesId ||
121
+ selectedSeriesId === d.point.series.id);
122
+ setActiveState({
123
+ element: list[index],
124
+ state: inactiveOptions,
125
+ active: isActive,
126
+ datum: d,
127
+ });
128
+ }
129
+ return d;
130
+ });
131
+ });
132
+ return () => {
133
+ dispatcher.on('hover-shape.area', null);
134
+ };
135
+ }, [dispatcher, preparedData, seriesOptions]);
136
+ return React.createElement("g", { ref: ref, className: b() });
137
+ };
@@ -0,0 +1,11 @@
1
+ import type { PreparedAreaSeries } from '../../useSeries/types';
2
+ import type { PreparedAxis } from '../../useChartOptions/types';
3
+ import type { ChartScale } from '../../useAxisScales';
4
+ import type { PreparedAreaData } from './types';
5
+ export declare const prepareAreaData: (args: {
6
+ series: PreparedAreaSeries[];
7
+ xAxis: PreparedAxis;
8
+ xScale: ChartScale;
9
+ yAxis: PreparedAxis[];
10
+ yScale: ChartScale;
11
+ }) => PreparedAreaData[];
@@ -0,0 +1,114 @@
1
+ import { group, sort } from 'd3';
2
+ import { getXValue, getYValue } from '../utils';
3
+ import { getLabelsSize, getLeftPosition } from '../../../utils';
4
+ function getLabelData(point, series, xMax) {
5
+ const text = String(point.data.label || point.data.y);
6
+ const style = series.dataLabels.style;
7
+ const size = getLabelsSize({ labels: [text], style });
8
+ const labelData = {
9
+ text,
10
+ x: point.x,
11
+ y: point.y - series.dataLabels.padding,
12
+ style,
13
+ size: { width: size.maxWidth, height: size.maxHeight },
14
+ textAnchor: 'middle',
15
+ series: series,
16
+ active: true,
17
+ };
18
+ const left = getLeftPosition(labelData);
19
+ if (left < 0) {
20
+ labelData.x = labelData.x + Math.abs(left);
21
+ }
22
+ else {
23
+ const right = left + labelData.size.width;
24
+ if (right > xMax) {
25
+ labelData.x = labelData.x - xMax - right;
26
+ }
27
+ }
28
+ return labelData;
29
+ }
30
+ function getXValues(series, xAxis, xScale) {
31
+ const xValues = series.reduce((acc, s) => {
32
+ s.data.forEach((d) => {
33
+ const key = String(d.x);
34
+ if (!acc.has(key)) {
35
+ acc.set(key, getXValue({ point: d, xAxis, xScale }));
36
+ }
37
+ });
38
+ return acc;
39
+ }, new Map());
40
+ if (xAxis.type === 'category') {
41
+ return (xAxis.categories || []).reduce((acc, category) => {
42
+ const xValue = xValues.get(category);
43
+ if (typeof xValue === 'number') {
44
+ acc.push([category, xValue]);
45
+ }
46
+ return acc;
47
+ }, []);
48
+ }
49
+ return sort(Array.from(xValues), ([_x, xValue]) => xValue);
50
+ }
51
+ export const prepareAreaData = (args) => {
52
+ const { series, xAxis, xScale, yScale } = args;
53
+ const yAxis = args.yAxis[0];
54
+ const [_xMin, xRangeMax] = xScale.range();
55
+ const xMax = xRangeMax / (1 - xAxis.maxPadding);
56
+ const [yMin, _yMax] = yScale.range();
57
+ return Array.from(group(series, (s) => s.stackId)).reduce((result, [_stackId, seriesStack]) => {
58
+ const xValues = getXValues(seriesStack, xAxis, xScale);
59
+ const accumulatedYValues = new Map();
60
+ xValues.forEach(([key]) => {
61
+ accumulatedYValues.set(key, 0);
62
+ });
63
+ const seriesStackData = seriesStack.reduce((acc, s) => {
64
+ const seriesData = s.data.reduce((m, d) => {
65
+ return m.set(String(d.x), d);
66
+ }, new Map());
67
+ const points = xValues.reduce((pointsAcc, [x, xValue]) => {
68
+ const accumulatedYValue = accumulatedYValues.get(x) || 0;
69
+ const d = seriesData.get(x) ||
70
+ {
71
+ x,
72
+ // FIXME: think about how to break the series into separate areas(null Y values)
73
+ y: 0,
74
+ };
75
+ const yValue = getYValue({ point: d, yAxis, yScale }) - accumulatedYValue;
76
+ accumulatedYValues.set(x, yMin - yValue);
77
+ pointsAcc.push({
78
+ y0: yMin - accumulatedYValue,
79
+ x: xValue,
80
+ y: yValue,
81
+ data: d,
82
+ series: s,
83
+ });
84
+ return pointsAcc;
85
+ }, []);
86
+ let labels = [];
87
+ if (s.dataLabels.enabled) {
88
+ labels = points.map((p) => getLabelData(p, s, xMax));
89
+ }
90
+ let markers = [];
91
+ if (s.marker.states.normal.enabled || s.marker.states.hover.enabled) {
92
+ markers = points.map((p) => ({
93
+ point: p,
94
+ active: true,
95
+ hovered: false,
96
+ }));
97
+ }
98
+ acc.push({
99
+ points,
100
+ markers,
101
+ labels,
102
+ color: s.color,
103
+ opacity: s.opacity,
104
+ width: s.lineWidth,
105
+ series: s,
106
+ hovered: false,
107
+ active: true,
108
+ id: s.id,
109
+ });
110
+ return acc;
111
+ }, []);
112
+ return result.concat(seriesStackData);
113
+ }, []);
114
+ };
@@ -0,0 +1,27 @@
1
+ import { PreparedAreaSeries } from '../../useSeries/types';
2
+ import { AreaSeriesData } from '../../../../../../types';
3
+ import { LabelData } from '../../../types';
4
+ export type PointData = {
5
+ y0: number;
6
+ x: number;
7
+ y: number;
8
+ data: AreaSeriesData;
9
+ series: PreparedAreaSeries;
10
+ };
11
+ export type MarkerData = {
12
+ point: PointData;
13
+ active: boolean;
14
+ hovered: boolean;
15
+ };
16
+ export type PreparedAreaData = {
17
+ id: string;
18
+ points: PointData[];
19
+ markers: MarkerData[];
20
+ color: string;
21
+ opacity: number;
22
+ width: number;
23
+ series: PreparedAreaSeries;
24
+ hovered: boolean;
25
+ active: boolean;
26
+ labels: LabelData[];
27
+ };
@@ -10,8 +10,9 @@ import type { PreparedLineData } from './line/types';
10
10
  import type { PreparedBarYData } from './bar-y/types';
11
11
  export type { PreparedBarXData } from './bar-x';
12
12
  export type { PreparedScatterData } from './scatter';
13
+ import type { PreparedAreaData } from './area/types';
13
14
  import './styles.css';
14
- export type ShapeData = PreparedBarXData | PreparedBarYData | PreparedScatterData | PreparedLineData | PreparedPieData;
15
+ export type ShapeData = PreparedBarXData | PreparedBarYData | PreparedScatterData | PreparedLineData | PreparedPieData | PreparedAreaData;
15
16
  type Args = {
16
17
  boundsWidth: number;
17
18
  boundsHeight: number;
@@ -8,6 +8,8 @@ import { preparePieData } from './pie/prepare-data';
8
8
  import { prepareLineData } from './line/prepare-data';
9
9
  import { LineSeriesShapes } from './line';
10
10
  import { BarYSeriesShapes, prepareBarYData } from './bar-y';
11
+ import { AreaSeriesShapes } from './area';
12
+ import { prepareAreaData } from './area/prepare-data';
11
13
  import './styles.css';
12
14
  export const useShapes = (args) => {
13
15
  const { boundsWidth, boundsHeight, dispatcher, series, seriesOptions, xAxis, xScale, yAxis, yScale, svgContainer, } = args;
@@ -62,6 +64,20 @@ export const useShapes = (args) => {
62
64
  }
63
65
  break;
64
66
  }
67
+ case 'area': {
68
+ if (xScale && yScale) {
69
+ const preparedData = prepareAreaData({
70
+ series: chartSeries,
71
+ xAxis,
72
+ xScale,
73
+ yAxis,
74
+ yScale,
75
+ });
76
+ acc.push(React.createElement(AreaSeriesShapes, { key: "area", dispatcher: dispatcher, seriesOptions: seriesOptions, preparedData: preparedData }));
77
+ shapesData.push(...preparedData);
78
+ }
79
+ break;
80
+ }
65
81
  case 'scatter': {
66
82
  if (xScale && yScale) {
67
83
  const preparedData = prepareScatterData({
@@ -1,43 +1,11 @@
1
1
  import React from 'react';
2
- import { color, line as lineGenerator, select, symbol, symbolCircle, symbolSquare } from 'd3';
2
+ import { color, line as lineGenerator, select } from 'd3';
3
3
  import get from 'lodash/get';
4
4
  import { block } from '../../../../../../utils/cn';
5
5
  import { filterOverlappingLabels } from '../../../utils';
6
- import { setActiveState } from '../utils';
6
+ import { getLineDashArray, setActiveState } from '../utils';
7
+ import { getMarkerHaloVisibility, getMarkerVisibility, renderMarker, selectMarkerHalo, selectMarkerSymbol, setMarker, } from '../marker';
7
8
  const b = block('d3-line');
8
- function setMarker(selection, state) {
9
- selection
10
- .attr('d', (d) => {
11
- const radius = d.point.series.marker.states[state].radius +
12
- d.point.series.marker.states[state].borderWidth;
13
- return getMarkerSymbol(d.point.series.marker.states.normal.symbol, radius);
14
- })
15
- .attr('stroke-width', (d) => d.point.series.marker.states[state].borderWidth)
16
- .attr('stroke', (d) => d.point.series.marker.states[state].borderColor);
17
- }
18
- function getMarkerSymbol(type, radius) {
19
- switch (type) {
20
- case 'square': {
21
- const size = Math.pow(radius, 2) * Math.PI;
22
- return symbol(symbolSquare, size)();
23
- }
24
- case 'circle':
25
- default: {
26
- const size = Math.pow(radius, 2) * Math.PI;
27
- return symbol(symbolCircle, size)();
28
- }
29
- }
30
- }
31
- const getMarkerVisibility = (d) => {
32
- const markerStates = d.point.series.marker.states;
33
- const enabled = (markerStates.hover.enabled && d.hovered) || markerStates.normal.enabled;
34
- return enabled ? '' : 'hidden';
35
- };
36
- const getMarkerHaloVisibility = (d) => {
37
- const markerStates = d.point.series.marker.states;
38
- const enabled = markerStates.hover.halo.enabled && d.hovered;
39
- return enabled ? '' : 'hidden';
40
- };
41
9
  export const LineSeriesShapes = (args) => {
42
10
  const { dispatcher, preparedData, seriesOptions } = args;
43
11
  const ref = React.useRef(null);
@@ -61,8 +29,9 @@ export const LineSeriesShapes = (args) => {
61
29
  .attr('fill', 'none')
62
30
  .attr('stroke', (d) => d.color)
63
31
  .attr('stroke-width', (d) => d.width)
64
- .attr('stroke-linejoin', 'round')
65
- .attr('stroke-linecap', 'round');
32
+ .attr('stroke-linejoin', (d) => d.linecap)
33
+ .attr('stroke-linecap', (d) => d.linecap)
34
+ .attr('stroke-dasharray', (d) => getLineDashArray(d.dashStyle, d.width));
66
35
  let dataLabels = preparedData.reduce((acc, d) => {
67
36
  return acc.concat(d.labels);
68
37
  }, []);
@@ -86,28 +55,7 @@ export const LineSeriesShapes = (args) => {
86
55
  .selectAll('marker')
87
56
  .data(markers)
88
57
  .join('g')
89
- .attr('class', b('marker'))
90
- .attr('visibility', getMarkerVisibility)
91
- .attr('transform', (d) => {
92
- return `translate(${d.point.x},${d.point.y})`;
93
- });
94
- markerSelection
95
- .append('path')
96
- .attr('class', b('marker-halo'))
97
- .attr('d', (d) => {
98
- const type = d.point.series.marker.states.normal.symbol;
99
- const radius = d.point.series.marker.states.hover.halo.radius;
100
- return getMarkerSymbol(type, radius);
101
- })
102
- .attr('fill', (d) => d.point.series.color)
103
- .attr('opacity', (d) => d.point.series.marker.states.hover.halo.opacity)
104
- .attr('z-index', -1)
105
- .attr('visibility', getMarkerHaloVisibility);
106
- markerSelection
107
- .append('path')
108
- .attr('class', b('marker-symbol'))
109
- .call(setMarker, 'normal')
110
- .attr('fill', (d) => d.point.series.color);
58
+ .call(renderMarker);
111
59
  const hoverEnabled = hoverOptions === null || hoverOptions === void 0 ? void 0 : hoverOptions.enabled;
112
60
  const inactiveEnabled = inactiveOptions === null || inactiveOptions === void 0 ? void 0 : inactiveOptions.enabled;
113
61
  dispatcher.on('hover-shape.line', (data) => {
@@ -150,12 +98,8 @@ export const LineSeriesShapes = (args) => {
150
98
  if (d.hovered !== hovered) {
151
99
  d.hovered = hovered;
152
100
  elementSelection.attr('visibility', getMarkerVisibility(d));
153
- elementSelection
154
- .select(`.${b('marker-halo')}`)
155
- .attr('visibility', getMarkerHaloVisibility);
156
- elementSelection
157
- .select(`.${b('marker-symbol')}`)
158
- .call(setMarker, hovered ? 'hover' : 'normal');
101
+ selectMarkerHalo(elementSelection).attr('visibility', getMarkerHaloVisibility);
102
+ selectMarkerSymbol(elementSelection).call(setMarker, hovered ? 'hover' : 'normal');
159
103
  }
160
104
  if (d.point.series.marker.states.normal.enabled) {
161
105
  const isActive = Boolean(!inactiveEnabled ||
@@ -51,7 +51,7 @@ export const prepareLineData = (args) => {
51
51
  hovered: false,
52
52
  }));
53
53
  }
54
- acc.push({
54
+ const result = {
55
55
  points,
56
56
  markers,
57
57
  labels,
@@ -61,7 +61,10 @@ export const prepareLineData = (args) => {
61
61
  hovered: false,
62
62
  active: true,
63
63
  id: s.id,
64
- });
64
+ dashStyle: s.dashStyle,
65
+ linecap: s.linecap,
66
+ };
67
+ acc.push(result);
65
68
  return acc;
66
69
  }, []);
67
70
  };
@@ -1,6 +1,7 @@
1
1
  import { PreparedLineSeries } from '../../useSeries/types';
2
2
  import { LineSeriesData } from '../../../../../../types';
3
3
  import { LabelData } from '../../../types';
4
+ import { DashStyle, LineCap } from '../../../../../../constants';
4
5
  export type PointData = {
5
6
  x: number;
6
7
  y: number;
@@ -22,4 +23,6 @@ export type PreparedLineData = {
22
23
  hovered: boolean;
23
24
  active: boolean;
24
25
  labels: LabelData[];
26
+ dashStyle: DashStyle;
27
+ linecap: LineCap;
25
28
  };
@@ -0,0 +1,12 @@
1
+ import { BaseType, Selection } from 'd3';
2
+ import { MarkerData as LineMarkerData } from './line/types';
3
+ import { MarkerData as AreaMarkerData } from './area/types';
4
+ type MarkerData = LineMarkerData | AreaMarkerData;
5
+ export declare function renderMarker<T extends MarkerData>(selection: Selection<BaseType | SVGGElement, T, SVGGElement, unknown>): Selection<SVGGElement | BaseType, T, SVGGElement, unknown>;
6
+ export declare function getMarkerVisibility(d: MarkerData): "" | "hidden";
7
+ export declare function getMarkerHaloVisibility(d: MarkerData): "" | "hidden";
8
+ export declare function setMarker<T extends BaseType, D extends MarkerData>(selection: Selection<T, D, BaseType | null, unknown>, state: 'normal' | 'hover'): void;
9
+ export declare function getMarkerSymbol(type: string, radius: number): string | null;
10
+ export declare function selectMarkerHalo<T>(parentSelection: Selection<BaseType, T, null, undefined>): Selection<BaseType, T, null, undefined>;
11
+ export declare function selectMarkerSymbol<T>(parentSelection: Selection<BaseType, T, null, undefined>): Selection<BaseType, T, null, undefined>;
12
+ export {};
@@ -0,0 +1,70 @@
1
+ import { symbol, symbolCircle, symbolSquare } from 'd3';
2
+ import { block } from '../../../../../utils/cn';
3
+ const b = block('d3-marker');
4
+ const haloClassName = b('halo');
5
+ const symbolClassName = b('symbol');
6
+ export function renderMarker(selection) {
7
+ const markerSelection = selection
8
+ .attr('class', b('wrapper'))
9
+ .attr('visibility', getMarkerVisibility)
10
+ .attr('transform', (d) => {
11
+ return `translate(${d.point.x},${d.point.y})`;
12
+ });
13
+ markerSelection
14
+ .append('path')
15
+ .attr('class', haloClassName)
16
+ .attr('d', (d) => {
17
+ const type = d.point.series.marker.states.normal.symbol;
18
+ const radius = d.point.series.marker.states.hover.halo.size;
19
+ return getMarkerSymbol(type, radius);
20
+ })
21
+ .attr('fill', (d) => d.point.series.color)
22
+ .attr('opacity', (d) => d.point.series.marker.states.hover.halo.opacity)
23
+ .attr('z-index', -1)
24
+ .attr('visibility', getMarkerHaloVisibility);
25
+ markerSelection
26
+ .append('path')
27
+ .attr('class', symbolClassName)
28
+ .call(setMarker, 'normal')
29
+ .attr('fill', (d) => d.point.series.color);
30
+ return markerSelection;
31
+ }
32
+ export function getMarkerVisibility(d) {
33
+ const markerStates = d.point.series.marker.states;
34
+ const enabled = (markerStates.hover.enabled && d.hovered) || markerStates.normal.enabled;
35
+ return enabled ? '' : 'hidden';
36
+ }
37
+ export function getMarkerHaloVisibility(d) {
38
+ const markerStates = d.point.series.marker.states;
39
+ const enabled = markerStates.hover.halo.enabled && d.hovered;
40
+ return enabled ? '' : 'hidden';
41
+ }
42
+ export function setMarker(selection, state) {
43
+ selection
44
+ .attr('d', (d) => {
45
+ const radius = d.point.series.marker.states[state].radius +
46
+ d.point.series.marker.states[state].borderWidth;
47
+ return getMarkerSymbol(d.point.series.marker.states.normal.symbol, radius);
48
+ })
49
+ .attr('stroke-width', (d) => d.point.series.marker.states[state].borderWidth)
50
+ .attr('stroke', (d) => d.point.series.marker.states[state].borderColor);
51
+ }
52
+ export function getMarkerSymbol(type, radius) {
53
+ switch (type) {
54
+ case 'square': {
55
+ const size = Math.pow(radius, 2) * Math.PI;
56
+ return symbol(symbolSquare, size)();
57
+ }
58
+ case 'circle':
59
+ default: {
60
+ const size = Math.pow(radius, 2) * Math.PI;
61
+ return symbol(symbolCircle, size)();
62
+ }
63
+ }
64
+ }
65
+ export function selectMarkerHalo(parentSelection) {
66
+ return parentSelection.select(`.${haloClassName}`);
67
+ }
68
+ export function selectMarkerSymbol(parentSelection) {
69
+ return parentSelection.select(`.${symbolClassName}`);
70
+ }
@@ -1,12 +1,13 @@
1
1
  import React from 'react';
2
- import type { Dispatch } from 'd3';
2
+ import type { Dispatch, PieArcDatum } from 'd3';
3
3
  import { PreparedSeriesOptions } from '../../useSeries/types';
4
- import { PreparedPieData } from './types';
4
+ import { PreparedPieData, SegmentData } from './types';
5
5
  type PreparePieSeriesArgs = {
6
6
  dispatcher: Dispatch<object>;
7
7
  preparedData: PreparedPieData[];
8
8
  seriesOptions: PreparedSeriesOptions;
9
9
  svgContainer: SVGSVGElement | null;
10
10
  };
11
+ export declare function getHaloVisibility(d: PieArcDatum<SegmentData>): "" | "hidden";
11
12
  export declare function PieSeriesShapes(args: PreparePieSeriesArgs): React.JSX.Element;
12
13
  export {};
@@ -7,6 +7,10 @@ import { line as lineGenerator } from 'd3-shape';
7
7
  import { setEllipsisForOverflowTexts } from '../../../utils';
8
8
  import { getCurveFactory } from './utils';
9
9
  const b = block('d3-pie');
10
+ export function getHaloVisibility(d) {
11
+ const enabled = d.data.pie.halo.enabled && d.data.hovered;
12
+ return enabled ? '' : 'hidden';
13
+ }
10
14
  export function PieSeriesShapes(args) {
11
15
  const { dispatcher, preparedData, seriesOptions, svgContainer } = args;
12
16
  const ref = React.useRef(null);
@@ -30,6 +34,27 @@ export function PieSeriesShapes(args) {
30
34
  })
31
35
  .style('stroke', (pieData) => pieData.borderColor)
32
36
  .style('stroke-width', (pieData) => pieData.borderWidth);
37
+ shapesSelection
38
+ .selectAll('halo')
39
+ .data((pieData) => {
40
+ if (pieData.halo.enabled) {
41
+ return pieData.segments;
42
+ }
43
+ return [];
44
+ })
45
+ .join('path')
46
+ .attr('d', (d) => {
47
+ const arcGenerator = arc()
48
+ .innerRadius(d.data.pie.innerRadius)
49
+ .outerRadius(d.data.pie.radius + d.data.pie.halo.size)
50
+ .cornerRadius(d.data.pie.borderRadius);
51
+ return arcGenerator(d);
52
+ })
53
+ .attr('class', b('halo'))
54
+ .attr('fill', (d) => d.data.color)
55
+ .attr('opacity', (d) => d.data.pie.halo.opacity)
56
+ .attr('z-index', -1)
57
+ .attr('visibility', getHaloVisibility);
33
58
  shapesSelection
34
59
  .selectAll(segmentSelector)
35
60
  .data((pieData) => pieData.segments)
@@ -108,6 +133,7 @@ export function PieSeriesShapes(args) {
108
133
  const inactiveEnabled = inactiveOptions === null || inactiveOptions === void 0 ? void 0 : inactiveOptions.enabled;
109
134
  shapesSelection.datum((_d, index, list) => {
110
135
  const pieSelection = select(list[index]);
136
+ const haloSelection = pieSelection.selectAll(`.${b('halo')}`);
111
137
  pieSelection
112
138
  .selectAll(segmentSelector)
113
139
  .datum((d, i, elements) => {
@@ -122,6 +148,8 @@ export function PieSeriesShapes(args) {
122
148
  }
123
149
  return initialColor;
124
150
  });
151
+ const currentSegmentHalo = haloSelection.nodes()[i];
152
+ select(currentSegmentHalo).attr('visibility', getHaloVisibility);
125
153
  }
126
154
  setActiveState({
127
155
  element: elements[i],
@@ -15,12 +15,13 @@ const getCenter = (boundsWidth, boundsHeight, center) => {
15
15
  return [resultX, resultY];
16
16
  };
17
17
  export function preparePieData(args) {
18
- const { series: prepapredSeries, boundsWidth, boundsHeight } = args;
18
+ const { series: preparedSeries, boundsWidth, boundsHeight } = args;
19
19
  const maxRadius = Math.min(boundsWidth, boundsHeight) / 2;
20
- const groupedPieSeries = group(prepapredSeries, (pieSeries) => pieSeries.stackId);
20
+ const groupedPieSeries = group(preparedSeries, (pieSeries) => pieSeries.stackId);
21
21
  return Array.from(groupedPieSeries).map(([stackId, items]) => {
22
22
  var _a, _b, _c;
23
- const { center, borderWidth, borderColor, borderRadius, radius: seriesRadius, innerRadius: seriesInnerRadius, dataLabels, } = items[0];
23
+ const series = items[0];
24
+ const { center, borderWidth, borderColor, borderRadius, radius: seriesRadius, innerRadius: seriesInnerRadius, dataLabels, } = series;
24
25
  const radius = (_a = calculateNumericProperty({ value: seriesRadius, base: maxRadius })) !== null && _a !== void 0 ? _a : maxRadius;
25
26
  const data = {
26
27
  id: stackId,
@@ -34,6 +35,11 @@ export function preparePieData(args) {
34
35
  borderRadius,
35
36
  series: items[0],
36
37
  connectorCurve: dataLabels.connectorCurve,
38
+ halo: {
39
+ enabled: series.states.hover.halo.enabled,
40
+ opacity: series.states.hover.halo.opacity,
41
+ size: series.states.hover.halo.size,
42
+ },
37
43
  };
38
44
  const segments = items.map((item) => {
39
45
  return {