@gravity-ui/charts 1.43.1 → 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 (77) hide show
  1. package/dist/cjs/components/ChartInner/utils/zoom.js +3 -1
  2. package/dist/cjs/components/Tooltip/DefaultTooltipContent/index.js +31 -6
  3. package/dist/cjs/components/Tooltip/DefaultTooltipContent/utils.js +4 -5
  4. package/dist/cjs/core/constants/chart-types.d.ts +1 -0
  5. package/dist/cjs/core/constants/chart-types.js +1 -0
  6. package/dist/cjs/core/constants/defaults/series-options.d.ts +5 -1
  7. package/dist/cjs/core/constants/defaults/series-options.js +13 -0
  8. package/dist/cjs/core/i18n/keysets/en.json +2 -1
  9. package/dist/cjs/core/i18n/keysets/ru.json +2 -1
  10. package/dist/cjs/core/series/prepare-legend.js +2 -2
  11. package/dist/cjs/core/series/prepare-x-range.d.ts +11 -0
  12. package/dist/cjs/core/series/prepare-x-range.js +41 -0
  13. package/dist/cjs/core/series/prepareSeries.js +9 -0
  14. package/dist/cjs/core/series/types.d.ts +18 -2
  15. package/dist/cjs/core/types/chart/area.d.ts +2 -1
  16. package/dist/cjs/core/types/chart/series.d.ts +29 -2
  17. package/dist/cjs/core/types/chart/tooltip.d.ts +6 -1
  18. package/dist/cjs/core/types/chart/x-range.d.ts +59 -0
  19. package/dist/cjs/core/types/chart/x-range.js +1 -0
  20. package/dist/cjs/core/types/chart/zoom.d.ts +1 -1
  21. package/dist/cjs/core/types/index.d.ts +1 -0
  22. package/dist/cjs/core/types/index.js +1 -0
  23. package/dist/cjs/core/utils/axis/x-axis.js +9 -1
  24. package/dist/cjs/core/utils/color.js +6 -0
  25. package/dist/cjs/core/utils/common.js +10 -0
  26. package/dist/cjs/core/utils/get-closest-data.js +19 -0
  27. package/dist/cjs/core/validation/index.js +13 -0
  28. package/dist/cjs/core/zoom/zoom.js +24 -7
  29. package/dist/cjs/hooks/useShapes/area/prepare-data.js +15 -14
  30. package/dist/cjs/hooks/useShapes/bar-x/prepare-data.js +22 -9
  31. package/dist/cjs/hooks/useShapes/index.d.ts +2 -1
  32. package/dist/cjs/hooks/useShapes/index.js +17 -0
  33. package/dist/cjs/hooks/useShapes/x-range/index.d.ts +14 -0
  34. package/dist/cjs/hooks/useShapes/x-range/index.js +115 -0
  35. package/dist/cjs/hooks/useShapes/x-range/prepare-data.d.ts +15 -0
  36. package/dist/cjs/hooks/useShapes/x-range/prepare-data.js +147 -0
  37. package/dist/cjs/hooks/useShapes/x-range/types.d.ts +12 -0
  38. package/dist/cjs/hooks/useShapes/x-range/types.js +1 -0
  39. package/dist/esm/components/ChartInner/utils/zoom.js +3 -1
  40. package/dist/esm/components/Tooltip/DefaultTooltipContent/index.js +31 -6
  41. package/dist/esm/components/Tooltip/DefaultTooltipContent/utils.js +4 -5
  42. package/dist/esm/core/constants/chart-types.d.ts +1 -0
  43. package/dist/esm/core/constants/chart-types.js +1 -0
  44. package/dist/esm/core/constants/defaults/series-options.d.ts +5 -1
  45. package/dist/esm/core/constants/defaults/series-options.js +13 -0
  46. package/dist/esm/core/i18n/keysets/en.json +2 -1
  47. package/dist/esm/core/i18n/keysets/ru.json +2 -1
  48. package/dist/esm/core/series/prepare-legend.js +2 -2
  49. package/dist/esm/core/series/prepare-x-range.d.ts +11 -0
  50. package/dist/esm/core/series/prepare-x-range.js +41 -0
  51. package/dist/esm/core/series/prepareSeries.js +9 -0
  52. package/dist/esm/core/series/types.d.ts +18 -2
  53. package/dist/esm/core/types/chart/area.d.ts +2 -1
  54. package/dist/esm/core/types/chart/series.d.ts +29 -2
  55. package/dist/esm/core/types/chart/tooltip.d.ts +6 -1
  56. package/dist/esm/core/types/chart/x-range.d.ts +59 -0
  57. package/dist/esm/core/types/chart/x-range.js +1 -0
  58. package/dist/esm/core/types/chart/zoom.d.ts +1 -1
  59. package/dist/esm/core/types/index.d.ts +1 -0
  60. package/dist/esm/core/types/index.js +1 -0
  61. package/dist/esm/core/utils/axis/x-axis.js +9 -1
  62. package/dist/esm/core/utils/color.js +6 -0
  63. package/dist/esm/core/utils/common.js +10 -0
  64. package/dist/esm/core/utils/get-closest-data.js +19 -0
  65. package/dist/esm/core/validation/index.js +13 -0
  66. package/dist/esm/core/zoom/zoom.js +24 -7
  67. package/dist/esm/hooks/useShapes/area/prepare-data.js +15 -14
  68. package/dist/esm/hooks/useShapes/bar-x/prepare-data.js +22 -9
  69. package/dist/esm/hooks/useShapes/index.d.ts +2 -1
  70. package/dist/esm/hooks/useShapes/index.js +17 -0
  71. package/dist/esm/hooks/useShapes/x-range/index.d.ts +14 -0
  72. package/dist/esm/hooks/useShapes/x-range/index.js +115 -0
  73. package/dist/esm/hooks/useShapes/x-range/prepare-data.d.ts +15 -0
  74. package/dist/esm/hooks/useShapes/x-range/prepare-data.js +147 -0
  75. package/dist/esm/hooks/useShapes/x-range/types.d.ts +12 -0
  76. package/dist/esm/hooks/useShapes/x-range/types.js +1 -0
  77. package/package.json +1 -1
