@gravity-ui/charts 1.18.0 → 1.18.2

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/AxisY/prepare-axis-data.d.ts +3 -2
  2. package/dist/cjs/components/AxisY/prepare-axis-data.js +6 -6
  3. package/dist/cjs/components/AxisY/utils.d.ts +4 -2
  4. package/dist/cjs/components/AxisY/utils.js +47 -14
  5. package/dist/cjs/components/ChartInner/index.js +2 -1
  6. package/dist/cjs/components/ChartInner/useChartInnerProps.js +1 -2
  7. package/dist/cjs/components/Legend/index.js +1 -1
  8. package/dist/cjs/hooks/useAxisScales/index.d.ts +0 -1
  9. package/dist/cjs/hooks/useAxisScales/index.js +42 -21
  10. package/dist/cjs/hooks/useChartOptions/utils.d.ts +5 -2
  11. package/dist/cjs/hooks/useChartOptions/utils.js +2 -3
  12. package/dist/cjs/hooks/useChartOptions/y-axis.d.ts +1 -3
  13. package/dist/cjs/hooks/useChartOptions/y-axis.js +3 -5
  14. package/dist/cjs/hooks/useSeries/prepare-bar-y.js +3 -1
  15. package/dist/cjs/hooks/useShapes/bar-y/prepare-data.d.ts +4 -2
  16. package/dist/cjs/hooks/useShapes/bar-y/prepare-data.js +46 -24
  17. package/dist/cjs/hooks/useShapes/index.js +2 -0
  18. package/dist/cjs/hooks/useZoom/utils.js +24 -8
  19. package/dist/cjs/hooks/utils/bar-y.d.ts +7 -10
  20. package/dist/cjs/hooks/utils/bar-y.js +33 -18
  21. package/dist/cjs/utils/chart/array.js +3 -0
  22. package/dist/cjs/utils/chart/format.js +2 -2
  23. package/dist/cjs/utils/chart/index.js +2 -1
  24. package/dist/cjs/utils/chart/labels.d.ts +27 -5
  25. package/dist/cjs/utils/chart/labels.js +39 -3
  26. package/dist/cjs/validation/index.js +4 -1
  27. package/dist/esm/components/AxisY/prepare-axis-data.d.ts +3 -2
  28. package/dist/esm/components/AxisY/prepare-axis-data.js +6 -6
  29. package/dist/esm/components/AxisY/utils.d.ts +4 -2
  30. package/dist/esm/components/AxisY/utils.js +47 -14
  31. package/dist/esm/components/ChartInner/index.js +2 -1
  32. package/dist/esm/components/ChartInner/useChartInnerProps.js +1 -2
  33. package/dist/esm/components/Legend/index.js +1 -1
  34. package/dist/esm/hooks/useAxisScales/index.d.ts +0 -1
  35. package/dist/esm/hooks/useAxisScales/index.js +42 -21
  36. package/dist/esm/hooks/useChartOptions/utils.d.ts +5 -2
  37. package/dist/esm/hooks/useChartOptions/utils.js +2 -3
  38. package/dist/esm/hooks/useChartOptions/y-axis.d.ts +1 -3
  39. package/dist/esm/hooks/useChartOptions/y-axis.js +3 -5
  40. package/dist/esm/hooks/useSeries/prepare-bar-y.js +3 -1
  41. package/dist/esm/hooks/useShapes/bar-y/prepare-data.d.ts +4 -2
  42. package/dist/esm/hooks/useShapes/bar-y/prepare-data.js +46 -24
  43. package/dist/esm/hooks/useShapes/index.js +2 -0
  44. package/dist/esm/hooks/useZoom/utils.js +24 -8
  45. package/dist/esm/hooks/utils/bar-y.d.ts +7 -10
  46. package/dist/esm/hooks/utils/bar-y.js +33 -18
  47. package/dist/esm/utils/chart/array.js +3 -0
  48. package/dist/esm/utils/chart/format.js +2 -2
  49. package/dist/esm/utils/chart/index.js +2 -1
  50. package/dist/esm/utils/chart/labels.d.ts +27 -5
  51. package/dist/esm/utils/chart/labels.js +39 -3
  52. package/dist/esm/validation/index.js +4 -1
  53. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- import type { AxisPlot, ChartYAxis } from '../../types';
