@gravity-ui/charts 1.21.0 → 1.23.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 (133) hide show
  1. package/dist/cjs/components/ChartInner/index.js +7 -3
  2. package/dist/cjs/components/ChartInner/useChartInnerProps.d.ts +16 -2
  3. package/dist/cjs/components/ChartInner/useChartInnerProps.js +2 -3
  4. package/dist/cjs/components/ChartInner/utils.d.ts +10 -1
  5. package/dist/cjs/components/ChartInner/utils.js +60 -0
  6. package/dist/cjs/components/Tooltip/DefaultTooltipContent/utils.d.ts +2 -2
  7. package/dist/cjs/constants/index.d.ts +1 -0
  8. package/dist/cjs/constants/index.js +1 -0
  9. package/dist/cjs/constants/zoom.d.ts +6 -0
  10. package/dist/cjs/constants/zoom.js +5 -0
  11. package/dist/cjs/hooks/useBrush/index.js +7 -6
  12. package/dist/cjs/hooks/useBrush/types.d.ts +3 -2
  13. package/dist/cjs/hooks/useChartOptions/chart.js +1 -77
  14. package/dist/cjs/hooks/useChartOptions/zoom.d.ts +11 -0
  15. package/dist/cjs/hooks/useChartOptions/zoom.js +88 -0
  16. package/dist/cjs/hooks/useSeries/prepare-area.js +15 -1
  17. package/dist/cjs/hooks/useSeries/prepare-bar-x.js +13 -1
  18. package/dist/cjs/hooks/useSeries/prepare-bar-y.d.ts +2 -2
  19. package/dist/cjs/hooks/useSeries/prepare-bar-y.js +13 -1
  20. package/dist/cjs/hooks/useSeries/prepare-heatmap.js +13 -1
  21. package/dist/cjs/hooks/useSeries/prepare-legend.js +1 -1
  22. package/dist/cjs/hooks/useSeries/prepare-line.js +15 -1
  23. package/dist/cjs/hooks/useSeries/prepare-pie.js +15 -2
  24. package/dist/cjs/hooks/useSeries/prepare-scatter.js +16 -1
  25. package/dist/cjs/hooks/useSeries/prepare-waterfall.js +18 -2
  26. package/dist/cjs/hooks/useShapes/area/index.js +2 -0
  27. package/dist/cjs/hooks/useShapes/area/prepare-data.js +37 -22
  28. package/dist/cjs/hooks/useShapes/area/types.d.ts +5 -2
  29. package/dist/cjs/hooks/useShapes/bar-x/prepare-data.js +8 -2
  30. package/dist/cjs/hooks/useShapes/bar-y/index.d.ts +1 -1
  31. package/dist/cjs/hooks/useShapes/bar-y/index.js +19 -22
  32. package/dist/cjs/hooks/useShapes/bar-y/prepare-data.js +24 -5
  33. package/dist/cjs/hooks/useShapes/bar-y/types.d.ts +2 -2
  34. package/dist/cjs/hooks/useShapes/bar-y/utils.d.ts +3 -0
  35. package/dist/cjs/hooks/useShapes/bar-y/utils.js +44 -0
  36. package/dist/cjs/hooks/useShapes/heatmap/prepare-data.js +7 -3
  37. package/dist/cjs/hooks/useShapes/line/index.js +1 -0
  38. package/dist/cjs/hooks/useShapes/line/prepare-data.js +41 -16
  39. package/dist/cjs/hooks/useShapes/line/types.d.ts +7 -3
  40. package/dist/cjs/hooks/useShapes/pie/prepare-data.js +8 -4
  41. package/dist/cjs/hooks/useShapes/scatter/prepare-data.js +1 -1
  42. package/dist/cjs/hooks/useShapes/utils.d.ts +14 -6
  43. package/dist/cjs/hooks/useShapes/utils.js +66 -18
  44. package/dist/cjs/hooks/useShapes/waterfall/prepare-data.js +18 -8
  45. package/dist/cjs/hooks/useTooltip/index.js +8 -4
  46. package/dist/cjs/hooks/useZoom/index.js +2 -0
  47. package/dist/cjs/hooks/useZoom/utils.d.ts +3 -2
  48. package/dist/cjs/hooks/useZoom/utils.js +4 -3
  49. package/dist/cjs/hooks/utils/bar-y.d.ts +8 -1
  50. package/dist/cjs/hooks/utils/bar-y.js +4 -0
  51. package/dist/cjs/i18n/keysets/en.json +2 -2
  52. package/dist/cjs/i18n/keysets/ru.json +2 -2
  53. package/dist/cjs/types/chart/area.d.ts +11 -1
  54. package/dist/cjs/types/chart/bar-x.d.ts +10 -1
  55. package/dist/cjs/types/chart/bar-y.d.ts +10 -1
  56. package/dist/cjs/types/chart/heatmap.d.ts +10 -1
  57. package/dist/cjs/types/chart/line.d.ts +11 -1
  58. package/dist/cjs/types/chart/pie.d.ts +10 -1
  59. package/dist/cjs/types/chart/scatter.d.ts +11 -2
  60. package/dist/cjs/types/chart/waterfall.d.ts +10 -1
  61. package/dist/cjs/types/chart/zoom.d.ts +31 -1
  62. package/dist/cjs/utils/chart/get-closest-data.js +12 -7
  63. package/dist/cjs/utils/chart/series/sorting.js +17 -4
  64. package/dist/cjs/utils/chart/text.js +24 -21
  65. package/dist/cjs/utils/chart/zoom.js +4 -2
  66. package/dist/cjs/validation/index.js +3 -3
  67. package/dist/esm/components/ChartInner/index.js +7 -3
  68. package/dist/esm/components/ChartInner/useChartInnerProps.d.ts +16 -2
  69. package/dist/esm/components/ChartInner/useChartInnerProps.js +2 -3
  70. package/dist/esm/components/ChartInner/utils.d.ts +10 -1
  71. package/dist/esm/components/ChartInner/utils.js +60 -0
  72. package/dist/esm/components/Tooltip/DefaultTooltipContent/utils.d.ts +2 -2
  73. package/dist/esm/constants/index.d.ts +1 -0
  74. package/dist/esm/constants/index.js +1 -0
  75. package/dist/esm/constants/zoom.d.ts +6 -0
  76. package/dist/esm/constants/zoom.js +5 -0
  77. package/dist/esm/hooks/useBrush/index.js +7 -6
  78. package/dist/esm/hooks/useBrush/types.d.ts +3 -2
  79. package/dist/esm/hooks/useChartOptions/chart.js +1 -77
  80. package/dist/esm/hooks/useChartOptions/zoom.d.ts +11 -0
  81. package/dist/esm/hooks/useChartOptions/zoom.js +88 -0
  82. package/dist/esm/hooks/useSeries/prepare-area.js +15 -1
  83. package/dist/esm/hooks/useSeries/prepare-bar-x.js +13 -1
  84. package/dist/esm/hooks/useSeries/prepare-bar-y.d.ts +2 -2
  85. package/dist/esm/hooks/useSeries/prepare-bar-y.js +13 -1
  86. package/dist/esm/hooks/useSeries/prepare-heatmap.js +13 -1
  87. package/dist/esm/hooks/useSeries/prepare-legend.js +1 -1
  88. package/dist/esm/hooks/useSeries/prepare-line.js +15 -1
  89. package/dist/esm/hooks/useSeries/prepare-pie.js +15 -2
  90. package/dist/esm/hooks/useSeries/prepare-scatter.js +16 -1
  91. package/dist/esm/hooks/useSeries/prepare-waterfall.js +18 -2
  92. package/dist/esm/hooks/useShapes/area/index.js +2 -0
  93. package/dist/esm/hooks/useShapes/area/prepare-data.js +37 -22
  94. package/dist/esm/hooks/useShapes/area/types.d.ts +5 -2
  95. package/dist/esm/hooks/useShapes/bar-x/prepare-data.js +8 -2
  96. package/dist/esm/hooks/useShapes/bar-y/index.d.ts +1 -1
  97. package/dist/esm/hooks/useShapes/bar-y/index.js +19 -22
  98. package/dist/esm/hooks/useShapes/bar-y/prepare-data.js +24 -5
  99. package/dist/esm/hooks/useShapes/bar-y/types.d.ts +2 -2
  100. package/dist/esm/hooks/useShapes/bar-y/utils.d.ts +3 -0
  101. package/dist/esm/hooks/useShapes/bar-y/utils.js +44 -0
  102. package/dist/esm/hooks/useShapes/heatmap/prepare-data.js +7 -3
  103. package/dist/esm/hooks/useShapes/line/index.js +1 -0
  104. package/dist/esm/hooks/useShapes/line/prepare-data.js +41 -16
  105. package/dist/esm/hooks/useShapes/line/types.d.ts +7 -3
  106. package/dist/esm/hooks/useShapes/pie/prepare-data.js +8 -4
  107. package/dist/esm/hooks/useShapes/scatter/prepare-data.js +1 -1
  108. package/dist/esm/hooks/useShapes/utils.d.ts +14 -6
  109. package/dist/esm/hooks/useShapes/utils.js +66 -18
  110. package/dist/esm/hooks/useShapes/waterfall/prepare-data.js +18 -8
  111. package/dist/esm/hooks/useTooltip/index.js +8 -4
  112. package/dist/esm/hooks/useZoom/index.js +2 -0
  113. package/dist/esm/hooks/useZoom/utils.d.ts +3 -2
  114. package/dist/esm/hooks/useZoom/utils.js +4 -3
  115. package/dist/esm/hooks/utils/bar-y.d.ts +8 -1
  116. package/dist/esm/hooks/utils/bar-y.js +4 -0
  117. package/dist/esm/i18n/keysets/en.json +2 -2
  118. package/dist/esm/i18n/keysets/ru.json +2 -2
  119. package/dist/esm/types/chart/area.d.ts +11 -1
  120. package/dist/esm/types/chart/bar-x.d.ts +10 -1
  121. package/dist/esm/types/chart/bar-y.d.ts +10 -1
  122. package/dist/esm/types/chart/heatmap.d.ts +10 -1
  123. package/dist/esm/types/chart/line.d.ts +11 -1
  124. package/dist/esm/types/chart/pie.d.ts +10 -1
  125. package/dist/esm/types/chart/scatter.d.ts +11 -2
  126. package/dist/esm/types/chart/waterfall.d.ts +10 -1
  127. package/dist/esm/types/chart/zoom.d.ts +31 -1
  128. package/dist/esm/utils/chart/get-closest-data.js +12 -7
  129. package/dist/esm/utils/chart/series/sorting.js +17 -4
  130. package/dist/esm/utils/chart/text.js +24 -21
  131. package/dist/esm/utils/chart/zoom.js +4 -2
  132. package/dist/esm/validation/index.js +3 -3
  133. package/package.json +1 -1
