@gravity-ui/charts 1.12.0 → 1.13.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 (71) hide show
  1. package/dist/cjs/components/Axis/AxisX.js +62 -36
  2. package/dist/cjs/components/Axis/AxisY.js +67 -31
  3. package/dist/cjs/components/ChartInner/styles.css +1 -0
  4. package/dist/cjs/components/ChartInner/useChartInnerProps.js +3 -3
  5. package/dist/cjs/components/Tooltip/DefaultTooltipContent/index.js +1 -1
  6. package/dist/cjs/hooks/useAxisScales/index.js +19 -6
  7. package/dist/cjs/hooks/useChartOptions/types.d.ts +5 -0
  8. package/dist/cjs/hooks/useChartOptions/utils.d.ts +11 -0
  9. package/dist/cjs/hooks/useChartOptions/utils.js +27 -0
  10. package/dist/cjs/hooks/useChartOptions/x-axis.js +5 -1
  11. package/dist/cjs/hooks/useChartOptions/y-axis.js +5 -1
  12. package/dist/cjs/hooks/useSeries/prepare-area.d.ts +1 -1
  13. package/dist/cjs/hooks/useSeries/prepare-bar-y.d.ts +3 -0
  14. package/dist/cjs/hooks/useSeries/prepare-bar-y.js +5 -2
  15. package/dist/cjs/hooks/useSeries/prepare-line.d.ts +1 -1
  16. package/dist/cjs/hooks/useSeries/prepare-radar.d.ts +1 -1
  17. package/dist/cjs/hooks/useSeries/types.d.ts +3 -0
  18. package/dist/cjs/hooks/useShapes/bar-x/prepare-data.js +15 -12
  19. package/dist/cjs/hooks/useShapes/bar-y/index.d.ts +2 -2
  20. package/dist/cjs/hooks/useShapes/bar-y/index.js +5 -9
  21. package/dist/cjs/hooks/useShapes/bar-y/prepare-data.d.ts +2 -2
  22. package/dist/cjs/hooks/useShapes/bar-y/prepare-data.js +65 -47
  23. package/dist/cjs/hooks/useShapes/bar-y/types.d.ts +7 -2
  24. package/dist/cjs/hooks/useShapes/index.js +1 -1
  25. package/dist/cjs/hooks/utils/bar-y.d.ts +3 -3
  26. package/dist/cjs/hooks/utils/bar-y.js +7 -21
  27. package/dist/cjs/types/chart/axis.d.ts +13 -1
  28. package/dist/cjs/types/chart/bar-y.d.ts +10 -0
  29. package/dist/cjs/types/chart/series.d.ts +10 -0
  30. package/dist/cjs/utils/chart/axis-generators/bottom.js +26 -13
  31. package/dist/cjs/utils/chart/get-closest-data.js +13 -12
  32. package/dist/cjs/utils/chart/index.js +1 -1
  33. package/dist/cjs/utils/chart/series/sorting.d.ts +6 -2
  34. package/dist/cjs/utils/chart/series/sorting.js +29 -4
  35. package/dist/cjs/utils/chart/zoom.js +2 -1
  36. package/dist/esm/components/Axis/AxisX.js +62 -36
  37. package/dist/esm/components/Axis/AxisY.js +67 -31
  38. package/dist/esm/components/ChartInner/styles.css +1 -0
  39. package/dist/esm/components/ChartInner/useChartInnerProps.js +3 -3
  40. package/dist/esm/components/Tooltip/DefaultTooltipContent/index.js +1 -1
  41. package/dist/esm/hooks/useAxisScales/index.js +19 -6
  42. package/dist/esm/hooks/useChartOptions/types.d.ts +5 -0
  43. package/dist/esm/hooks/useChartOptions/utils.d.ts +11 -0
  44. package/dist/esm/hooks/useChartOptions/utils.js +27 -0
  45. package/dist/esm/hooks/useChartOptions/x-axis.js +5 -1
  46. package/dist/esm/hooks/useChartOptions/y-axis.js +5 -1
  47. package/dist/esm/hooks/useSeries/prepare-area.d.ts +1 -1
  48. package/dist/esm/hooks/useSeries/prepare-bar-y.d.ts +3 -0
  49. package/dist/esm/hooks/useSeries/prepare-bar-y.js +5 -2
  50. package/dist/esm/hooks/useSeries/prepare-line.d.ts +1 -1
  51. package/dist/esm/hooks/useSeries/prepare-radar.d.ts +1 -1
  52. package/dist/esm/hooks/useSeries/types.d.ts +3 -0
  53. package/dist/esm/hooks/useShapes/bar-x/prepare-data.js +15 -12
  54. package/dist/esm/hooks/useShapes/bar-y/index.d.ts +2 -2
  55. package/dist/esm/hooks/useShapes/bar-y/index.js +5 -9
  56. package/dist/esm/hooks/useShapes/bar-y/prepare-data.d.ts +2 -2
  57. package/dist/esm/hooks/useShapes/bar-y/prepare-data.js +65 -47
  58. package/dist/esm/hooks/useShapes/bar-y/types.d.ts +7 -2
  59. package/dist/esm/hooks/useShapes/index.js +1 -1
  60. package/dist/esm/hooks/utils/bar-y.d.ts +3 -3
  61. package/dist/esm/hooks/utils/bar-y.js +7 -21
  62. package/dist/esm/types/chart/axis.d.ts +13 -1
  63. package/dist/esm/types/chart/bar-y.d.ts +10 -0
  64. package/dist/esm/types/chart/series.d.ts +10 -0
  65. package/dist/esm/utils/chart/axis-generators/bottom.js +26 -13
  66. package/dist/esm/utils/chart/get-closest-data.js +13 -12
  67. package/dist/esm/utils/chart/index.js +1 -1
  68. package/dist/esm/utils/chart/series/sorting.d.ts +6 -2
  69. package/dist/esm/utils/chart/series/sorting.js +29 -4
  70. package/dist/esm/utils/chart/zoom.js +2 -1
  71. package/package.json +1 -1
