@gravity-ui/charts 1.43.0 → 1.44.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 (125) hide show
  1. package/dist/cjs/components/ChartInner/index.js +3 -3
  2. package/dist/cjs/components/ChartInner/useChartInnerHandlers.d.ts +3 -3
  3. package/dist/cjs/components/ChartInner/useChartInnerHandlers.js +10 -12
  4. package/dist/cjs/components/ChartInner/useChartInnerProps.js +8 -4
  5. package/dist/cjs/components/ChartInner/utils/title.d.ts +3 -2
  6. package/dist/cjs/components/ChartInner/utils/title.js +19 -14
  7. package/dist/cjs/components/ChartInner/utils/zoom.js +3 -1
  8. package/dist/cjs/components/Title/index.d.ts +1 -3
  9. package/dist/cjs/components/Title/index.js +2 -2
  10. package/dist/cjs/components/Tooltip/DefaultTooltipContent/index.js +31 -6
  11. package/dist/cjs/components/Tooltip/DefaultTooltipContent/utils.js +4 -5
  12. package/dist/cjs/core/constants/chart-types.d.ts +1 -0
  13. package/dist/cjs/core/constants/chart-types.js +1 -0
  14. package/dist/cjs/core/constants/defaults/series-options.d.ts +5 -1
  15. package/dist/cjs/core/constants/defaults/series-options.js +13 -0
  16. package/dist/cjs/core/constants/index.d.ts +0 -1
  17. package/dist/cjs/core/constants/index.js +0 -1
  18. package/dist/cjs/core/i18n/keysets/en.json +2 -1
  19. package/dist/cjs/core/i18n/keysets/ru.json +2 -1
  20. package/dist/cjs/core/series/prepare-legend.js +2 -2
  21. package/dist/cjs/core/series/prepare-x-range.d.ts +11 -0
  22. package/dist/cjs/core/series/prepare-x-range.js +41 -0
  23. package/dist/cjs/core/series/prepareSeries.js +9 -0
  24. package/dist/cjs/core/series/types.d.ts +18 -2
  25. package/dist/cjs/core/types/chart/area.d.ts +2 -1
  26. package/dist/cjs/core/types/chart/series.d.ts +29 -2
  27. package/dist/cjs/core/types/chart/tooltip.d.ts +6 -1
  28. package/dist/cjs/core/types/chart/x-range.d.ts +59 -0
  29. package/dist/cjs/core/types/chart/x-range.js +1 -0
  30. package/dist/cjs/core/types/chart/zoom.d.ts +1 -1
  31. package/dist/cjs/core/types/index.d.ts +1 -0
  32. package/dist/cjs/core/types/index.js +1 -0
  33. package/dist/cjs/core/utils/axis/x-axis.js +9 -1
  34. package/dist/cjs/core/utils/color.js +6 -0
  35. package/dist/cjs/core/utils/common.js +10 -0
  36. package/dist/cjs/core/utils/get-closest-data.js +19 -0
  37. package/dist/cjs/core/utils/labels.d.ts +1 -1
  38. package/dist/cjs/core/utils/labels.js +3 -2
  39. package/dist/cjs/core/validation/index.js +13 -0
  40. package/dist/cjs/core/zoom/zoom.js +24 -7
  41. package/dist/cjs/hooks/useSeries/index.js +8 -2
  42. package/dist/cjs/hooks/useShapes/area/index.js +2 -2
  43. package/dist/cjs/hooks/useShapes/area/prepare-data.js +19 -18
  44. package/dist/cjs/hooks/useShapes/area/types.d.ts +2 -2
  45. package/dist/cjs/hooks/useShapes/bar-x/index.js +2 -2
  46. package/dist/cjs/hooks/useShapes/bar-x/prepare-data.js +37 -21
  47. package/dist/cjs/hooks/useShapes/bar-x/types.d.ts +2 -2
  48. package/dist/cjs/hooks/useShapes/index.d.ts +2 -1
  49. package/dist/cjs/hooks/useShapes/index.js +35 -5
  50. package/dist/cjs/hooks/useShapes/line/index.js +7 -16
  51. package/dist/cjs/hooks/useShapes/line/prepare-data.d.ts +2 -0
  52. package/dist/cjs/hooks/useShapes/line/prepare-data.js +11 -7
  53. package/dist/cjs/hooks/useShapes/line/types.d.ts +2 -2
  54. package/dist/cjs/hooks/useShapes/x-range/index.d.ts +14 -0
  55. package/dist/cjs/hooks/useShapes/x-range/index.js +115 -0
  56. package/dist/cjs/hooks/useShapes/x-range/prepare-data.d.ts +15 -0
  57. package/dist/cjs/hooks/useShapes/x-range/prepare-data.js +147 -0
  58. package/dist/cjs/hooks/useShapes/x-range/types.d.ts +12 -0
  59. package/dist/cjs/hooks/useShapes/x-range/types.js +1 -0
  60. package/dist/cjs/types/chart-ui.d.ts +4 -0
  61. package/dist/esm/components/ChartInner/index.js +3 -3
  62. package/dist/esm/components/ChartInner/useChartInnerHandlers.d.ts +3 -3
  63. package/dist/esm/components/ChartInner/useChartInnerHandlers.js +10 -12
  64. package/dist/esm/components/ChartInner/useChartInnerProps.js +8 -4
  65. package/dist/esm/components/ChartInner/utils/title.d.ts +3 -2
  66. package/dist/esm/components/ChartInner/utils/title.js +19 -14
  67. package/dist/esm/components/ChartInner/utils/zoom.js +3 -1
  68. package/dist/esm/components/Title/index.d.ts +1 -3
  69. package/dist/esm/components/Title/index.js +2 -2
  70. package/dist/esm/components/Tooltip/DefaultTooltipContent/index.js +31 -6
  71. package/dist/esm/components/Tooltip/DefaultTooltipContent/utils.js +4 -5
  72. package/dist/esm/core/constants/chart-types.d.ts +1 -0
  73. package/dist/esm/core/constants/chart-types.js +1 -0
  74. package/dist/esm/core/constants/defaults/series-options.d.ts +5 -1
  75. package/dist/esm/core/constants/defaults/series-options.js +13 -0
  76. package/dist/esm/core/constants/index.d.ts +0 -1
  77. package/dist/esm/core/constants/index.js +0 -1
  78. package/dist/esm/core/i18n/keysets/en.json +2 -1
  79. package/dist/esm/core/i18n/keysets/ru.json +2 -1
  80. package/dist/esm/core/series/prepare-legend.js +2 -2
  81. package/dist/esm/core/series/prepare-x-range.d.ts +11 -0
  82. package/dist/esm/core/series/prepare-x-range.js +41 -0
  83. package/dist/esm/core/series/prepareSeries.js +9 -0
  84. package/dist/esm/core/series/types.d.ts +18 -2
  85. package/dist/esm/core/types/chart/area.d.ts +2 -1
  86. package/dist/esm/core/types/chart/series.d.ts +29 -2
  87. package/dist/esm/core/types/chart/tooltip.d.ts +6 -1
  88. package/dist/esm/core/types/chart/x-range.d.ts +59 -0
  89. package/dist/esm/core/types/chart/x-range.js +1 -0
  90. package/dist/esm/core/types/chart/zoom.d.ts +1 -1
  91. package/dist/esm/core/types/index.d.ts +1 -0
  92. package/dist/esm/core/types/index.js +1 -0
  93. package/dist/esm/core/utils/axis/x-axis.js +9 -1
  94. package/dist/esm/core/utils/color.js +6 -0
  95. package/dist/esm/core/utils/common.js +10 -0
  96. package/dist/esm/core/utils/get-closest-data.js +19 -0
  97. package/dist/esm/core/utils/labels.d.ts +1 -1
  98. package/dist/esm/core/utils/labels.js +3 -2
  99. package/dist/esm/core/validation/index.js +13 -0
  100. package/dist/esm/core/zoom/zoom.js +24 -7
  101. package/dist/esm/hooks/useSeries/index.js +8 -2
  102. package/dist/esm/hooks/useShapes/area/index.js +2 -2
  103. package/dist/esm/hooks/useShapes/area/prepare-data.js +19 -18
  104. package/dist/esm/hooks/useShapes/area/types.d.ts +2 -2
  105. package/dist/esm/hooks/useShapes/bar-x/index.js +2 -2
  106. package/dist/esm/hooks/useShapes/bar-x/prepare-data.js +37 -21
  107. package/dist/esm/hooks/useShapes/bar-x/types.d.ts +2 -2
  108. package/dist/esm/hooks/useShapes/index.d.ts +2 -1
  109. package/dist/esm/hooks/useShapes/index.js +35 -5
  110. package/dist/esm/hooks/useShapes/line/index.js +7 -16
  111. package/dist/esm/hooks/useShapes/line/prepare-data.d.ts +2 -0
  112. package/dist/esm/hooks/useShapes/line/prepare-data.js +11 -7
  113. package/dist/esm/hooks/useShapes/line/types.d.ts +2 -2
  114. package/dist/esm/hooks/useShapes/x-range/index.d.ts +14 -0
  115. package/dist/esm/hooks/useShapes/x-range/index.js +115 -0
  116. package/dist/esm/hooks/useShapes/x-range/prepare-data.d.ts +15 -0
  117. package/dist/esm/hooks/useShapes/x-range/prepare-data.js +147 -0
  118. package/dist/esm/hooks/useShapes/x-range/types.d.ts +12 -0
  119. package/dist/esm/hooks/useShapes/x-range/types.js +1 -0
  120. package/dist/esm/types/chart-ui.d.ts +4 -0
  121. package/package.json +1 -1
  122. package/dist/cjs/core/constants/misc.d.ts +0 -1
  123. package/dist/cjs/core/constants/misc.js +0 -7
  124. package/dist/esm/core/constants/misc.d.ts +0 -1
  125. package/dist/esm/core/constants/misc.js +0 -7
