@gravity-ui/chartkit 4.1.0 → 4.2.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.
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import type { TooltipHoveredData } from '../../../../../types/widget-data';
2
+ import type { TooltipHoveredData } from '../../../../../types';
3
3
  import type { PreparedAxis } from '../../hooks';
4
4
  type Props = {
5
5
  hovered: TooltipHoveredData;
@@ -1,11 +1,34 @@
1
1
  import React from 'react';
2
+ import get from 'lodash/get';
3
+ import { dateTime } from '@gravity-ui/date-utils';
4
+ import { formatNumber } from '../../../../shared';
5
+ import { getDataCategoryValue } from '../../utils';
6
+ const DEFAULT_DATE_FORMAT = 'DD.MM.YY';
7
+ const getRowData = (fieldName, axis, data) => {
8
+ const categories = get(axis, 'categories', []);
9
+ switch (axis.type) {
10
+ case 'category': {
11
+ return getDataCategoryValue({ axisDirection: fieldName, categories, data });
12
+ }
13
+ case 'datetime': {
14
+ const value = get(data, fieldName);
15
+ return dateTime({ input: value }).format(DEFAULT_DATE_FORMAT);
16
+ }
17
+ case 'linear':
18
+ default: {
19
+ const value = get(data, fieldName);
20
+ return formatNumber(value);
21
+ }
22
+ }
23
+ };
24
+ const getXRowData = (xAxis, data) => getRowData('x', xAxis, data);
25
+ const getYRowData = (yAxis, data) => getRowData('y', yAxis, data);
2
26
  export const DefaultContent = ({ hovered, xAxis, yAxis }) => {
3
27
  const { data, series } = hovered;
4
28
  switch (series.type) {
5
29
  case 'scatter': {
6
- const scatterData = data;
7
- const xRow = xAxis.type === 'category' ? scatterData.category : scatterData.x;
8
- const yRow = yAxis.type === 'category' ? scatterData.category : scatterData.y;
30
+ const xRow = getXRowData(xAxis, data);
31
+ const yRow = getYRowData(yAxis, data);
9
32
  return (React.createElement("div", null,
10
33
  React.createElement("div", null,
11
34
  React.createElement("span", null, "X:\u00A0"),
@@ -15,9 +38,8 @@ export const DefaultContent = ({ hovered, xAxis, yAxis }) => {
15
38
  React.createElement("b", null, yRow))));
16
39
  }
17
40
  case 'bar-x': {
18
- const barXData = data;
19
- const xRow = xAxis.type === 'category' ? barXData.category : barXData.x;
20
- const yRow = yAxis.type === 'category' ? barXData.category : barXData.y;
41
+ const xRow = getXRowData(xAxis, data);
42
+ const yRow = getYRowData(yAxis, data);
21
43
  return (React.createElement("div", null,
22
44
  React.createElement("div", null, xRow),
23
45
  React.createElement("div", null,
@@ -1,14 +1,19 @@
1
1
  import React from 'react';
2
2
  import { scaleBand, scaleLinear, scaleUtc, extent } from 'd3';
3
3
  import get from 'lodash/get';
4
- import { getOnlyVisibleSeries, getDomainDataYBySeries, isAxisRelatedSeries, getDomainDataXBySeries, isSeriesWithCategoryValues, } from '../../utils';
4
+ import { getOnlyVisibleSeries, getDataCategoryValue, getDomainDataYBySeries, getDomainDataXBySeries, isAxisRelatedSeries, isSeriesWithCategoryValues, } from '../../utils';
5
5
  const isNumericalArrayData = (data) => {
6
6
  return data.every((d) => typeof d === 'number' || d === null);
7
7
  };
8
- const filterCategoriesByVisibleSeries = (categories, series) => {
8
+ const filterCategoriesByVisibleSeries = (args) => {
9
+ const { axisDirection, categories, series } = args;
9
10
  return categories.filter((category) => {
10
11
  return series.some((s) => {
11
- return isSeriesWithCategoryValues(s) && s.data.some((d) => d.category === category);
12
+ return (isSeriesWithCategoryValues(s) &&
13
+ s.data.some((d) => {
14
+ const dataCategory = getDataCategoryValue({ axisDirection, categories, data: d });
15
+ return dataCategory === category;
16
+ }));
12
17
  });
13
18
  });
14
19
  };
@@ -41,7 +46,11 @@ const createScales = (args) => {
41
46
  }
42
47
  case 'category': {
43
48
  if (xCategories) {
44
- const filteredCategories = filterCategoriesByVisibleSeries(xCategories, visibleSeries);
49
+ const filteredCategories = filterCategoriesByVisibleSeries({
50
+ axisDirection: 'x',
51
+ categories: xCategories,
52
+ series: visibleSeries,
53
+ });
45
54
  xScale = scaleBand().domain(filteredCategories).range([0, boundsWidth]);
46
55
  }
47
56
  break;
@@ -78,7 +87,11 @@ const createScales = (args) => {
78
87
  }
79
88
  case 'category': {
80
89
  if (yCategories) {
81
- const filteredCategories = filterCategoriesByVisibleSeries(yCategories, visibleSeries);
90
+ const filteredCategories = filterCategoriesByVisibleSeries({
91
+ axisDirection: 'y',
92
+ categories: yCategories,
93
+ series: visibleSeries,
94
+ });
82
95
  yScale = scaleBand().domain(filteredCategories).range([boundsHeight, 0]);
83
96
  }
84
97
  break;
@@ -39,30 +39,34 @@ function prepareAxisRelatedSeries(args) {
39
39
  return [preparedSeries];
40
40
  }
41
41
  function prepareBarXSeries(args) {
42
- const { colorScale, series, legend } = args;
42
+ const { colorScale, series: seriesList, legend } = args;
43
43
  const commonStackId = getRandomCKId();
44
- return series.map((singleSeries) => {
44
+ return seriesList.map((series) => {
45
45
  var _a, _b, _c, _d;
46
- const name = singleSeries.name || '';
47
- const color = singleSeries.color || colorScale(name);
46
+ const name = series.name || '';
47
+ const color = series.color || colorScale(name);
48
+ let stackId = series.stackId;
49
+ if (!stackId) {
50
+ stackId = series.stacking === 'normal' ? commonStackId : getRandomCKId();
51
+ }
48
52
  return {
49
- type: singleSeries.type,
53
+ type: series.type,
50
54
  color: color,
51
55
  name: name,
52
- visible: get(singleSeries, 'visible', true),
56
+ visible: get(series, 'visible', true),
53
57
  legend: {
54
- enabled: get(singleSeries, 'legend.enabled', legend.enabled),
55
- symbol: prepareLegendSymbol(singleSeries),
58
+ enabled: get(series, 'legend.enabled', legend.enabled),
59
+ symbol: prepareLegendSymbol(series),
56
60
  },
57
- data: singleSeries.data,
58
- stacking: singleSeries.stacking,
59
- stackId: singleSeries.stacking === 'normal' ? commonStackId : getRandomCKId(),
61
+ data: series.data,
62
+ stacking: series.stacking,
63
+ stackId,
60
64
  dataLabels: {
61
- enabled: ((_a = singleSeries.dataLabels) === null || _a === void 0 ? void 0 : _a.enabled) || false,
62
- inside: typeof ((_b = singleSeries.dataLabels) === null || _b === void 0 ? void 0 : _b.inside) === 'boolean'
63
- ? (_c = singleSeries.dataLabels) === null || _c === void 0 ? void 0 : _c.inside
65
+ enabled: ((_a = series.dataLabels) === null || _a === void 0 ? void 0 : _a.enabled) || false,
66
+ inside: typeof ((_b = series.dataLabels) === null || _b === void 0 ? void 0 : _b.inside) === 'boolean'
67
+ ? (_c = series.dataLabels) === null || _c === void 0 ? void 0 : _c.inside
64
68
  : false,
65
- style: Object.assign({}, DEFAULT_DATALABELS_STYLE, (_d = singleSeries.dataLabels) === null || _d === void 0 ? void 0 : _d.style),
69
+ style: Object.assign({}, DEFAULT_DATALABELS_STYLE, (_d = series.dataLabels) === null || _d === void 0 ? void 0 : _d.style),
66
70
  },
67
71
  };
68
72
  }, []);
@@ -1,8 +1,8 @@
1
1
  import React from 'react';
2
- import { ChartOptions } from '../useChartOptions/types';
3
- import { ChartScale } from '../useAxisScales';
4
- import { OnSeriesMouseLeave, OnSeriesMouseMove } from '../useTooltip/types';
5
- import { PreparedBarXSeries } from '../useSeries/types';
2
+ import type { ChartScale } from '../useAxisScales';
3
+ import type { ChartOptions } from '../useChartOptions/types';
4
+ import type { OnSeriesMouseLeave, OnSeriesMouseMove } from '../useTooltip/types';
5
+ import type { PreparedBarXSeries } from '../useSeries/types';
6
6
  type Args = {
7
7
  top: number;
8
8
  left: number;
@@ -1,51 +1,93 @@
1
+ import { max, pointer, select } from 'd3';
1
2
  import React from 'react';
3
+ import get from 'lodash/get';
2
4
  import { block } from '../../../../../utils/cn';
3
- import { group, pointer, select } from 'd3';
4
- const DEFAULT_BAR_RECT_WIDTH = 50;
5
- const DEFAULT_LINEAR_BAR_RECT_WIDTH = 20;
5
+ import { getDataCategoryValue } from '../../utils';
6
+ const RECT_PADDING = 0.1;
6
7
  const MIN_RECT_GAP = 1;
8
+ const MAX_RECT_WIDTH = 50;
9
+ const GROUP_PADDING = 0.1;
10
+ const MIN_GROUP_GAP = 1;
7
11
  const DEFAULT_LABEL_PADDING = 7;
8
12
  const b = block('d3-bar-x');
9
- const getRectProperties = (args) => {
10
- const { point, xAxis, xScale, yAxis, yScale, minPointDistance } = args;
11
- let cx;
12
- let cy;
13
- let width;
14
- let height;
13
+ function prepareData(args) {
14
+ const { series, xAxis, xScale, yScale } = args;
15
+ const categories = get(xAxis, 'categories', []);
16
+ const data = {};
17
+ series.forEach((s) => {
18
+ s.data.forEach((d) => {
19
+ const xValue = xAxis.type === 'category'
20
+ ? getDataCategoryValue({ axisDirection: 'x', categories, data: d })
21
+ : d.x;
22
+ if (xValue) {
23
+ if (!data[xValue]) {
24
+ data[xValue] = {};
25
+ }
26
+ const xGroup = data[xValue];
27
+ if (!xGroup[s.stackId]) {
28
+ xGroup[s.stackId] = [];
29
+ }
30
+ xGroup[s.stackId].push({ data: d, series: s });
31
+ }
32
+ });
33
+ });
34
+ let bandWidth = Infinity;
15
35
  if (xAxis.type === 'category') {
16
36
  const xBandScale = xScale;
17
- const maxWidth = xBandScale.bandwidth() - MIN_RECT_GAP;
18
- width = Math.min(maxWidth, DEFAULT_BAR_RECT_WIDTH);
19
- cx = (xBandScale(point.category) || 0) + xBandScale.step() / 2 - width / 2;
37
+ bandWidth = xBandScale.bandwidth();
20
38
  }
21
39
  else {
22
40
  const xLinearScale = xScale;
23
- const [min, max] = xLinearScale.domain();
24
- const range = xLinearScale.range();
25
- const maxWidth = ((range[1] - range[0]) * minPointDistance) / (Number(max) - Number(min)) - MIN_RECT_GAP;
26
- width = Math.min(Math.max(maxWidth, 1), DEFAULT_LINEAR_BAR_RECT_WIDTH);
27
- cx = xLinearScale(point.x) - width / 2;
28
- }
29
- if (yAxis[0].type === 'linear') {
30
- const yLinearScale = yScale;
31
- cy = yLinearScale(point.y);
32
- height = yLinearScale(yLinearScale.domain()[0]) - cy;
33
- }
34
- else {
35
- throw Error(`The "${yAxis[0].type}" type for the Y axis is not supported`);
36
- }
37
- return { x: cx, y: cy, width, height };
38
- };
39
- function minDiff(arr) {
40
- let result = Infinity;
41
- for (let i = 0; i < arr.length - 1; i++) {
42
- for (let j = i + 1; j < arr.length; j++) {
43
- const diff = Math.abs(arr[i] - arr[j]);
44
- if (diff < result) {
45
- result = diff;
41
+ const xValues = series.reduce((acc, s) => {
42
+ s.data.forEach((dataItem) => acc.push(Number(dataItem.x)));
43
+ return acc;
44
+ }, []);
45
+ xValues.sort().forEach((xValue, index) => {
46
+ if (index > 0 && xValue !== xValues[index - 1]) {
47
+ const dist = xLinearScale(xValue) - xLinearScale(xValues[index - 1]);
48
+ if (dist < bandWidth) {
49
+ bandWidth = dist;
50
+ }
46
51
  }
47
- }
52
+ });
48
53
  }
54
+ const maxGroupSize = max(Object.values(data), (d) => Object.values(d).length) || 1;
55
+ const groupGap = Math.max(bandWidth * GROUP_PADDING, MIN_GROUP_GAP);
56
+ const maxGroupWidth = bandWidth - groupGap;
57
+ const rectGap = Math.max((maxGroupWidth / maxGroupSize) * RECT_PADDING, MIN_RECT_GAP);
58
+ const rectWidth = Math.min(maxGroupWidth / maxGroupSize - rectGap, MAX_RECT_WIDTH);
59
+ const result = [];
60
+ Object.entries(data).forEach(([xValue, val]) => {
61
+ const stacks = Object.values(val);
62
+ const currentGroupWidth = rectWidth * stacks.length + rectGap * (stacks.length - 1);
63
+ stacks.forEach((yValues, groupItemIndex) => {
64
+ let stackHeight = 0;
65
+ yValues.forEach((yValue) => {
66
+ let xCenter;
67
+ if (xAxis.type === 'category') {
68
+ const xBandScale = xScale;
69
+ xCenter = (xBandScale(xValue) || 0) + xBandScale.bandwidth() / 2;
70
+ }
71
+ else {
72
+ const xLinearScale = xScale;
73
+ xCenter = xLinearScale(Number(xValue));
74
+ }
75
+ const x = xCenter - currentGroupWidth / 2 + (rectWidth + rectGap) * groupItemIndex;
76
+ const yLinearScale = yScale;
77
+ const y = yLinearScale(yValue.data.y);
78
+ const height = yLinearScale(yLinearScale.domain()[0]) - y;
79
+ result.push({
80
+ x,
81
+ y: y - stackHeight,
82
+ width: rectWidth,
83
+ height,
84
+ data: yValue.data,
85
+ series: yValue.series,
86
+ });
87
+ stackHeight += height + 1;
88
+ });
89
+ });
90
+ });
49
91
  return result;
50
92
  }
51
93
  export function BarXSeriesShapes(args) {
@@ -57,83 +99,56 @@ export function BarXSeriesShapes(args) {
57
99
  }
58
100
  const svgElement = select(ref.current);
59
101
  svgElement.selectAll('*').remove();
60
- const xValues = xAxis.type === 'category'
61
- ? []
62
- : series.reduce((acc, { data }) => {
63
- data.forEach((dataItem) => acc.push(Number(dataItem.x)));
64
- return acc;
65
- }, []);
66
- const minPointDistance = minDiff(xValues);
67
- const stackedSeriesMap = group(series, (item) => item.stackId);
68
- Array.from(stackedSeriesMap).forEach(([, stackedSeries]) => {
69
- const stackHeights = {};
70
- stackedSeries.forEach((item) => {
71
- const shapes = item.data.map((dataItem) => {
72
- const rectProps = getRectProperties({
73
- point: dataItem,
74
- xAxis,
75
- xScale,
76
- yAxis,
77
- yScale,
78
- minPointDistance,
79
- });
80
- if (!stackHeights[rectProps.x]) {
81
- stackHeights[rectProps.x] = 0;
82
- }
83
- const rectY = rectProps.y - stackHeights[rectProps.x];
84
- stackHeights[rectProps.x] += rectProps.height + 1;
85
- return Object.assign(Object.assign({}, rectProps), { y: rectY, data: dataItem });
86
- });
87
- svgElement
88
- .selectAll('allRects')
89
- .data(shapes)
90
- .join('rect')
91
- .attr('class', b('segment'))
92
- .attr('x', (d) => d.x)
93
- .attr('y', (d) => d.y)
94
- .attr('height', (d) => d.height)
95
- .attr('width', (d) => d.width)
96
- .attr('fill', (d) => d.data.color || item.color)
97
- .on('mousemove', (e, point) => {
98
- const [x, y] = pointer(e, svgContainer);
99
- onSeriesMouseMove === null || onSeriesMouseMove === void 0 ? void 0 : onSeriesMouseMove({
100
- hovered: {
101
- data: point.data,
102
- series: item,
103
- },
104
- pointerPosition: [x - left, y - top],
105
- });
106
- })
107
- .on('mouseleave', () => {
108
- if (onSeriesMouseLeave) {
109
- onSeriesMouseLeave();
110
- }
111
- });
112
- if (item.dataLabels.enabled) {
113
- const selection = svgElement
114
- .selectAll('allLabels')
115
- .data(shapes)
116
- .join('text')
117
- .text((d) => String(d.data.label || d.data.y))
118
- .attr('class', b('label'))
119
- .attr('x', (d) => d.x + d.width / 2)
120
- .attr('y', (d) => {
121
- if (item.dataLabels.inside) {
122
- return d.y + d.height / 2;
123
- }
124
- return d.y - DEFAULT_LABEL_PADDING;
125
- })
126
- .attr('text-anchor', 'middle')
127
- .style('font-size', item.dataLabels.style.fontSize);
128
- if (item.dataLabels.style.fontWeight) {
129
- selection.style('font-weight', item.dataLabels.style.fontWeight);
130
- }
131
- if (item.dataLabels.style.fontColor) {
132
- selection.style('fill', item.dataLabels.style.fontColor);
133
- }
134
- }
102
+ const shapes = prepareData({
103
+ series,
104
+ xAxis,
105
+ xScale,
106
+ yAxis,
107
+ yScale,
108
+ });
109
+ svgElement
110
+ .selectAll('allRects')
111
+ .data(shapes)
112
+ .join('rect')
113
+ .attr('class', b('segment'))
114
+ .attr('x', (d) => d.x)
115
+ .attr('y', (d) => d.y)
116
+ .attr('height', (d) => d.height)
117
+ .attr('width', (d) => d.width)
118
+ .attr('fill', (d) => d.data.color || d.series.color)
119
+ .on('mousemove', (e, d) => {
120
+ const [x, y] = pointer(e, svgContainer);
121
+ onSeriesMouseMove === null || onSeriesMouseMove === void 0 ? void 0 : onSeriesMouseMove({
122
+ hovered: {
123
+ data: d.data,
124
+ series: d.series,
125
+ },
126
+ pointerPosition: [x - left, y - top],
135
127
  });
128
+ })
129
+ .on('mouseleave', () => {
130
+ if (onSeriesMouseLeave) {
131
+ onSeriesMouseLeave();
132
+ }
136
133
  });
134
+ const dataLabels = shapes.filter((s) => s.series.dataLabels.enabled);
135
+ svgElement
136
+ .selectAll('allLabels')
137
+ .data(dataLabels)
138
+ .join('text')
139
+ .text((d) => String(d.data.label || d.data.y))
140
+ .attr('class', b('label'))
141
+ .attr('x', (d) => d.x + d.width / 2)
142
+ .attr('y', (d) => {
143
+ if (d.series.dataLabels.inside) {
144
+ return d.y + d.height / 2;
145
+ }
146
+ return d.y - DEFAULT_LABEL_PADDING;
147
+ })
148
+ .attr('text-anchor', 'middle')
149
+ .style('font-size', (d) => d.series.dataLabels.style.fontSize)
150
+ .style('font-weight', (d) => d.series.dataLabels.style.fontWeight || null)
151
+ .style('fill', (d) => d.series.dataLabels.style.fontColor || null);
137
152
  }, [
138
153
  onSeriesMouseMove,
139
154
  onSeriesMouseLeave,
@@ -1,15 +1,15 @@
1
1
  import React from 'react';
2
- import { ChartOptions } from '../useChartOptions/types';
3
- import { ChartScale } from '../useAxisScales';
4
- import { OnSeriesMouseLeave, OnSeriesMouseMove } from '../useTooltip/types';
5
- import { ScatterSeries } from '../../../../../types/widget-data';
2
+ import type { ScatterSeries } from '../../../../../types/widget-data';
3
+ import type { ChartScale } from '../useAxisScales';
4
+ import type { PreparedAxis } from '../useChartOptions/types';
5
+ import type { OnSeriesMouseLeave, OnSeriesMouseMove } from '../useTooltip/types';
6
6
  type ScatterSeriesShapeProps = {
7
7
  top: number;
8
8
  left: number;
9
9
  series: ScatterSeries;
10
- xAxis: ChartOptions['xAxis'];
10
+ xAxis: PreparedAxis;
11
11
  xScale: ChartScale;
12
- yAxis: ChartOptions['yAxis'];
12
+ yAxis: PreparedAxis[];
13
13
  yScale: ChartScale;
14
14
  svgContainer: SVGSVGElement | null;
15
15
  onSeriesMouseMove?: OnSeriesMouseMove;
@@ -1,11 +1,10 @@
1
- import { pointer, select } from 'd3';
2
1
  import React from 'react';
2
+ import { pointer, select } from 'd3';
3
+ import get from 'lodash/get';
3
4
  import { block } from '../../../../../utils/cn';
5
+ import { getDataCategoryValue } from '../../utils';
4
6
  const b = block('d3-scatter');
5
7
  const DEFAULT_SCATTER_POINT_RADIUS = 4;
6
- const prepareCategoricalScatterData = (data) => {
7
- return data.filter((d) => typeof d.category === 'string');
8
- };
9
8
  const prepareLinearScatterData = (data) => {
10
9
  return data.filter((d) => typeof d.x === 'number' && typeof d.y === 'number');
11
10
  };
@@ -14,7 +13,9 @@ const getCxAttr = (args) => {
14
13
  let cx;
15
14
  if (xAxis.type === 'category') {
16
15
  const xBandScale = xScale;
17
- cx = (xBandScale(point.category) || 0) + xBandScale.step() / 2;
16
+ const categories = get(xAxis, 'categories', []);
17
+ const dataCategory = getDataCategoryValue({ axisDirection: 'x', categories, data: point });
18
+ cx = (xBandScale(dataCategory) || 0) + xBandScale.step() / 2;
18
19
  }
19
20
  else {
20
21
  const xLinearScale = xScale;
@@ -25,9 +26,11 @@ const getCxAttr = (args) => {
25
26
  const getCyAttr = (args) => {
26
27
  const { point, yAxis, yScale } = args;
27
28
  let cy;
28
- if (yAxis[0].type === 'category') {
29
+ if (yAxis.type === 'category') {
29
30
  const yBandScale = yScale;
30
- cy = (yBandScale(point.category) || 0) + yBandScale.step() / 2;
31
+ const categories = get(yAxis, 'categories', []);
32
+ const dataCategory = getDataCategoryValue({ axisDirection: 'y', categories, data: point });
33
+ cy = (yBandScale(dataCategory) || 0) + yBandScale.step() / 2;
31
34
  }
32
35
  else {
33
36
  const yLinearScale = yScale;
@@ -46,7 +49,7 @@ export function ScatterSeriesShape(props) {
46
49
  const svgElement = select(ref.current);
47
50
  svgElement.selectAll('*').remove();
48
51
  const preparedData = xAxis.type === 'category' || ((_a = yAxis[0]) === null || _a === void 0 ? void 0 : _a.type) === 'category'
49
- ? prepareCategoricalScatterData(series.data)
52
+ ? series.data
50
53
  : prepareLinearScatterData(series.data);
51
54
  svgElement
52
55
  .selectAll('allPoints')
@@ -57,7 +60,7 @@ export function ScatterSeriesShape(props) {
57
60
  .attr('fill', (d) => d.color || series.color || '')
58
61
  .attr('r', (d) => d.radius || DEFAULT_SCATTER_POINT_RADIUS)
59
62
  .attr('cx', (d) => getCxAttr({ point: d, xAxis, xScale }))
60
- .attr('cy', (d) => getCyAttr({ point: d, yAxis, yScale }))
63
+ .attr('cy', (d) => getCyAttr({ point: d, yAxis: yAxis[0], yScale }))
61
64
  .on('mousemove', (e, d) => {
62
65
  const [x, y] = pointer(e, svgContainer);
63
66
  onSeriesMouseMove === null || onSeriesMouseMove === void 0 ? void 0 : onSeriesMouseMove({
@@ -1,6 +1,7 @@
1
1
  import { AxisDomain } from 'd3';
2
- import type { BaseTextStyle, ChartKitWidgetSeries, ChartKitWidgetAxisType, ChartKitWidgetAxisLabels } from '../../../../types/widget-data';
2
+ import type { BaseTextStyle, ChartKitWidgetSeries, ChartKitWidgetSeriesData, ChartKitWidgetAxisType, ChartKitWidgetAxisLabels } from '../../../../types/widget-data';
3
3
  export * from './math';
4
+ export type AxisDirection = 'x' | 'y';
4
5
  type UnknownSeries = {
5
6
  type: ChartKitWidgetSeries['type'];
6
7
  data: unknown;
@@ -58,3 +59,8 @@ export declare const getHorisontalSvgTextHeight: (args: {
58
59
  text: string;
59
60
  style?: Partial<BaseTextStyle>;
60
61
  }) => number;
62
+ export declare const getDataCategoryValue: (args: {
63
+ axisDirection: AxisDirection;
64
+ categories: string[];
65
+ data: ChartKitWidgetSeriesData;
66
+ }) => string;
@@ -1,5 +1,6 @@
1
1
  import { group, select } from 'd3';
2
2
  import get from 'lodash/get';
3
+ import isNil from 'lodash/isNil';
3
4
  import { dateTime } from '@gravity-ui/date-utils';
4
5
  import { formatNumber } from '../../../shared';
5
6
  import { DEFAULT_AXIS_LABEL_FONT_SIZE } from '../constants';
@@ -46,7 +47,7 @@ export const getDomainDataYBySeries = (series) => {
46
47
  if (typeof values[key] === 'undefined') {
47
48
  values[key] = 0;
48
49
  }
49
- if (point.y) {
50
+ if (point.y && typeof point.y === 'number') {
50
51
  values[key] += point.y;
51
52
  }
52
53
  });
@@ -128,3 +129,29 @@ export const getHorisontalSvgTextHeight = (args) => {
128
129
  .remove();
129
130
  return height;
130
131
  };
132
+ const extractCategoryValue = (args) => {
133
+ const { axisDirection, categories, data } = args;
134
+ const dataCategory = get(data, axisDirection);
135
+ let categoryValue;
136
+ if ('category' in data && data.category) {
137
+ categoryValue = data.category;
138
+ }
139
+ if (typeof dataCategory === 'string') {
140
+ categoryValue = dataCategory;
141
+ }
142
+ if (typeof dataCategory === 'number') {
143
+ categoryValue = categories[dataCategory];
144
+ }
145
+ if (isNil(categoryValue)) {
146
+ throw new Error('It seems you are trying to get non-existing category value');
147
+ }
148
+ return categoryValue;
149
+ };
150
+ export const getDataCategoryValue = (args) => {
151
+ const { axisDirection, categories, data } = args;
152
+ const categoryValue = extractCategoryValue({ axisDirection, categories, data });
153
+ if (!categories.includes(categoryValue)) {
154
+ throw new Error('It seems you are trying to use category value that is not in categories array');
155
+ }
156
+ return categoryValue;
157
+ };
@@ -2,11 +2,25 @@ import type { BaseSeries, BaseSeriesData } from './base';
2
2
  import type { ChartKitWidgetSeriesOptions } from './series';
3
3
  import { ChartKitWidgetLegend, RectLegendSymbolOptions } from './legend';
4
4
  export type BarXSeriesData<T = any> = BaseSeriesData<T> & {
5
- /** The x value of the point */
6
- x?: number;
7
- /** The y value of the point */
8
- y?: number;
9
- /** Corresponding value of axis category */
5
+ /**
6
+ * The `x` value of the bar. Depending on the context , it may represents:
7
+ * - numeric value (for `linear` x axis)
8
+ * - timestamp value (for `datetime` x axis)
9
+ * - x axis category value (for `category` x axis). If the type is a string, then it is a category value itself. If the type is a number, then it is the index of an element in the array of categories described in `xAxis.categories`
10
+ */
11
+ x?: string | number;
12
+ /**
13
+ * The `y` value of the bar. Depending on the context , it may represents:
14
+ * - numeric value (for `linear` y axis)
15
+ * - timestamp value (for `datetime` y axis)
16
+ * - y axis category value (for `category` y axis). If the type is a string, then it is a category value itself. If the type is a number, then it is the index of an element in the array of categories described in `yAxis[0].categories`
17
+ */
18
+ y?: string | number;
19
+ /**
20
+ * Corresponding value of axis category.
21
+ *
22
+ * @deprecated use `x` or `y` instead
23
+ */
10
24
  category?: string;
11
25
  /** Data label value of the bar-x column. If not specified, the y value is used. */
12
26
  label?: string | number;
@@ -1,11 +1,25 @@
1
1
  import type { BaseSeries, BaseSeriesData } from './base';
2
2
  import type { ChartKitWidgetLegend, RectLegendSymbolOptions } from './legend';
3
3
  export type ScatterSeriesData<T = any> = BaseSeriesData<T> & {
4
- /** The x value of the point */
5
- x?: number;
6
- /** The y value of the point */
7
- y?: number;
8
- /** Corresponding value of axis category */
4
+ /**
5
+ * The `x` value of the point. Depending on the context , it may represents:
6
+ * - numeric value (for `linear` x axis)
7
+ * - timestamp value (for `datetime` x axis)
8
+ * - x axis category value (for `category` x axis). If the type is a string, then it is a category value itself. If the type is a number, then it is the index of an element in the array of categories described in `xAxis.categories`
9
+ */
10
+ x?: string | number;
11
+ /**
12
+ * The `y` value of the point. Depending on the context , it may represents:
13
+ * - numeric value (for `linear` y axis)
14
+ * - timestamp value (for `datetime` y axis)
15
+ * - y axis category value (for `category` y axis). If the type is a string, then it is a category value itself. If the type is a number, then it is the index of an element in the array of categories described in `yAxis[0].categories`
16
+ */
17
+ y?: string | number;
18
+ /**
19
+ * Corresponding value of axis category.
20
+ *
21
+ * @deprecated use `x` or `y` instead
22
+ */
9
23
  category?: string;
10
24
  radius?: number;
11
25
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gravity-ui/chartkit",
3
- "version": "4.1.0",
3
+ "version": "4.2.0",
4
4
  "description": "React component used to render charts based on any sources you need",
5
5
  "license": "MIT",
6
6
  "repository": "git@github.com:gravity-ui/ChartKit.git",