@gravity-ui/charts 1.12.0 → 1.13.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 (75) 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/constants/defaults/series-options.d.ts +3 -1
  7. package/dist/cjs/constants/defaults/series-options.js +2 -0
  8. package/dist/cjs/hooks/useAxisScales/index.js +19 -6
  9. package/dist/cjs/hooks/useChartOptions/types.d.ts +5 -0
  10. package/dist/cjs/hooks/useChartOptions/utils.d.ts +11 -0
  11. package/dist/cjs/hooks/useChartOptions/utils.js +27 -0
  12. package/dist/cjs/hooks/useChartOptions/x-axis.js +5 -1
  13. package/dist/cjs/hooks/useChartOptions/y-axis.js +5 -1
  14. package/dist/cjs/hooks/useSeries/prepare-area.d.ts +1 -1
  15. package/dist/cjs/hooks/useSeries/prepare-bar-y.d.ts +3 -0
  16. package/dist/cjs/hooks/useSeries/prepare-bar-y.js +5 -2
  17. package/dist/cjs/hooks/useSeries/prepare-line.d.ts +1 -1
  18. package/dist/cjs/hooks/useSeries/prepare-radar.d.ts +1 -1
  19. package/dist/cjs/hooks/useSeries/types.d.ts +3 -0
  20. package/dist/cjs/hooks/useShapes/bar-x/prepare-data.js +24 -15
  21. package/dist/cjs/hooks/useShapes/bar-y/index.d.ts +2 -2
  22. package/dist/cjs/hooks/useShapes/bar-y/index.js +5 -9
  23. package/dist/cjs/hooks/useShapes/bar-y/prepare-data.d.ts +2 -2
  24. package/dist/cjs/hooks/useShapes/bar-y/prepare-data.js +86 -61
  25. package/dist/cjs/hooks/useShapes/bar-y/types.d.ts +7 -2
  26. package/dist/cjs/hooks/useShapes/index.js +1 -1
  27. package/dist/cjs/hooks/utils/bar-y.d.ts +3 -3
  28. package/dist/cjs/hooks/utils/bar-y.js +7 -21
  29. package/dist/cjs/types/chart/axis.d.ts +13 -1
  30. package/dist/cjs/types/chart/bar-y.d.ts +10 -0
  31. package/dist/cjs/types/chart/series.d.ts +20 -0
  32. package/dist/cjs/utils/chart/axis-generators/bottom.js +26 -13
  33. package/dist/cjs/utils/chart/get-closest-data.js +13 -12
  34. package/dist/cjs/utils/chart/index.js +1 -1
  35. package/dist/cjs/utils/chart/series/sorting.d.ts +6 -2
  36. package/dist/cjs/utils/chart/series/sorting.js +29 -4
  37. package/dist/cjs/utils/chart/zoom.js +2 -1
  38. package/dist/esm/components/Axis/AxisX.js +62 -36
  39. package/dist/esm/components/Axis/AxisY.js +67 -31
  40. package/dist/esm/components/ChartInner/styles.css +1 -0
  41. package/dist/esm/components/ChartInner/useChartInnerProps.js +3 -3
  42. package/dist/esm/components/Tooltip/DefaultTooltipContent/index.js +1 -1
  43. package/dist/esm/constants/defaults/series-options.d.ts +3 -1
  44. package/dist/esm/constants/defaults/series-options.js +2 -0
  45. package/dist/esm/hooks/useAxisScales/index.js +19 -6
  46. package/dist/esm/hooks/useChartOptions/types.d.ts +5 -0
  47. package/dist/esm/hooks/useChartOptions/utils.d.ts +11 -0
  48. package/dist/esm/hooks/useChartOptions/utils.js +27 -0
  49. package/dist/esm/hooks/useChartOptions/x-axis.js +5 -1
  50. package/dist/esm/hooks/useChartOptions/y-axis.js +5 -1
  51. package/dist/esm/hooks/useSeries/prepare-area.d.ts +1 -1
  52. package/dist/esm/hooks/useSeries/prepare-bar-y.d.ts +3 -0
  53. package/dist/esm/hooks/useSeries/prepare-bar-y.js +5 -2
  54. package/dist/esm/hooks/useSeries/prepare-line.d.ts +1 -1
  55. package/dist/esm/hooks/useSeries/prepare-radar.d.ts +1 -1
  56. package/dist/esm/hooks/useSeries/types.d.ts +3 -0
  57. package/dist/esm/hooks/useShapes/bar-x/prepare-data.js +24 -15
  58. package/dist/esm/hooks/useShapes/bar-y/index.d.ts +2 -2
  59. package/dist/esm/hooks/useShapes/bar-y/index.js +5 -9
  60. package/dist/esm/hooks/useShapes/bar-y/prepare-data.d.ts +2 -2
  61. package/dist/esm/hooks/useShapes/bar-y/prepare-data.js +86 -61
  62. package/dist/esm/hooks/useShapes/bar-y/types.d.ts +7 -2
  63. package/dist/esm/hooks/useShapes/index.js +1 -1
  64. package/dist/esm/hooks/utils/bar-y.d.ts +3 -3
  65. package/dist/esm/hooks/utils/bar-y.js +7 -21
  66. package/dist/esm/types/chart/axis.d.ts +13 -1
  67. package/dist/esm/types/chart/bar-y.d.ts +10 -0
  68. package/dist/esm/types/chart/series.d.ts +20 -0
  69. package/dist/esm/utils/chart/axis-generators/bottom.js +26 -13
  70. package/dist/esm/utils/chart/get-closest-data.js +13 -12
  71. package/dist/esm/utils/chart/index.js +1 -1
  72. package/dist/esm/utils/chart/series/sorting.d.ts +6 -2
  73. package/dist/esm/utils/chart/series/sorting.js +29 -4
  74. package/dist/esm/utils/chart/zoom.js +2 -1
  75. package/package.json +1 -1