@@ -95,36 +95,39 @@ export const prepareBarXData = async (args) => {
95
95
  const rectGap = Math.max(bandWidth * barPadding, MIN_BAR_GAP);
96
96
  const rectWidth = Math.max(MIN_BAR_WIDTH, Math.min(groupWidth / maxGroupSize - rectGap, barMaxWidth));
97
97
  const result = [];
98
- await Promise.all(Object.entries(data).map(async ([xValue, val]) => {
98
+ const groupedData = Object.entries(data);
99
+ for (let groupedDataIndex = 0; groupedDataIndex < groupedData.length; groupedDataIndex++) {
100
+ const [xValue, val] = groupedData[groupedDataIndex];
99
101
  const stacks = Object.values(val);
100
102
  const currentGroupWidth = rectWidth * stacks.length + rectGap * (stacks.length - 1);
101
- await Promise.all(stacks.map(async (yValues, groupItemIndex) => {
103
+ for (let groupItemIndex = 0; groupItemIndex < stacks.length; groupItemIndex++) {
104
+ const yValues = stacks[groupItemIndex];
102
105
  let stackHeight = 0;
103
106
  const stackItems = [];
104
107
  const sortedData = sortKey
105
108
  ? sort(yValues, (a, b) => comparator(get(a, sortKey), get(b, sortKey)))
106
109
  : yValues;
107
- await Promise.all(sortedData.map(async (yValue, yValueIndex) => {
110
+ for (let yValueIndex = 0; yValueIndex < sortedData.length; yValueIndex++) {
111
+ const yValue = sortedData[yValueIndex];
108
112
  const yAxisIndex = yValue.series.yAxis;
109
113
  const seriesYScale = yScale[yAxisIndex];
110
114
  let xCenter;
111
115
  if (xAxis.type === 'category') {
112
116
  const xBandScale = xScale;
113
- xCenter =
114
- (xBandScale(xValue) || 0) +
115
- xBandScale.bandwidth() / 2;
117
+ xCenter = (xBandScale(xValue) || 0) + xBandScale.bandwidth() / 2;
116
118
  }
117
119
  else {
118
120
  const xLinearScale = xScale;
119
121
  xCenter = xLinearScale(Number(xValue));
120
122
  }
121
- const x = xCenter -
122
- currentGroupWidth / 2 +
123
- (rectWidth + rectGap) * groupItemIndex;
123
+ const x = xCenter - currentGroupWidth / 2 + (rectWidth + rectGap) * groupItemIndex;
124
124
  const yDataValue = yValue.data.y;
125
125
  const y = seriesYScale(yDataValue);
126
126
  const base = seriesYScale(0);
127
127
  const height = yDataValue > 0 ? base - y : y - base;
128
+ if (height <= 0) {
129
+ continue;
130
+ }
128
131
  const barData = {
129
132
  x,
130
133
  y: yDataValue > 0 ? y - stackHeight : seriesYScale(0),
@@ -151,7 +154,7 @@ export const prepareBarXData = async (args) => {
151
154
  }
152
155
  stackItems.push(barData);
153
156
  stackHeight += height + 1;
154
- }));
157
+ }
155
158
  if (series.some((s) => s.stacking === 'percent')) {
156
159
  let acc = 0;
157
160
  const ratio = plotHeight / (stackHeight - stackItems.length);
@@ -162,7 +165,7 @@ export const prepareBarXData = async (args) => {
162
165
  });
163
166
  }
164
167
  result.push(...stackItems);
165
- }));
166
- }));
168
+ }
169
+ }
167
170
  return result;
168
171
  };
@@ -1,11 +1,11 @@
1
1
  import React from 'react';