@@ -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,
@@ -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,9 +179,7 @@ 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,
@@ -180,14 +192,15 @@ export const prepareBarXData = async (args) => {
180
192
  };
181
193
  stackItems.push(barData);
182
194
  if (yDataValue > 0) {
183
- positiveStackHeight += height;
195
+ positiveStackSum += yDataValue;
184
196
  }
185
197
  else {
186
- negativeStackHeight += height;
198
+ negativeStackSum += yDataValue;
187
199
  }
188
200
  }
189
201
  if (series.some((s) => s.stacking === 'percent')) {
190
202
  let acc = 0;
203
+ const positiveStackHeight = stackItems.reduce((sum, item) => sum + item._height, 0);
191
204
  const ratio = plotHeight / positiveStackHeight;
192
205
  stackItems.forEach((item) => {
193
206
  item.height = item._height * ratio;
@@ -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;
@@ -22,6 +22,7 @@ import { TreemapSeriesShape } from './treemap';
22
22
  import { prepareTreemapData } from './treemap/prepare-data';
23
23
  import { getSeriesClipPathId } from './utils';
24
24
  import { WaterfallSeriesShapes, prepareWaterfallData } from './waterfall';
25
+ import { XRangeSeriesShapes, prepareXRangeData } from './x-range';
25
26
  import './styles.css';
26
27
  function IS_OUTSIDE_BOUNDS() {
27
28
  return false;
@@ -224,6 +225,22 @@ export async function getShapes(args) {
224
225
  shapesData.splice(index, 0, preparedData);
225
226
  break;
226
227
  }
228
+ case SERIES_TYPE.XRange: {
229
+ if (xAxis && xScale && (yScale === null || yScale === void 0 ? void 0 : yScale.length)) {
230
+ const preparedData = await prepareXRangeData({
231
+ series: chartSeries,
232
+ xAxis,
233
+ xScale,
234
+ yAxis,
235
+ yScale,
236
+ boundsWidth,
237
+ isRangeSlider,
238
+ });
239
+ shapes[index] = (React.createElement(XRangeSeriesShapes, { key: SERIES_TYPE.XRange, dispatcher: dispatcher, preparedData: preparedData, seriesOptions: seriesOptions, htmlLayout: htmlLayout, clipPathId: clipPathId }));
240
+ shapesData.splice(index, 0, ...preparedData);
241
+ }
242
+ break;
243
+ }
227
244
  default: {
228
245
  throw new ChartError({
229
246
  message: `The display method is not defined for a series with type "${seriesType}"`,
@@ -0,0 +1,14 @@
1
+ import React from 'react';
2
+ import type { Dispatch } from 'd3-dispatch';
3
+ import type { PreparedSeriesOptions } from '../../useSeries/types';
4
+ export { prepareXRangeData } from './prepare-data';
5
+ export type { PreparedXRangeData } from './types';
6
+ import type { PreparedXRangeData } from './types';
7
+ type Args = {
8
+ clipPathId: string;
9
+ htmlLayout: HTMLElement | null;
10
+ preparedData: PreparedXRangeData[];
11
+ seriesOptions: PreparedSeriesOptions;
12
+ dispatcher?: Dispatch<object>;
13
+ };
14
+ export declare function XRangeSeriesShapes(args: Args): React.JSX.Element;
@@ -0,0 +1,115 @@
1
+ import React from 'react';
2
+ import { color } from 'd3-color';
3
+ import { select } from 'd3-selection';
4
+ import get from 'lodash/get';
5
+ import { getLineDashArray } from '../../../core/utils';
6
+ import { block } from '../../../utils';
7
+ import { HtmlLayer } from '../HtmlLayer';
8
+ import { getRectPath } from '../utils';
9
+ export { prepareXRangeData } from './prepare-data';
10
+ const b = block('x-range');
11
+ export function XRangeSeriesShapes(args) {
12
+ const { dispatcher, preparedData, seriesOptions, htmlLayout, clipPathId } = args;
13
+ const hoveredDataRef = React.useRef(null);
14
+ const ref = React.useRef(null);
15
+ React.useEffect(() => {
16
+ var _a;
17
+ if (!ref.current) {
18
+ return () => { };
19
+ }
20
+ const svgElement = select(ref.current);
21
+ svgElement.selectAll('*').remove();
22
+ const segmentSelection = svgElement
23
+ .selectAll(`path.${b('segment')}`)
24
+ .data(preparedData)
25
+ .join('path')
26
+ .attr('d', (d) => {
27
+ const borderRadius = Math.min(d.height / 2, d.width / 2, d.series.borderRadius);
28
+ return getRectPath({
29
+ x: d.x,
30
+ y: d.y,
31
+ width: d.width,
32
+ height: d.height,
33
+ borderRadius,
34
+ }).toString();
35
+ })
36
+ .attr('class', b('segment'))
37
+ .attr('fill', (d) => d.color)
38
+ .attr('opacity', (d) => { var _a; return (_a = d.data.opacity) !== null && _a !== void 0 ? _a : d.series.opacity; })
39
+ .attr('cursor', (d) => d.series.cursor);
40
+ svgElement
41
+ .selectAll(`path.${b('segment-border')}`)
42
+ .data(preparedData.filter((d) => d.series.borderWidth > 0))
43
+ .join('path')
44
+ .attr('d', (d) => {
45
+ const borderRadius = Math.min(d.height / 2, d.width / 2, d.series.borderRadius);
46
+ return getRectPath({
47
+ x: d.x,
48
+ y: d.y,
49
+ width: d.width,
50
+ height: d.height,
51
+ borderRadius,
52
+ }).toString();
53
+ })
54
+ .attr('class', b('segment-border'))
55
+ .attr('fill', 'none')
56
+ .attr('stroke', (d) => d.series.borderColor)
57
+ .attr('stroke-width', (d) => d.series.borderWidth)
58
+ .attr('stroke-dasharray', (d) => getLineDashArray(d.series.borderDashStyle, d.series.borderWidth))
59
+ .attr('opacity', (d) => { var _a; return (_a = d.data.opacity) !== null && _a !== void 0 ? _a : d.series.opacity; })
60
+ .attr('pointer-events', 'none');
61
+ const svgLabels = preparedData.flatMap((d) => d.svgLabels);
62
+ svgElement
63
+ .selectAll(`text.${b('label')}`)
64
+ .data(svgLabels)
65
+ .join('text')
66
+ .attr('class', b('label'))
67
+ .attr('x', (d) => d.x)
68
+ .attr('y', (d) => d.y)
69
+ .attr('text-anchor', (d) => d.textAnchor)
70
+ .attr('dominant-baseline', 'central')
71
+ .attr('pointer-events', 'none')
72
+ .style('font-size', (d) => d.style.fontSize)
73
+ .style('font-weight', (d) => d.style.fontWeight || null)
74
+ .style('fill', (d) => d.style.fontColor || null)
75
+ .html((d) => d.text);
76
+ const hoverOptions = get(seriesOptions, 'x-range.states.hover');
77
+ const inactiveOptions = get(seriesOptions, 'x-range.states.inactive');
78
+ function handleShapeHover(data) {
79
+ hoveredDataRef.current = data;
80
+ if (hoverOptions === null || hoverOptions === void 0 ? void 0 : hoverOptions.enabled) {
81
+ const hoveredSet = new Set(data === null || data === void 0 ? void 0 : data.map((d) => d.data));
82
+ segmentSelection.attr('fill', (d) => {
83
+ var _a;
84
+ const fillColor = d.color;
85
+ if (hoveredSet.has(d.data)) {
86
+ return (((_a = color(fillColor)) === null || _a === void 0 ? void 0 : _a.brighter(hoverOptions.brightness).toString()) ||
87
+ fillColor);
88
+ }
89
+ return fillColor;
90
+ });
91
+ }
92
+ if (inactiveOptions === null || inactiveOptions === void 0 ? void 0 : inactiveOptions.enabled) {
93
+ const hoveredSeries = data === null || data === void 0 ? void 0 : data.map((d) => d.series.id);
94
+ segmentSelection.attr('opacity', (d) => {
95
+ var _a, _b;
96
+ if ((hoveredSeries === null || hoveredSeries === void 0 ? void 0 : hoveredSeries.length) && !hoveredSeries.includes(d.series.id)) {
97
+ return inactiveOptions.opacity || null;
98
+ }
99
+ return (_b = (_a = d.data.opacity) !== null && _a !== void 0 ? _a : d.series.opacity) !== null && _b !== void 0 ? _b : null;
100
+ });
101
+ }
102
+ }
103
+ if (hoveredDataRef.current !== null) {
104
+ handleShapeHover((_a = hoveredDataRef.current) !== null && _a !== void 0 ? _a : undefined);
105
+ }
106
+ dispatcher === null || dispatcher === void 0 ? void 0 : dispatcher.on('hover-shape.x-range', handleShapeHover);
107
+ return () => {
108
+ dispatcher === null || dispatcher === void 0 ? void 0 : dispatcher.on('hover-shape.x-range', null);
109
+ };
110
+ }, [dispatcher, preparedData, seriesOptions]);
111
+ const htmlLayerData = React.useMemo(() => ({ htmlElements: preparedData.flatMap((d) => d.htmlLabels) }), [preparedData]);
112
+ return (React.createElement(React.Fragment, null,
113
+ React.createElement("g", { ref: ref, className: b(), clipPath: `url(#${clipPathId})` }),
114
+ React.createElement(HtmlLayer, { preparedData: htmlLayerData, htmlLayout: htmlLayout })));
115
+ }
@@ -0,0 +1,15 @@
1
+ import type { ChartScale } from '../../../core/scales/types';
2
+ import type { PreparedXAxis, PreparedYAxis } from '../../useAxis/types';
3
+ import type { PreparedXRangeSeries } from '../../useSeries/types';
4
+ import type { PreparedXRangeData } from './types';
5
+ type PrepareXRangeDataArgs = {
6
+ series: PreparedXRangeSeries[];
7
+ xAxis: PreparedXAxis;
8
+ xScale: ChartScale;
9
+ yAxis: PreparedYAxis[];
10
+ yScale: (ChartScale | undefined)[];
11
+ boundsWidth?: number;
12
+ isRangeSlider?: boolean;
13
+ };
14
+ export declare function prepareXRangeData(args: PrepareXRangeDataArgs): Promise<PreparedXRangeData[]>;
15
+ export {};
@@ -0,0 +1,147 @@
1
+ import get from 'lodash/get';
2
+ import { getDataCategoryValue, getLabelsSize, getTextSizeFn, getTextWithElipsis } from '../../../core/utils';
3
+ import { getFormattedValue } from '../../../core/utils/format';
4
+ import { MIN_BAR_WIDTH } from '../../constants';
5
+ import { getBandSize } from '../../utils/get-band-size';
6
+ const DEFAULT_BAR_PADDING = 0.2;
7
+ export async function prepareXRangeData(args) {
8
+ var _a;
9
+ const { series, xAxis, xScale, yAxis, yScale: [yScale], boundsWidth, isRangeSlider, } = args;
10
+ if (!yScale) {
11
+ return [];
12
+ }
13
+ // Collect unique y-domain values
14
+ const domain = [];
15
+ const seen = new Set();
16
+ const categories = get(yAxis[0], 'categories', []);
17
+ series.forEach((s) => {
18
+ s.data.forEach((d) => {
19
+ const key = yAxis[0].type === 'category'
20
+ ? getDataCategoryValue({ axisDirection: 'y', categories, data: d })
21
+ : d.y;
22
+ if (key !== undefined && !seen.has(key)) {
23
+ seen.add(key);
24
+ domain.push(key);
25
+ }
26
+ });
27
+ });
28
+ const bandSize = getBandSize({ domain, scale: yScale });
29
+ const barSize = Math.max(MIN_BAR_WIDTH, bandSize * (1 - DEFAULT_BAR_PADDING));
30
+ const result = [];
31
+ series.forEach((s) => {
32
+ s.data.forEach((d) => {
33
+ let center;
34
+ if (yAxis[0].type === 'category') {
35
+ const bandScale = yScale;
36
+ const yCategory = getDataCategoryValue({ axisDirection: 'y', categories, data: d });
37
+ if (!bandScale.domain().includes(yCategory)) {
38
+ return;
39
+ }
40
+ center = (bandScale(yCategory) || 0) + bandSize / 2;
41
+ }
42
+ else {
43
+ const linearScale = yScale;
44
+ if (d.y === undefined) {
45
+ return;
46
+ }
47
+ center = linearScale(Number(d.y));
48
+ }
49
+ let xStart;
50
+ let xEnd;
51
+ if (xAxis.type === 'category') {
52
+ // x-range on a category x-axis is unusual but supported
53
+ const xBandScale = xScale;
54
+ const xCategories = get(xAxis, 'categories', []);
55
+ const startCategory = getDataCategoryValue({
56
+ axisDirection: 'x',
57
+ categories: xCategories,
58
+ data: { x: d.x0 },
59
+ });
60
+ const endCategory = getDataCategoryValue({
61
+ axisDirection: 'x',
62
+ categories: xCategories,
63
+ data: { x: d.x1 },
64
+ });
65
+ xStart = xBandScale(startCategory) || 0;
66
+ xEnd = (xBandScale(endCategory) || 0) + xBandScale.bandwidth();
67
+ }
68
+ else {
69
+ const linearScale = xScale;
70
+ xStart = linearScale(Number(d.x0));
71
+ xEnd = linearScale(Number(d.x1));
72
+ }
73
+ const width = xEnd - xStart;
74
+ if (width <= 0) {
75
+ return;
76
+ }
77
+ result.push({
78
+ x: xStart,
79
+ y: center - barSize / 2,
80
+ width,
81
+ height: barSize,
82
+ color: d.color || s.color,
83
+ data: d,
84
+ series: s,
85
+ htmlLabels: [],
86
+ svgLabels: [],
87
+ });
88
+ });
89
+ });
90
+ const textSizeFnCache = new Map();
91
+ for (let i = 0; i < result.length; i++) {
92
+ const item = result[i];
93
+ const { dataLabels } = item.series;
94
+ if (!dataLabels.enabled || item.data.label === null || isRangeSlider) {
95
+ continue;
96
+ }
97
+ const content = getFormattedValue(Object.assign({ value: item.data.label }, dataLabels));
98
+ const visibleStart = Math.max(0, item.x);
99
+ const visibleEnd = boundsWidth === undefined
100
+ ? item.x + item.width
101
+ : Math.min(boundsWidth, item.x + item.width);
102
+ const visibleWidth = visibleEnd - visibleStart;
103
+ const visibleCenterX = visibleStart + visibleWidth / 2;
104
+ if (dataLabels.html) {
105
+ const { maxHeight: height, maxWidth: width } = await getLabelsSize({
106
+ labels: [content],
107
+ style: dataLabels.style,
108
+ html: true,
109
+ });
110
+ const htmlItem = {
111
+ content,
112
+ size: { width, height },
113
+ style: dataLabels.style,
114
+ x: visibleCenterX - width / 2,
115
+ y: item.y + item.height / 2 - height / 2,
116
+ };
117
+ item.htmlLabels.push(htmlItem);
118
+ }
119
+ else {
120
+ if (!textSizeFnCache.has(dataLabels.style)) {
121
+ textSizeFnCache.set(dataLabels.style, getTextSizeFn({ style: dataLabels.style }));
122
+ }
123
+ const getTextSize = (_a = textSizeFnCache.get(dataLabels.style)) !== null && _a !== void 0 ? _a : getTextSizeFn({ style: dataLabels.style });
124
+ const availableWidth = Math.max(0, visibleWidth - 2 * dataLabels.padding);
125
+ const text = await getTextWithElipsis({
126
+ text: content,
127
+ getTextWidth: (s) => getTextSize(s).then((r) => r.width),
128
+ maxWidth: availableWidth,
129
+ });
130
+ if (!text) {
131
+ continue;
132
+ }
133
+ const { width, height, hangingOffset } = await getTextSize(text);
134
+ const svgItem = {
135
+ text,
136
+ size: { width, height, hangingOffset },
137
+ style: dataLabels.style,
138
+ textAnchor: 'middle',
139
+ x: visibleCenterX,
140
+ y: item.y + item.height / 2,
141
+ series: item.series,
142
+ };
143
+ item.svgLabels.push(svgItem);
144
+ }
145
+ }
146
+ return result;
147
+ }
@@ -0,0 +1,12 @@
1
+ import type { HtmlItem, LabelData, TooltipDataChunkXRange } from '../../../types';
2
+ import type { PreparedXRangeSeries } from '../../useSeries/types';
3
+ export type PreparedXRangeData = Omit<TooltipDataChunkXRange, 'series'> & {
4
+ x: number;
5
+ y: number;
6
+ width: number;
7
+ height: number;
8
+ color: string;
9
+ series: PreparedXRangeSeries;
10
+ svgLabels: LabelData[];
11
+ htmlLabels: HtmlItem[];
12
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -6,7 +6,8 @@ function mapSeriesTypeToZoomType(seriesType) {
6
6
  case SERIES_TYPE.Area: {
7
7
  return [ZOOM_TYPE.X, ZOOM_TYPE.XY, ZOOM_TYPE.Y];
8
8
  }
9
- case SERIES_TYPE.BarX: {
9
+ case SERIES_TYPE.BarX:
10
+ case SERIES_TYPE.XRange: {
10
11
  return [ZOOM_TYPE.X];
11
12
  }
12
13
  case SERIES_TYPE.BarY: {
@@ -36,6 +37,7 @@ function getDefaultZoomType(seriesType) {
36
37
  }
37
38
  case SERIES_TYPE.Area:
38
39
  case SERIES_TYPE.BarX:
40
+ case SERIES_TYPE.XRange:
39
41
  case SERIES_TYPE.Line:
40
42
  case SERIES_TYPE.Waterfall: {
41
43
  return ZOOM_TYPE.X;
@@ -43,12 +43,15 @@ export const DefaultTooltipContent = ({ hovered, pinned, rowRenderer, totals, va
43
43
  const colorSymbol = getTooltipRowColorSymbol({ series, color });
44
44
  return (React.createElement(Row, { key: id, active: active, color: color, colorSymbol: colorSymbol ? (React.createElement("div", { dangerouslySetInnerHTML: { __html: colorSymbol.outerHTML } })) : undefined, label: React.createElement("span", { dangerouslySetInnerHTML: { __html: name } }), striped: striped, value: formattedValue }));
45
45
  };
46
- const formattedHeadValue = headerFormat
47
- ? getFormattedValue({
48
- value: measureValue === null || measureValue === void 0 ? void 0 : measureValue.value,
49
- format: headerFormat,
50
- })
51
- : measureValue === null || measureValue === void 0 ? void 0 : measureValue.formattedValue;
46
+ let formattedHeadValue;
47
+ if (measureValue) {
48
+ formattedHeadValue = headerFormat
49
+ ? getFormattedValue({
50
+ value: measureValue.value,
51
+ format: headerFormat,
52
+ })
53
+ : measureValue.formattedValue;
54
+ }
52
55
  React.useEffect(() => {
53
56
  if (!contentRowsRef.current) {
54
57
  return;
@@ -186,6 +189,28 @@ export const DefaultTooltipContent = ({ hovered, pinned, rowRenderer, totals, va
186
189
  formattedValue,
187
190
  });
188
191
  }
192
+ case 'x-range': {
193
+ const xRangeData = data;
194
+ const format = rowValueFormat || getDefaultValueFormat({ axis: xAxis });
195
+ const x0Formatted = getFormattedValue({
196
+ value: xRangeData.x0,
197
+ format,
198
+ });
199
+ const x1Formatted = getFormattedValue({
200
+ value: xRangeData.x1,
201
+ format,
202
+ });
203
+ return renderRow({
204
+ id,
205
+ active,
206
+ color,
207
+ name: series.name,
208
+ striped,
209
+ value: hoveredValues[i],
210
+ formattedValue: `${x0Formatted} — ${x1Formatted}`,
211
+ series,
212
+ });
213
+ }
189
214
  default: {
190
215
  return null;
191
216
  }
@@ -48,7 +48,7 @@ export const getMeasureValue = ({ data, xAxis, yAxis, headerFormat, }) => {
48
48
  const value = (_b = (_a = data[0].category) === null || _a === void 0 ? void 0 : _a.key) !== null && _b !== void 0 ? _b : null;
49
49
  return { value };
50
50
  }
51
- if (data.some((item) => item.series.type === 'bar-y')) {
51
+ if (data.some((item) => ['bar-y', 'x-range'].includes(item.series.type))) {
52
52
  const value = getYRowData((_c = data[0]) === null || _c === void 0 ? void 0 : _c.data, yAxis);
53
53
  const formattedValue = getFormattedValue({
54
54
  value: getYRowData((_d = data[0]) === null || _d === void 0 ? void 0 : _d.data, yAxis),
@@ -72,7 +72,9 @@ export function getHoveredValues(args) {
72
72
  case 'area':
73
73
  case 'line':
74
74
  case 'bar-x':
75
- case 'scatter': {
75
+ case 'waterfall':
76
+ case 'scatter':
77
+ case 'x-range': {
76
78
  return getYRowData(data, yAxis);
77
79
  }
78
80
  case 'bar-y': {
@@ -90,9 +92,6 @@ export function getHoveredValues(args) {
90
92
  const { target, data: source } = seriesItem;
91
93
  return (_a = source.links.find((d) => d.name === (target === null || target === void 0 ? void 0 : target.name))) === null || _a === void 0 ? void 0 : _a.value;
92
94
  }
93
- case 'waterfall': {
94
- return getYRowData(data, yAxis);
95
- }
96
95
  default: {
97
96
  return undefined;
98
97
  }
@@ -11,5 +11,6 @@ export declare const SERIES_TYPE: {
11
11
  readonly Radar: "radar";
12
12
  readonly Heatmap: "heatmap";
13
13
  readonly Funnel: "funnel";
14
+ readonly XRange: "x-range";
14
15
  };
15
16
  export type SeriesType = (typeof SERIES_TYPE)[keyof typeof SERIES_TYPE];
@@ -11,4 +11,5 @@ export const SERIES_TYPE = {
11
11
  Radar: 'radar',
12
12
  Heatmap: 'heatmap',
13
13
  Funnel: 'funnel',
14
+ XRange: 'x-range',
14
15
  };
@@ -21,7 +21,11 @@ type DefaultWaterfallSeriesOptions = Partial<ChartSeriesOptions['waterfall']> &
21
21
  barPadding: number;
22
22
  };
23
23
  };
24
- export type SeriesOptionsDefaults = Partial<ChartSeriesOptions> & DefaultBarXSeriesOptions & DefaultBarYSeriesOptions & DefaultWaterfallSeriesOptions;
24
+ export type SeriesOptionsDefaults = Partial<ChartSeriesOptions> & DefaultBarXSeriesOptions & DefaultBarYSeriesOptions & DefaultWaterfallSeriesOptions & {
25
+ 'x-range': {
26
+ borderRadius: number;
27
+ };
28
+ };
25
29
  export declare const seriesOptionsDefaults: SeriesOptionsDefaults;
26
30
  export declare const seriesRangeSliderOptionsDefaults: Required<ChartSeriesRangeSliderOptions>;
27
31
  export {};
@@ -133,6 +133,19 @@ export const seriesOptionsDefaults = {
133
133
  },
134
134
  },
135
135
  },
136
+ 'x-range': {
137
+ borderRadius: 0,
138
+ states: {
139
+ hover: {
140
+ enabled: true,
141
+ brightness: 0.3,
142
+ },
143
+ inactive: {
144
+ enabled: false,
145
+ opacity: 0.5,
146
+ },
147
+ },
148
+ },
136
149
  };
137
150
  export const seriesRangeSliderOptionsDefaults = {
138
151
  visible: true,