@@ -32,6 +32,7 @@ async function getLabelData(d) {
32
32
  }
33
33
  export const prepareBarXData = async (args) => {
34
34
  const { series, seriesOptions, xAxis, xScale, yScale, boundsHeight: plotHeight } = args;
35
+ const stackGap = seriesOptions['bar-x'].stackGap;
35
36
  const categories = get(xAxis, 'categories', []);
36
37
  const barMaxWidth = get(seriesOptions, 'bar-x.barMaxWidth');
37
38
  const barPadding = get(seriesOptions, 'bar-x.barPadding');
@@ -95,46 +96,54 @@ export const prepareBarXData = async (args) => {
95
96
  const rectGap = Math.max(bandWidth * barPadding, MIN_BAR_GAP);
96
97
  const rectWidth = Math.max(MIN_BAR_WIDTH, Math.min(groupWidth / maxGroupSize - rectGap, barMaxWidth));
97
98
  const result = [];
98
- await Promise.all(Object.entries(data).map(async ([xValue, val]) => {
99
+ const groupedData = Object.entries(data);
100
+ for (let groupedDataIndex = 0; groupedDataIndex < groupedData.length; groupedDataIndex++) {
101
+ const [xValue, val] = groupedData[groupedDataIndex];
99
102
  const stacks = Object.values(val);
100
103
  const currentGroupWidth = rectWidth * stacks.length + rectGap * (stacks.length - 1);
101
- await Promise.all(stacks.map(async (yValues, groupItemIndex) => {
104
+ for (let groupItemIndex = 0; groupItemIndex < stacks.length; groupItemIndex++) {
105
+ const yValues = stacks[groupItemIndex];
102
106
  let stackHeight = 0;
103
107
  const stackItems = [];
104
108
  const sortedData = sortKey
105
109
  ? sort(yValues, (a, b) => comparator(get(a, sortKey), get(b, sortKey)))
106
110
  : yValues;
107
- await Promise.all(sortedData.map(async (yValue, yValueIndex) => {
111
+ for (let yValueIndex = 0; yValueIndex < sortedData.length; yValueIndex++) {
112
+ const yValue = sortedData[yValueIndex];
108
113
  const yAxisIndex = yValue.series.yAxis;
109
114
  const seriesYScale = yScale[yAxisIndex];
110
115
  let xCenter;
111
116
  if (xAxis.type === 'category') {
112
117
  const xBandScale = xScale;
113
- xCenter =
114
- (xBandScale(xValue) || 0) +
115
- xBandScale.bandwidth() / 2;
118
+ xCenter = (xBandScale(xValue) || 0) + xBandScale.bandwidth() / 2;
116
119
  }
117
120
  else {
118
121
  const xLinearScale = xScale;
119
122
  xCenter = xLinearScale(Number(xValue));
120
123
  }
121
- const x = xCenter -
122
- currentGroupWidth / 2 +
123
- (rectWidth + rectGap) * groupItemIndex;
124
+ const x = xCenter - currentGroupWidth / 2 + (rectWidth + rectGap) * groupItemIndex;
124
125
  const yDataValue = yValue.data.y;
125
126
  const y = seriesYScale(yDataValue);
126
127
  const base = seriesYScale(0);
128
+ const isLastStackItem = yValueIndex === sortedData.length - 1;
127
129
  const height = yDataValue > 0 ? base - y : y - base;
130
+ let shapeHeight = height - (stackItems.length ? stackGap : 0);
131
+ if (shapeHeight < 0) {
132
+ shapeHeight = height;
133
+ }
134
+ if (height <= 0) {
135
+ continue;
136
+ }
128
137
  const barData = {
129
138
  x,
130
139
  y: yDataValue > 0 ? y - stackHeight : seriesYScale(0),
131
140
  width: rectWidth,
132
- height,
141
+ height: shapeHeight,
133
142
  opacity: get(yValue.data, 'opacity', null),
134
143
  data: yValue.data,
135
144
  series: yValue.series,
136
145
  htmlElements: [],
137
- isLastStackItem: yValueIndex === sortedData.length - 1,
146
+ isLastStackItem,
138
147
  };
139
148
  const label = await getLabelData(barData);
140
149
  if (yValue.series.dataLabels.html && label) {
@@ -150,8 +159,8 @@ export const prepareBarXData = async (args) => {
150
159
  barData.label = await getLabelData(barData);
151
160
  }
152
161
  stackItems.push(barData);
153
- stackHeight += height + 1;
154
- }));
162
+ stackHeight += height;
163
+ }
155
164
  if (series.some((s) => s.stacking === 'percent')) {
156
165
  let acc = 0;
157
166
  const ratio = plotHeight / (stackHeight - stackItems.length);
@@ -162,7 +171,7 @@ export const prepareBarXData = async (args) => {
162
171
  });
163
172
  }
164
173
  result.push(...stackItems);
165
- }));
166
- }));
174
+ }
175
+ }
167
176
  return result;
168
177
  };
@@ -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,52 +1,16 @@
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;
10
+ const stackGap = seriesOptions['bar-y'].stackGap;
46
11
  const xLinearScale = xScale;
47
12
  const yLinearScale = yScale;
48
13
  const plotHeight = yLinearScale(yLinearScale.domain()[0]);
49
- const plotWidth = xLinearScale(xLinearScale.domain()[1]);
50
14
  const sortingOptions = get(seriesOptions, 'bar-y.dataSorting');
51
15
  const comparator = (sortingOptions === null || sortingOptions === void 0 ? void 0 : sortingOptions.direction) === 'desc' ? descending : ascending;
52
16
  const sortKey = (() => {
@@ -66,21 +30,32 @@ 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
82
47
  ? sort(measureValues, (a, b) => comparator(get(a, sortKey), get(b, sortKey)))
83
48
  : measureValues;
49
+ let ratio = 1;
50
+ if (series.some((s) => s.stacking === 'percent')) {
51
+ const sum = sortedData.reduce((acc, item) => {
52
+ if (item.data.x) {
53
+ return acc + xLinearScale(Number(item.data.x));
54
+ }
55
+ return acc;
56
+ }, 0);
57
+ ratio = xLinearScale.range()[1] / sum;
58
+ }
84
59
  sortedData.forEach(({ data, series: s }, xValueIndex) => {
85
60
  let center;
86
61
  if (yAxis[0].type === 'category') {
@@ -93,36 +68,86 @@ export const prepareBarYData = async (args) => {
93
68
  }
94
69
  const y = center - currentBarHeight / 2 + (barSize + barGap) * groupItemIndex;
95
70
  const xValue = Number(data.x);
96
- const width = xValue > 0 ? xLinearScale(xValue) - base : base - xLinearScale(xValue);
71
+ const isLastStackItem = xValueIndex === sortedData.length - 1;
72
+ const width = Math.abs(xLinearScale(xValue) * ratio - base);
73
+ let shapeWidth = width - (stackItems.length ? stackGap : 0);
74
+ if (shapeWidth < 0) {
75
+ shapeWidth = width;
76
+ }
77
+ if (shapeWidth <= 0) {
78
+ return;
79
+ }
80
+ const itemStackGap = width - shapeWidth;
97
81
  const item = {
98
- x: xValue > 0 ? stackSum : stackSum - width,
99
- y,
100
- width,
82
+ x: (xValue > baseRangeValue ? stackSum : stackSum - width) + itemStackGap,
83
+ y: y,
84
+ width: shapeWidth,
101
85
  height: barSize,
102
86
  color: data.color || s.color,
87
+ borderColor: s.borderColor,
88
+ borderWidth: s.borderWidth,
103
89
  opacity: get(data, 'opacity', null),
104
90
  data,
105
91
  series: s,
106
- htmlElements: [],
107
- isLastStackItem: xValueIndex === sortedData.length - 1,
92
+ isLastStackItem,
108
93
  };
109
94
  stackItems.push(item);
110
- stackSum += width + 1;
95
+ stackSum += width;
111
96
  });
112
- if (series.some((s) => s.stacking === 'percent')) {
113
- let acc = 0;
114
- const ratio = plotWidth / (stackSum - stackItems.length);
115
- stackItems.forEach((item) => {
116
- item.width = item.width * ratio;
117
- item.x = acc;
118
- acc += item.width;
119
- });
120
- }
121
97
  result.push(...stackItems);
122
98
  });
123
99
  });