2
2
  import type { Dispatch } from 'd3';
3
3
  import type { PreparedSeriesOptions } from '../../useSeries/types';
4
- import type { PreparedBarYData } from './types';
4
+ import type { BarYShapesArgs } from './types';
5
5
  export { prepareBarYData } from './prepare-data';
6
6
  type Args = {
7
7
  dispatcher: Dispatch<object>;
8
- preparedData: PreparedBarYData[];
8
+ preparedData: BarYShapesArgs;
9
9
  seriesOptions: PreparedSeriesOptions;
10
10
  htmlLayout: HTMLElement | null;
11
11
  clipPathId: string;
@@ -7,7 +7,7 @@ import { getRectPath } from '../utils';
7
7
  export { prepareBarYData } from './prepare-data';
8
8
  const b = block('bar-y');
9
9
  export const BarYSeriesShapes = (args) => {
10
- const { dispatcher, preparedData, seriesOptions, htmlLayout, clipPathId } = args;
10
+ const { dispatcher, preparedData: { shapes: preparedData, labels: dataLabels, htmlElements }, seriesOptions, htmlLayout, clipPathId, } = args;
11
11
  const hoveredDataRef = React.useRef(null);
12
12
  const ref = React.useRef(null);
13
13
  React.useEffect(() => {
@@ -39,14 +39,10 @@ export const BarYSeriesShapes = (args) => {
39
39
  .attr('height', (d) => d.height)
40
40
  .attr('width', (d) => d.width)
41
41
  .attr('fill', (d) => d.color)
42
+ .attr('stroke', (d) => d.borderColor)
43
+ .attr('stroke-width', (d) => d.borderWidth)
42
44
  .attr('opacity', (d) => d.data.opacity || null)
43
45
  .attr('cursor', (d) => d.series.cursor);
44
- const dataLabels = preparedData.reduce((acc, d) => {
45
- if (d.label) {
46
- acc.push(d.label);
47
- }
48
- return acc;
49
- }, []);
50
46
  const labelSelection = svgElement
51
47
  .selectAll('text')
52
48
  .data(dataLabels)
@@ -97,8 +93,8 @@ export const BarYSeriesShapes = (args) => {
97
93
  return () => {
98
94
  dispatcher.on('hover-shape.bar-y', null);
99
95
  };
100
- }, [dispatcher, preparedData, seriesOptions]);
96
+ }, [dataLabels, dispatcher, preparedData, seriesOptions]);
101
97
  return (React.createElement(React.Fragment, null,
102
98
  React.createElement("g", { ref: ref, className: b(), clipPath: `url(#${clipPathId})` }),
103
- React.createElement(HtmlLayer, { preparedData: preparedData, htmlLayout: htmlLayout })));
99
+ React.createElement(HtmlLayer, { preparedData: { htmlElements }, htmlLayout: htmlLayout })));
104
100
  };
@@ -1,7 +1,7 @@
1
1
  import type { ChartScale } from '../../useAxisScales';
2
2
  import type { PreparedAxis } from '../../useChartOptions/types';
3
3
  import type { PreparedBarYSeries, PreparedSeriesOptions } from '../../useSeries/types';
4
- import type { PreparedBarYData } from './types';
4
+ import type { BarYShapesArgs } from './types';
5
5
  export declare const prepareBarYData: (args: {
6
6
  series: PreparedBarYSeries[];
7
7
  seriesOptions: PreparedSeriesOptions;
@@ -9,4 +9,4 @@ export declare const prepareBarYData: (args: {
9
9
  xScale: ChartScale;
10
10
  yAxis: PreparedAxis[];
11
11
  yScale: ChartScale[];
12
- }) => Promise<PreparedBarYData[]>;
12
+ }) => Promise<BarYShapesArgs>;
@@ -1,47 +1,11 @@
1
1
  import { ascending, descending, sort } from 'd3';
2
2
  import get from 'lodash/get';
3
- import { getLabelsSize } from '../../../utils';
3
+ import { filterOverlappingLabels, getLabelsSize, getTextSizeFn } from '../../../utils';
4
4
  import { getFormattedValue } from '../../../utils/chart/format';
5
5
  import { getBarYLayoutForCategoryScale, getBarYLayoutForNumericScale, groupBarYDataByYValue, } from '../../utils';
6
6
  const DEFAULT_LABEL_PADDING = 7;