@@ -4,13 +4,26 @@ import { DEFAULT_DATALABELS_STYLE } from '../../constants';
4
4
  import { getUniqId } from '../../utils';
5
5
  import { DEFAULT_DATALABELS_PADDING } from './constants';
6
6
  import { prepareLegendSymbol } from './utils';
7
+ function prepareSeriesData(series) {
8
+ var _a;
9
+ const nullMode = (_a = series.nullMode) !== null && _a !== void 0 ? _a : 'skip';
10
+ const data = series.data;
11
+ switch (nullMode) {
12
+ case 'zero':
13
+ return data.map((p) => { var _a; return (Object.assign(Object.assign({}, p), { value: (_a = p.value) !== null && _a !== void 0 ? _a : 0 })); });
14
+ case 'skip':
15
+ default:
16
+ return data.filter((p) => p.value !== null);
17
+ }
18
+ }
7
19
  export function preparePieSeries(args) {
8
20
  const { series, seriesOptions, legend, colors } = args;
9
- const dataNames = series.data.map((d) => d.name);
21
+ const preparedData = prepareSeriesData(series);
22
+ const dataNames = preparedData.map((d) => d.name);
10
23
  const colorScale = scaleOrdinal(dataNames, colors);
11
24
  const stackId = getUniqId();
12
25
  const seriesHoverState = get(seriesOptions, 'pie.states.hover');
13
- const preparedSeries = series.data.map((dataItem, i) => {
26
+ const preparedSeries = preparedData.map((dataItem, i) => {
14
27
  var _a, _b, _c, _d, _e, _f;
15
28
  const result = {
16
29
  type: 'pie',
@@ -20,6 +20,21 @@ function prepareMarker(series, seriesOptions, index) {
20
20
  },
21
21
  };
22
22
  }
23
+ function prepareSeriesData(series) {
24
+ var _a;
25
+ const nullMode = (_a = series.nullMode) !== null && _a !== void 0 ? _a : 'skip';
26
+ const data = series.data;
27
+ switch (nullMode) {
28
+ case 'zero':
29
+ return data.map((p) => {
30
+ var _a, _b;
31
+ return (Object.assign(Object.assign({}, p), { x: (_a = p.x) !== null && _a !== void 0 ? _a : 0, y: (_b = p.y) !== null && _b !== void 0 ? _b : 0 }));
32
+ });
33
+ case 'skip':
34
+ default:
35
+ return data.filter((p) => p.y !== null && p.x !== null);
36
+ }
37
+ }
23
38
  export function prepareScatterSeries(args) {
24
39
  const { colorScale, series, seriesOptions, legend } = args;
25
40
  return series.map((s, index) => {
@@ -36,7 +51,7 @@ export function prepareScatterSeries(args) {
36
51
  enabled: get(s, 'legend.enabled', legend.enabled),
37
52
  symbol: prepareLegendSymbol(s, symbolType),
38
53
  },
39
- data: s.data,
54
+ data: prepareSeriesData(s),
40
55
  marker: prepareMarker(s, seriesOptions, index),
41
56
  cursor: get(s, 'cursor', null),
42
57
  yAxis: get(s, 'yAxis', 0),
@@ -3,6 +3,21 @@ import { DEFAULT_DATALABELS_STYLE } from '../../constants';
3
3
  import { getUniqId } from '../../utils';
4
4
  import { DEFAULT_DATALABELS_PADDING } from './constants';
5
5
  import { prepareLegendSymbol } from './utils';
6
+ function prepareSeriesData(series) {
7
+ var _a;
8
+ const nullMode = (_a = series.nullMode) !== null && _a !== void 0 ? _a : 'skip';
9
+ const data = series.data;
10
+ switch (nullMode) {
11
+ case 'zero':
12
+ return data.map((d) => {
13
+ var _a;
14
+ return (Object.assign(Object.assign({}, d), { y: d.total ? d.y : ((_a = d.y) !== null && _a !== void 0 ? _a : 0) }));
15
+ });
16
+ case 'skip':
17
+ default:
18
+ return data.filter((d) => d.y !== null);
19
+ }
20
+ }
6
21
  export function prepareWaterfallSeries(args) {
7
22
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
8
23
  const { colorScale, series: seriesList, legend, colors } = args;
@@ -33,14 +48,15 @@ export function prepareWaterfallSeries(args) {
33
48
  const positive = Object.assign(Object.assign({}, common), { name: (_g = (_f = (_e = series.legend) === null || _e === void 0 ? void 0 : _e.itemText) === null || _f === void 0 ? void 0 : _f.positive) !== null && _g !== void 0 ? _g : `${series.name} ↑`, id: getUniqId(), color: series.positiveColor || positiveColor, data: [] });
34
49
  const negative = Object.assign(Object.assign({}, common), { name: (_k = (_j = (_h = series.legend) === null || _h === void 0 ? void 0 : _h.itemText) === null || _j === void 0 ? void 0 : _j.negative) !== null && _k !== void 0 ? _k : `${series.name} ↓`, id: getUniqId(), color: series.negativeColor || negativeColor, data: [] });
35
50
  const totals = Object.assign(Object.assign({}, common), { name: (_o = (_m = (_l = series.legend) === null || _l === void 0 ? void 0 : _l.itemText) === null || _m === void 0 ? void 0 : _m.totals) !== null && _o !== void 0 ? _o : series.name, id: getUniqId(), data: [] });
36
- series.data.forEach((d, index) => {
51
+ const preparedData = prepareSeriesData(series);
52
+ preparedData.forEach((d, index) => {
37
53
  var _a;
38
54
  const value = (_a = d === null || d === void 0 ? void 0 : d.y) !== null && _a !== void 0 ? _a : 0;
39
55
  const dataItem = Object.assign(Object.assign({}, d), { index });
40
56
  if (d === null || d === void 0 ? void 0 : d.total) {
41
57
  totals.data.push(dataItem);
42
58
  }
43
- else if (value > 0) {
59
+ else if (value >= 0) {
44
60
  positive.data.push(dataItem);
45
61
  }
46
62
  else if (value < 0) {
@@ -22,6 +22,7 @@ export const AreaSeriesShapes = (args) => {
22
22
  const inactiveOptions = get(seriesOptions, 'area.states.inactive');
23
23
  const line = lineGenerator()
24
24
  .x((d) => d.x)
25
+ .defined((d) => d.y !== null)
25
26
  .y((d) => d.y);
26
27
  plotSvgElement.selectAll('*').remove();
27
28
  markersSvgElement.selectAll('*').remove();
@@ -41,6 +42,7 @@ export const AreaSeriesShapes = (args) => {
41
42
  .attr('stroke-linejoin', 'round')
42
43
  .attr('stroke-linecap', 'round');
43
44
  const area = areaGenerator()
45
+ .defined((d) => d.y !== null)
44
46
  .x((d) => d.x)
45
47
  .y0((d) => d.y0)
46
48
  .y1((d) => d.y);
@@ -35,8 +35,9 @@ function getXValues(series, xAxis, xScale) {
35
35
  const key = String(xAxis.type === 'category'
36
36
  ? getDataCategoryValue({ axisDirection: 'x', categories, data: d })
37
37
  : d.x);
38
- if (!acc.has(key)) {
39
- acc.set(key, getXValue({ point: d, points: s.data, xAxis, xScale }));
38
+ const xValue = getXValue({ point: d, points: s.data, xAxis, xScale });
39
+ if (!acc.has(key) && xValue !== null) {
40
+ acc.set(key, xValue);
40
41
  }
41
42
  });
42
43
  return acc;
@@ -53,6 +54,7 @@ function getXValues(series, xAxis, xScale) {
53
54
  return Array.from(xValues);
54
55
  }
55
56
  export const prepareAreaData = async (args) => {
57
+ var _a;
56
58
  const { series, xAxis, xScale, yAxis, yScale, boundsHeight: plotHeight, isOutsideBounds } = args;
57
59
  const [_xMin, xRangeMax] = xScale.range();
58
60
  const xMax = xRangeMax / (1 - xAxis.maxPadding);
@@ -74,12 +76,12 @@ export const prepareAreaData = async (args) => {
74
76
  if (!seriesYScale) {
75
77
  continue;
76
78
  }
77
- const yMin = getYValue({
79
+ const yMin = (_a = getYValue({
78
80
  point: { y: 0 },
79
81
  points: s.data,
80
82
  yAxis: seriesYAxis,
81
83
  yScale: seriesYScale,
82
- });
84
+ })) !== null && _a !== void 0 ? _a : 0;
83
85
  const seriesData = s.data.reduce((m, d) => {
84
86
  const key = String(xAxis.type === 'category'
85
87
  ? getDataCategoryValue({
@@ -91,20 +93,21 @@ export const prepareAreaData = async (args) => {
91
93
  return m.set(key, d);
92
94
  }, new Map());
93
95
  const points = xValues.reduce((pointsAcc, [x, xValue]) => {
96
+ var _a;
94
97
  const accumulatedYValue = accumulatedYValues.get(x) || 0;
95
- const d = seriesData.get(x) ||
96
- {
97
- x,
98
- // FIXME: think about how to break the series into separate areas(null Y values)
99
- y: 0,
100
- };
101
- const yValue = getYValue({ point: d, yAxis: seriesYAxis, yScale: seriesYScale }) -
102
- accumulatedYValue;
103
- accumulatedYValues.set(x, yMin - yValue);
98
+ const d = (_a = seriesData.get(x)) !== null && _a !== void 0 ? _a : {
99
+ x,
100
+ y: 0,
101
+ };
102
+ const yValue = getYValue({ point: d, yAxis: seriesYAxis, yScale: seriesYScale });
103
+ const yPointValue = yValue === null ? null : yValue - accumulatedYValue;
104
+ if (yPointValue !== null) {
105
+ accumulatedYValues.set(x, yMin - yPointValue);
106
+ }
104
107
  pointsAcc.push({
105
108
  y0: yMin - accumulatedYValue,
106
109
  x: xValue,
107
- y: yValue,
110
+ y: yPointValue,
108
111
  data: d,
109
112
  series: s,
110
113
  });
@@ -113,7 +116,13 @@ export const prepareAreaData = async (args) => {
113
116
  let labels = [];
114
117
  const htmlElements = [];
115
118
  if (s.dataLabels.enabled) {
116
- const labelItems = await Promise.all(points.map((p) => getLabelData(p, s, xMax)));
119
+ const labelItems = await Promise.all(points.reduce((labelItemsAcc, p) => {
120
+ if (p.y === null) {
121
+ return labelItemsAcc;
122
+ }
123
+ labelItemsAcc.push(getLabelData(p, s, xMax));
124
+ return labelItemsAcc;
125
+ }, []));
117
126
  if (s.dataLabels.html) {
118
127
  const htmlLabels = await Promise.all(labelItems.map(async (l) => {
119
128
  var _a;
@@ -142,12 +151,18 @@ export const prepareAreaData = async (args) => {
142
151
  }
143
152
  let markers = [];
144
153
  if (s.marker.states.normal.enabled || s.marker.states.hover.enabled) {
145
- markers = points.map((p) => ({
146
- point: p,
147
- active: true,
148
- hovered: false,
149
- clipped: isOutsideBounds(p.x, p.y),
150
- }));
154
+ markers = points.reduce((markersAcc, p) => {
155
+ if (p.y === null) {
156
+ return markersAcc;
157
+ }
158
+ markersAcc.push({
159
+ point: p,
160
+ active: true,
161
+ hovered: false,
162
+ clipped: isOutsideBounds(p.x, p.y),
163
+ });
164
+ return markersAcc;
165
+ }, []);
151
166
  }
152
167
  seriesStackData.push({
153
168
  points,
@@ -170,7 +185,7 @@ export const prepareAreaData = async (args) => {
170
185
  const ratio = plotHeight / stackHeight;
171
186
  seriesStackData.forEach((item) => {
172
187
  const point = item.points[index];
173
- if (point) {
188
+ if (point.y !== null && point.y !== undefined) {
174
189
  const height = (point.y0 - point.y) * ratio;
175
190
  point.y0 = plotHeight - height - acc;
176
191
  point.y = point.y0 + height;
@@ -3,13 +3,16 @@ import type { PreparedAreaSeries } from '../../useSeries/types';
3
3
  export type PointData = {
4
4
  y0: number;
5
5
  x: number;
6
- y: number;
6
+ y: number | null;
7
7
  data: AreaSeriesData;
8
8
  series: PreparedAreaSeries;
9
9
  color?: string;
10
10
  };
11
+ export type MarkerPointData = PointData & {
12
+ y: number;
13
+ };
11
14
  export type MarkerData = {
12
- point: PointData;
15
+ point: MarkerPointData;
13
16
  active: boolean;
14
17
  hovered: boolean;
15
18
  clipped: boolean;
@@ -3,6 +3,7 @@ import get from 'lodash/get';
3
3
  import { getDataCategoryValue, getLabelsSize } from '../../../utils';
4
4
  import { getFormattedValue } from '../../../utils/chart/format';
5
5
  import { MIN_BAR_GAP, MIN_BAR_GROUP_GAP, MIN_BAR_WIDTH } from '../../constants';
6
+ const isSeriesDataValid = (d) => d.y !== null;
6
7
  async function getLabelData(d) {
7
8
  if (!d.series.dataLabels.enabled) {
8
9
  return undefined;
@@ -30,7 +31,9 @@ async function getLabelData(d) {
30
31
  series: d.series,
31
32
  };
32
33
  }
34
+ // eslint-disable-next-line complexity
33
35
  export const prepareBarXData = async (args) => {
36
+ var _a;
34
37
  const { series, seriesOptions, xAxis, xScale, yScale, boundsHeight: plotHeight } = args;
35
38
  const stackGap = seriesOptions['bar-x'].stackGap;
36
39
  const categories = get(xAxis, 'categories', []);
@@ -55,6 +58,9 @@ export const prepareBarXData = async (args) => {
55
58
  const data = {};
56
59
  series.forEach((s) => {
57
60
  s.data.forEach((d) => {
61
+ if (!isSeriesDataValid(d)) {
62
+ return;
63
+ }
58
64
  const xValue = xAxis.type === 'category'
59
65
  ? getDataCategoryValue({ axisDirection: 'x', categories, data: d })
60
66
  : d.x;
@@ -129,7 +135,7 @@ export const prepareBarXData = async (args) => {
129
135
  xCenter = xLinearScale(Number(xValue));
130
136
  }
131
137
  const x = xCenter - currentGroupWidth / 2 + (rectWidth + rectGap) * groupItemIndex;
132
- const yDataValue = yValue.data.y;
138
+ const yDataValue = ((_a = yValue.data.y) !== null && _a !== void 0 ? _a : 0);
133
139
  const y = seriesYScale(yDataValue);
134
140
  const base = seriesYScale(0);
135
141
  const isLastStackItem = yValueIndex === sortedData.length - 1;
@@ -138,7 +144,7 @@ export const prepareBarXData = async (args) => {
138
144
  if (shapeHeight < 0) {
139
145
  shapeHeight = height;
140
146
  }
141
- if (height <= 0) {
147
+ if (shapeHeight < 0) {
142
148
  continue;
143
149
  }
144
150
  const barData = {
@@ -10,4 +10,4 @@ type Args = {
10
10
  htmlLayout: HTMLElement | null;
11
11
  clipPathId: string;
12
12
  };
13
- export declare const BarYSeriesShapes: (args: Args) => React.JSX.Element;
13
+ export declare function BarYSeriesShapes(args: Args): React.JSX.Element;
@@ -3,10 +3,10 @@ import { color, select } from 'd3';
3
3
  import get from 'lodash/get';
4
4
  import { block } from '../../../utils';
5
5
  import { HtmlLayer } from '../HtmlLayer';
6
- import { getRectPath } from '../utils';
6
+ import { getAdjustedRectBorderPath, getAdjustedRectPath } from './utils';
7
7
  export { prepareBarYData } from './prepare-data';
8
8
  const b = block('bar-y');
9
- export const BarYSeriesShapes = (args) => {
9
+ export function BarYSeriesShapes(args) {
10
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);
@@ -16,33 +16,29 @@ export const BarYSeriesShapes = (args) => {
16
16
  }
17
17
  const svgElement = select(ref.current);
18
18
  svgElement.selectAll('*').remove();
19
- const rectSelection = svgElement
20
- .selectAll('rect')
19
+ const segmentSelection = svgElement
20
+ .selectAll(`path.${b('segment')}`)
21
21
  .data(preparedData)
22
22
  .join('path')
23
- .attr('d', (d) => {
24
- const borderRadius = d.isLastStackItem
25
- ? Math.min(d.height, d.width / 2, d.series.borderRadius)
26
- : 0;
27
- const p = getRectPath({
28
- x: d.x,
29
- y: d.y,
30
- width: d.width,
31
- height: d.height,
32
- borderRadius: [0, borderRadius, borderRadius, 0],
33
- });
34
- return p.toString();
35
- })
23
+ .attr('d', (d) => getAdjustedRectPath(d))
36
24
  .attr('class', b('segment'))
37
25
  .attr('x', (d) => d.x)
38
26
  .attr('y', (d) => d.y)
39
27
  .attr('height', (d) => d.height)
40
28
  .attr('width', (d) => d.width)
41
29
  .attr('fill', (d) => d.color)
42
- .attr('stroke', (d) => d.borderColor)
43
- .attr('stroke-width', (d) => d.borderWidth)
44
30
  .attr('opacity', (d) => d.data.opacity || null)
45
31
  .attr('cursor', (d) => d.series.cursor);
32
+ const borderSelection = svgElement
33
+ .selectAll(`path.${b('segment-border')}`)
34
+ .data(preparedData.filter((d) => d.borderWidth > 0))
35
+ .join('path')
36
+ .attr('d', (d) => getAdjustedRectBorderPath(d))
37
+ .attr('class', b('segment-border'))
38
+ .attr('fill', (d) => d.borderColor)
39
+ .attr('fill-rule', 'evenodd')
40
+ .attr('opacity', (d) => d.data.opacity || null)
41
+ .attr('pointer-events', 'none');
46
42
  const labelSelection = svgElement
47
43
  .selectAll('text')
48
44
  .data(dataLabels)
@@ -64,7 +60,7 @@ export const BarYSeriesShapes = (args) => {
64
60
  acc.add(d.data.y);
65
61
  return acc;
66
62
  }, new Set());
67
- rectSelection.attr('fill', (d) => {
63
+ segmentSelection.attr('fill', (d) => {
68
64
  var _a;
69
65
  const fillColor = d.color;
70
66
  if (hovered === null || hovered === void 0 ? void 0 : hovered.has(d.data.y)) {
@@ -82,7 +78,8 @@ export const BarYSeriesShapes = (args) => {
82
78
  }
83
79
  return null;
84
80
  };
85
- rectSelection.attr('opacity', newOpacity);
81
+ segmentSelection.attr('opacity', newOpacity);
82
+ borderSelection.attr('opacity', newOpacity);
86
83
  labelSelection.attr('opacity', newOpacity);
87
84
  }
88
85
  }
@@ -97,4 +94,4 @@ export const BarYSeriesShapes = (args) => {
97
94
  return (React.createElement(React.Fragment, null,
98
95
  React.createElement("g", { ref: ref, className: b(), clipPath: `url(#${clipPathId})` }),
99
96
  React.createElement(HtmlLayer, { preparedData: { htmlElements }, htmlLayout: htmlLayout })));
100
- };
97
+ }
@@ -10,6 +10,7 @@ export async function prepareBarYData(args) {
10
10
  const stackGap = seriesOptions['bar-y'].stackGap;
11
11
  const xLinearScale = xScale;
12
12
  const yLinearScale = yScale;
13
+ const [baseRangeValue] = xLinearScale.range();
13
14
  if (!yLinearScale) {
14
15
  return {
15
16
  shapes: [],
@@ -42,7 +43,6 @@ export async function prepareBarYData(args) {
42
43
  scale: yScale,
43
44
  });
44
45
  const result = [];
45
- const baseRangeValue = xLinearScale.range()[0];
46
46
  Object.entries(groupedData).forEach(([yValue, val]) => {
47
47
  const stacks = Object.values(val);
48
48
  const currentBarHeight = barSize * stacks.length + barGap * (stacks.length - 1);
@@ -64,6 +64,9 @@ export async function prepareBarYData(args) {
64
64
  ratio = xLinearScale.range()[1] / sum;
65
65
  }
66
66
  sortedData.forEach(({ data, series: s }, xValueIndex) => {
67
+ if (data.x === null) {
68
+ return;
69
+ }
67
70
  let center;
68
71
  if (yAxis[0].type === 'category') {
69
72
  const bandScale = yScale;
@@ -79,24 +82,40 @@ export async function prepareBarYData(args) {
79
82
  }
80
83
  const y = center - currentBarHeight / 2 + (barSize + barGap) * groupItemIndex;
81
84
  const xValue = Number(data.x);
82
- const isLastStackItem = xValueIndex === sortedData.length - 1;
83
85
  const width = Math.abs(xLinearScale(xValue) * ratio - base);
84
86
  let shapeWidth = width - (stackItems.length ? stackGap : 0);
85
87
  if (shapeWidth < 0) {
86
88
  shapeWidth = width;
87
89
  }
88
- if (shapeWidth <= 0) {
90
+ if (shapeWidth < 0) {
89
91
  return;
90
92
  }
91
93
  const itemStackGap = width - shapeWidth;
94
+ const borderWidth = barSize > s.borderWidth * 2 ? s.borderWidth : 0;
95
+ const isFirstInStack = xValueIndex === 0;
96
+ const isLastStackItem = xValueIndex === sortedData.length - 1;
97
+ // Calculate position with border compensation
98
+ // Border extends halfBorder outward from the shape, so we need to adjust position
99
+ let itemX = (xValue > baseRangeValue ? stackSum : stackSum - width) + itemStackGap;
100
+ const halfBorder = borderWidth / 2;
101
+ if (isFirstInStack && xValue > 0) {
102
+ // Positive bar: border extends left, so shift position left by halfBorder
103
+ // to keep the visual left edge at the zero line
104
+ itemX -= halfBorder;
105
+ }
106
+ else if (isFirstInStack && xValue < 0) {
107
+ // Negative bar: border extends right, so shift position right by halfBorder
108
+ // to keep the visual right edge at the zero line
109
+ itemX += halfBorder;
110
+ }
92
111
  const item = {
93
- x: (xValue > baseRangeValue ? stackSum : stackSum - width) + itemStackGap,
112
+ x: itemX,
94
113
  y: y,
95
114
  width: shapeWidth,
96
115
  height: barSize,
97
116
  color: data.color || s.color,
98
117
  borderColor: s.borderColor,
99
- borderWidth: barSize > s.borderWidth * 2 ? s.borderWidth : 0,
118
+ borderWidth,
100
119
  opacity: get(data, 'opacity', null),
101
120
  data,
102
121
  series: s,
@@ -1,6 +1,6 @@
1
- import type { HtmlItem, LabelData, TooltipDataChunkBarX } from '../../../types';
1
+ import type { HtmlItem, LabelData, TooltipDataChunkBarY } from '../../../types';
2
2
  import type { PreparedBarYSeries } from '../../useSeries/types';
3
- export type PreparedBarYData = Omit<TooltipDataChunkBarX, 'series'> & {
3
+ export type PreparedBarYData = Omit<TooltipDataChunkBarY, 'series'> & {
4
4
  x: number;
5
5
  y: number;
6
6
  width: number;
@@ -0,0 +1,3 @@
1
+ import type { PreparedBarYData } from './types';
2
+ export declare function getAdjustedRectPath(d: PreparedBarYData): string;
3
+ export declare function getAdjustedRectBorderPath(d: PreparedBarYData): string;
@@ -0,0 +1,44 @@
1
+ import { getRectBorderPath, getRectPath } from '../utils';
2
+ export function getAdjustedRectPath(d) {
3
+ const borderRadius = d.isLastStackItem
4
+ ? Math.min(d.height, d.width / 2, d.series.borderRadius)
5
+ : 0;
6
+ // Fill should match the inner border dimensions to prevent color bleeding
7
+ const halfBorder = d.borderWidth / 2;
8
+ const innerBorderRadius = Math.max(borderRadius - halfBorder, 0);
9
+ // Adjust fill position and size based on border width
10
+ let fillX = d.x;
11
+ let fillY = d.y;
12
+ let fillWidth = d.width;
13
+ let fillHeight = d.height;
14
+ let fillBorderRadiusRight = borderRadius;
15
+ if (d.borderWidth > 0) {
16
+ // Inset fill by halfBorder on all sides
17
+ fillX = d.x + halfBorder;
18
+ fillY = d.y + halfBorder;
19
+ fillWidth = d.width - d.borderWidth;
20
+ fillHeight = d.height - d.borderWidth;
21
+ fillBorderRadiusRight = innerBorderRadius;
22
+ }
23
+ const p = getRectPath({
24
+ x: fillX,
25
+ y: fillY,
26
+ width: fillWidth,
27
+ height: fillHeight,
28
+ borderRadius: [0, fillBorderRadiusRight, fillBorderRadiusRight, 0],
29
+ });
30
+ return p.toString();
31
+ }
32
+ export function getAdjustedRectBorderPath(d) {
33
+ const borderRadius = d.isLastStackItem
34
+ ? Math.min(d.height, d.width / 2, d.series.borderRadius)
35
+ : 0;
36
+ return getRectBorderPath({
37
+ x: d.x,
38
+ y: d.y,
39
+ width: d.width,
40
+ height: d.height,
41
+ borderWidth: d.borderWidth,
42
+ borderRadius: [0, borderRadius, borderRadius, 0],
43
+ });
44
+ }
@@ -8,8 +8,11 @@ export async function prepareHeatmapData({ series, xAxis, xScale, yAxis, yScale,
8
8
  const xDomainData = getDomainDataXBySeries([series]);
9
9
  const bandWidth = getBandSize({ domain: xDomainData, scale: xScale });
10
10
  const xAxisCategories = (_b = xAxis.categories) !== null && _b !== void 0 ? _b : [];
11
- const heatmapItems = series.data.map((d) => {
11
+ const heatmapItems = series.data.reduce((items, d) => {
12
12
  var _a, _b, _c;
13
+ if (d.value === null) {
14
+ return items;
15
+ }
13
16
  let x = 0;
14
17
  if (isBandScale(xScale)) {
15
18
  x = (_a = xScale(xAxisCategories[d.x])) !== null && _a !== void 0 ? _a : 0;
@@ -36,8 +39,9 @@ export async function prepareHeatmapData({ series, xAxis, xScale, yAxis, yScale,
36
39
  borderWidth: series.borderWidth,
37
40
  data: d,
38
41
  };
39
- return item;
40
- });
42
+ items.push(item);
43
+ return items;
44
+ }, []);
41
45
  const svgDataLabels = [];
42
46
  const htmlDataLabels = [];
43
47
  if (series.dataLabels.enabled) {
@@ -21,6 +21,7 @@ export const LineSeriesShapes = (args) => {
21
21
  const hoverOptions = get(seriesOptions, 'line.states.hover');
22
22
  const inactiveOptions = get(seriesOptions, 'line.states.inactive');
23
23
  const line = lineGenerator()
24
+ .defined((d) => d.y !== null && d.x !== null)
24
25
  .x((d) => d.x)
25
26
  .y((d) => d.y);
26
27
  plotSvgElement.selectAll('*').remove();
@@ -53,33 +53,58 @@ export const prepareLineData = async (args) => {
53
53
  if (!seriesYScale) {
54
54
  continue;
55
55
  }
56
- const points = s.data.map((d) => ({
57
- x: getXValue({ point: d, points: s.data, xAxis, xScale }),
58
- y: yAxisTop +
59
- getYValue({ point: d, points: s.data, yAxis: seriesYAxis, yScale: seriesYScale }),
60
- active: true,
61
- data: d,
62
- series: s,
63
- }));
56
+ const points = s.data.map((d) => {
57
+ const yValue = getYValue({
58
+ point: d,
59
+ points: s.data,
60
+ yAxis: seriesYAxis,
61
+ yScale: seriesYScale,
62
+ });
63
+ return {
64
+ x: getXValue({ point: d, points: s.data, xAxis, xScale }),
65
+ y: yValue === null ? null : yAxisTop + yValue,
66
+ active: true,
67
+ data: d,
68
+ series: s,
69
+ };
70
+ });
64
71
  const htmlElements = [];
65
72
  let labels = [];
66
73
  if (s.dataLabels.enabled) {
67
74
  if (s.dataLabels.html) {
68
- const list = await Promise.all(points.map((p) => getHtmlLabel(p, s, xMax)));
75
+ const list = await Promise.all(points.reduce((result, p) => {
76
+ if (p.y === null) {
77
+ return result;
78
+ }
79
+ result.push(getHtmlLabel(p, s, xMax));
80
+ return result;
81
+ }, []));
69
82
  htmlElements.push(...list);
70
83
  }
71
84
  else {
72
- labels = await Promise.all(points.map((p) => getLabelData(p, s, xMax)));
85
+ labels = await Promise.all(points.reduce((result, p) => {
86
+ if (p.y === null) {
87
+ return result;
88
+ }
89
+ result.push(getLabelData(p, s, xMax));
90
+ return result;
91
+ }, []));
73
92
  }
74
93
  }
75
94
  let markers = [];
76
95
  if (s.marker.states.normal.enabled || s.marker.states.hover.enabled) {
77
- markers = points.map((p) => ({
78
- point: p,
79
- active: true,
80
- hovered: false,
81
- clipped: isOutsideBounds(p.x, p.y),
82
- }));
96
+ markers = points.reduce((result, p) => {
97
+ if (p.y === null || p.x === null) {
98
+ return result;
99
+ }
100
+ result.push({
101
+ point: p,
102
+ active: true,
103
+ hovered: false,
104
+ clipped: isOutsideBounds(p.x, p.y),
105
+ });
106
+ return result;
107
+ }, []);
83
108
  }
84
109
  const result = {
85
110
  points,
@@ -2,14 +2,18 @@ import type { DashStyle, LineCap } from '../../../constants';
2
2
  import type { HtmlItem, LabelData, LineSeriesData } from '../../../types';
3
3
  import type { PreparedLineSeries } from '../../useSeries/types';
4
4
  export type PointData = {
5
- x: number;
6
- y: number;
5
+ x: number | null;
6
+ y: number | null;
7
7
  data: LineSeriesData;
8
8
  series: PreparedLineSeries;
9
9
  color?: string;
10
10
  };
11
+ export type MarkerPointData = PointData & {
12
+ y: number;
13
+ x: number;
14
+ };
11
15
  export type MarkerData = {
12
- point: PointData;
16
+ point: MarkerPointData;
13
17
  active: boolean;
14
18
  hovered: boolean;
15
19
  clipped: boolean;