@gravity-ui/chartkit 4.12.0 → 4.14.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 (58) hide show
  1. package/build/i18n/keysets/en.json +6 -1
  2. package/build/i18n/keysets/ru.json +6 -1
  3. package/build/libs/chartkit-error/chartkit-error.d.ts +1 -0
  4. package/build/libs/chartkit-error/chartkit-error.js +1 -0
  5. package/build/plugins/d3/examples/area/Basic.d.ts +2 -0
  6. package/build/plugins/d3/examples/area/Basic.js +35 -0
  7. package/build/plugins/d3/examples/area/StackedArea.d.ts +2 -0
  8. package/build/plugins/d3/examples/area/StackedArea.js +48 -0
  9. package/build/plugins/d3/examples/bar-x/Basic.js +11 -5
  10. package/build/plugins/d3/examples/line/LineWithMarkers.d.ts +2 -0
  11. package/build/plugins/d3/examples/line/LineWithMarkers.js +67 -0
  12. package/build/plugins/d3/renderer/D3Widget.js +27 -23
  13. package/build/plugins/d3/renderer/components/Tooltip/DefaultContent.js +1 -0
  14. package/build/plugins/d3/renderer/components/Tooltip/TooltipTriggerArea.js +1 -1
  15. package/build/plugins/d3/renderer/constants/defaults/axis.d.ts +2 -0
  16. package/build/plugins/d3/renderer/constants/defaults/axis.js +1 -0
  17. package/build/plugins/d3/renderer/constants/defaults/series-options.js +16 -4
  18. package/build/plugins/d3/renderer/hooks/useAxisScales/index.js +3 -2
  19. package/build/plugins/d3/renderer/hooks/useChartOptions/y-axis.js +2 -1
  20. package/build/plugins/d3/renderer/hooks/useSeries/constants.d.ts +3 -1
  21. package/build/plugins/d3/renderer/hooks/useSeries/constants.js +5 -0
  22. package/build/plugins/d3/renderer/hooks/useSeries/prepare-area.d.ts +19 -0
  23. package/build/plugins/d3/renderer/hooks/useSeries/prepare-area.js +66 -0
  24. package/build/plugins/d3/renderer/hooks/useSeries/prepare-bar-x.js +2 -7
  25. package/build/plugins/d3/renderer/hooks/useSeries/prepare-bar-y.js +2 -7
  26. package/build/plugins/d3/renderer/hooks/useSeries/prepare-line-series.d.ts +9 -2
  27. package/build/plugins/d3/renderer/hooks/useSeries/prepare-line-series.js +30 -2
  28. package/build/plugins/d3/renderer/hooks/useSeries/prepareSeries.js +9 -0
  29. package/build/plugins/d3/renderer/hooks/useSeries/types.d.ts +61 -2
  30. package/build/plugins/d3/renderer/hooks/useSeries/utils.d.ts +2 -1
  31. package/build/plugins/d3/renderer/hooks/useSeries/utils.js +10 -0
  32. package/build/plugins/d3/renderer/hooks/useShapes/area/index.d.ts +11 -0
  33. package/build/plugins/d3/renderer/hooks/useShapes/area/index.js +194 -0
  34. package/build/plugins/d3/renderer/hooks/useShapes/area/prepare-data.d.ts +11 -0
  35. package/build/plugins/d3/renderer/hooks/useShapes/area/prepare-data.js +114 -0
  36. package/build/plugins/d3/renderer/hooks/useShapes/area/types.d.ts +27 -0
  37. package/build/plugins/d3/renderer/hooks/useShapes/area/types.js +1 -0
  38. package/build/plugins/d3/renderer/hooks/useShapes/index.d.ts +2 -1
  39. package/build/plugins/d3/renderer/hooks/useShapes/index.js +16 -0
  40. package/build/plugins/d3/renderer/hooks/useShapes/line/index.js +91 -3
  41. package/build/plugins/d3/renderer/hooks/useShapes/line/prepare-data.js +11 -0
  42. package/build/plugins/d3/renderer/hooks/useShapes/line/types.d.ts +7 -0
  43. package/build/plugins/d3/renderer/utils/index.d.ts +1 -1
  44. package/build/plugins/d3/renderer/utils/index.js +16 -9
  45. package/build/plugins/d3/renderer/validation/__mocks__/index.d.ts +3 -0
  46. package/build/plugins/d3/renderer/validation/__mocks__/index.js +44 -0
  47. package/build/plugins/d3/renderer/validation/index.d.ts +2 -0
  48. package/build/plugins/d3/renderer/validation/index.js +145 -0
  49. package/build/types/widget-data/area.d.ts +57 -0
  50. package/build/types/widget-data/area.js +1 -0
  51. package/build/types/widget-data/index.d.ts +1 -0
  52. package/build/types/widget-data/index.js +1 -0
  53. package/build/types/widget-data/line.d.ts +9 -2
  54. package/build/types/widget-data/marker.d.ts +18 -0
  55. package/build/types/widget-data/marker.js +1 -0
  56. package/build/types/widget-data/series.d.ts +32 -4
  57. package/build/types/widget-data/tooltip.d.ts +10 -1
  58. package/package.json +2 -2