@@ -15,6 +15,7 @@ import type { SankeySeries, SankeySeriesData } from './sankey';
15
15
  import type { ScatterSeries, ScatterSeriesData } from './scatter';
16
16
  import type { TreemapSeries, TreemapSeriesData } from './treemap';
17
17
  import type { WaterfallSeries, WaterfallSeriesData } from './waterfall';
18
+ import type { XRangeSeries, XRangeSeriesData } from './x-range';
18
19
  export interface TooltipDataChunkBarX<T = MeaningfulAny> {
19
20
  data: BarXSeriesData<T>;
20
21
  series: BarXSeries<T>;
@@ -87,7 +88,11 @@ export interface TooltipDataChunkFunnel<T = MeaningfulAny> {
87
88
  name: string;
88
89
  };
89
90
  }
90
- export type TooltipDataChunk<T = MeaningfulAny> = (TooltipDataChunkBarX<T> | TooltipDataChunkBarY<T> | TooltipDataChunkPie<T> | TooltipDataChunkScatter<T> | TooltipDataChunkLine<T> | TooltipDataChunkArea<T> | TooltipDataChunkTreemap<T> | TooltipDataChunkSankey<T> | TooltipDataChunkWaterfall<T> | TooltipDataChunkRadar<T> | TooltipDataChunkHeatmap<T> | TooltipDataChunkFunnel<T>) & {
91
+ export interface TooltipDataChunkXRange<T = MeaningfulAny> {
92
+ data: XRangeSeriesData<T>;
93
+ series: XRangeSeries<T>;
94
+ }
95
+ export type TooltipDataChunk<T = MeaningfulAny> = (TooltipDataChunkBarX<T> | TooltipDataChunkBarY<T> | TooltipDataChunkPie<T> | TooltipDataChunkScatter<T> | TooltipDataChunkLine<T> | TooltipDataChunkArea<T> | TooltipDataChunkTreemap<T> | TooltipDataChunkSankey<T> | TooltipDataChunkWaterfall<T> | TooltipDataChunkRadar<T> | TooltipDataChunkHeatmap<T> | TooltipDataChunkFunnel<T> | TooltipDataChunkXRange<T>) & {
91
96
  closest?: boolean;
92
97
  };
93
98
  export interface ChartTooltipRendererArgs<T = MeaningfulAny> {
@@ -0,0 +1,59 @@
1
+ import type { DashStyle, SERIES_TYPE } from '../../constants';
2
+ import type { MeaningfulAny } from '../misc';
3
+ import type { BaseSeries, BaseSeriesData, BaseSeriesLegend } from './base';
4
+ import type { RectLegendSymbolOptions } from './legend';
5
+ export interface XRangeSeriesData<T = MeaningfulAny> extends BaseSeriesData<T> {
6
+ /**
7
+ * The start value of the bar on the x-axis. Can be a numeric or timestamp value.
8
+ */
9
+ x0: number | string;
10
+ /**
11
+ * The end value of the bar on the x-axis. Can be a numeric or timestamp value.
12
+ */
13
+ x1: number | string;
14
+ /**
15
+ * The `y` value. Depending on the context it may represent:
16
+ * - a category value (for `category` y axis). If string, it is the category itself. If number, it is the index in `yAxis[0].categories`.
17
+ * - a numeric value (for `linear` y axis)
18
+ */
19
+ y?: string | number;
20
+ /** Individual color for the bar. Overrides the series color. */
21
+ color?: string;
22
+ /** Data label value. If not specified, the series name is used. */
23
+ label?: string | number;
24
+ /** Individual opacity for the bar. */
25
+ opacity?: number;
26
+ }
27
+ export interface XRangeSeries<T = MeaningfulAny> extends BaseSeries {
28
+ type: typeof SERIES_TYPE.XRange;
29
+ data: XRangeSeriesData<T>[];
30
+ /** The name of the series (used in legend, tooltip etc) */
31
+ name: string;
32
+ /** The main color of the series (hex, rgba) */
33
+ color?: string;
34
+ /** Individual opacity for the entire series. */
35
+ opacity?: number | null;
36
+ /**
37
+ * The corner radius of the border surrounding each bar.
38
+ * @default 0
39
+ */
40
+ borderRadius?: number;
41
+ /**
42
+ * The width of the border surrounding each bar.
43
+ * @default 0
44
+ */
45
+ borderWidth?: number;
46
+ /**
47
+ * The color of the border surrounding each bar.
48
+ */
49
+ borderColor?: string;
50
+ /**
51
+ * The dash style of the border surrounding each bar.
52
+ * @default 'Solid'
53
+ */
54
+ borderDashStyle?: DashStyle;
55
+ /** Individual series legend options. Has higher priority than legend options in widget data */
56
+ legend?: BaseSeriesLegend & {
57
+ symbol?: RectLegendSymbolOptions;
58
+ };
59
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -18,7 +18,7 @@ export interface ChartZoom {
18
18
  *
19
19
  * Supported zoom types by series type:
20
20
  * - `Area`, `Line`, `Scatter`: `x`, `y`, `xy`
21
- * - `BarX`: `x`
21
+ * - `BarX`, `XRange`: `x`
22
22
  * - `BarY`: `y`
23
23
  *
24
24
  * Default zoom type by series type:
@@ -30,6 +30,7 @@ export * from './chart/radar';
30
30
  export * from './chart/heatmap';
31
31
  export * from './chart/funnel';
32
32
  export * from './chart/brush';
33
+ export * from './chart/x-range';
33
34
  export interface ChartData<T = MeaningfulAny> {
34
35
  /**
35
36
  * General options for the chart.
@@ -22,3 +22,4 @@ export * from './chart/radar';
22
22
  export * from './chart/heatmap';
23
23
  export * from './chart/funnel';
24
24
  export * from './chart/brush';
25
+ export * from './chart/x-range';
@@ -12,7 +12,15 @@ function getTicksCount(args) {
12
12
  if (series) {
13
13
  const xDataSet = new Set();
14
14
  series === null || series === void 0 ? void 0 : series.forEach((item) => {
15
- if (isSeriesWithNumericalXValues(item)) {
15
+ if (item.type === 'x-range') {
16
+ item.data.forEach((d) => {
17
+ if (d.x0 !== null && d.x1 !== null) {
18
+ xDataSet.add(d.x0);
19
+ xDataSet.add(d.x1);
20
+ }
21
+ });
22
+ }
23
+ else if (isSeriesWithNumericalXValues(item)) {
16
24
  item.data.forEach((data) => {
17
25
  xDataSet.add(data.x);
18
26
  });
@@ -22,6 +22,12 @@ export function getDomainForContinuousColorScale(args) {
22
22
  acc.push(...s.data.map((d) => Number(d.y)));
23
23
  break;
24
24
  }
25
+ case 'x-range': {
26
+ // Use bar duration (x1 - x0) as the color domain value so that
27
+ // longer bars can be visually distinguished by color intensity.
28
+ acc.push(...s.data.map((d) => Math.abs(Number(d.x1) - Number(d.x0))));
29
+ break;
30
+ }
25
31
  default: {
26
32
  throw Error(`The method for calculation a domain for a continuous color scale for the "${s.type}" series is not defined`);
27
33
  }
@@ -57,6 +57,16 @@ export const getDomainDataXBySeries = (series) => {
57
57
  acc.push(...getDomainDataForStackedSeries(seriesList, 'y', 'x'));
58
58
  break;
59
59
  }
60
+ case 'x-range': {
61
+ seriesList.forEach((s) => {
62
+ s.data.forEach((d) => {
63
+ if (!isNil(d.x0) && !isNil(d.x1)) {
64
+ acc.push(d.x0, d.x1);
65
+ }
66
+ });
67
+ });
68
+ break;
69
+ }
60
70
  default: {
61
71
  seriesList.filter(isSeriesWithNumericalXValues).forEach((s) => {
62
72
  acc.push(...s.data.map((d) => d.x));
@@ -288,6 +288,25 @@ export function getClosestPoints(args) {
288
288
  }
289
289
  break;
290
290
  }
291
+ case 'x-range': {
292
+ const data = list;
293
+ const pointsInXRange = data.filter((d) => pointerX >= d.x &&
294
+ pointerX <= d.x + d.width &&
295
+ pointerY >= d.y &&
296
+ pointerY <= d.y + d.height);
297
+ if (pointsInXRange.length === 0) {
298
+ break;
299
+ }
300
+ const closestByX = pointsInXRange.length === 1
301
+ ? pointsInXRange[0]
302
+ : sort(pointsInXRange, (d) => Math.abs(d.x + d.width / 2 - pointerX))[0];
303
+ result.push(...pointsInXRange.map((d) => ({
304
+ data: d.data,
305
+ series: d.series,
306
+ closest: d === closestByX,
307
+ })));
308
+ break;
309
+ }
291
310
  }
292
311
  });
293
312
  if (closestPointsByXValue.length) {
@@ -3,7 +3,7 @@ export declare function getLeftPosition(label: LabelData): number;
3
3
  export declare function getOverlappingByX(rect1: LabelData | HtmlItem, rect2: LabelData | HtmlItem, gap?: number): number;
4
4
  export declare function getOverlappingByY(rect1: LabelData | HtmlItem, rect2: LabelData | HtmlItem, gap?: number): number;
5
5
  export declare function isLabelsOverlapping<T extends LabelData | HtmlItem>(label1: T, label2: T, padding?: number): boolean;
6
- export declare function filterOverlappingLabels<T extends LabelData | HtmlItem>(labels: T[]): T[];
6
+ export declare function filterOverlappingLabels<T extends LabelData | HtmlItem>(labels: T[], renderedSvgLabels?: T[]): T[];
7
7
  export declare function getSvgLabelConstraintedPosition(args: {
8
8
  boundsHeight: number;
9
9
  boundsWidth: number;
@@ -32,11 +32,12 @@ export function getOverlappingByY(rect1, rect2, gap = 0) {
32
32
  export function isLabelsOverlapping(label1, label2, padding = 0) {
33
33
  return Boolean(getOverlappingByX(label1, label2, padding) && getOverlappingByY(label1, label2, padding));
34
34
  }
35
- export function filterOverlappingLabels(labels) {
35
+ export function filterOverlappingLabels(labels, renderedSvgLabels) {
36
36
  const result = [];
37
37
  const sorted = sortBy(labels, (d) => d.y, (d) => ('textAnchor' in d ? getLeftPosition(d) : d.x));
38
38
  sorted.forEach((label) => {
39
- if (!result.some((l) => isLabelsOverlapping(label, l))) {
39
+ if (!(renderedSvgLabels === null || renderedSvgLabels === void 0 ? void 0 : renderedSvgLabels.some((l) => isLabelsOverlapping(label, l))) &&
40
+ !result.some((l) => isLabelsOverlapping(label, l))) {
40
41
  result.push(label);
41
42
  }
42
43
  });
@@ -265,6 +265,18 @@ function validateStacking({ series }) {
265
265
  });
266
266
  }
267
267
  }
268
+ function validateStackingAreaNullMode({ series }) {
269
+ const availableStackingValues = ['normal', 'percent'];
270
+ const invalid = series.find((s) => s.type === 'area' &&
271
+ availableStackingValues.includes(s.stacking) &&
272
+ s.nullMode === 'connect');
273
+ if (invalid) {
274
+ throw new ChartError({
275
+ code: CHART_ERROR_CODE.INVALID_DATA,
276
+ message: i18n('error', 'label_stacking-area-connect-null-mode'),
277
+ });
278
+ }
279
+ }
268
280
  function validateTreemapSeries({ series }) {
269
281
  const parentIds = {};
270
282
  series.data.forEach((d) => {
@@ -379,6 +391,7 @@ export function validateData(data) {
379
391
  }
380
392
  validateAxes({ xAxis: data.xAxis, yAxis: data.yAxis });
381
393
  validateTooltip({ tooltip: data.tooltip });
394
+ validateStackingAreaNullMode({ series: data.series.data });
382
395
  if (data.series.data.some((s) => isEmpty(s.data))) {
383
396
  throw new ChartError({
384
397
  code: CHART_ERROR_CODE.INVALID_DATA,
@@ -69,13 +69,30 @@ export function getZoomedSeriesData(args) {
69
69
  prevPointInRange = currentPointInRange;
70
70
  if (zoomState.x) {
71
71
  const [xMin, xMax] = zoomState.x;
72
- const x = 'x' in point ? ((_a = point.x) !== null && _a !== void 0 ? _a : undefined) : undefined;
73
- inXRange = isValueInRange({
74
- axis: xAxis,
75
- value: x,
76
- min: xMin,
77
- max: xMax,
78
- });
72
+ if ('x0' in point && 'x1' in point) {
73
+ const isStartInRange = isValueInRange({
74
+ axis: xAxis,
75
+ value: point.x0,
76
+ min: xMin,
77
+ max: xMax,
78
+ });
79
+ const isEndInRange = isValueInRange({
80
+ axis: xAxis,
81
+ value: point.x1,
82
+ min: xMin,
83
+ max: xMax,
84
+ });
85
+ inXRange = isStartInRange || isEndInRange;
86
+ }
87
+ else {
88
+ const x = 'x' in point ? ((_a = point.x) !== null && _a !== void 0 ? _a : undefined) : undefined;
89
+ inXRange = isValueInRange({
90
+ axis: xAxis,
91
+ value: x,
92
+ min: xMin,
93
+ max: xMax,
94
+ });
95
+ }
79
96
  }
80
97
  if (zoomState.y) {
81
98
  const yAxisIndex = 'yAxis' in seriesItem && typeof seriesItem.yAxis === 'number'
@@ -13,14 +13,20 @@ export const getVisibleSeries = ({ preparedSeries, activeLegendItems, }) => {
13
13
  export const getPreparedSeries = async ({ seriesData, seriesOptions, colors, preparedLegend, }) => {
14
14
  const seriesNames = getSeriesNames(seriesData);
15
15
  const colorScale = scaleOrdinal(seriesNames, colors);
16
- const groupedSeries = group(seriesData, (item) => item.type);
16
+ const groupedSeries = group(seriesData, (item, index) => {
17
+ if (item.type === 'line') {
18
+ return `${item.type}_${index}`;
19
+ }
20
+ return item.type;
21
+ });
17
22
  const acc = [];
18
23
  if (!preparedLegend) {
19
24
  return acc;
20
25
  }
21
26
  const list = Array.from(groupedSeries);
22
27
  for (let i = 0; i < list.length; i++) {
23
- const [seriesType, seriesList] = list[i];
28
+ const [_groupId, seriesList] = list[i];
29
+ const seriesType = seriesList[0].type;
24
30
  acc.push(...(await prepareSeries({
25
31
  type: seriesType,
26
32
  series: seriesList,
@@ -60,7 +60,7 @@ export const AreaSeriesShapes = (args) => {
60
60
  .attr('fill', (d) => d.color)
61
61
  .attr('opacity', (d) => d.opacity);
62
62
  let dataLabels = preparedData.reduce((acc, d) => {
63
- return acc.concat(d.labels);
63
+ return acc.concat(d.svgLabels);
64
64
  }, []);
65
65
  if (!allowOverlapDataLabels) {
66
66
  dataLabels = filterOverlappingLabels(dataLabels);
@@ -191,7 +191,7 @@ export const AreaSeriesShapes = (args) => {
191
191
  };
192
192
  }, [allowOverlapDataLabels, dispatcher, preparedData, seriesOptions]);
193
193
  const htmlLayerData = React.useMemo(() => {
194
- const items = preparedData.map((d) => d === null || d === void 0 ? void 0 : d.htmlElements).flat();
194
+ const items = preparedData.map((d) => d === null || d === void 0 ? void 0 : d.htmlLabels).flat();
195
195
  if (allowOverlapDataLabels) {
196
196
  return { htmlElements: items };
197
197
  }
@@ -1,4 +1,4 @@
1
- import { group, min } from 'd3-array';
1
+ import { group, min, sort } from 'd3-array';
2
2
  import isNil from 'lodash/isNil';
3
3
  import round from 'lodash/round';
4
4
  import { getDataCategoryValue, getLabelsSize, getTextSizeFn } from '../../../core/utils';
@@ -27,7 +27,7 @@ function getXValues(series, xAxis, xScale) {
27
27
  return acc;
28
28
  }, []);
29
29
  }
30
- return Array.from(xValues);
30
+ return sort(Array.from(xValues), (d) => d[1]);
31
31
  }
32
32
  async function prepareDataLabels({ series, points, xMax, yAxisTop, isOutsideBounds, }) {
33
33
  var _a;
@@ -166,13 +166,14 @@ export const prepareAreaData = async (args) => {
166
166
  return m.set(key, d);
167
167
  }, new Map());
168
168
  const points = xValues.reduce((pointsAcc, [x, xValue], index) => {
169
- var _a, _b, _c, _d, _e, _f, _g, _h;
170
- const d = (_a = seriesData.get(x)) !== null && _a !== void 0 ? _a : {
169
+ var _a, _b, _c, _d, _e, _f, _g;
170
+ const rawData = seriesData.get(x);
171
+ const d = rawData !== null && rawData !== void 0 ? rawData : {
171
172
  x,
172
173
  y: 0,
173
174
  };
174
- let yDataValue = (_b = d.y) !== null && _b !== void 0 ? _b : null;
175
- if (s.nullMode === 'connect' && yDataValue === null) {
175
+ let yDataValue = (_a = d.y) !== null && _a !== void 0 ? _a : null;
176
+ if (s.nullMode === 'connect' && (yDataValue === null || !rawData)) {
176
177
  return pointsAcc;
177
178
  }
178
179
  if (yDataValue && isPercentStacking) {
@@ -187,13 +188,13 @@ export const prepareAreaData = async (args) => {
187
188
  });
188
189
  if (typeof yDataValue === 'number' && yValue !== null) {
189
190
  yValue = round(yValue, 2);
190
- const prevPoint = seriesData.get((_c = xValues[index - 1]) === null || _c === void 0 ? void 0 : _c[0]);
191
- const nextPoint = seriesData.get((_d = xValues[index + 1]) === null || _d === void 0 ? void 0 : _d[0]);
191
+ const prevPoint = seriesData.get((_b = xValues[index - 1]) === null || _b === void 0 ? void 0 : _b[0]);
192
+ const nextPoint = seriesData.get((_c = xValues[index + 1]) === null || _c === void 0 ? void 0 : _c[0]);
192
193
  const currentPointStackHeight = Math.abs(yMin - yValue);
193
194
  if (yDataValue >= 0) {
194
195
  const positiveStackHeights = positiveStackValues.get(x);
195
- let prevSectionStackHeight = (_e = positiveStackHeights === null || positiveStackHeights === void 0 ? void 0 : positiveStackHeights.prev) !== null && _e !== void 0 ? _e : 0;
196
- let nextSectionStackHeight = (_f = positiveStackHeights === null || positiveStackHeights === void 0 ? void 0 : positiveStackHeights.next) !== null && _f !== void 0 ? _f : 0;
196
+ let prevSectionStackHeight = (_d = positiveStackHeights === null || positiveStackHeights === void 0 ? void 0 : positiveStackHeights.prev) !== null && _d !== void 0 ? _d : 0;
197
+ let nextSectionStackHeight = (_e = positiveStackHeights === null || positiveStackHeights === void 0 ? void 0 : positiveStackHeights.next) !== null && _e !== void 0 ? _e : 0;
197
198
  const point = {
198
199
  y0: yAxisTop + yMin - prevSectionStackHeight,
199
200
  x: xValue,
@@ -219,11 +220,11 @@ export const prepareAreaData = async (args) => {
219
220
  point2.y = newYValue;
220
221
  }
221
222
  }
222
- if ((prevPoint === null || prevPoint === void 0 ? void 0 : prevPoint.y) !== null) {
223
+ if ((prevPoint === null || prevPoint === void 0 ? void 0 : prevPoint.y) !== null || s.nullMode === 'zero') {
223
224
  prevSectionStackHeight =
224
225
  prevSectionStackHeight + currentPointStackHeight;
225
226
  }
226
- if ((nextPoint === null || nextPoint === void 0 ? void 0 : nextPoint.y) !== null) {
227
+ if ((nextPoint === null || nextPoint === void 0 ? void 0 : nextPoint.y) !== null || s.nullMode === 'zero') {
227
228
  nextSectionStackHeight =
228
229
  nextSectionStackHeight + currentPointStackHeight;
229
230
  }
@@ -234,8 +235,8 @@ export const prepareAreaData = async (args) => {
234
235
  }
235
236
  else {
236
237
  const negativeStackHeights = negativeStackValues.get(x);
237
- let prevSectionStackHeight = (_g = negativeStackHeights === null || negativeStackHeights === void 0 ? void 0 : negativeStackHeights.prev) !== null && _g !== void 0 ? _g : 0;
238
- let nextSectionStackHeight = (_h = negativeStackHeights === null || negativeStackHeights === void 0 ? void 0 : negativeStackHeights.next) !== null && _h !== void 0 ? _h : 0;
238
+ let prevSectionStackHeight = (_f = negativeStackHeights === null || negativeStackHeights === void 0 ? void 0 : negativeStackHeights.prev) !== null && _f !== void 0 ? _f : 0;
239
+ let nextSectionStackHeight = (_g = negativeStackHeights === null || negativeStackHeights === void 0 ? void 0 : negativeStackHeights.next) !== null && _g !== void 0 ? _g : 0;
239
240
  pointsAcc.push({
240
241
  y0: yAxisTop + yMin + prevSectionStackHeight,
241
242
  x: xValue,
@@ -300,7 +301,7 @@ export const prepareAreaData = async (args) => {
300
301
  seriesStackData.push({
301
302
  points,
302
303
  markers,
303
- labels: [],
304
+ svgLabels: [],
304
305
  color: s.color,
305
306
  opacity: s.opacity,
306
307
  width: s.lineWidth,
@@ -308,7 +309,7 @@ export const prepareAreaData = async (args) => {
308
309
  hovered: false,
309
310
  active: true,
310
311
  id: s.id,
311
- htmlElements: [],
312
+ htmlLabels: [],
312
313
  });
313
314
  }
314
315
  for (let itemIndex = 0; itemIndex < seriesStackData.length; itemIndex++) {
@@ -323,8 +324,8 @@ export const prepareAreaData = async (args) => {
323
324
  yAxisTop: itemYAxisTop,
324
325
  isOutsideBounds,
325
326
  });
326
- item.labels.push(...labelsData.svgLabels);
327
- item.htmlElements.push(...labelsData.htmlLabels);
327
+ item.svgLabels.push(...labelsData.svgLabels);
328
+ item.htmlLabels.push(...labelsData.htmlLabels);
328
329
  }
329
330
  }
330
331
  result.push(...seriesStackData);
@@ -27,6 +27,6 @@ export type PreparedAreaData = {
27
27
  series: PreparedAreaSeries;
28
28
  hovered: boolean;
29
29
  active: boolean;
30
- labels: LabelData[];
31
- htmlElements: HtmlItem[];
30
+ svgLabels: LabelData[];
31
+ htmlLabels: HtmlItem[];
32
32
  };
@@ -49,7 +49,7 @@ export const BarXSeriesShapes = (args) => {
49
49
  .attr('fill', (d) => d.data.color || d.series.color)
50
50
  .attr('opacity', (d) => d.opacity)
51
51
  .attr('cursor', (d) => d.series.cursor);
52
- let dataLabels = preparedData.map((d) => d.label).filter(Boolean);
52
+ let dataLabels = preparedData.map((d) => d.svgLabels).flat();
53
53
  if (!allowOverlapDataLabels) {
54
54
  dataLabels = filterOverlappingLabels(dataLabels);
55
55
  }
@@ -114,7 +114,7 @@ export const BarXSeriesShapes = (args) => {
114
114
  };
115
115
  }, [allowOverlapDataLabels, dispatcher, preparedData, seriesOptions]);
116
116
  const htmlLayerData = React.useMemo(() => {
117
- const items = preparedData.map((d) => d === null || d === void 0 ? void 0 : d.htmlElements).flat();
117
+ const items = preparedData.map((d) => d === null || d === void 0 ? void 0 : d.htmlLabels).flat();
118
118
  if (allowOverlapDataLabels) {
119
119
  return { htmlElements: items };
120
120
  }
@@ -110,8 +110,8 @@ export const prepareBarXData = async (args) => {
110
110
  const currentGroupWidth = rectWidth * stacks.length + rectGap * (stacks.length - 1);
111
111
  for (let groupItemIndex = 0; groupItemIndex < stacks.length; groupItemIndex++) {
112
112
  const yValues = stacks[groupItemIndex];
113
- let positiveStackHeight = 0;
114
- let negativeStackHeight = 0;
113
+ let positiveStackSum = 0;
114
+ let negativeStackSum = 0;
115
115
  const stackItems = [];
116
116
  let sortedData = yValues;
117
117
  if (sortKey) {
@@ -144,7 +144,6 @@ export const prepareBarXData = async (args) => {
144
144
  }
145
145
  const x = xCenter - currentGroupWidth / 2 + (rectWidth + rectGap) * groupItemIndex;
146
146
  const yDataValue = ((_d = yValue.data.y) !== null && _d !== void 0 ? _d : 0);
147
- const y = seriesYScale(yDataValue);
148
147
  let base = 0;
149
148
  if (seriesYAxis.type === 'logarithmic') {
150
149
  const domainData = seriesYScale.domain();
@@ -155,7 +154,22 @@ export const prepareBarXData = async (args) => {
155
154
  base = seriesYScale(0);
156
155
  }
157
156
  const isLastStackItem = yValueIndex === sortedData.length - 1;
158
- const height = Math.abs(base - y);
157
+ let height;
158
+ let barPositionY;
159
+ if (yDataValue > 0) {
160
+ const newSum = positiveStackSum + yDataValue;
161
+ const topPixel = seriesYScale(newSum);
162
+ const bottomPixel = positiveStackSum === 0 ? base : seriesYScale(positiveStackSum);
163
+ height = Math.abs(bottomPixel - topPixel);
164
+ barPositionY = yAxisTop + topPixel;
165
+ }
166
+ else {
167
+ const newSum = negativeStackSum + yDataValue;
168
+ const bottomPixel = negativeStackSum === 0 ? base : seriesYScale(negativeStackSum);
169
+ const topPixel = seriesYScale(newSum);
170
+ height = Math.abs(bottomPixel - topPixel);
171
+ barPositionY = yAxisTop + bottomPixel;
172
+ }
159
173
  let shapeHeight = height - (stackItems.length ? stackGap : 0);
160
174
  if (shapeHeight < 0) {
161
175
  shapeHeight = height;
@@ -165,28 +179,28 @@ export const prepareBarXData = async (args) => {
165
179
  }
166
180
  const barData = {
167
181
  x,
168
- y: yDataValue > 0
169
- ? yAxisTop + y - positiveStackHeight
170
- : yAxisTop + base + negativeStackHeight,
182
+ y: barPositionY,
171
183
  width: rectWidth,
172
184
  height: shapeHeight,
173
185
  _height: height,
174
186
  opacity: get(yValue.data, 'opacity', null),
175
187
  data: yValue.data,
176
188
  series: yValue.series,
177
- htmlElements: [],
189
+ htmlLabels: [],
190
+ svgLabels: [],
178
191
  isLastStackItem,
179
192
  };
180
193
  stackItems.push(barData);
181
194
  if (yDataValue > 0) {
182
- positiveStackHeight += height;
195
+ positiveStackSum += yDataValue;
183
196
  }
184
197
  else {
185
- negativeStackHeight += height;
198
+ negativeStackSum += yDataValue;
186
199
  }
187
200
  }
188
201
  if (series.some((s) => s.stacking === 'percent')) {
189
202
  let acc = 0;
203
+ const positiveStackHeight = stackItems.reduce((sum, item) => sum + item._height, 0);
190
204
  const ratio = plotHeight / positiveStackHeight;
191
205
  stackItems.forEach((item) => {
192
206
  item.height = item._height * ratio;
@@ -211,17 +225,19 @@ export const prepareBarXData = async (args) => {
211
225
  !isRangeSlider &&
212
226
  (!isBarOutsideBounds || isZeroValue)) {
213
227
  const label = await getLabelData(barData, xMax);
214
- if (barData.series.dataLabels.html && label) {
215
- barData.htmlElements.push({
216
- x: label.x,
217
- y: label.y,
218
- content: label.text,
219
- size: label.size,
220
- style: label.style,
221
- });
222
- }
223
- else {
224
- barData.label = label;
228
+ if (label) {
229
+ if (barData.series.dataLabels.html) {
230
+ barData.htmlLabels.push({
231
+ x: label.x,
232
+ y: label.y,
233
+ content: label.text,
234
+ size: label.size,
235
+ style: label.style,
236
+ });
237
+ }
238
+ else {
239
+ barData.svgLabels.push(label);
240
+ }
225
241
  }
226
242
  }
227
243
  }
@@ -7,8 +7,8 @@ export type PreparedBarXData = Omit<TooltipDataChunkBarX, 'series'> & {
7
7
  height: number;
8
8
  opacity: number | null;
9
9
  series: PreparedBarXSeries;
10
- label?: LabelData;
11
- htmlElements: HtmlItem[];
10
+ svgLabels: LabelData[];
11
+ htmlLabels: HtmlItem[];
12
12
  isLastStackItem: boolean;
13
13
  /**
14
14
  * the utility field for storing the original height (for recalculations, etc.)
@@ -19,8 +19,9 @@ import type { PreparedScatterData } from './scatter/types';
19
19
  export type { PreparedBarXData } from './bar-x';
20
20
  export type { PreparedScatterData } from './scatter/types';
21
21
  import type { PreparedWaterfallData } from './waterfall';
22
+ import type { PreparedXRangeData } from './x-range';
22
23
  import './styles.css';
23
- export type ShapeData = PreparedBarXData | PreparedBarYData | PreparedScatterData | PreparedLineData | PreparedPieData | PreparedAreaData | PreparedWaterfallData | PreparedSankeyData | PreparedRadarData | PreparedHeatmapData | PreparedFunnelData;
24
+ export type ShapeData = PreparedBarXData | PreparedBarYData | PreparedScatterData | PreparedLineData | PreparedPieData | PreparedAreaData | PreparedWaterfallData | PreparedSankeyData | PreparedRadarData | PreparedHeatmapData | PreparedFunnelData | PreparedXRangeData;
24
25
  export type ClipPathBySeriesType = Partial<Record<SeriesType, boolean>>;
25
26
  type Args = {
26
27
  boundsWidth: number;