124
- await Promise.all(result.map(async (d) => {
125
- await setLabel(d);
126
- }));
127
- return result;
100
+ let labels = [];
101
+ const htmlElements = [];
102
+ const map = new Map();
103
+ for (let i = 0; i < result.length; i++) {
104
+ const prepared = result[i];
105
+ const dataLabels = prepared.series.dataLabels;
106
+ if (dataLabels.enabled) {
107
+ const data = prepared.data;
108
+ const content = getFormattedValue(Object.assign({ value: data.label || data.x }, dataLabels));
109
+ const x = dataLabels.inside
110
+ ? prepared.x + prepared.width / 2
111
+ : prepared.x + prepared.width + DEFAULT_LABEL_PADDING;
112
+ const y = prepared.y + prepared.height / 2;
113
+ if (dataLabels.html) {
114
+ const { maxHeight: height, maxWidth: width } = await getLabelsSize({
115
+ labels: [content],
116
+ style: dataLabels.style,
117
+ html: dataLabels.html,
118
+ });
119
+ htmlElements.push({
120
+ x,
121
+ y: y - height / 2,
122
+ content,
123
+ size: { width, height },
124
+ style: dataLabels.style,
125
+ });
126
+ }
127
+ else {
128
+ if (!map.has(dataLabels.style)) {
129
+ map.set(dataLabels.style, getTextSizeFn({ style: dataLabels.style }));
130
+ }
131
+ const getTextSize = map.get(dataLabels.style);
132
+ const { width, height } = await getTextSize(content);
133
+ labels.push({
134
+ x,
135
+ y: y + height / 2,
136
+ text: content,
137
+ textAnchor: dataLabels.inside ? 'middle' : 'right',
138
+ style: dataLabels.style,
139
+ series: prepared.series,
140
+ size: { width, height },
141
+ });
142
+ }
143
+ }
144
+ }
145
+ if (!((_a = result[0]) === null || _a === void 0 ? void 0 : _a.series.dataLabels.allowOverlap)) {
146
+ labels = filterOverlappingLabels(labels);
147
+ }
148
+ return {
149
+ shapes: result,
150
+ labels,
151
+ htmlElements,
152
+ };
128
153
  };