7
- async function setLabel(prepared) {
8
- const dataLabels = prepared.series.dataLabels;
9
- if (!dataLabels.enabled) {
10
- return;
11
- }
12
- const data = prepared.data;
13
- const content = getFormattedValue(Object.assign({ value: data.label || data.x }, dataLabels));
14
- const { maxHeight: height, maxWidth: width } = await getLabelsSize({
15
- labels: [content],
16
- style: dataLabels.style,
17
- html: dataLabels.html,
18
- });
19
- const x = dataLabels.inside
20
- ? prepared.x + prepared.width / 2
21
- : prepared.x + prepared.width + DEFAULT_LABEL_PADDING;
22
- const y = prepared.y + prepared.height / 2;
23
- if (dataLabels.html) {
24
- prepared.htmlElements.push({
25
- x,
26
- y: y - height / 2,
27
- content,
28
- size: { width, height },
29
- style: dataLabels.style,
30
- });
31
- }
32
- else {
33
- prepared.label = {
34
- x,
35
- y: y + height / 2,
36
- text: content,
37
- textAnchor: dataLabels.inside ? 'middle' : 'right',
38
- style: dataLabels.style,
39
- series: prepared.series,
40
- size: { width, height },
41
- };
42
- }
43
- }
44
7
  export const prepareBarYData = async (args) => {
8
+ var _a;
45
9
  const { series, seriesOptions, yAxis, xScale, yScale: [yScale], } = args;
46
10
  const xLinearScale = xScale;
47
11
  const yLinearScale = yScale;
@@ -66,16 +30,17 @@ export const prepareBarYData = async (args) => {
66
30
  const { bandSize, barGap, barSize } = yAxis[0].type === 'category'
67
31
  ? getBarYLayoutForCategoryScale({ groupedData, seriesOptions, yScale })
68
32
  : getBarYLayoutForNumericScale({
69
- series,
33
+ groupedData,
70
34
  seriesOptions,
71
35
  plotHeight: plotHeight - plotHeight * yAxis[0].maxPadding,
72
36
  });
73
37
  const result = [];
38
+ const baseRangeValue = xLinearScale.range()[0];
74
39
  Object.entries(groupedData).forEach(([yValue, val]) => {
75
40
  const stacks = Object.values(val);
76
41
  const currentBarHeight = barSize * stacks.length + barGap * (stacks.length - 1);
77
42
  stacks.forEach((measureValues, groupItemIndex) => {
78
- const base = xLinearScale(0);
43
+ const base = xLinearScale(0 - measureValues[0].series.borderWidth);
79
44
  let stackSum = base;
80
45
  const stackItems = [];
81
46
  const sortedData = sortKey
@@ -93,17 +58,21 @@ export const prepareBarYData = async (args) => {
93
58
  }
94
59
  const y = center - currentBarHeight / 2 + (barSize + barGap) * groupItemIndex;
95
60
  const xValue = Number(data.x);
96
- const width = xValue > 0 ? xLinearScale(xValue) - base : base - xLinearScale(xValue);
61
+ const width = Math.abs(xLinearScale(xValue) - base);
62
+ if (width <= 0) {
63
+ return;
64
+ }
97
65
  const item = {
98
- x: xValue > 0 ? stackSum : stackSum - width,
66
+ x: xValue > baseRangeValue ? stackSum : stackSum - width,
99
67
  y,
100
68
  width,
101
69
  height: barSize,
102
70
  color: data.color || s.color,
71
+ borderColor: s.borderColor,
72
+ borderWidth: s.borderWidth,
103
73
  opacity: get(data, 'opacity', null),
104
74
  data,
105
75
  series: s,
106
- htmlElements: [],
107
76
  isLastStackItem: xValueIndex === sortedData.length - 1,
108
77
  };
109
78
  stackItems.push(item);
@@ -121,8 +90,57 @@ export const prepareBarYData = async (args) => {
121
90
  result.push(...stackItems);
122
91
  });
123
92
  });
124
- await Promise.all(result.map(async (d) => {
125
- await setLabel(d);
126
- }));
127
- return result;
93
+ let labels = [];
94
+ const htmlElements = [];
95
+ const map = new Map();
96
+ for (let i = 0; i < result.length; i++) {
97
+ const prepared = result[i];
98
+ const dataLabels = prepared.series.dataLabels;
99
+ if (dataLabels.enabled) {
100
+ const data = prepared.data;
101
+ const content = getFormattedValue(Object.assign({ value: data.label || data.x }, dataLabels));
102
+ const x = dataLabels.inside
103
+ ? prepared.x + prepared.width / 2
104
+ : prepared.x + prepared.width + DEFAULT_LABEL_PADDING;
105
+ const y = prepared.y + prepared.height / 2;
106
+ if (dataLabels.html) {
107
+ const { maxHeight: height, maxWidth: width } = await getLabelsSize({
108
+ labels: [content],
109
+ style: dataLabels.style,
110
+ html: dataLabels.html,
111
+ });
112
+ htmlElements.push({
113
+ x,
114
+ y: y - height / 2,
115
+ content,
116
+ size: { width, height },
117
+ style: dataLabels.style,
118
+ });
119
+ }
120
+ else {
121
+ if (!map.has(dataLabels.style)) {
122
+ map.set(dataLabels.style, getTextSizeFn({ style: dataLabels.style }));
123
+ }
124
+ const getTextSize = map.get(dataLabels.style);
125
+ const { width, height } = await getTextSize(content);
126
+ labels.push({
127
+ x,
128
+ y: y + height / 2,
129
+ text: content,
130
+ textAnchor: dataLabels.inside ? 'middle' : 'right',
131
+ style: dataLabels.style,
132
+ series: prepared.series,
133
+ size: { width, height },
134
+ });
135
+ }
136
+ }
137
+ }
138
+ if (!((_a = result[0]) === null || _a === void 0 ? void 0 : _a.series.dataLabels.allowOverlap)) {
139
+ labels = filterOverlappingLabels(labels);
140
+ }
141
+ return {
142
+ shapes: result,
143
+ labels,
144
+ htmlElements,
145
+ };
128
146
  };
@@ -6,9 +6,14 @@ export type PreparedBarYData = Omit<TooltipDataChunkBarX, 'series'> & {
6
6
  width: number;
7
7
  height: number;
8
8
  color: string;
9
+ borderWidth: number;
10
+ borderColor: string;
9
11
  opacity: number | null;
10
12
  series: PreparedBarYSeries;
11
- label?: LabelData;
12
- htmlElements: HtmlItem[];
13
13
  isLastStackItem: boolean;
14
14
  };
15
+ export type BarYShapesArgs = {
16
+ shapes: PreparedBarYData[];
17
+ labels: LabelData[];
18
+ htmlElements: HtmlItem[];
19
+ };
@@ -64,7 +64,7 @@ export const useShapes = (args) => {
64
64
  yScale,
65
65
  });
66
66
  shapes.push(React.createElement(BarYSeriesShapes, { key: "bar-y", dispatcher: dispatcher, seriesOptions: seriesOptions, preparedData: preparedData, htmlLayout: htmlLayout, clipPathId: clipPathId }));
67
- shapesData.push(...preparedData);
67
+ shapesData.push(...preparedData.shapes);
68
68
  }
