@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
@@ -2,6 +2,7 @@ import get from 'lodash/get';
2
2
  import { DASH_STYLE, DEFAULT_AXIS_LABEL_FONT_SIZE, DEFAULT_AXIS_TYPE, axisCrosshairDefaults, axisLabelsDefaults, yAxisTitleDefaults, } from '../../constants';
3
3
  import { formatAxisTickLabel, getClosestPointsRange, getDefaultMinYAxisValue, getHorisontalSvgTextHeight, getLabelsSize, getScaleTicks, isAxisRelatedSeries, wrapText, } from '../../utils';
4
4
  import { createYScale } from '../useAxisScales';
5
+ import { getAxisCategories, prepareAxisPlotLabel } from './utils';
5
6
  const getAxisLabelMaxWidth = async (args) => {
6
7
  const { axis, seriesData, seriesOptions } = args;
7
8
  if (!axis.labels.enabled) {
@@ -78,7 +79,7 @@ export const getPreparedYAxis = ({ height, seriesData, seriesOptions, yAxis, })
78
79
  maxWidth: get(axisItem, 'labels.maxWidth', axisLabelsDefaults.maxWidth),
79
80
  },
80
81
  lineColor: get(axisItem, 'lineColor'),
81
- categories: get(axisItem, 'categories'),
82
+ categories: getAxisCategories(axisItem),
82
83
  timestamps: get(axisItem, 'timestamps'),
83
84
  title: {
84
85
  text: titleText,
@@ -108,6 +109,7 @@ export const getPreparedYAxis = ({ height, seriesData, seriesOptions, yAxis, })
108
109
  dashStyle: get(d, 'dashStyle', DASH_STYLE.Solid),
109
110
  opacity: get(d, 'opacity', 1),
110
111
  layerPlacement: get(d, 'layerPlacement', 'before'),
112
+ label: prepareAxisPlotLabel(d),
111
113
  })),
112
114
  plotBands: get(axisItem, 'plotBands', []).map((d) => ({
113
115
  color: get(d, 'color', 'var(--g-color-base-brand)'),
@@ -115,6 +117,7 @@ export const getPreparedYAxis = ({ height, seriesData, seriesOptions, yAxis, })
115
117
  from: get(d, 'from', 0),
116
118
  to: get(d, 'to', 0),
117
119
  layerPlacement: get(d, 'layerPlacement', 'before'),
120
+ label: prepareAxisPlotLabel(d),
118
121
  })),
119
122
  crosshair: {
120
123
  enabled: get(axisItem, 'crosshair.enabled', axisCrosshairDefaults.enabled),
@@ -126,6 +129,7 @@ export const getPreparedYAxis = ({ height, seriesData, seriesOptions, yAxis, })
126
129
  opacity: get(axisItem, 'crosshair.opacity', axisCrosshairDefaults.opacity),
127
130
  },
128
131
  visible: get(axisItem, 'visible', true),
132
+ order: axisItem.order,
129
133
  };