@@ -1,8 +1,16 @@
1
1
  import get from 'lodash/get';
2
- import { DEFAULT_DATALABELS_PADDING, DEFAULT_DATALABELS_STYLE, DEFAULT_LEGEND_SYMBOL_PADDING, } from './constants';
2
+ import merge from 'lodash/merge';
3
+ import { DEFAULT_DATALABELS_PADDING, DEFAULT_DATALABELS_STYLE, DEFAULT_HALO_OPTIONS, DEFAULT_LEGEND_SYMBOL_PADDING, } from './constants';
3
4
  import { getRandomCKId } from '../../../../../utils';
4
5
  export const DEFAULT_LEGEND_SYMBOL_SIZE = 16;
5
6
  export const DEFAULT_LINE_WIDTH = 1;
7
+ export const DEFAULT_MARKER = {
8
+ enabled: false,
9
+ symbol: 'circle',
10
+ radius: 4,
11
+ borderWidth: 0,
12
+ borderColor: '',
13
+ };
6
14
  function prepareLineLegendSymbol(series, seriesOptions) {
7
15
  var _a;
8
16
  const symbolOptions = ((_a = series.legend) === null || _a === void 0 ? void 0 : _a.symbol) || {};
@@ -14,6 +22,24 @@ function prepareLineLegendSymbol(series, seriesOptions) {
14
22
  strokeWidth: get(series, 'lineWidth', defaultLineWidth),
15
23
  };
16
24
  }
25
+ function prepareMarker(series, seriesOptions) {
26
+ var _a;
27
+ const seriesHoverState = get(seriesOptions, 'line.states.hover');
28
+ const markerNormalState = Object.assign({}, DEFAULT_MARKER, (_a = seriesOptions === null || seriesOptions === void 0 ? void 0 : seriesOptions.line) === null || _a === void 0 ? void 0 : _a.marker, series.marker);
29
+ const hoveredMarkerDefaultOptions = {
30
+ enabled: true,
31
+ radius: markerNormalState.radius,
32
+ borderWidth: 1,
33
+ borderColor: '#ffffff',
34
+ halo: DEFAULT_HALO_OPTIONS,
35
+ };
36
+ return {
37
+ states: {
38
+ normal: markerNormalState,
39
+ hover: merge(hoveredMarkerDefaultOptions, seriesHoverState === null || seriesHoverState === void 0 ? void 0 : seriesHoverState.marker),
40
+ },
41
+ };
42
+ }
17
43
  export function prepareLineSeries(args) {
18
44
  const { colorScale, series: seriesList, seriesOptions, legend } = args;
19
45
  const defaultLineWidth = get(seriesOptions, 'line.lineWidth', DEFAULT_LINE_WIDTH);
@@ -22,7 +48,7 @@ export function prepareLineSeries(args) {
22
48
  const id = getRandomCKId();
23
49
  const name = series.name || '';
24
50
  const color = series.color || colorScale(name);
25
- return {
51
+ const prepared = {
26
52
  type: series.type,
27
53
  color,
28
54
  lineWidth: get(series, 'lineWidth', defaultLineWidth),
@@ -40,6 +66,8 @@ export function prepareLineSeries(args) {
40
66
  padding: get(series, 'dataLabels.padding', DEFAULT_DATALABELS_PADDING),
41
67
  allowOverlap: get(series, 'dataLabels.allowOverlap', false),
42
68
  },
69
+ marker: prepareMarker(series, seriesOptions),
43
70
  };
71
+ return prepared;
44
72
  }, []);
45
73
  }
@@ -6,6 +6,7 @@ import { prepareBarYSeries } from './prepare-bar-y';
6
6
  import { prepareLegendSymbol } from './utils';
7
7
  import { ChartKitError } from '../../../../../libs';
8
8
  import { preparePieSeries } from './prepare-pie';
9
+ import { prepareArea } from './prepare-area';
9
10
  function prepareAxisRelatedSeries(args) {
10
11
  const { colorScale, series, legend } = args;
11
12
  const preparedSeries = cloneDeep(series);
@@ -48,6 +49,14 @@ export function prepareSeries(args) {
48
49
  colorScale,
49
50
  });