69
69
  break;
70
70
  }
@@ -2,14 +2,14 @@ import type { BarYSeries, BarYSeriesData } from '../../types';
2
2
  import type { ChartScale } from '../useAxisScales';
3
3
  import type { PreparedAxis } from '../useChartOptions/types';
4
4
  import type { PreparedBarYSeries, PreparedSeriesOptions } from '../useSeries/types';
5
- export declare function groupBarYDataByYValue(series: PreparedBarYSeries[], yAxis: PreparedAxis[]): Record<string | number, Record<string, {
5
+ export declare function groupBarYDataByYValue<T extends BarYSeries | PreparedBarYSeries>(series: T[], yAxis: PreparedAxis[]): Record<string | number, Record<string, {
6
6
  data: BarYSeriesData;
7
- series: PreparedBarYSeries;
7
+ series: T;
8
8
  }[]>>;
9
9
  export declare function getBarYLayoutForNumericScale(args: {
10
10
  plotHeight: number;
11
- series: (BarYSeries | PreparedBarYSeries)[];
12
11
  seriesOptions: PreparedSeriesOptions;
12
+ groupedData: ReturnType<typeof groupBarYDataByYValue>;
13
13
  }): {
14
14
  bandSize: number;
15
15
  barGap: number;
@@ -2,6 +2,7 @@ import { max } from 'd3';
2
2
  import get from 'lodash/get';
3
3
  import { getDataCategoryValue } from '../../utils';
4
4
  import { MIN_BAR_GAP, MIN_BAR_GROUP_GAP, MIN_BAR_WIDTH } from '../constants';
5
+ import { getSeriesStackId } from '../useSeries/utils';
5
6
  export function groupBarYDataByYValue(series, yAxis) {
6
7
  const data = {};
7
8
  series.forEach((s) => {
@@ -16,37 +17,22 @@ export function groupBarYDataByYValue(series, yAxis) {
16
17
  if (!data[key]) {
17
18
  data[key] = {};
18
19
  }
19
- if (!data[key][s.stackId]) {
20
- data[key][s.stackId] = [];
20
+ const stackId = getSeriesStackId(s);
21
+ if (!data[key][stackId]) {
22
+ data[key][stackId] = [];
21
23
  }
22
- data[key][s.stackId].push({ data: d, series: s });
24
+ data[key][stackId].push({ data: d, series: s });
23
25
  }
24
26
  });
25
27
  });
26
28
  return data;
27
29
  }
28
30
  export function getBarYLayoutForNumericScale(args) {
29
- const { plotHeight, series, seriesOptions } = args;
31
+ const { plotHeight, groupedData, seriesOptions } = args;
30
32
  const barMaxWidth = get(seriesOptions, 'bar-y.barMaxWidth');
31
33
  const barPadding = get(seriesOptions, 'bar-y.barPadding');
32
34
  const groupPadding = get(seriesOptions, 'bar-y.groupPadding');
33
- let yValuesWithoutStacking = 0;
34
- const yValuesByStackingIdMap = {};
35
- series.forEach((s) => {
36
- s.data.forEach((d) => {
37
- if (s.stackId) {
38
- if (!yValuesByStackingIdMap[s.stackId]) {
39
- yValuesByStackingIdMap[s.stackId] = new Set();
40
- }
41
- yValuesByStackingIdMap[s.stackId].add(Number(d.y));
42
- }
43
- else {
44
- yValuesWithoutStacking += 1;
45
- }
46
- });
47
- });
48
- const stackedSeriesLength = Object.values(yValuesByStackingIdMap).reduce((acc, set) => acc + set.size, 0);
49
- const dataLength = yValuesWithoutStacking + stackedSeriesLength;
35
+ const dataLength = Object.values(groupedData).reduce((sum, items) => sum + Object.keys(items).length, 0);
50
36
  const bandSize = plotHeight / dataLength;
51
37
  const groupGap = Math.max(bandSize * groupPadding, MIN_BAR_GROUP_GAP);
52
38
  const groupSize = bandSize - groupGap;
@@ -78,6 +78,10 @@ export interface ChartAxis {
78
78
  plotBands?: AxisPlotBand[];
79
79
  /** Whether axis, including axis title, line, ticks and labels, should be visible. */
80
80
  visible?: boolean;
81
+ /** Setting the order of the axis values. It is not applied by default.
82
+ * the "reverse" value is needed to use the reverse order without sorting
83
+ */
84
+ order?: 'sortAsc' | 'sortDesc' | 'reverse';
81
85
  }
82
86
  export interface ChartXAxis extends ChartAxis {
83
87
  }
@@ -92,6 +96,14 @@ export interface AxisPlot {
92
96
  * @default 1
93
97
  * */
94
98
  opacity?: number;
99
+ label?: {
100
+ text: string;
101
+ style?: Partial<BaseTextStyle>;
102
+ /** The pixel padding for label.
103
+ * @default 5
104
+ */
105
+ padding?: number;
106
+ };
95
107
  }
96
108
  export interface AxisPlotLine extends AxisPlot {
97
109
  /** The position of the line in axis units. */
@@ -121,7 +133,7 @@ export interface AxisPlotBand extends AxisPlot {
121
133
  */
122
134
  to: number | string;
123
135
  }
124
- export interface AxisCrosshair extends Omit<AxisPlotLine, 'value'> {
136
+ export interface AxisCrosshair extends Omit<AxisPlotLine, 'value' | 'label'> {
125
137
  /** Whether the crosshair should snap to the point or follow the pointer independent of points.
126
138
  * @default true
127
139
  */
@@ -29,6 +29,16 @@ export interface BarYSeries<T = MeaningfulAny> extends BaseSeries {
29
29
  name: string;
30
30
  /** The main color of the series (hex, rgba) */
31
31
  color?: string;
32
+ /**
33
+ * The width of the border surrounding each bar.
34
+ *
35
+ * @default 0
36
+ */
37
+ borderWidth?: number;
38
+ /**
39
+ * The color of the border surrounding each bar.
40
+ */
41
+ borderColor?: string;
32
42
  /**
33
43
  * The corner radius of the border surrounding each bar.
34
44
  * @default 0
@@ -115,6 +115,16 @@ export interface ChartSeriesOptions {
115
115
  * @default 0.2
116
116
  */
117
117
  groupPadding?: number;
118
+ /**
119
+ * The width of the border surrounding each bar.
120
+ *
121
+ * @default 0
122
+ */
123
+ borderWidth?: number;
124
+ /**
125
+ * The color of the border surrounding each bar.
126
+ */
127
+ borderColor?: string;
118
128
  /**
119
129
  * The corner radius of the border surrounding each bar.
120
130
  * @default 0
@@ -93,18 +93,31 @@ export async function axisBottom(args) {
93
93
  let elementX = 0;
94
94
  // add an ellipsis to the labels that go beyond the boundaries of the chart
95
95
  // and remove overlapping labels
96
- labels.each(function (_d, i, nodes) {
97
- var _a, _b;
98
- const currentElement = this;
99
- const currentElementPosition = currentElement.getBoundingClientRect();
96
+ labels
97
+ .nodes()
98
+ .map((element) => {
99
+ const r = element.getBoundingClientRect();
100
+ return {
101
+ left: r.left,
102
+ right: r.right,
103
+ node: element,
104
+ };
105
+ }, {})
106
+ .sort((a, b) => {
107
+ return a.left - b.left;
108
+ })
109
+ .forEach(function (item, i, nodes) {
110
+ var _a, _b, _c, _d;
111
+ const { node, left, right: currentElementPositionRigth } = item;
112
+ const currentElement = node;
100
113
  if (i === 0) {
101
114
  const text = select(currentElement);
102
- const nextElement = nodes[i + 1];
115
+ const nextElement = (_a = nodes[i + 1]) === null || _a === void 0 ? void 0 : _a.node;
103
116
  const nextElementPosition = nextElement === null || nextElement === void 0 ? void 0 : nextElement.getBoundingClientRect();
104
- if (currentElementPosition.left < leftmostLimit) {
105
- const rightmostPossiblePoint = (_a = nextElementPosition === null || nextElementPosition === void 0 ? void 0 : nextElementPosition.left) !== null && _a !== void 0 ? _a : right;
117
+ if (left < leftmostLimit) {
118
+ const rightmostPossiblePoint = (_b = nextElementPosition === null || nextElementPosition === void 0 ? void 0 : nextElementPosition.left) !== null && _b !== void 0 ? _b : right;
106
119
  const remainSpace = rightmostPossiblePoint -
107
- currentElementPosition.right +
120
+ currentElementPositionRigth +
108
121
  x -
109
122
  labelsMargin;
110
123
  text.attr('text-anchor', 'start');
@@ -112,16 +125,16 @@ export async function axisBottom(args) {
112
125
  }
113
126
  }
114
127
  else {
115
- if (currentElementPosition.left < elementX) {
116
- (_b = currentElement.closest('.tick')) === null || _b === void 0 ? void 0 : _b.remove();
128
+ if (left < elementX) {
129
+ (_c = currentElement.closest('.tick')) === null || _c === void 0 ? void 0 : _c.remove();
117
130
  return;
118
131
  }
119
- elementX = currentElementPosition.right + labelsPaddings;
132
+ elementX = currentElementPositionRigth + labelsPaddings;
120
133
  if (i === nodes.length - 1) {
121
- const prevElement = nodes[i - 1];
134
+ const prevElement = (_d = nodes[i - 1]) === null || _d === void 0 ? void 0 : _d.node;
122
135
  const text = select(currentElement);
123
136
  const prevElementPosition = prevElement === null || prevElement === void 0 ? void 0 : prevElement.getBoundingClientRect();
124
- const lackingSpace = Math.max(0, currentElementPosition.right - right);
137
+ const lackingSpace = Math.max(0, currentElementPositionRigth - right);
125
138
  if (lackingSpace) {
126
139
  const remainSpace = right - ((prevElementPosition === null || prevElementPosition === void 0 ? void 0 : prevElementPosition.right) || 0) - labelsPaddings;
127
140
  const translateX = -lackingSpace;
@@ -49,7 +49,7 @@ export function getClosestPoints(args) {
49
49
  const groups = groupBy(shapesData, getSeriesType);
50
50
  // eslint-disable-next-line complexity
51
51
  Object.entries(groups).forEach(([seriesType, list]) => {
52
- var _a, _b;
52
+ var _a, _b, _c;
53
53
  switch (seriesType) {
54
54
  case 'bar-x': {
55
55
  const points = list.map((d) => ({
@@ -106,26 +106,27 @@ export function getClosestPoints(args) {
106
106
  const points = list;
107
107
  const sorted = sort(points, (p) => p.y);
108
108
  const closestYIndex = bisector((p) => p.y).center(sorted, pointerY);
109
- let closestPoints = [];
110
- let closestXIndex = -1;
111
- if (closestYIndex !== -1) {
112
- const closestY = sorted[closestYIndex].y;
113
- closestPoints = sort(points.filter((p) => p.y === closestY), (p) => p.x);
109
+ const closestYPoint = sorted[closestYIndex];
110
+ let selectedPoints = [];
111
+ let closestPointXValue = -1;
112
+ if (closestYPoint) {
113
+ selectedPoints = points.filter((p) => p.data.y === closestYPoint.data.y);
114
+ const closestPoints = sort(selectedPoints.filter((p) => p.y === closestYPoint.y), (p) => p.x);
114
115
  const lastPoint = closestPoints[closestPoints.length - 1];
115
116
  if (pointerX < ((_a = closestPoints[0]) === null || _a === void 0 ? void 0 : _a.x)) {
116
- closestXIndex = 0;
117
+ closestPointXValue = closestPoints[0].x;
117
118
  }
118
119
  else if (lastPoint && pointerX > lastPoint.x + lastPoint.width) {
119
- closestXIndex = closestPoints.length - 1;
120
+ closestPointXValue = lastPoint.x;
120
121
  }
121
122
  else {
122
- closestXIndex = closestPoints.findIndex((p) => pointerX > p.x && pointerX < p.x + p.width);
123
+ closestPointXValue = (_b = closestPoints.find((p) => pointerX > p.x && pointerX < p.x + p.width)) === null || _b === void 0 ? void 0 : _b.x;
123
124
  }
124
125
  }
125
- result.push(...closestPoints.map((p, i) => ({
126
+ result.push(...selectedPoints.map((p) => ({
126
127
  data: p.data,
127
128
  series: p.series,
128
- closest: i === closestXIndex,
129
+ closest: p.x === closestPointXValue && p.y === closestYPoint.y,
129
130
  })));
130
131
  break;
131
132
  }
@@ -164,7 +165,7 @@ export function getClosestPoints(args) {
164
165
  }
165
166
  case 'treemap': {
166
167
  const data = list;
167
- const closestPoint = (_b = data[0]) === null || _b === void 0 ? void 0 : _b.leaves.find((l) => {
168
+ const closestPoint = (_c = data[0]) === null || _c === void 0 ? void 0 : _c.leaves.find((l) => {
168
169
  return (pointerX >= l.x0 && pointerX <= l.x1 && pointerY >= l.y0 && pointerY <= l.y1);
169
170
  });
170
171
  if (closestPoint) {
@@ -238,5 +238,5 @@ export function getClosestPointsRange(axis, points) {
238
238
  if (axis.type === 'category') {
239
239
  return undefined;
240
240
  }
241
- return points[1] - points[0];
241
+ return Math.abs(points[1] - points[0]);
242
242
  }
@@ -1,2 +1,6 @@
1
- import type { ChartSeries } from '../../../types';
2
- export declare function getSortedSeriesData(seriesData: ChartSeries[]): ChartSeries[];
1
+ import type { ChartAxis, ChartSeries } from '../../../types';
2
+ export declare function getSortedSeriesData({ seriesData, yAxes, xAxis, }: {
3
+ seriesData: ChartSeries[];
4
+ yAxes?: ChartAxis[];
5
+ xAxis?: ChartAxis;
6
+ }): ChartSeries[];
@@ -1,12 +1,37 @@
1
1
  import { sort } from 'd3';
2
+ import { isEmpty } from 'lodash';
3
+ import get from 'lodash/get';
2
4
  import { SeriesType } from '../../../constants';
3
- export function getSortedSeriesData(seriesData) {
5
+ import { getAxisCategories } from '../../../hooks/useChartOptions/utils';
6
+ function applyAxisCategoriesOrder({ series, axis, key, }) {
7
+ var _a, _b;
8
+ const originalCategories = (_a = axis === null || axis === void 0 ? void 0 : axis.categories) !== null && _a !== void 0 ? _a : [];
9
+ if (isEmpty(originalCategories)) {
10
+ return series;
11
+ }
12
+ const axisCategories = (_b = getAxisCategories(axis)) !== null && _b !== void 0 ? _b : [];
13
+ const order = Object.fromEntries(axisCategories.map((value, index) => [value, index]));
14
+ const newSeriesData = series.data.map((d) => {
15
+ const value = get(d, key);
16
+ if (typeof value === 'number') {
17
+ return Object.assign(Object.assign({}, d), { [key]: order[originalCategories[value]] });
18
+ }
19
+ return d;
20
+ });
21
+ return Object.assign(Object.assign({}, series), { data: newSeriesData });
22
+ }
23
+ export function getSortedSeriesData({ seriesData, yAxes, xAxis, }) {
4
24
  return seriesData.map((s) => {
5
- switch (s.type) {
25
+ const yAxis = yAxes === null || yAxes === void 0 ? void 0 : yAxes[0];
26
+ let sortedSeries = s;
27
+ sortedSeries = applyAxisCategoriesOrder({ series: sortedSeries, axis: yAxis, key: 'y' });
28
+ sortedSeries = applyAxisCategoriesOrder({ series: sortedSeries, axis: xAxis, key: 'x' });
29
+ switch (sortedSeries.type) {
6
30
  case SeriesType.Area: {
7
- s.data = sort(s.data, (d) => d.x);
31
+ sortedSeries = Object.assign(Object.assign({}, sortedSeries), { data: sort(sortedSeries.data, (d) => d.x) });
32
+ break;
8
33
  }
9
34
  }
10
- return s;
35
+ return sortedSeries;
11
36
  });
12
37
  }
@@ -1,4 +1,5 @@
1
1
  import { SeriesType } from '../../constants';
2
+ import { getAxisCategories } from '../../hooks/useChartOptions/utils';
2
3
  const SERIES_TYPE_WITH_HIDDEN_POINTS = [SeriesType.Area, SeriesType.Line];
3
4
  // eslint-disable-next-line complexity
4
5
  function isValueInRange(args) {
@@ -20,7 +21,7 @@ function isValueInRange(args) {
20
21
  return numValue >= numMin && numValue <= numMax;
21
22
  }
22
23
  case 'category': {
23
- const categories = (axis === null || axis === void 0 ? void 0 : axis.categories) || [];
24
+ const categories = getAxisCategories(axis) || [];
24
25
  if (typeof value === 'string' && typeof min === 'number' && typeof max === 'number') {
25
26
  const valueIndex = categories.indexOf(value);
26
27
  if (min === -1 || max === -1 || valueIndex === -1) {