1
+ import type { AxisPlot, ChartAxis } from '../../types';
2
2
  export declare function prepareAxisPlotLabel(d: AxisPlot): {
3
3
  text: string;
4
4
  style: {
@@ -8,4 +8,7 @@ export declare function prepareAxisPlotLabel(d: AxisPlot): {
8
8
  };
9
9
  padding: number;
10
10
  };
11
- export declare function getAxisCategories(axis?: ChartYAxis): string[] | undefined;
11
+ export declare function getAxisCategories({ categories, order, }?: {
12
+ categories?: string[];
13
+ order?: ChartAxis['order'];
14
+ }): string[] | undefined;
@@ -8,10 +8,9 @@ export function prepareAxisPlotLabel(d) {
8
8
  padding: (_e = (_d = d.label) === null || _d === void 0 ? void 0 : _d.padding) !== null && _e !== void 0 ? _e : 5,
9
9
  };
10
10
  }
11
- export function getAxisCategories(axis) {
12
- const categories = axis === null || axis === void 0 ? void 0 : axis.categories;
11
+ export function getAxisCategories({ categories, order, } = {}) {
13
12
  if (categories) {
14
- switch (axis.order) {
13
+ switch (order) {
15
14
  case 'reverse': {
16
15
  return reverse(categories);
17
16
  }
@@ -1,11 +1,9 @@
1
1
  import type { ChartSeries, ChartYAxis } from '../../types';
2
- import type { PreparedSeriesOptions } from '../useSeries/types';
3
2
  import type { PreparedAxis } from './types';
4
- export declare const getPreparedYAxis: ({ height, boundsHeight, width, seriesData, seriesOptions, yAxis, }: {
3
+ export declare const getPreparedYAxis: ({ height, boundsHeight, width, seriesData, yAxis, }: {
5
4
  height: number;
6
5
  boundsHeight: number;
7
6
  width: number;
8
7
  seriesData: ChartSeries[];
9
- seriesOptions: PreparedSeriesOptions;
10
8
  yAxis: ChartYAxis[] | undefined;
11
9
  }) => Promise<PreparedAxis[]>;
@@ -5,7 +5,7 @@ import { calculateNumericProperty, formatAxisTickLabel, getClosestPointsRange, g
5
5
  import { createYScale } from '../useAxisScales';
6
6
  import { getAxisCategories, prepareAxisPlotLabel } from './utils';
7
7
  const getAxisLabelMaxWidth = async (args) => {
8
- const { axis, seriesData, seriesOptions, height } = args;
8
+ const { axis, seriesData, height } = args;
9
9
  if (!axis.labels.enabled) {
10
10
  return { height: 0, width: 0 };
11
11
  }
@@ -13,14 +13,13 @@ const getAxisLabelMaxWidth = async (args) => {
13
13
  axis,
14
14
  boundsHeight: height,
15
15
  series: seriesData,
16
- seriesOptions,
17
16
  });
18
17
  if (!scale) {
19
18
  return { height: 0, width: 0 };
20
19
  }
21
20
  const getTextSize = getTextSizeFn({ style: axis.labels.style });
22
21
  const labelLineHeight = (await getTextSize('Tmp')).height;
23
- const tickValues = getTickValues({ axis, scale, labelLineHeight });
22
+ const tickValues = getTickValues({ axis, scale, labelLineHeight, series: seriesData });
24
23
  const ticks = getScaleTicks(scale);
25
24
  const tickStep = getClosestPointsRange(axis, ticks);
26
25
  if (axis.type === 'datetime' && !axis.labels.dateFormat) {
@@ -39,7 +38,7 @@ const getAxisLabelMaxWidth = async (args) => {
39
38
  });
40
39
  return { height: size.maxHeight, width: size.maxWidth };
41
40
  };
42
- export const getPreparedYAxis = ({ height, boundsHeight, width, seriesData, seriesOptions, yAxis, }) => {
41
+ export const getPreparedYAxis = ({ height, boundsHeight, width, seriesData, yAxis, }) => {
43
42
  const axisByPlot = [];
44
43
  const axisItems = yAxis || [{}];
45
44
  const hasAxisRelatedSeries = seriesData.some(isAxisRelatedSeries);
@@ -153,7 +152,6 @@ export const getPreparedYAxis = ({ height, boundsHeight, width, seriesData, seri
153
152
  const { height: labelsHeight, width: labelsWidth } = await getAxisLabelMaxWidth({
154
153
  axis: preparedAxis,
155
154
  seriesData,
156
- seriesOptions,
157
155
  height: boundsHeight,
158
156
  });
159
157
  preparedAxis.labels.height = labelsHeight;
@@ -9,7 +9,9 @@ async function prepareDataLabels(series) {
9
9
  const style = Object.assign({}, DEFAULT_DATALABELS_STYLE, (_a = series.dataLabels) === null || _a === void 0 ? void 0 : _a.style);
10
10
  const html = get(series, 'dataLabels.html', false);
11
11
  const labels = enabled
12
- ? series.data.map((d) => getFormattedValue(Object.assign({ value: d.x || d.label }, series.dataLabels)))
12
+ ? series.data
13
+ .filter((d) => Boolean(d.x || d.label))
14
+ .map((d) => getFormattedValue(Object.assign({ value: d.x || d.label }, series.dataLabels)))
13
15
  : [];
14
16
  const { maxHeight = 0, maxWidth = 0 } = await getLabelsSize({
15
17
  labels,
@@ -2,11 +2,13 @@ import type { ChartScale } from '../../useAxisScales';
2
2
  import type { PreparedAxis } from '../../useChartOptions/types';
3
3
  import type { PreparedBarYSeries, PreparedSeriesOptions } from '../../useSeries/types';
4
4
  import type { BarYShapesArgs } from './types';
5
- export declare const prepareBarYData: (args: {
5
+ export declare function prepareBarYData(args: {
6
+ boundsHeight: number;
7
+ boundsWidth: number;
6
8
  series: PreparedBarYSeries[];
7
9
  seriesOptions: PreparedSeriesOptions;
8
10
  xAxis: PreparedAxis;
9
11
  xScale: ChartScale;
10
12
  yAxis: PreparedAxis[];
11
13
  yScale: (ChartScale | undefined)[];
12
- }) => Promise<BarYShapesArgs>;
14
+ }): Promise<BarYShapesArgs>;
@@ -1,12 +1,12 @@
1
1
  import { ascending, descending, sort } from 'd3';
2
2
  import get from 'lodash/get';
3
- import { filterOverlappingLabels, getLabelsSize, getTextSizeFn } from '../../../utils';
3
+ import { filterOverlappingLabels, getHtmlLabelConstraintedPosition, getLabelsSize, getSvgLabelConstraintedPosition, getTextSizeFn, } from '../../../utils';
4
4
  import { getFormattedValue } from '../../../utils/chart/format';
5
- import { getBarYLayoutForCategoryScale, getBarYLayoutForNumericScale, groupBarYDataByYValue, } from '../../utils';
5
+ import { getBarYLayout, groupBarYDataByYValue } from '../../utils';
6
6
  const DEFAULT_LABEL_PADDING = 7;
7
- export const prepareBarYData = async (args) => {
7
+ export async function prepareBarYData(args) {
8
8
  var _a;
9
- const { series, seriesOptions, yAxis, xScale, yScale: [yScale], } = args;
9
+ const { boundsHeight, boundsWidth, series, seriesOptions, yAxis, xScale, yScale: [yScale], } = args;
10
10
  const stackGap = seriesOptions['bar-y'].stackGap;
11
11
  const xLinearScale = xScale;
12
12
  const yLinearScale = yScale;
@@ -18,7 +18,6 @@ export const prepareBarYData = async (args) => {
18
18
  };
19
19
  }
20
20
  const yScaleRange = yLinearScale.range();
21
- const plotHeight = Math.abs(yScaleRange[0] - yScaleRange[1]);
22
21
  const sortingOptions = get(seriesOptions, 'bar-y.dataSorting');
23
22
  const comparator = (sortingOptions === null || sortingOptions === void 0 ? void 0 : sortingOptions.direction) === 'desc' ? descending : ascending;
24
23
  const sortKey = (() => {
@@ -35,13 +34,13 @@ export const prepareBarYData = async (args) => {
35
34
  }
36
35
  })();
37
36
  const groupedData = groupBarYDataByYValue(series, yAxis);
38
- const { bandSize, barGap, barSize } = yAxis[0].type === 'category'
39
- ? getBarYLayoutForCategoryScale({ groupedData, seriesOptions, yScale: yLinearScale })
40
- : getBarYLayoutForNumericScale({
41
- groupedData,
42
- seriesOptions,
43
- plotHeight: plotHeight - plotHeight * yAxis[0].maxPadding,
44
- });
37
+ const plotHeight = Math.abs(yScaleRange[0] - yScaleRange[1]);
38
+ const { bandSize, barGap, barSize } = getBarYLayout({
39
+ groupedData,
40
+ seriesOptions,
41
+ plotHeight,
42
+ scale: yScale,
43
+ });
45
44
  const result = [];
46
45
  const baseRangeValue = xLinearScale.range()[0];
47
46
  Object.entries(groupedData).forEach(([yValue, val]) => {
@@ -106,7 +105,7 @@ export const prepareBarYData = async (args) => {
106
105
  });
107
106
  });
108
107
  let labels = [];
109
- const htmlElements = [];
108
+ let htmlElements = [];
110
109
  const map = new Map();
111
110
  for (let i = 0; i < result.length; i++) {
112
111
  const prepared = result[i];
@@ -114,9 +113,6 @@ export const prepareBarYData = async (args) => {
114
113
  if (dataLabels.enabled) {
115
114
  const data = prepared.data;
116
115
  const content = getFormattedValue(Object.assign({ value: data.label || data.x }, dataLabels));
117
- const x = dataLabels.inside
118
- ? prepared.x + prepared.width / 2
119
- : prepared.x + prepared.width + DEFAULT_LABEL_PADDING;
120
116
  const y = prepared.y + prepared.height / 2;
121
117
  if (dataLabels.html) {
122
118
  const { maxHeight: height, maxWidth: width } = await getLabelsSize({
@@ -124,12 +120,23 @@ export const prepareBarYData = async (args) => {
124
120
  style: dataLabels.style,
125
121
  html: dataLabels.html,
126
122
  });
127
- htmlElements.push({
123
+ const x = dataLabels.inside
124
+ ? prepared.x + prepared.width / 2 - width / 2
125
+ : prepared.x + prepared.width + DEFAULT_LABEL_PADDING;
126
+ const constrainedPosition = getHtmlLabelConstraintedPosition({
127
+ boundsHeight,
128
+ boundsWidth,
129
+ height,
130
+ width,
128
131
  x,
129
132
  y: y - height / 2,
133
+ });
134
+ htmlElements.push({
130
135
  content,
131
136
  size: { width, height },
132
137
  style: dataLabels.style,
138
+ x: constrainedPosition.x,
139
+ y: constrainedPosition.y,
133
140
  });
134
141
  }
135
142
  else {
@@ -138,24 +145,39 @@ export const prepareBarYData = async (args) => {
138
145
  }
139
146
  const getTextSize = map.get(dataLabels.style);
140
147
  const { width, height } = await getTextSize(content);
141
- labels.push({
148
+ const x = dataLabels.inside
149
+ ? prepared.x + prepared.width / 2 - width / 2
150
+ : prepared.x + prepared.width + DEFAULT_LABEL_PADDING;
151
+ const constrainedPosition = getSvgLabelConstraintedPosition({
152
+ boundsHeight,
153
+ boundsWidth,
154
+ height,
155
+ width,
142
156
  x,
143
157
  y: y + height / 2,
144
- text: content,
145
- textAnchor: dataLabels.inside ? 'middle' : 'right',
146
- style: dataLabels.style,
147
- series: prepared.series,
158
+ });
159
+ labels.push({
148
160
  size: { width, height },
161
+ series: prepared.series,
162
+ style: dataLabels.style,
163
+ text: content,
164
+ textAnchor: 'start',
165
+ x: constrainedPosition.x,
166
+ y: constrainedPosition.y,
149
167
  });
150
168
  }
151
169
  }
152
170
  }
153
- if (!((_a = result[0]) === null || _a === void 0 ? void 0 : _a.series.dataLabels.allowOverlap)) {
171
+ const allowOverlap = (_a = result[0]) === null || _a === void 0 ? void 0 : _a.series.dataLabels.allowOverlap;
172
+ if (labels.length && !allowOverlap) {
154
173
  labels = filterOverlappingLabels(labels);
155
174
  }
175
+ else if (htmlElements.length && !allowOverlap) {
176
+ htmlElements = filterOverlappingLabels(htmlElements);
177
+ }
156
178
  return {
157
179
  shapes: result,
158
180
  labels,
159
181
  htmlElements,
160
182
  };
161
- };
183
+ }
@@ -56,6 +56,8 @@ export const useShapes = (args) => {
56
56
  case 'bar-y': {
57
57
  if (xAxis && xScale && (yScale === null || yScale === void 0 ? void 0 : yScale.length)) {
58
58
  const preparedData = await prepareBarYData({
59
+ boundsHeight,
60
+ boundsWidth,
59
61
  series: chartSeries,
60
62
  seriesOptions,
61
63
  xAxis,
@@ -95,6 +95,22 @@ function selectionXToZoomBounds(args) {
95
95
  }
96
96
  }
97
97
  }
98
+ function getYMinMaxFromSelection(args) {
99
+ const { selection, yAxis } = args;
100
+ let yMin;
101
+ let yMax;
102
+ switch (yAxis.order) {
103
+ case 'reverse':
104
+ case 'sortDesc': {
105
+ [yMin, yMax] = selection;
106
+ break;
107
+ }
108
+ default: {
109
+ [yMax, yMin] = selection;
110
+ }
111
+ }
112
+ return [yMin, yMax];
113
+ }
98
114
  function selectionYToZoomBounds(args) {
99
115
  const { yAxis, yScale, selection } = args;
100
116
  switch (yAxis.type) {
@@ -104,8 +120,8 @@ function selectionYToZoomBounds(args) {
104
120
  const categories = yAxis.categories || [];
105
121
  const currentDomain = bandScale.domain();
106
122
  const step = bandScale.step();
107
- let startIndex = currentDomain.length - 1 - Math.floor(y0 / step);
108
- let endIndex = currentDomain.length - 1 - Math.floor(y1 / step);
123
+ let startIndex = Math.max(0, currentDomain.length - 1 - Math.floor(y0 / step));
124
+ let endIndex = Math.min(currentDomain.length - 1, currentDomain.length - 1 - Math.floor(y1 / step));
109
125
  const startCategory = currentDomain[startIndex];
110
126
  const endCategory = currentDomain[endIndex];
111
127
  startIndex = categories.indexOf(startCategory);
@@ -119,18 +135,18 @@ function selectionYToZoomBounds(args) {
119
135
  return [startIndex, endIndex];
120
136
  }
121
137
  case 'datetime': {
122
- const [y1, y0] = selection;
138
+ const [yMin, yMax] = getYMinMaxFromSelection({ selection, yAxis });
123
139
  const timeScale = yScale;
124
- const minTimestamp = timeScale.invert(y0).getTime();
125
- const maxTimestamp = timeScale.invert(y1).getTime();
140
+ const minTimestamp = timeScale.invert(yMin).getTime();
141
+ const maxTimestamp = timeScale.invert(yMax).getTime();
126
142
  return [minTimestamp, maxTimestamp];
127
143
  }
128
144
  case 'linear':
129
145
  case 'logarithmic': {
130
- const [y1, y0] = selection;
146
+ const [yMin, yMax] = getYMinMaxFromSelection({ selection, yAxis });
131
147
  const linearScale = yScale;
132
- const minValue = linearScale.invert(y0);
133
- const maxValue = linearScale.invert(y1);
148
+ const minValue = linearScale.invert(yMin);
149
+ const maxValue = linearScale.invert(yMax);
134
150
  return [minValue, maxValue];
135
151
  }
136
152
  default: {
@@ -1,3 +1,4 @@
1
+ import type { AxisDomain, AxisScale } from 'd3';
1
2
  import type { BarYSeries, BarYSeriesData } from '../../types';
2
3
  import type { ChartScale } from '../useAxisScales';
3
4
  import type { PreparedAxis } from '../useChartOptions/types';
@@ -6,19 +7,15 @@ export declare function groupBarYDataByYValue<T extends BarYSeries | PreparedBar
6
7
  data: BarYSeriesData;
7
8
  series: T;
8
9
  }[]>>;
9
- export declare function getBarYLayoutForNumericScale(args: {
10
+ export declare function getBandSize({ domain, scale, }: {
11
+ domain: AxisDomain[];
12
+ scale: AxisScale<AxisDomain> | undefined;
13
+ }): number;
14
+ export declare function getBarYLayout(args: {
10
15
  plotHeight: number;
11
16
  seriesOptions: PreparedSeriesOptions;
12
17
  groupedData: ReturnType<typeof groupBarYDataByYValue>;
13
- }): {
14
- bandSize: number;
15
- barGap: number;
16
- barSize: number;
17
- };
18
- export declare function getBarYLayoutForCategoryScale(args: {
19
- groupedData: ReturnType<typeof groupBarYDataByYValue>;
20
- seriesOptions: PreparedSeriesOptions;
21
- yScale: ChartScale;
18
+ scale: ChartScale | undefined;
22
19
  }): {
23
20
  bandSize: number;
24
21
  barGap: number;
@@ -1,6 +1,6 @@
1
1
  import { max } from 'd3';
2
2
  import get from 'lodash/get';
3
- import { getDataCategoryValue } from '../../utils';
3
+ import { getDataCategoryValue, isBandScale } from '../../utils';
4
4
  import { MIN_BAR_GAP, MIN_BAR_GROUP_GAP, MIN_BAR_WIDTH } from '../constants';
5
5
  import { getSeriesStackId } from '../useSeries/utils';
6
6
  export function groupBarYDataByYValue(series, yAxis) {
@@ -27,28 +27,43 @@ export function groupBarYDataByYValue(series, yAxis) {
27
27
  });
28
28
  return data;
29
29
  }
30
- export function getBarYLayoutForNumericScale(args) {
31
- const { plotHeight, groupedData, seriesOptions } = args;
32
- const barMaxWidth = get(seriesOptions, 'bar-y.barMaxWidth');
33
- const barPadding = get(seriesOptions, 'bar-y.barPadding');
34
- const groupPadding = get(seriesOptions, 'bar-y.groupPadding');
35
- const groups = Object.values(groupedData);
36
- const maxGroupItemCount = groups.reduce((acc, items) => Math.max(acc, Object.keys(items).length), 0);
37
- const bandSize = plotHeight / groups.length;
38
- const groupGap = Math.max(bandSize * groupPadding, MIN_BAR_GROUP_GAP);
39
- const groupSize = bandSize - groupGap;
40
- const barGap = Math.max(bandSize * barPadding, MIN_BAR_GAP);
41
- const barSize = Math.max(MIN_BAR_WIDTH, Math.min((groupSize - barGap) / maxGroupItemCount, barMaxWidth));
42
- return { bandSize, barGap, barSize };
30
+ export function getBandSize({ domain, scale, }) {
31
+ if (!scale || !domain.length) {
32
+ return 0;
33
+ }
34
+ if (isBandScale(scale)) {
35
+ return scale.bandwidth();
36
+ }
37
+ const range = scale.range();
38
+ const plotHeight = Math.abs(range[0] - range[1]);
39
+ if (domain.length === 1) {
40
+ return plotHeight;
41
+ }
42
+ // for the numeric or datetime axes, you first need to count domain.length + 1,
43
+ // since the extreme points are located not in the center of the bar, but along the edges of the axes
44
+ let bandWidth = plotHeight / (domain.length - 1);
45
+ domain.forEach((current, index) => {
46
+ if (index > 0) {
47
+ const prev = domain[index - 1];
48
+ const prevY = scale(prev);
49
+ const currentY = scale(current);
50
+ if (typeof prevY === 'number' && typeof currentY === 'number') {
51
+ const distance = Math.abs(prevY - currentY);
52
+ bandWidth = Math.min(bandWidth, distance);
53
+ }
54
+ }
55
+ });
56
+ return bandWidth;
43
57
  }
44
- export function getBarYLayoutForCategoryScale(args) {
45
- const { groupedData, seriesOptions, yScale } = args;
58
+ export function getBarYLayout(args) {
59
+ const { groupedData, seriesOptions, scale } = args;
46
60
  const barMaxWidth = get(seriesOptions, 'bar-y.barMaxWidth');
47
61
  const barPadding = get(seriesOptions, 'bar-y.barPadding');
48
62
  const groupPadding = get(seriesOptions, 'bar-y.groupPadding');
49
- const bandSize = yScale.bandwidth();
50
- const maxGroupSize = max(Object.values(groupedData), (d) => Object.values(d).length) || 1;
63
+ const domain = Object.keys(groupedData);
64
+ const bandSize = getBandSize({ domain, scale: scale });
51
65
  const groupGap = Math.max(bandSize * groupPadding, MIN_BAR_GROUP_GAP);
66
+ const maxGroupSize = max(Object.values(groupedData), (d) => Object.values(d).length) || 1;
52
67
  const groupSize = bandSize - groupGap;
53
68
  const barGap = Math.max(bandSize * barPadding, MIN_BAR_GAP);
54
69
  const barSize = Math.max(MIN_BAR_WIDTH, Math.min(groupSize / maxGroupSize - barGap, barMaxWidth));
@@ -1,4 +1,7 @@
1
1
  export function getMinSpaceBetween(arr, iterator) {
2
+ if (arr.length < 2) {
3
+ return 0;
4
+ }
2
5
  return arr.reduce((acc, item, index) => {
3
6
  const prev = arr[index - 1];
4
7
  if (prev) {
@@ -1,4 +1,4 @@
1
- import { dateTime } from '@gravity-ui/date-utils';
1
+ import { dateTimeUtc } from '@gravity-ui/date-utils';
2
2
  import capitalize from 'lodash/capitalize';
3
3
  import { DEFAULT_DATE_FORMAT } from '../../constants';
4
4
  import { formatNumber, getDefaultUnit } from '../../libs';
@@ -6,7 +6,7 @@ import { getDefaultDateFormat } from './time';
6
6
  const LETTER_MOUNTH_AT_START_FORMAT_REGEXP = /^M{3,}/;
7
7
  function getFormattedDate(args) {
8
8
  const { value, format = DEFAULT_DATE_FORMAT } = args;
9
- const date = dateTime({ input: value });
9
+ const date = dateTimeUtc({ input: value });
10
10
  if (date === null || date === void 0 ? void 0 : date.isValid()) {
11
11
  const formattedDate = date.format(format);
12
12
  if (LETTER_MOUNTH_AT_START_FORMAT_REGEXP.test(format)) {
@@ -128,7 +128,7 @@ export function getDefaultMinYAxisValue(series) {
128
128
  }
129
129
  export const getDomainDataYBySeries = (series) => {
130
130
  const groupedSeries = group(series, (item) => item.type);
131
- return Array.from(groupedSeries).reduce((acc, [type, seriesList]) => {
131
+ const items = Array.from(groupedSeries).reduce((acc, [type, seriesList]) => {
132
132
  switch (type) {
133
133
  case 'area':
134
134
  case 'bar-x': {
@@ -152,6 +152,7 @@ export const getDomainDataYBySeries = (series) => {
152
152
  }
153
153
  return acc;
154
154
  }, []);
155
+ return Array.from(new Set(items));
155
156
  };
156
157
  // Uses to get all series names array (except `pie` charts)
157
158
  export const getSeriesNames = (series) => {
@@ -1,6 +1,28 @@
1
- import type { LabelData } from '../../types';
1
+ import type { HtmlItem, LabelData } from '../../types';
2
2
  export declare function getLeftPosition(label: LabelData): number;
3
- export declare function getOverlappingByX(rect1: LabelData, rect2: LabelData, gap?: number): number;
4
- export declare function getOverlappingByY(rect1: LabelData, rect2: LabelData, gap?: number): number;
5
- export declare function isLabelsOverlapping<T extends LabelData>(label1: T, label2: T, padding?: number): boolean;
6
- export declare function filterOverlappingLabels<T extends LabelData>(labels: T[]): T[];
3
+ export declare function getOverlappingByX(rect1: LabelData | HtmlItem, rect2: LabelData | HtmlItem, gap?: number): number;
4
+ export declare function getOverlappingByY(rect1: LabelData | HtmlItem, rect2: LabelData | HtmlItem, gap?: number): number;
5
+ export declare function isLabelsOverlapping<T extends LabelData | HtmlItem>(label1: T, label2: T, padding?: number): boolean;
6
+ export declare function filterOverlappingLabels<T extends LabelData | HtmlItem>(labels: T[]): T[];
7
+ export declare function getSvgLabelConstraintedPosition(args: {
8
+ boundsHeight: number;
9
+ boundsWidth: number;
10
+ height: number;
11
+ width: number;
12
+ x: number;
13
+ y: number;
14
+ }): {
15
+ x: number;
16
+ y: number;
17
+ };
18
+ export declare function getHtmlLabelConstraintedPosition(args: {
19
+ boundsHeight: number;
20
+ boundsWidth: number;
21
+ height: number;
22
+ width: number;
23
+ x: number;
24
+ y: number;
25
+ }): {
26
+ x: number;
27
+ y: number;
28
+ };
@@ -16,9 +16,9 @@ export function getLeftPosition(label) {
16
16
  }
17
17
  }
18
18
  export function getOverlappingByX(rect1, rect2, gap = 0) {
19
- const left1 = getLeftPosition(rect1);
19
+ const left1 = 'textAnchor' in rect1 ? getLeftPosition(rect1) : rect1.x;
20
20
  const right1 = left1 + rect1.size.width;
21
- const left2 = getLeftPosition(rect2);
21
+ const left2 = 'textAnchor' in rect2 ? getLeftPosition(rect2) : rect2.x;
22
22
  const right2 = left2 + rect2.size.width;
23
23
  return Math.max(0, Math.min(right1, right2) - Math.max(left1, left2) + gap);
24
24
  }
@@ -34,7 +34,7 @@ export function isLabelsOverlapping(label1, label2, padding = 0) {
34
34
  }
35
35
  export function filterOverlappingLabels(labels) {
36
36
  const result = [];
37
- const sorted = sortBy(labels, (d) => d.y, getLeftPosition);
37
+ const sorted = sortBy(labels, (d) => d.y, (d) => ('textAnchor' in d ? getLeftPosition(d) : d.x));
38
38
  sorted.forEach((label) => {
39
39
  if (!result.some((l) => isLabelsOverlapping(label, l))) {
40
40
  result.push(label);
@@ -42,3 +42,39 @@ export function filterOverlappingLabels(labels) {
42
42
  });
43
43
  return result;
44
44
  }
45
+ export function getSvgLabelConstraintedPosition(args) {
46
+ const { boundsHeight, boundsWidth, height, width, x, y } = args;
47
+ let resultX = x;
48
+ let resultY = y;
49
+ if (x < 0) {
50
+ resultX = 0;
51
+ }
52
+ if (x + width > boundsWidth) {
53
+ resultX = boundsWidth - width;
54
+ }
55
+ if (y - height < 0) {
56
+ resultY = 0;
57
+ }
58
+ if (y > boundsHeight) {
59
+ resultY = boundsHeight;
60
+ }
61
+ return { x: resultX, y: resultY };
62
+ }
63
+ export function getHtmlLabelConstraintedPosition(args) {
64
+ const { boundsHeight, boundsWidth, height, width, x, y } = args;
65
+ let resultX = x;
66
+ let resultY = y;
67
+ if (x < 0) {
68
+ resultX = 0;
69
+ }
70
+ if (x + width > boundsWidth) {
71
+ resultX = boundsWidth - width;
72
+ }
73
+ if (y < 0) {
74
+ resultY = 0;
75
+ }
76
+ if (y + height > boundsHeight) {
77
+ resultY = boundsHeight - height;
78
+ }
79
+ return { x: resultX, y: resultY };
80
+ }
@@ -368,7 +368,10 @@ function validateTooltip({ tooltip }) {
368
368
  }
369
369
  }
370
370
  export function validateData(data) {
371
- if (isEmpty(data) || isEmpty(data.series) || isEmpty(data.series.data)) {
371
+ if (isEmpty(data) ||
372
+ isEmpty(data.series) ||
373
+ isEmpty(data.series.data) ||
374
+ data.series.data.every((s) => isEmpty(s.data))) {
372
375
  throw new ChartError({
373
376
  code: CHART_ERROR_CODE.NO_DATA,
374
377
  message: i18n('error', 'label_no-data'),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gravity-ui/charts",
3
- "version": "1.18.0",
3
+ "version": "1.18.2",
4
4
  "description": "React component used to render charts",
5
5
  "license": "MIT",
6
6
  "main": "dist/cjs/index.js",