@@ -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
@@ -79,6 +79,11 @@ export interface ChartSeriesOptions {
79
79
  * @default 0
80
80
  */
81
81
  borderRadius?: number;
82
+ /**
83
+ * The distance between the shapes of the stacked values, in pixels.
84
+ * @default 1
85
+ */
86
+ stackGap?: number;
82
87
  dataSorting?: {
83
88
  /** Determines what data value should be used to sort by.
84
89
  * Possible values are undefined to disable, "name" to sort by series name or "y"
@@ -115,11 +120,26 @@ export interface ChartSeriesOptions {
115
120
  * @default 0.2
116
121
  */
117
122
  groupPadding?: number;
123
+ /**
124
+ * The width of the border surrounding each bar.
125
+ *
126
+ * @default 0
127
+ */
128
+ borderWidth?: number;
129
+ /**
130
+ * The color of the border surrounding each bar.
131
+ */
132
+ borderColor?: string;
118
133
  /**
119
134
  * The corner radius of the border surrounding each bar.
120
135
  * @default 0
121
136
  */
122
137
  borderRadius?: number;
138
+ /**
139
+ * The distance between the shapes of the stacked values, in pixels.
140
+ * @default 1
141
+ */
142
+ stackGap?: number;
123
143
  dataSorting?: {
124
144
  /** Determines what data value should be used to sort by.
125
145
  * Possible values are undefined to disable, "name" to sort by series name or "x"
@@ -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;