@gravity-ui/charts 1.18.0 → 1.18.1

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 (49) 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 +5 -5
  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/hooks/useAxisScales/index.d.ts +0 -1
  8. package/dist/cjs/hooks/useAxisScales/index.js +42 -21
  9. package/dist/cjs/hooks/useChartOptions/utils.d.ts +5 -2
  10. package/dist/cjs/hooks/useChartOptions/utils.js +2 -3
  11. package/dist/cjs/hooks/useChartOptions/y-axis.d.ts +1 -3
  12. package/dist/cjs/hooks/useChartOptions/y-axis.js +3 -5
  13. package/dist/cjs/hooks/useSeries/prepare-bar-y.js +3 -1
  14. package/dist/cjs/hooks/useShapes/bar-y/prepare-data.d.ts +4 -2
  15. package/dist/cjs/hooks/useShapes/bar-y/prepare-data.js +39 -20
  16. package/dist/cjs/hooks/useShapes/index.js +2 -0
  17. package/dist/cjs/hooks/useZoom/utils.js +24 -8
  18. package/dist/cjs/hooks/utils/bar-y.d.ts +7 -10
  19. package/dist/cjs/hooks/utils/bar-y.js +33 -18
  20. package/dist/cjs/utils/chart/array.js +3 -0
  21. package/dist/cjs/utils/chart/format.js +2 -2
  22. package/dist/cjs/utils/chart/index.js +2 -1
  23. package/dist/cjs/utils/chart/labels.d.ts +27 -5
  24. package/dist/cjs/utils/chart/labels.js +39 -3
  25. package/dist/esm/components/AxisY/prepare-axis-data.d.ts +3 -2
  26. package/dist/esm/components/AxisY/prepare-axis-data.js +5 -5
  27. package/dist/esm/components/AxisY/utils.d.ts +4 -2
  28. package/dist/esm/components/AxisY/utils.js +47 -14
  29. package/dist/esm/components/ChartInner/index.js +2 -1
  30. package/dist/esm/components/ChartInner/useChartInnerProps.js +1 -2
  31. package/dist/esm/hooks/useAxisScales/index.d.ts +0 -1
  32. package/dist/esm/hooks/useAxisScales/index.js +42 -21
  33. package/dist/esm/hooks/useChartOptions/utils.d.ts +5 -2
  34. package/dist/esm/hooks/useChartOptions/utils.js +2 -3
  35. package/dist/esm/hooks/useChartOptions/y-axis.d.ts +1 -3
  36. package/dist/esm/hooks/useChartOptions/y-axis.js +3 -5
  37. package/dist/esm/hooks/useSeries/prepare-bar-y.js +3 -1
  38. package/dist/esm/hooks/useShapes/bar-y/prepare-data.d.ts +4 -2
  39. package/dist/esm/hooks/useShapes/bar-y/prepare-data.js +39 -20
  40. package/dist/esm/hooks/useShapes/index.js +2 -0
  41. package/dist/esm/hooks/useZoom/utils.js +24 -8
  42. package/dist/esm/hooks/utils/bar-y.d.ts +7 -10
  43. package/dist/esm/hooks/utils/bar-y.js +33 -18
  44. package/dist/esm/utils/chart/array.js +3 -0
  45. package/dist/esm/utils/chart/format.js +2 -2
  46. package/dist/esm/utils/chart/index.js +2 -1
  47. package/dist/esm/utils/chart/labels.d.ts +27 -5
  48. package/dist/esm/utils/chart/labels.js +39 -3
  49. package/package.json +1 -1
@@ -1,10 +1,11 @@
1
- import type { ChartScale, PreparedAxis, PreparedSplit } from '../../hooks';
1
+ import type { ChartScale, PreparedAxis, PreparedSeries, PreparedSplit } from '../../hooks';
2
2
  import type { AxisYData } from './types';