50
51
  }
52
+ case 'area': {
53
+ return prepareArea({
54
+ series: series,
55
+ seriesOptions,
56
+ legend,
57
+ colorScale,
58
+ });
59
+ }
51
60
  default: {
52
61
  throw new ChartKitError({
53
62
  message: `Series type "${type}" does not support data preparation for series that do not support the presence of axes`,
@@ -1,4 +1,4 @@
1
- import { BarXSeries, BarXSeriesData, BaseTextStyle, ChartKitWidgetLegend, PieSeries, PieSeriesData, RectLegendSymbolOptions, ScatterSeries, ScatterSeriesData, BarYSeries, BarYSeriesData, LineSeries, LineSeriesData, ConnectorShape, ConnectorCurve, PathLegendSymbolOptions } from '../../../../../types';
1
+ import { BarXSeries, BarXSeriesData, BaseTextStyle, ChartKitWidgetLegend, PieSeries, PieSeriesData, RectLegendSymbolOptions, ScatterSeries, ScatterSeriesData, BarYSeries, BarYSeriesData, LineSeries, LineSeriesData, ConnectorShape, ConnectorCurve, PathLegendSymbolOptions, AreaSeries, AreaSeriesData } from '../../../../../types';
2
2
  import type { SeriesOptionsDefaults } from '../../constants';
3
3
  export type RectLegendSymbol = {
4
4
  shape: 'rect';
@@ -104,7 +104,66 @@ export type PreparedLineSeries = {
104
104
  padding: number;
105
105
  allowOverlap: boolean;
106
106
  };
107
+ marker: {
108
+ states: {
109
+ normal: {
110
+ symbol: string;
111
+ enabled: boolean;
112
+ radius: number;
113
+ borderWidth: number;
114
+ borderColor: string;
115
+ };
116
+ hover: {
117
+ enabled: boolean;
118
+ radius: number;
119
+ borderWidth: number;
120
+ borderColor: string;
121
+ halo: {
122
+ enabled: boolean;
123
+ opacity: number;
124
+ radius: number;
125
+ };
126
+ };
127
+ };
128
+ };
129
+ } & BasePreparedSeries;
130
+ export type PreparedAreaSeries = {
131
+ type: AreaSeries['type'];
132
+ data: AreaSeriesData[];
133
+ stacking: AreaSeries['stacking'];
134
+ stackId: string;
135
+ lineWidth: number;
136
+ opacity: number;
137
+ dataLabels: {
138
+ enabled: boolean;
139
+ style: BaseTextStyle;
140
+ padding: number;
141
+ allowOverlap: boolean;
142
+ };
143
+ marker: {
144
+ states: {
145
+ normal: {
146
+ symbol: string;
147
+ enabled: boolean;
148
+ radius: number;
149
+ borderWidth: number;
150
+ borderColor: string;
151
+ };
152
+ hover: {
153
+ enabled: boolean;
154
+ radius: number;
155
+ borderWidth: number;
156
+ borderColor: string;
157
+ halo: {
158
+ enabled: boolean;
159
+ opacity: number;
160
+ radius: number;
161
+ };
162
+ };
163
+ };
164
+ };
107
165
  } & BasePreparedSeries;
108
- export type PreparedSeries = PreparedScatterSeries | PreparedBarXSeries | PreparedBarYSeries | PreparedPieSeries | PreparedLineSeries;
166
+ export type PreparedSeries = PreparedScatterSeries | PreparedBarXSeries | PreparedBarYSeries | PreparedPieSeries | PreparedLineSeries | PreparedAreaSeries;
109
167
  export type PreparedSeriesOptions = SeriesOptionsDefaults;
168
+ export type StackedSeries = BarXSeries | AreaSeries | BarYSeries;
110
169
  export {};
@@ -1,5 +1,6 @@
1
- import { PreparedLegendSymbol, PreparedSeries } from './types';
1
+ import { PreparedLegendSymbol, PreparedSeries, StackedSeries } from './types';
2
2
  import { ChartKitWidgetSeries } from '../../../../../types';
3
3
  export declare const getActiveLegendItems: (series: PreparedSeries[]) => string[];
4
4
  export declare const getAllLegendItems: (series: PreparedSeries[]) => string[];
5
5
  export declare function prepareLegendSymbol(series: ChartKitWidgetSeries): PreparedLegendSymbol;
6
+ export declare function getSeriesStackId(series: StackedSeries): string;
@@ -1,4 +1,6 @@
1
+ import memoize from 'lodash/memoize';
1
2
  import { DEFAULT_LEGEND_SYMBOL_PADDING, DEFAULT_LEGEND_SYMBOL_SIZE } from './constants';
3
+ import { getRandomCKId } from '../../../../../utils';
2
4
  export const getActiveLegendItems = (series) => {
3
5
  return series.reduce((acc, s) => {
4
6
  if (s.legend.enabled && s.visible) {
@@ -22,3 +24,11 @@ export function prepareLegendSymbol(series) {
22
24
  padding: (symbolOptions === null || symbolOptions === void 0 ? void 0 : symbolOptions.padding) || DEFAULT_LEGEND_SYMBOL_PADDING,
23
25
  };
24
26
  }
27
+ const getCommonStackId = memoize(getRandomCKId);
28
+ export function getSeriesStackId(series) {
29
+ let stackId = series.stackId;
30
+ if (!stackId) {
31
+ stackId = series.stacking === 'normal' ? getCommonStackId() : getRandomCKId();
32
+ }
33
+ return stackId;
34
+ }
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ import type { Dispatch } from 'd3';
3
+ import type { PreparedSeriesOptions } from '../../useSeries/types';
4
+ import type { PreparedAreaData } from './types';
5
+ type Args = {
6
+ dispatcher: Dispatch<object>;
7
+ preparedData: PreparedAreaData[];
8
+ seriesOptions: PreparedSeriesOptions;
9
+ };
10
+ export declare const AreaSeriesShapes: (args: Args) => React.JSX.Element;
11
+ export {};
@@ -0,0 +1,194 @@
1
+ import React from 'react';
2
+ import { color, line as lineGenerator, area as areaGenerator, select, symbol, symbolCircle, symbolSquare, } 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
+ const b = block('d3-area');
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
+ export const AreaSeriesShapes = (args) => {
42
+ const { dispatcher, preparedData, seriesOptions } = args;
43
+ const ref = React.useRef(null);
44
+ React.useEffect(() => {
45
+ var _a;
46
+ if (!ref.current) {
47
+ return () => { };
48
+ }
49
+ const svgElement = select(ref.current);
50
+ const hoverOptions = get(seriesOptions, 'area.states.hover');
51
+ const inactiveOptions = get(seriesOptions, 'area.states.inactive');
52
+ const line = lineGenerator()
53
+ .x((d) => d.x)
54
+ .y((d) => d.y);
55
+ svgElement.selectAll('*').remove();
56
+ const shapeSelection = svgElement
57
+ .selectAll('shape')
58
+ .data(preparedData)
59
+ .join('g')
60
+ .attr('class', b('series'));
61
+ shapeSelection
62
+ .append('path')
63
+ .attr('class', b('line'))
64
+ .attr('d', (d) => line(d.points))
65
+ .attr('fill', 'none')
66
+ .attr('stroke', (d) => d.color)
67
+ .attr('stroke-width', (d) => d.width)
68
+ .attr('stroke-linejoin', 'round')
69
+ .attr('stroke-linecap', 'round');
70
+ const area = areaGenerator()
71
+ .x((d) => d.x)
72
+ .y0((d) => d.y0)
73
+ .y1((d) => d.y);
74
+ shapeSelection
75
+ .append('path')
76
+ .attr('class', b('region'))
77
+ .attr('d', (d) => area(d.points))
78
+ .attr('fill', (d) => d.color)
79
+ .attr('opacity', (d) => d.opacity);
80
+ let dataLabels = preparedData.reduce((acc, d) => {
81
+ return acc.concat(d.labels);
82
+ }, []);
83
+ if (!((_a = preparedData[0]) === null || _a === void 0 ? void 0 : _a.series.dataLabels.allowOverlap)) {
84
+ dataLabels = filterOverlappingLabels(dataLabels);
85
+ }
86
+ const labelsSelection = svgElement
87
+ .selectAll('text')
88
+ .data(dataLabels)
89
+ .join('text')
90
+ .text((d) => d.text)
91
+ .attr('class', b('label'))
92
+ .attr('x', (d) => d.x)
93
+ .attr('y', (d) => d.y)
94
+ .attr('text-anchor', (d) => d.textAnchor)
95
+ .style('font-size', (d) => d.style.fontSize)
96
+ .style('font-weight', (d) => d.style.fontWeight || null)
97
+ .style('fill', (d) => d.style.fontColor || null);
98
+ const markers = preparedData.reduce((acc, d) => acc.concat(d.markers), []);
99
+ const markerSelection = svgElement
100
+ .selectAll('marker')
101
+ .data(markers)
102
+ .join('g')
103
+ .attr('class', b('marker'))
104
+ .attr('visibility', getMarkerVisibility)
105
+ .attr('transform', (d) => {
106
+ return `translate(${d.point.x},${d.point.y})`;
107
+ });
108
+ markerSelection
109
+ .append('path')
110
+ .attr('class', b('marker-halo'))
111
+ .attr('d', (d) => {
112
+ const type = d.point.series.marker.states.normal.symbol;
113
+ const radius = d.point.series.marker.states.hover.halo.radius;
114
+ return getMarkerSymbol(type, radius);
115
+ })
116
+ .attr('fill', (d) => d.point.series.color)
117
+ .attr('opacity', (d) => d.point.series.marker.states.hover.halo.opacity)
118
+ .attr('z-index', -1)
119
+ .attr('visibility', getMarkerHaloVisibility);
120
+ markerSelection
121
+ .append('path')
122
+ .attr('class', b('marker-symbol'))
123
+ .call(setMarker, 'normal')
124
+ .attr('fill', (d) => d.point.series.color);
125
+ const hoverEnabled = hoverOptions === null || hoverOptions === void 0 ? void 0 : hoverOptions.enabled;
126
+ const inactiveEnabled = inactiveOptions === null || inactiveOptions === void 0 ? void 0 : inactiveOptions.enabled;
127
+ dispatcher.on('hover-shape.area', (data) => {
128
+ var _a;
129
+ const selected = data === null || data === void 0 ? void 0 : data.find((d) => d.series.type === 'area');
130
+ const selectedDataItem = selected === null || selected === void 0 ? void 0 : selected.data;
131
+ const selectedSeriesId = (_a = selected === null || selected === void 0 ? void 0 : selected.series) === null || _a === void 0 ? void 0 : _a.id;
132
+ shapeSelection.datum((d, index, list) => {
133
+ var _a;
134
+ const elementSelection = select(list[index]);
135
+ const hovered = Boolean(hoverEnabled && d.id === selectedSeriesId);
136
+ if (d.hovered !== hovered) {
137
+ d.hovered = hovered;
138
+ let strokeColor = d.color || '';
139
+ if (d.hovered) {
140
+ strokeColor =
141
+ ((_a = color(strokeColor)) === null || _a === void 0 ? void 0 : _a.brighter(hoverOptions === null || hoverOptions === void 0 ? void 0 : hoverOptions.brightness).toString()) ||
142
+ strokeColor;
143
+ }
144
+ elementSelection.selectAll(`.${b('line')}`).attr('stroke', strokeColor);
145
+ elementSelection.selectAll(`.${b('region')}`).attr('fill', strokeColor);
146
+ }
147
+ return setActiveState({
148
+ element: list[index],
149
+ state: inactiveOptions,
150
+ active: Boolean(!inactiveEnabled || !selectedSeriesId || selectedSeriesId === d.id),
151
+ datum: d,
152
+ });
153
+ });
154
+ labelsSelection.datum((d, index, list) => {
155
+ return setActiveState({
156
+ element: list[index],
157
+ state: inactiveOptions,
158
+ active: Boolean(!inactiveEnabled || !selectedSeriesId || selectedSeriesId === d.series.id),
159
+ datum: d,
160
+ });
161
+ });
162
+ markerSelection.datum((d, index, list) => {
163
+ const elementSelection = select(list[index]);
164
+ const hovered = Boolean(hoverEnabled && d.point.data === selectedDataItem);
165
+ if (d.hovered !== hovered) {
166
+ d.hovered = hovered;
167
+ elementSelection.attr('visibility', getMarkerVisibility(d));
168
+ elementSelection
169
+ .select(`.${b('marker-halo')}`)
170
+ .attr('visibility', getMarkerHaloVisibility);
171
+ elementSelection
172
+ .select(`.${b('marker-symbol')}`)
173
+ .call(setMarker, hovered ? 'hover' : 'normal');
174
+ }
175
+ if (d.point.series.marker.states.normal.enabled) {
176
+ const isActive = Boolean(!inactiveEnabled ||
177
+ !selectedSeriesId ||
178
+ selectedSeriesId === d.point.series.id);
179
+ setActiveState({
180
+ element: list[index],
181
+ state: inactiveOptions,
182
+ active: isActive,
183
+ datum: d,
184
+ });
185
+ }
186
+ return d;
187
+ });
188
+ });
189
+ return () => {
190
+ dispatcher.on('hover-shape.area', null);
191
+ };
192
+ }, [dispatcher, preparedData, seriesOptions]);
193
+ return React.createElement("g", { ref: ref, className: b() });
194
+ };
@@ -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({