130
134
  if (labelsEnabled) {
131
135
  preparedAxis.labels.width = await getAxisLabelMaxWidth({
@@ -6,8 +6,8 @@ export declare const DEFAULT_MARKER: {
6
6
  enabled: boolean;
7
7
  symbol: `${import("../../constants").SymbolType}`;
8
8
  radius: number;
9
- borderColor: string;
10
9
  borderWidth: number;
10
+ borderColor: string;
11
11
  };
12
12
  type PrepareAreaSeriesArgs = {
13
13
  colorScale: ScaleOrdinal<string, string>;
@@ -20,8 +20,11 @@ export declare function prepareBarYSeries(args: PrepareBarYSeriesArgs): Promise<
20
20
  maxWidth: number;
21
21
  html: boolean;
22
22
  format?: import("../..").ValueFormat;
23
+ allowOverlap: boolean;
23
24
  };
24
25
  borderRadius: number;
26
+ borderWidth: number;
27
+ borderColor: string;
25
28
  } & {
26
29
  color: string;
27
30
  name: string;
@@ -4,7 +4,7 @@ import { getLabelsSize, getUniqId } from '../../utils';
4
4
  import { getFormattedValue } from '../../utils/chart/format';
5
5
  import { getSeriesStackId, prepareLegendSymbol } from './utils';
6
6
  async function prepareDataLabels(series) {
7
- var _a, _b;
7
+ var _a, _b, _c, _d;
8
8
  const enabled = get(series, 'dataLabels.enabled', false);
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);
@@ -25,12 +25,13 @@ async function prepareDataLabels(series) {
25
25
  maxWidth,
26
26
  html,
27
27
  format: (_b = series.dataLabels) === null || _b === void 0 ? void 0 : _b.format,
28
+ allowOverlap: (_d = (_c = series.dataLabels) === null || _c === void 0 ? void 0 : _c.allowOverlap) !== null && _d !== void 0 ? _d : false,
28
29
  };
29
30
  }
30
31
  export function prepareBarYSeries(args) {
31
32
  const { colorScale, series: seriesList, seriesOptions, legend } = args;
32
33
  return Promise.all(seriesList.map(async (series) => {
33
- var _a, _b, _c;
34
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
34
35
  const name = series.name || '';
35
36
  const color = series.color || colorScale(name);
36
37
  return {
@@ -49,6 +50,8 @@ export function prepareBarYSeries(args) {
49
50
  dataLabels: await prepareDataLabels(series),
50
51
  cursor: get(series, 'cursor', null),
51
52
  borderRadius: (_c = (_a = series.borderRadius) !== null && _a !== void 0 ? _a : (_b = seriesOptions === null || seriesOptions === void 0 ? void 0 : seriesOptions['bar-y']) === null || _b === void 0 ? void 0 : _b.borderRadius) !== null && _c !== void 0 ? _c : 0,
53
+ borderWidth: (_f = (_d = series.borderWidth) !== null && _d !== void 0 ? _d : (_e = seriesOptions === null || seriesOptions === void 0 ? void 0 : seriesOptions['bar-y']) === null || _e === void 0 ? void 0 : _e.borderWidth) !== null && _f !== void 0 ? _f : 0,
54
+ borderColor: (_j = (_g = series.borderColor) !== null && _g !== void 0 ? _g : (_h = seriesOptions === null || seriesOptions === void 0 ? void 0 : seriesOptions['bar-y']) === null || _h === void 0 ? void 0 : _h.borderColor) !== null && _j !== void 0 ? _j : 'var(--gcharts-shape-border-color)',
52
55
  };
53
56
  }));
54
57
  }
@@ -8,8 +8,8 @@ export declare const DEFAULT_MARKER: {
8
8
  enabled: boolean;
9
9
  symbol: `${import("../../constants").SymbolType}`;
10
10
  radius: number;
11
- borderColor: string;
12
11
  borderWidth: number;
12
+ borderColor: string;
13
13
  };
14
14
  type PrepareLineSeriesArgs = {
15
15
  colorScale: ScaleOrdinal<string, string>;
@@ -10,8 +10,8 @@ export declare const DEFAULT_MARKER: {
10
10
  enabled: boolean;
11
11
  radius: number;
12
12
  symbol: `${import("../../constants").SymbolType}`;
13
- borderColor: string;
14
13
  borderWidth: number;
14
+ borderColor: string;
15
15
  };
16
16
  export declare function prepareRadarSeries(args: PrepareRadarSeriesArgs): PreparedRadarSeries[];
17
17
  export {};
@@ -130,8 +130,11 @@ export type PreparedBarYSeries = {
130
130
  maxWidth: number;
131
131
  html: boolean;
132
132
  format?: ValueFormat;
133
+ allowOverlap: boolean;
133
134
  };
134
135
  borderRadius: number;
136
+ borderWidth: number;
137
+ borderColor: string;
135
138
  } & BasePreparedSeries;
136
139
  export type PreparedPieSeries = {
137
140
  type: PieSeries['type'];
@@ -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