3
- export declare function prepareAxisData({ axis, split, scale, top: topOffset, width, height, }: {
3
+ export declare function prepareAxisData({ axis, split, scale, top: topOffset, width, height, series, }: {
4
4
  axis: PreparedAxis;
5
5
  split: PreparedSplit;
6
6
  scale: ChartScale;
7
7
  top: number;
8
8
  width: number;
9
9
  height: number;
10
+ series: PreparedSeries[];
10
11
  }): Promise<AxisYData>;
@@ -1,5 +1,5 @@
1
1
  import { getUniqId } from '@gravity-ui/uikit';
2
- import { calculateCos, calculateSin, getBandsPosition, getLabelFormatter, getLabelsSize, getTextSizeFn, getTextWithElipsis, wrapText, } from '../../utils';
2
+ import { calculateCos, calculateSin, formatAxisTickLabel, getBandsPosition, getLabelsSize, getMinSpaceBetween, getTextSizeFn, getTextWithElipsis, wrapText, } from '../../utils';
3
3
  import { getTickValues } from './utils';
4
4
  async function getSvgAxisLabel({ getTextSize, text, axis, top, left, labelMaxHeight, topOffset, }) {
5
5
  var _a;
@@ -81,7 +81,7 @@ async function getSvgAxisLabel({ getTextSize, text, axis, top, left, labelMaxHei
81
81
  return svgLabel;
82
82
  }
83
83
  // eslint-disable-next-line complexity
84
- export async function prepareAxisData({ axis, split, scale, top: topOffset, width, height, }) {
84
+ export async function prepareAxisData({ axis, split, scale, top: topOffset, width, height, series, }) {
85
85
  var _a, _b, _c;
86
86
  const axisPlotTopPosition = ((_a = split.plots[axis.plotIndex]) === null || _a === void 0 ? void 0 : _a.top) || 0;
87
87
  const axisHeight = ((_b = split.plots[axis.plotIndex]) === null || _b === void 0 ? void 0 : _b.height) || height;
@@ -94,9 +94,9 @@ export async function prepareAxisData({ axis, split, scale, top: topOffset, widt
94
94
  const ticks = [];
95
95
  const getTextSize = getTextSizeFn({ style: axis.labels.style });
96
96
  const labelLineHeight = (await getTextSize('Tmp')).height;
97
- const values = getTickValues({ scale, axis, labelLineHeight });
97
+ const values = getTickValues({ scale, axis, labelLineHeight, series });
98
+ const tickStep = getMinSpaceBetween(values, (d) => Number(d.value));
98
99
  const labelMaxHeight = values.length > 1 ? values[0].y - values[1].y - axis.labels.padding * 2 : axisHeight;
99
- const labelFormatter = getLabelFormatter({ axis, scale });
100
100
  for (let i = 0; i < values.length; i++) {
101
101
  const tickValue = values[i];
102
102
  const y = axisPlotTopPosition + tickValue.y;
@@ -125,7 +125,7 @@ export async function prepareAxisData({ axis, split, scale, top: topOffset, widt
125
125
  };
126
126
  }
127
127
  else {
128
- const text = labelFormatter(tickValue.value);
128
+ const text = formatAxisTickLabel({ value: tickValue.value, axis, step: tickStep });
129
129
  svgLabel = await getSvgAxisLabel({
130
130
  getTextSize,
131
131
  text,
@@ -1,8 +1,10 @@
1
- import type { ChartScale, PreparedAxis } from '../../hooks';
2
- export declare function getTickValues({ scale, axis, labelLineHeight, }: {
1
+ import type { ChartScale, PreparedAxis, PreparedSeries } from '../../hooks';
2
+ import type { ChartSeries } from '../../types';
3
+ export declare function getTickValues({ scale, axis, labelLineHeight, series, }: {
3
4
  scale: ChartScale;
4
5
  axis: PreparedAxis;
5
6
  labelLineHeight: number;
7
+ series: PreparedSeries[] | ChartSeries[];
6
8
  }): {
7
9
  y: number;
8
10
  value: number | Date;
@@ -1,4 +1,4 @@
1
- import { getMinSpaceBetween, getTicksCount, isBandScale } from '../../utils';
1
+ import { getDomainDataYBySeries, getMinSpaceBetween, getTicksCount, isBandScale } from '../../utils';
2
2
  function thinOut(items, delta) {
3
3
  const arr = [];
4
4
  for (let i = 0; i < items.length; i = i + delta) {
@@ -6,30 +6,63 @@ function thinOut(items, delta) {
6
6
  }
7
7
  return arr;
8
8
  }
9
- export function getTickValues({ scale, axis, labelLineHeight, }) {
9
+ export function getTickValues({ scale, axis, labelLineHeight, series, }) {
10
10
  if ('ticks' in scale && typeof scale.ticks === 'function') {
11
11
  const range = scale.range();
12
12
  const height = Math.abs(range[0] - range[1]);
13
13
  if (!height) {
14
14
  return [];
15
15
  }
16
- let ticksCount = getTicksCount({ axis, range: height });
17
- let result = scale.ticks(ticksCount).map((t) => ({
16
+ const getScaleTicks = () => {
17
+ var _a;
18
+ if (series.some((s) => s.type === 'bar-y')) {
19
+ const domainData = getDomainDataYBySeries(series);
20
+ if (domainData.length < 3) {
21
+ return domainData;
22
+ }
23
+ const ticksCount = (_a = getTicksCount({ axis, range: height })) !== null && _a !== void 0 ? _a : domainData.length;
24
+ return scale.ticks(Math.min(ticksCount, domainData.length));
25
+ }
26
+ const ticksCount = getTicksCount({ axis, range: height });
27
+ return scale.ticks(ticksCount);
28
+ };
29
+ const scaleTicks = getScaleTicks();
30
+ const originalTickValues = scaleTicks.map((t) => ({
18
31
  y: scale(t),
19
32
  value: t,
20
33
  }));
21
- if (result.length <= 1) {
22
- return result;
34
+ if (originalTickValues.length <= 1) {
35
+ return originalTickValues;
23
36
  }
24
- let labelHeight = getMinSpaceBetween(result, (d) => d.y) - axis.labels.padding * 2;
25
- ticksCount = result.length - 1;
26
- while (labelHeight < labelLineHeight && result.length > 1) {
37
+ // first, we try to draw "beautiful" tick values
38
+ let result = originalTickValues;
39
+ let availableSpaceForLabel = getMinSpaceBetween(result, (d) => d.y) - axis.labels.padding * 2;
40
+ let ticksCount = result.length - 1;
41
+ while (availableSpaceForLabel < labelLineHeight && result.length > 1) {
27
42
  ticksCount = ticksCount ? ticksCount - 1 : result.length - 1;
28
- result = scale.ticks(ticksCount).map((t) => ({
43
+ const newScaleTicks = scale.ticks(ticksCount);
44
+ result = newScaleTicks.map((t) => ({
29
45
  y: scale(t),
30
46
  value: t,
31
47
  }));
32
- labelHeight = getMinSpaceBetween(result, (d) => d.y) - axis.labels.padding * 2;
48
+ availableSpaceForLabel =
49
+ getMinSpaceBetween(result, (d) => d.y) - axis.labels.padding * 2;
50
+ }
51
+ // when this is not possible (for example, such values cannot be selected for the logarithmic axis with a small range)
52
+ // just thin out the originally proposed result
53
+ if (!result.length) {
54
+ result = originalTickValues;
55
+ availableSpaceForLabel =
56
+ getMinSpaceBetween(result, (d) => d.y) - axis.labels.padding * 2;
57
+ let delta = 2;
58
+ while (availableSpaceForLabel < labelLineHeight && result.length > 1) {
59
+ result = thinOut(result, delta);
60
+ if (result.length > 1) {
61
+ delta += 1;
62
+ availableSpaceForLabel =
63
+ getMinSpaceBetween(result, (d) => d.y) - axis.labels.padding * 2;
64
+ }
65
+ }
33
66
  }
34
67
  return result;
35
68
  }
@@ -47,13 +80,13 @@ export function getTickValues({ scale, axis, labelLineHeight, }) {
47
80
  return items;
48
81
  }
49
82
  let result = [...items];
50
- let labelHeight = result[0].y - result[1].y - axis.labels.padding * 2;
83
+ let availableSpaceForLabel = Math.abs(result[0].y - result[1].y) - axis.labels.padding * 2;
51
84
  let delta = 2;
52
- while (labelHeight < labelLineHeight && result.length > 1) {
85
+ while (availableSpaceForLabel < labelLineHeight && result.length > 1) {
53
86
  result = thinOut(items, delta);
54
87
  if (result.length > 1) {
55
88
  delta += 1;
56
- labelHeight = result[0].y - result[1].y - axis.labels.padding * 2;
89
+ availableSpaceForLabel = result[0].y - result[1].y - axis.labels.padding * 2;
57
90
  }
58
91
  }
59
92
  return result;
@@ -100,6 +100,7 @@ export const ChartInner = (props) => {
100
100
  width: boundsWidth,
101
101
  height: boundsHeight,
102
102
  split: preparedSplit,
103
+ series: preparedSeries,
103
104
  });
104
105
  items.push(axisData);
105
106
  }
@@ -108,7 +109,7 @@ export const ChartInner = (props) => {
108
109
  setYAxisDataItems(items);
109
110
  }
110
111
  })();
111
- }, [boundsHeight, boundsOffsetTop, boundsWidth, preparedSplit, yAxis, yScale]);
112
+ }, [boundsHeight, boundsOffsetTop, boundsWidth, preparedSeries, preparedSplit, yAxis, yScale]);
112
113
  return (React.createElement("div", { className: b() },
113
114
  React.createElement("svg", { ref: svgRef, width: width, height: height,
114
115
  // We use onPointerMove here because onMouseMove works incorrectly when the zoom setting is enabled:
@@ -66,10 +66,9 @@ export function useChartInnerProps(props) {
66
66
  boundsHeight: estimatedBoundsHeight,
67
67
  width,
68
68
  seriesData: zoomedSeriesData,
69
- seriesOptions: preparedSeriesOptions,
70
69
  yAxis: data.yAxis,
71
70
  }).then((val) => setYAxis(val));
72
- }, [data.yAxis, estimatedBoundsHeight, height, preparedSeriesOptions, width, zoomedSeriesData]);
71
+ }, [data.yAxis, estimatedBoundsHeight, height, width, zoomedSeriesData]);
73
72
  const { preparedSeries, preparedLegend, handleLegendItemClick } = useSeries({
74
73
  colors,
75
74
  legend: data.legend,
@@ -23,7 +23,6 @@ export declare function createYScale(args: {
23
23
  axis: PreparedAxis;
24
24
  boundsHeight: number;
25
25
  series: (PreparedSeries | ChartSeries)[];
26
- seriesOptions: PreparedSeriesOptions;
27
26
  }): ScaleBand<string> | ScaleLinear<number, number, never> | ScaleTime<number, number, never> | undefined;
28
27
  export declare function createXScale(args: {
29
28
  axis: PreparedAxis | ChartAxis;
@@ -3,7 +3,7 @@ import { extent, scaleBand, scaleLinear, scaleLog, scaleUtc } from 'd3';
3
3
  import get from 'lodash/get';
4
4
  import { DEFAULT_AXIS_TYPE, SeriesType } from '../../constants';
5
5
  import { CHART_SERIES_WITH_VOLUME_ON_Y_AXIS, getAxisHeight, getDataCategoryValue, getDefaultMaxXAxisValue, getDefaultMinXAxisValue, getDomainDataXBySeries, getDomainDataYBySeries, getOnlyVisibleSeries, isAxisRelatedSeries, isSeriesWithCategoryValues, } from '../../utils';
6
- import { getBarYLayoutForNumericScale, groupBarYDataByYValue } from '../utils';
6
+ import { getBandSize } from '../utils';
7
7
  import { getBarXLayoutForNumericScale, groupBarXDataByXValue } from '../utils/bar-x';
8
8
  const X_AXIS_ZOOM_PADDING = 0.02;
9
9
  function validateArrayData(data) {
@@ -40,25 +40,12 @@ function filterCategoriesByVisibleSeries(args) {
40
40
  // axis is validated in `validation/index.ts`, so the value of `axis.type` is definitely valid.
41
41
  // eslint-disable-next-line consistent-return
42
42
  function getYScaleRange(args) {
43
- const { axis, boundsHeight, series, seriesOptions } = args;
43
+ const { axis, boundsHeight } = args;
44
44
  switch (axis.type) {
45
45
  case 'datetime':
46
46
  case 'linear':
47
47
  case 'logarithmic': {
48
- let range = [boundsHeight, boundsHeight * axis.maxPadding];
49
- const barYSeries = series.filter((s) => s.type === SeriesType.BarY);
50
- if (barYSeries.length) {
51
- const groupedData = groupBarYDataByYValue(barYSeries, [axis]);
52
- if (Object.keys(groupedData).length > 1) {
53
- const { bandSize } = getBarYLayoutForNumericScale({
54
- plotHeight: boundsHeight - boundsHeight * axis.maxPadding,
55
- groupedData,
56
- seriesOptions: seriesOptions,
57
- });
58
- const offset = bandSize / 2;
59
- range = [range[0] - offset, range[1] + offset];
60
- }
61
- }
48
+ const range = [boundsHeight, 0];
62
49
  switch (axis.order) {
63
50
  case 'sortDesc':
64
51
  case 'reverse': {
@@ -74,12 +61,12 @@ function getYScaleRange(args) {
74
61
  }
75
62
  // eslint-disable-next-line complexity
76
63
  export function createYScale(args) {
77
- const { axis, boundsHeight, series, seriesOptions } = args;
64
+ const { axis, boundsHeight, series } = args;
78
65
  const yMinProps = get(axis, 'min');
79
66
  const yMaxProps = get(axis, 'max');
80
67
  const yCategories = get(axis, 'categories');
81
68
  const yTimestamps = get(axis, 'timestamps');
82
- const range = getYScaleRange({ axis, boundsHeight, series, seriesOptions });
69
+ const range = getYScaleRange({ axis, boundsHeight });
83
70
  switch (axis.type) {
84
71
  case 'linear':
85
72
  case 'logarithmic': {
@@ -100,7 +87,23 @@ export function createYScale(args) {
100
87
  yMax = hasSeriesWithVolumeOnYAxis ? Math.max(yMaxDomain, 0) : yMaxDomain;
101
88
  }
102
89
  const scaleFn = axis.type === 'logarithmic' ? scaleLog : scaleLinear;
103
- return scaleFn().domain([yMin, yMax]).range(range).nice();
90
+ const scale = scaleFn().domain([yMin, yMax]).range(range);
91
+ let offsetMin = 0;
92
+ let offsetMax = boundsHeight * axis.maxPadding;
93
+ const barYSeries = series.filter((s) => s.type === SeriesType.BarY);
94
+ if (barYSeries.length) {
95
+ if (domain.length > 1) {
96
+ const bandWidth = getBandSize({
97
+ scale: scale,
98
+ domain: domain,
99
+ });
100
+ offsetMin += bandWidth / 2;
101
+ offsetMax += bandWidth / 2;
102
+ }
103
+ }
104
+ const domainOffsetMin = Math.abs(scale.invert(offsetMin) - scale.invert(0));
105
+ const domainOffsetMax = Math.abs(scale.invert(offsetMax) - scale.invert(0));
106
+ return scale.domain([yMin - domainOffsetMin, yMax + domainOffsetMax]);
104
107
  }
105
108
  break;
106
109
  }
@@ -132,7 +135,23 @@ export function createYScale(args) {
132
135
  const [yMinTimestamp, yMaxTimestamp] = extent(domain);
133
136
  const yMin = typeof yMinProps === 'number' ? yMinProps : yMinTimestamp;
134
137
  const yMax = typeof yMaxProps === 'number' ? yMaxProps : yMaxTimestamp;
135
- return scaleUtc().domain([yMin, yMax]).range(range).nice();
138
+ const scale = scaleUtc().domain([yMin, yMax]).range(range);
139
+ let offsetMin = 0;
140
+ let offsetMax = boundsHeight * axis.maxPadding;
141
+ const barYSeries = series.filter((s) => s.type === SeriesType.BarY);
142
+ if (barYSeries.length) {
143
+ if (Object.keys(domain).length > 1) {
144
+ const bandWidth = getBandSize({
145
+ scale: scale,
146
+ domain: domain,
147
+ });
148
+ offsetMin += bandWidth / 2;
149
+ offsetMax += bandWidth / 2;
150
+ }
151
+ }
152
+ const domainOffsetMin = Math.abs(scale.invert(offsetMin).getTime() - scale.invert(0).getTime());
153
+ const domainOffsetMax = Math.abs(scale.invert(offsetMax).getTime() - scale.invert(0).getTime());
154
+ return scale.domain([yMin - domainOffsetMin, yMax + domainOffsetMax]);
136
155
  }
137
156
  }
138
157
  }
@@ -207,6 +226,9 @@ export function createXScale(args) {
207
226
  if (hasOnlyNullValues || domainData.length === 0) {
208
227
  return undefined;
209
228
  }
229
+ if (series.some((s) => s.stacking === 'percent')) {
230
+ return scaleLinear().domain([0, 100]).range(range);
231
+ }
210
232
  if (hasNumberAndNullValues) {
211
233
  const [xMinDomain, xMaxDomain] = extent(domainData);
212
234
  let xMin;
@@ -304,7 +326,6 @@ const createScales = (args) => {
304
326
  axis,
305
327
  boundsHeight: axisHeight,
306
328
  series: visibleAxisSeries.length ? visibleAxisSeries : axisSeries,
307
- seriesOptions,
308
329
  });
309
330
  }),
310
331
  };
@@ -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];
@@ -124,12 +123,20 @@ export const prepareBarYData = async (args) => {
124
123
  style: dataLabels.style,
125
124
  html: dataLabels.html,
126
125
  });
127
- htmlElements.push({
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,36 @@ 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 constrainedPosition = getSvgLabelConstraintedPosition({
149
+ boundsHeight,
150
+ boundsWidth,
151
+ height,
152
+ width,
142
153
  x,
143
154
  y: y + height / 2,
155
+ });
156
+ labels.push({
157
+ size: { width, height },
158
+ series: prepared.series,
159
+ style: dataLabels.style,
144
160
  text: content,
145
161
  textAnchor: dataLabels.inside ? 'middle' : 'right',
146
- style: dataLabels.style,
147
- series: prepared.series,
148
- size: { width, height },
162
+ x: constrainedPosition.x,
163
+ y: constrainedPosition.y,
149
164
  });
150
165
  }
151
166
  }
152
167
  }
153
- if (!((_a = result[0]) === null || _a === void 0 ? void 0 : _a.series.dataLabels.allowOverlap)) {
168
+ const allowOverlap = (_a = result[0]) === null || _a === void 0 ? void 0 : _a.series.dataLabels.allowOverlap;
169
+ if (labels.length && !allowOverlap) {
154
170
  labels = filterOverlappingLabels(labels);
155
171
  }
172
+ else if (htmlElements.length && !allowOverlap) {
173
+ htmlElements = filterOverlappingLabels(htmlElements);
174
+ }
156
175
  return {
157
176
  shapes: result,
158
177
  labels,
159
178
  htmlElements,
160
179
  };
161
- };
180
+ }
@@ -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: {