@gravity-ui/charts 1.43.1 → 1.45.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 (135) 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/annotation.d.ts +12 -0
  7. package/dist/cjs/core/constants/defaults/annotation.js +12 -0
  8. package/dist/cjs/core/constants/defaults/index.d.ts +1 -0
  9. package/dist/cjs/core/constants/defaults/index.js +1 -0
  10. package/dist/cjs/core/constants/defaults/series-options.d.ts +5 -1
  11. package/dist/cjs/core/constants/defaults/series-options.js +13 -0
  12. package/dist/cjs/core/i18n/keysets/en.json +2 -1
  13. package/dist/cjs/core/i18n/keysets/ru.json +2 -1
  14. package/dist/cjs/core/series/constants.d.ts +1 -1
  15. package/dist/cjs/core/series/constants.js +1 -1
  16. package/dist/cjs/core/series/prepare-annotation.d.ts +12 -0
  17. package/dist/cjs/core/series/prepare-annotation.js +31 -0
  18. package/dist/cjs/core/series/prepare-legend.js +2 -2
  19. package/dist/cjs/core/series/prepare-x-range.d.ts +11 -0
  20. package/dist/cjs/core/series/prepare-x-range.js +41 -0
  21. package/dist/cjs/core/series/prepareSeries.js +9 -0
  22. package/dist/cjs/core/series/types.d.ts +34 -2
  23. package/dist/cjs/core/types/chart/annotation.d.ts +45 -0
  24. package/dist/cjs/core/types/chart/annotation.js +1 -0
  25. package/dist/cjs/core/types/chart/area.d.ts +10 -1
  26. package/dist/cjs/core/types/chart/bar-x.d.ts +6 -0
  27. package/dist/cjs/core/types/chart/line.d.ts +8 -0
  28. package/dist/cjs/core/types/chart/marker.d.ts +6 -4
  29. package/dist/cjs/core/types/chart/series.d.ts +36 -2
  30. package/dist/cjs/core/types/chart/tooltip.d.ts +7 -1
  31. package/dist/cjs/core/types/chart/x-range.d.ts +59 -0
  32. package/dist/cjs/core/types/chart/x-range.js +1 -0
  33. package/dist/cjs/core/types/chart/zoom.d.ts +1 -1
  34. package/dist/cjs/core/types/index.d.ts +2 -0
  35. package/dist/cjs/core/types/index.js +2 -0
  36. package/dist/cjs/core/utils/axis/x-axis.js +9 -1
  37. package/dist/cjs/core/utils/color.js +6 -0
  38. package/dist/cjs/core/utils/common.js +10 -0
  39. package/dist/cjs/core/utils/get-closest-data.js +19 -0
  40. package/dist/cjs/core/utils/text.d.ts +8 -0
  41. package/dist/cjs/core/utils/text.js +9 -1
  42. package/dist/cjs/core/validation/index.js +13 -0
  43. package/dist/cjs/core/zoom/zoom.js +24 -7
  44. package/dist/cjs/hooks/useShapes/annotation/index.d.ts +14 -0
  45. package/dist/cjs/hooks/useShapes/annotation/index.js +200 -0
  46. package/dist/cjs/hooks/useShapes/area/index.d.ts +2 -0
  47. package/dist/cjs/hooks/useShapes/area/index.js +21 -2
  48. package/dist/cjs/hooks/useShapes/area/prepare-data.d.ts +2 -1
  49. package/dist/cjs/hooks/useShapes/area/prepare-data.js +45 -26
  50. package/dist/cjs/hooks/useShapes/area/types.d.ts +4 -0
  51. package/dist/cjs/hooks/useShapes/bar-x/index.d.ts +2 -0
  52. package/dist/cjs/hooks/useShapes/bar-x/index.js +30 -2
  53. package/dist/cjs/hooks/useShapes/bar-x/prepare-data.js +32 -11
  54. package/dist/cjs/hooks/useShapes/bar-x/types.d.ts +2 -0
  55. package/dist/cjs/hooks/useShapes/index.d.ts +2 -1
  56. package/dist/cjs/hooks/useShapes/index.js +22 -3
  57. package/dist/cjs/hooks/useShapes/line/index.d.ts +2 -0
  58. package/dist/cjs/hooks/useShapes/line/index.js +21 -7
  59. package/dist/cjs/hooks/useShapes/line/prepare-data.d.ts +2 -1
  60. package/dist/cjs/hooks/useShapes/line/prepare-data.js +28 -10
  61. package/dist/cjs/hooks/useShapes/line/types.d.ts +4 -0
  62. package/dist/cjs/hooks/useShapes/x-range/index.d.ts +14 -0
  63. package/dist/cjs/hooks/useShapes/x-range/index.js +115 -0
  64. package/dist/cjs/hooks/useShapes/x-range/prepare-data.d.ts +15 -0
  65. package/dist/cjs/hooks/useShapes/x-range/prepare-data.js +147 -0
  66. package/dist/cjs/hooks/useShapes/x-range/types.d.ts +12 -0
  67. package/dist/cjs/hooks/useShapes/x-range/types.js +1 -0
  68. package/dist/esm/components/ChartInner/utils/zoom.js +3 -1
  69. package/dist/esm/components/Tooltip/DefaultTooltipContent/index.js +31 -6
  70. package/dist/esm/components/Tooltip/DefaultTooltipContent/utils.js +4 -5
  71. package/dist/esm/core/constants/chart-types.d.ts +1 -0
  72. package/dist/esm/core/constants/chart-types.js +1 -0
  73. package/dist/esm/core/constants/defaults/annotation.d.ts +12 -0
  74. package/dist/esm/core/constants/defaults/annotation.js +12 -0
  75. package/dist/esm/core/constants/defaults/index.d.ts +1 -0
  76. package/dist/esm/core/constants/defaults/index.js +1 -0
  77. package/dist/esm/core/constants/defaults/series-options.d.ts +5 -1
  78. package/dist/esm/core/constants/defaults/series-options.js +13 -0
  79. package/dist/esm/core/i18n/keysets/en.json +2 -1
  80. package/dist/esm/core/i18n/keysets/ru.json +2 -1
  81. package/dist/esm/core/series/constants.d.ts +1 -1
  82. package/dist/esm/core/series/constants.js +1 -1
  83. package/dist/esm/core/series/prepare-annotation.d.ts +12 -0
  84. package/dist/esm/core/series/prepare-annotation.js +31 -0
  85. package/dist/esm/core/series/prepare-legend.js +2 -2
  86. package/dist/esm/core/series/prepare-x-range.d.ts +11 -0
  87. package/dist/esm/core/series/prepare-x-range.js +41 -0
  88. package/dist/esm/core/series/prepareSeries.js +9 -0
  89. package/dist/esm/core/series/types.d.ts +34 -2
  90. package/dist/esm/core/types/chart/annotation.d.ts +45 -0
  91. package/dist/esm/core/types/chart/annotation.js +1 -0
  92. package/dist/esm/core/types/chart/area.d.ts +10 -1
  93. package/dist/esm/core/types/chart/bar-x.d.ts +6 -0
  94. package/dist/esm/core/types/chart/line.d.ts +8 -0
  95. package/dist/esm/core/types/chart/marker.d.ts +6 -4
  96. package/dist/esm/core/types/chart/series.d.ts +36 -2
  97. package/dist/esm/core/types/chart/tooltip.d.ts +7 -1
  98. package/dist/esm/core/types/chart/x-range.d.ts +59 -0
  99. package/dist/esm/core/types/chart/x-range.js +1 -0
  100. package/dist/esm/core/types/chart/zoom.d.ts +1 -1
  101. package/dist/esm/core/types/index.d.ts +2 -0
  102. package/dist/esm/core/types/index.js +2 -0
  103. package/dist/esm/core/utils/axis/x-axis.js +9 -1
  104. package/dist/esm/core/utils/color.js +6 -0
  105. package/dist/esm/core/utils/common.js +10 -0
  106. package/dist/esm/core/utils/get-closest-data.js +19 -0
  107. package/dist/esm/core/utils/text.d.ts +8 -0
  108. package/dist/esm/core/utils/text.js +9 -1
  109. package/dist/esm/core/validation/index.js +13 -0
  110. package/dist/esm/core/zoom/zoom.js +24 -7
  111. package/dist/esm/hooks/useShapes/annotation/index.d.ts +14 -0
  112. package/dist/esm/hooks/useShapes/annotation/index.js +200 -0
  113. package/dist/esm/hooks/useShapes/area/index.d.ts +2 -0
  114. package/dist/esm/hooks/useShapes/area/index.js +21 -2
  115. package/dist/esm/hooks/useShapes/area/prepare-data.d.ts +2 -1
  116. package/dist/esm/hooks/useShapes/area/prepare-data.js +45 -26
  117. package/dist/esm/hooks/useShapes/area/types.d.ts +4 -0
  118. package/dist/esm/hooks/useShapes/bar-x/index.d.ts +2 -0
  119. package/dist/esm/hooks/useShapes/bar-x/index.js +30 -2
  120. package/dist/esm/hooks/useShapes/bar-x/prepare-data.js +32 -11
  121. package/dist/esm/hooks/useShapes/bar-x/types.d.ts +2 -0
  122. package/dist/esm/hooks/useShapes/index.d.ts +2 -1
  123. package/dist/esm/hooks/useShapes/index.js +22 -3
  124. package/dist/esm/hooks/useShapes/line/index.d.ts +2 -0
  125. package/dist/esm/hooks/useShapes/line/index.js +21 -7
  126. package/dist/esm/hooks/useShapes/line/prepare-data.d.ts +2 -1
  127. package/dist/esm/hooks/useShapes/line/prepare-data.js +28 -10
  128. package/dist/esm/hooks/useShapes/line/types.d.ts +4 -0
  129. package/dist/esm/hooks/useShapes/x-range/index.d.ts +14 -0
  130. package/dist/esm/hooks/useShapes/x-range/index.js +115 -0
  131. package/dist/esm/hooks/useShapes/x-range/prepare-data.d.ts +15 -0
  132. package/dist/esm/hooks/useShapes/x-range/prepare-data.js +147 -0
  133. package/dist/esm/hooks/useShapes/x-range/types.d.ts +12 -0
  134. package/dist/esm/hooks/useShapes/x-range/types.js +1 -0
  135. package/package.json +2 -2
@@ -5,14 +5,16 @@ import get from 'lodash/get';
5
5
  import { filterOverlappingLabels } from '../../../core/utils';
6
6
  import { block } from '../../../utils';
7
7
  import { HtmlLayer } from '../HtmlLayer';
8
+ import { renderAnnotations } from '../annotation';
8
9
  import { getRectPath } from '../utils';
9
10
  export { prepareBarXData } from './prepare-data';
10
11
  export * from './types';
11
12
  const b = block('bar-x');
12
13
  export const BarXSeriesShapes = (args) => {
13
- const { dispatcher, preparedData, seriesOptions, htmlLayout, clipPathId } = args;
14
+ const { boundsHeight, boundsWidth, dispatcher, preparedData, seriesOptions, htmlLayout, clipPathId, } = args;
14
15
  const hoveredDataRef = React.useRef(null);
15
16
  const ref = React.useRef(null);
17
+ const annotationsRef = React.useRef(null);
16
18
  const allowOverlapDataLabels = React.useMemo(() => {
17
19
  return preparedData.some((d) => d === null || d === void 0 ? void 0 : d.series.dataLabels.allowOverlap);
18
20
  }, [preparedData]);
@@ -65,6 +67,24 @@ export const BarXSeriesShapes = (args) => {
65
67
  .style('font-size', (d) => d.style.fontSize)
66
68
  .style('font-weight', (d) => d.style.fontWeight || null)
67
69
  .style('fill', (d) => d.style.fontColor || null);
70
+ if (annotationsRef.current) {
71
+ const anchors = [];
72
+ for (const d of preparedData) {
73
+ if (d.annotation) {
74
+ anchors.push({
75
+ annotation: d.annotation,
76
+ x: d.x + d.width / 2,
77
+ y: d.y,
78
+ });
79
+ }
80
+ }
81
+ renderAnnotations({
82
+ anchors,
83
+ container: select(annotationsRef.current),
84
+ plotHeight: boundsHeight,
85
+ plotWidth: boundsWidth,
86
+ });
87
+ }
68
88
  function handleShapeHover(data) {
69
89
  hoveredDataRef.current = data;
70
90
  const hoverEnabled = hoverOptions === null || hoverOptions === void 0 ? void 0 : hoverOptions.enabled;
@@ -112,7 +132,14 @@ export const BarXSeriesShapes = (args) => {
112
132
  return () => {
113
133
  dispatcher === null || dispatcher === void 0 ? void 0 : dispatcher.on('hover-shape.bar-x', null);
114
134
  };
115
- }, [allowOverlapDataLabels, dispatcher, preparedData, seriesOptions]);
135
+ }, [
136
+ allowOverlapDataLabels,
137
+ boundsHeight,
138
+ boundsWidth,
139
+ dispatcher,
140
+ preparedData,
141
+ seriesOptions,
142
+ ]);
116
143
  const htmlLayerData = React.useMemo(() => {
117
144
  const items = preparedData.map((d) => d === null || d === void 0 ? void 0 : d.htmlLabels).flat();
118
145
  if (allowOverlapDataLabels) {
@@ -122,5 +149,6 @@ export const BarXSeriesShapes = (args) => {
122
149
  }, [allowOverlapDataLabels, preparedData]);
123
150
  return (React.createElement(React.Fragment, null,
124
151
  React.createElement("g", { ref: ref, className: b(), clipPath: `url(#${clipPathId})` }),
152
+ React.createElement("g", { ref: annotationsRef }),
125
153
  React.createElement(HtmlLayer, { preparedData: htmlLayerData, htmlLayout: htmlLayout })));
126
154
  };
@@ -1,5 +1,6 @@
1
1
  import { ascending, descending, max, min, reverse, sort } from 'd3-array';
2
2
  import get from 'lodash/get';
3
+ import { prepareAnnotation } from '../../../core/series/prepare-annotation';
3
4
  import { getDataCategoryValue, getLabelsSize } from '../../../core/utils';
4
5
  import { getFormattedValue } from '../../../core/utils/format';
5
6
  import { MIN_BAR_GAP, MIN_BAR_GROUP_GAP, MIN_BAR_WIDTH } from '../../constants';
@@ -35,7 +36,7 @@ async function getLabelData(d, xMax) {
35
36
  };
36
37
  }
37
38
  export const prepareBarXData = async (args) => {
38
- var _a, _b, _c, _d, _e, _f;
39
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
39
40
  const { series, seriesOptions, xAxis, xScale, yAxis, yScale, boundsHeight: plotHeight, split, isRangeSlider, } = args;
40
41
  const stackGap = seriesOptions['bar-x'].stackGap;
41
42
  const categories = (_a = xAxis === null || xAxis === void 0 ? void 0 : xAxis.categories) !== null && _a !== void 0 ? _a : [];
@@ -110,8 +111,8 @@ export const prepareBarXData = async (args) => {
110
111
  const currentGroupWidth = rectWidth * stacks.length + rectGap * (stacks.length - 1);
111
112
  for (let groupItemIndex = 0; groupItemIndex < stacks.length; groupItemIndex++) {
112
113
  const yValues = stacks[groupItemIndex];
113
- let positiveStackHeight = 0;
114
- let negativeStackHeight = 0;
114
+ let positiveStackSum = 0;
115
+ let negativeStackSum = 0;
115
116
  const stackItems = [];
116
117
  let sortedData = yValues;
117
118
  if (sortKey) {
@@ -144,7 +145,6 @@ export const prepareBarXData = async (args) => {
144
145
  }
145
146
  const x = xCenter - currentGroupWidth / 2 + (rectWidth + rectGap) * groupItemIndex;
146
147
  const yDataValue = ((_d = yValue.data.y) !== null && _d !== void 0 ? _d : 0);
147
- const y = seriesYScale(yDataValue);
148
148
  let base = 0;
149
149
  if (seriesYAxis.type === 'logarithmic') {
150
150
  const domainData = seriesYScale.domain();
@@ -155,7 +155,22 @@ export const prepareBarXData = async (args) => {
155
155
  base = seriesYScale(0);
156
156
  }
157
157
  const isLastStackItem = yValueIndex === sortedData.length - 1;
158
- const height = Math.abs(base - y);
158
+ let height;
159
+ let barPositionY;
160
+ if (yDataValue > 0) {
161
+ const newSum = positiveStackSum + yDataValue;
162
+ const topPixel = seriesYScale(newSum);
163
+ const bottomPixel = positiveStackSum === 0 ? base : seriesYScale(positiveStackSum);
164
+ height = Math.abs(bottomPixel - topPixel);
165
+ barPositionY = yAxisTop + topPixel;
166
+ }
167
+ else {
168
+ const newSum = negativeStackSum + yDataValue;
169
+ const bottomPixel = negativeStackSum === 0 ? base : seriesYScale(negativeStackSum);
170
+ const topPixel = seriesYScale(newSum);
171
+ height = Math.abs(bottomPixel - topPixel);
172
+ barPositionY = yAxisTop + bottomPixel;
173
+ }
159
174
  let shapeHeight = height - (stackItems.length ? stackGap : 0);
160
175
  if (shapeHeight < 0) {
161
176
  shapeHeight = height;
@@ -164,10 +179,15 @@ export const prepareBarXData = async (args) => {
164
179
  continue;
165
180
  }
166
181
  const barData = {
182
+ annotation: yValue.data.annotation && !isRangeSlider
183
+ ? await prepareAnnotation({
184
+ annotation: yValue.data.annotation,
185
+ optionsLabel: (_g = (_f = seriesOptions['bar-x']) === null || _f === void 0 ? void 0 : _f.annotation) === null || _g === void 0 ? void 0 : _g.label,
186
+ optionsPopup: (_j = (_h = seriesOptions['bar-x']) === null || _h === void 0 ? void 0 : _h.annotation) === null || _j === void 0 ? void 0 : _j.popup,
187
+ })
188
+ : undefined,
167
189
  x,
168
- y: yDataValue > 0
169
- ? yAxisTop + y - positiveStackHeight
170
- : yAxisTop + base + negativeStackHeight,
190
+ y: barPositionY,
171
191
  width: rectWidth,
172
192
  height: shapeHeight,
173
193
  _height: height,
@@ -180,14 +200,15 @@ export const prepareBarXData = async (args) => {
180
200
  };
181
201
  stackItems.push(barData);
182
202
  if (yDataValue > 0) {
183
- positiveStackHeight += height;
203
+ positiveStackSum += yDataValue;
184
204
  }
185
205
  else {
186
- negativeStackHeight += height;
206
+ negativeStackSum += yDataValue;
187
207
  }
188
208
  }
189
209
  if (series.some((s) => s.stacking === 'percent')) {
190
210
  let acc = 0;
211
+ const positiveStackHeight = stackItems.reduce((sum, item) => sum + item._height, 0);
191
212
  const ratio = plotHeight / positiveStackHeight;
192
213
  stackItems.forEach((item) => {
193
214
  item.height = item._height * ratio;
@@ -207,7 +228,7 @@ export const prepareBarXData = async (args) => {
207
228
  barData.x >= xMax ||
208
229
  barData.y + barData.height <= 0 ||
209
230
  barData.y >= plotHeight;
210
- const isZeroValue = ((_f = barData.data.y) !== null && _f !== void 0 ? _f : 0) === 0;
231
+ const isZeroValue = ((_k = barData.data.y) !== null && _k !== void 0 ? _k : 0) === 0;
211
232
  if (barData.series.dataLabels.enabled &&
212
233
  !isRangeSlider &&
213
234
  (!isBarOutsideBounds || isZeroValue)) {
@@ -1,6 +1,8 @@
1
+ import type { PreparedAnnotation } from '../../../core/series/types';
1
2
  import type { HtmlItem, LabelData, TooltipDataChunkBarX } from '../../../types';
2
3
  import type { PreparedBarXSeries } from '../../useSeries/types';
3
4
  export type PreparedBarXData = Omit<TooltipDataChunkBarX, 'series'> & {
5
+ annotation?: PreparedAnnotation;
4
6
  x: number;
5
7
  y: number;
6
8
  width: number;
@@ -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;
@@ -61,7 +62,7 @@ export async function getShapes(args) {
61
62
  split,
62
63
  isRangeSlider,
63
64
  });
64
- shapes[index] = (React.createElement(BarXSeriesShapes, { key: SERIES_TYPE.BarX, dispatcher: dispatcher, seriesOptions: seriesOptions, preparedData: preparedData, htmlLayout: htmlLayout, clipPathId: clipPathId }));
65
+ shapes[index] = (React.createElement(BarXSeriesShapes, { key: SERIES_TYPE.BarX, boundsHeight: boundsHeight, boundsWidth: boundsWidth, dispatcher: dispatcher, seriesOptions: seriesOptions, preparedData: preparedData, htmlLayout: htmlLayout, clipPathId: clipPathId }));
65
66
  shapesData.splice(index, 0, ...preparedData);
66
67
  layers.push(...preparedData);
67
68
  }
@@ -103,6 +104,7 @@ export async function getShapes(args) {
103
104
  if (xAxis && xScale && (yScale === null || yScale === void 0 ? void 0 : yScale.length)) {
104
105
  const preparedData = await prepareLineData({
105
106
  series: chartSeries,
107
+ seriesOptions,
106
108
  xAxis,
107
109
  xScale,
108
110
  yAxis,
@@ -117,7 +119,7 @@ export async function getShapes(args) {
117
119
  yAxis,
118
120
  zoomState,
119
121
  });
120
- shapes[index] = (React.createElement(LineSeriesShapes, { key: groupId, dispatcher: dispatcher, seriesOptions: seriesOptions, preparedData: preparedData, htmlLayout: htmlLayout, clipPathId: resultClipPathId }));
122
+ shapes[index] = (React.createElement(LineSeriesShapes, { key: groupId, boundsHeight: boundsHeight, boundsWidth: boundsWidth, dispatcher: dispatcher, seriesOptions: seriesOptions, preparedData: preparedData, htmlLayout: htmlLayout, clipPathId: resultClipPathId }));
121
123
  shapesData.splice(index, 0, ...preparedData);
122
124
  layers.push(...preparedData);
123
125
  }
@@ -127,6 +129,7 @@ export async function getShapes(args) {
127
129
  if (xAxis && xScale && (yScale === null || yScale === void 0 ? void 0 : yScale.length)) {
128
130
  const preparedData = await prepareAreaData({
129
131
  series: chartSeries,
132
+ seriesOptions,
130
133
  xAxis,
131
134
  xScale,
132
135
  yAxis,
@@ -135,7 +138,7 @@ export async function getShapes(args) {
135
138
  isOutsideBounds,
136
139
  isRangeSlider,
137
140
  });
138
- shapes[index] = (React.createElement(AreaSeriesShapes, { key: SERIES_TYPE.Area, dispatcher: dispatcher, seriesOptions: seriesOptions, preparedData: preparedData, htmlLayout: htmlLayout, clipPathId: clipPathId }));
141
+ shapes[index] = (React.createElement(AreaSeriesShapes, { key: SERIES_TYPE.Area, boundsHeight: boundsHeight, boundsWidth: boundsWidth, dispatcher: dispatcher, seriesOptions: seriesOptions, preparedData: preparedData, htmlLayout: htmlLayout, clipPathId: clipPathId }));
139
142
  shapesData.splice(index, 0, ...preparedData);
140
143
  layers.push(...preparedData);
141
144
  }
@@ -224,6 +227,22 @@ export async function getShapes(args) {
224
227
  shapesData.splice(index, 0, preparedData);
225
228
  break;
226
229
  }
230
+ case SERIES_TYPE.XRange: {
231
+ if (xAxis && xScale && (yScale === null || yScale === void 0 ? void 0 : yScale.length)) {
232
+ const preparedData = await prepareXRangeData({
233
+ series: chartSeries,
234
+ xAxis,
235
+ xScale,
236
+ yAxis,
237
+ yScale,
238
+ boundsWidth,
239
+ isRangeSlider,
240
+ });
241
+ shapes[index] = (React.createElement(XRangeSeriesShapes, { key: SERIES_TYPE.XRange, dispatcher: dispatcher, preparedData: preparedData, seriesOptions: seriesOptions, htmlLayout: htmlLayout, clipPathId: clipPathId }));
242
+ shapesData.splice(index, 0, ...preparedData);
243
+ }
244
+ break;
245
+ }
227
246
  default: {
228
247
  throw new ChartError({
229
248
  message: `The display method is not defined for a series with type "${seriesType}"`,
@@ -3,6 +3,8 @@ import type { Dispatch } from 'd3-dispatch';
3
3
  import type { PreparedSeriesOptions } from '../../useSeries/types';
4
4
  import type { PreparedLineData } from './types';
5
5
  type Args = {
6
+ boundsHeight: number;
7
+ boundsWidth: number;
6
8
  clipPathId: string;
7
9
  htmlLayout: HTMLElement | null;
8
10
  preparedData: PreparedLineData[];
@@ -6,16 +6,19 @@ import get from 'lodash/get';
6
6
  import { getLineDashArray } from '../../../core/utils';
7
7
  import { block } from '../../../utils';
8
8
  import { HtmlLayer } from '../HtmlLayer';
9
+ import { renderAnnotations } from '../annotation';
9
10
  import { getMarkerHaloVisibility, getMarkerVisibility, renderMarker, selectMarkerHalo, selectMarkerSymbol, setMarker, } from '../marker';
10
11
  import { setActiveState } from '../utils';
11
12
  const b = block('line');
12
13
  export const LineSeriesShapes = (args) => {
13
- const { dispatcher, preparedData, seriesOptions, htmlLayout, clipPathId } = args;
14
+ const { boundsHeight, boundsWidth, dispatcher, preparedData, seriesOptions, htmlLayout, clipPathId, } = args;
14
15
  const hoveredDataRef = React.useRef(null);
15
16
  const plotRef = React.useRef(null);
16
17
  const markersRef = React.useRef(null);
17
18
  const hoverMarkersRef = React.useRef(null);
19
+ const annotationsRef = React.useRef(null);
18
20
  React.useEffect(() => {
21
+ var _a, _b;
19
22
  if (!plotRef.current || !markersRef.current) {
20
23
  return () => { };
21
24
  }
@@ -64,13 +67,22 @@ export const LineSeriesShapes = (args) => {
64
67
  .data(markers)
65
68
  .join('g')
66
69
  .call(renderMarker);
70
+ if (annotationsRef.current) {
71
+ const anchors = preparedData.flatMap((d) => d.annotations);
72
+ renderAnnotations({
73
+ anchors,
74
+ container: select(annotationsRef.current),
75
+ plotHeight: boundsHeight,
76
+ plotWidth: boundsWidth,
77
+ });
78
+ }
67
79
  const hoverEnabled = hoverOptions === null || hoverOptions === void 0 ? void 0 : hoverOptions.enabled;
68
80
  const inactiveEnabled = inactiveOptions === null || inactiveOptions === void 0 ? void 0 : inactiveOptions.enabled;
69
81
  function handleShapeHover(data) {
70
82
  hoveredDataRef.current = data;
71
83
  const selected = (data === null || data === void 0 ? void 0 : data.filter((d) => d.series.type === 'line')) || [];
72
- const selectedDataItems = selected.map((d) => d.data);
73
84
  const selectedSeriesIds = selected.map((d) => { var _a; return (_a = d.series) === null || _a === void 0 ? void 0 : _a.id; });
85
+ const closestChunk = selected.find((d) => d.closest);
74
86
  lineSelection.datum((d, index, list) => {
75
87
  const elementSelection = select(list[index]);
76
88
  const hovered = Boolean(hoverEnabled && selectedSeriesIds.includes(d.id));
@@ -106,7 +118,7 @@ export const LineSeriesShapes = (args) => {
106
118
  });
107
119
  markerSelection.datum((d, index, list) => {
108
120
  const elementSelection = select(list[index]);
109
- const hovered = Boolean(hoverEnabled && selectedDataItems.includes(d.point.data));
121
+ const hovered = Boolean(hoverEnabled && d.point.data === (closestChunk === null || closestChunk === void 0 ? void 0 : closestChunk.data));
110
122
  if (d.hovered !== hovered) {
111
123
  d.hovered = hovered;
112
124
  elementSelection.attr('visibility', getMarkerVisibility(d));
@@ -129,7 +141,7 @@ export const LineSeriesShapes = (args) => {
129
141
  hoverMarkersSvgElement.selectAll('*').remove();
130
142
  if (hoverEnabled && selected.length > 0) {
131
143
  const hoverOnlyMarkers = [];
132
- for (const chunk of selected) {
144
+ for (const chunk of selected.filter((c) => c.closest)) {
133
145
  const seriesData = preparedData.find((pd) => pd.id === chunk.series.id);
134
146
  if (!seriesData) {
135
147
  continue;
@@ -165,11 +177,12 @@ export const LineSeriesShapes = (args) => {
165
177
  if (hoveredDataRef.current !== null) {
166
178
  handleShapeHover(hoveredDataRef.current);
167
179
  }
168
- dispatcher === null || dispatcher === void 0 ? void 0 : dispatcher.on('hover-shape.line', handleShapeHover);
180
+ const eventName = `hover-shape.line-${(_b = (_a = preparedData[0]) === null || _a === void 0 ? void 0 : _a.id) !== null && _b !== void 0 ? _b : 'unknown'}`;
181
+ dispatcher === null || dispatcher === void 0 ? void 0 : dispatcher.on(eventName, handleShapeHover);
169
182
  return () => {
170
- dispatcher === null || dispatcher === void 0 ? void 0 : dispatcher.on('hover-shape.line', null);
183
+ dispatcher === null || dispatcher === void 0 ? void 0 : dispatcher.on(eventName, null);
171
184
  };
172
- }, [dispatcher, preparedData, seriesOptions]);
185
+ }, [boundsHeight, boundsWidth, dispatcher, preparedData, seriesOptions]);
173
186
  const htmlLayerData = React.useMemo(() => {
174
187
  const items = preparedData.map((d) => d === null || d === void 0 ? void 0 : d.htmlLabels).flat();
175
188
  return { htmlElements: items };
@@ -178,5 +191,6 @@ export const LineSeriesShapes = (args) => {
178
191
  React.createElement("g", { ref: plotRef, className: b(), clipPath: `url(#${clipPathId})` }),
179
192
  React.createElement("g", { ref: markersRef }),
180
193
  React.createElement("g", { ref: hoverMarkersRef }),
194
+ React.createElement("g", { ref: annotationsRef }),
181
195
  React.createElement(HtmlLayer, { preparedData: htmlLayerData, htmlLayout: htmlLayout })));
182
196
  };
@@ -2,10 +2,11 @@ import type { PreparedSplit } from '../../../core/layout/split-types';
2
2
  import type { ChartScale } from '../../../core/scales/types';
3
3
  import type { ShapeDataWithLabels } from '../../../types';
4
4
  import type { PreparedXAxis, PreparedYAxis } from '../../useAxis/types';
5
- import type { PreparedLineSeries } from '../../useSeries/types';
5
+ import type { PreparedLineSeries, PreparedSeriesOptions } from '../../useSeries/types';
6
6
  import type { PreparedLineData } from './types';
7
7
  export declare const prepareLineData: (args: {
8
8
  series: PreparedLineSeries[];
9
+ seriesOptions?: PreparedSeriesOptions;
9
10
  xAxis: PreparedXAxis;
10
11
  xScale: ChartScale;
11
12
  yAxis: PreparedYAxis[];
@@ -1,3 +1,4 @@
1
+ import { prepareAnnotation } from '../../../core/series/prepare-annotation';
1
2
  import { filterOverlappingLabels, getLabelsSize, getTextSizeFn } from '../../../core/utils';
2
3
  import { getFormattedValue } from '../../../core/utils/format';
3
4
  import { getXValue, getYValue } from '../utils';
@@ -15,8 +16,8 @@ async function getHtmlLabel(point, series, xMax) {
15
16
  };
16
17
  }
17
18
  export const prepareLineData = async (args) => {
18
- var _a, _b, _c, _d;
19
- const { series, xAxis, yAxis, xScale, yScale, split, isOutsideBounds, isRangeSlider, otherLayers, } = args;
19
+ var _a, _b, _c, _d, _e, _f, _g;
20
+ const { series, seriesOptions, xAxis, yAxis, xScale, yScale, split, isOutsideBounds, isRangeSlider, otherLayers, } = args;
20
21
  const [_xMin, xRangeMax] = xScale.range();
21
22
  const xMax = xRangeMax;
22
23
  const acc = [];
@@ -29,21 +30,31 @@ export const prepareLineData = async (args) => {
29
30
  if (!seriesYScale) {
30
31
  continue;
31
32
  }
32
- const points = s.data.map((d) => {
33
+ const annotationOpts = (_b = seriesOptions === null || seriesOptions === void 0 ? void 0 : seriesOptions.line) === null || _b === void 0 ? void 0 : _b.annotation;
34
+ const points = [];
35
+ for (let j = 0; j < s.data.length; j++) {
36
+ const d = s.data[j];
33
37
  const yValue = getYValue({
34
38
  point: d,
35
39
  points: s.data,
36
40
  yAxis: seriesYAxis,
37
41
  yScale: seriesYScale,
38
42
  });
39
- return {
43
+ points.push({
40
44
  x: getXValue({ point: d, points: s.data, xAxis, xScale }),
41
45
  y: yValue === null ? null : yAxisTop + yValue,
42
- active: true,
46
+ color: (_d = (_c = d.marker) === null || _c === void 0 ? void 0 : _c.color) !== null && _d !== void 0 ? _d : d.color,
43
47
  data: d,
44
48
  series: s,
45
- };
46
- });
49
+ annotation: d.annotation && !isRangeSlider
50
+ ? await prepareAnnotation({
51
+ annotation: d.annotation,
52
+ optionsLabel: annotationOpts === null || annotationOpts === void 0 ? void 0 : annotationOpts.label,
53
+ optionsPopup: annotationOpts === null || annotationOpts === void 0 ? void 0 : annotationOpts.popup,
54
+ })
55
+ : undefined,
56
+ });
57
+ }
47
58
  let htmlElements = [];
48
59
  let svgLabels = [];
49
60
  if (s.dataLabels.enabled && !isRangeSlider) {
@@ -64,7 +75,7 @@ export const prepareLineData = async (args) => {
64
75
  if (point.y !== null &&
65
76
  point.x !== null &&
66
77
  !isOutsideBounds(point.x, point.y)) {
67
- const labelValue = (_b = point.data.label) !== null && _b !== void 0 ? _b : point.data.y;
78
+ const labelValue = (_e = point.data.label) !== null && _e !== void 0 ? _e : point.data.y;
68
79
  const text = getFormattedValue(Object.assign({ value: labelValue }, s.dataLabels));
69
80
  const labelSize = await getTextSize(text);
70
81
  const style = s.dataLabels.style;
@@ -112,7 +123,14 @@ export const prepareLineData = async (args) => {
112
123
  return result;
113
124
  }, []);
114
125
  }
126
+ const annotations = points.reduce((result, p) => {
127
+ if (p.annotation && p.x !== null && p.y !== null) {
128
+ result.push({ annotation: p.annotation, x: p.x, y: p.y });
129
+ }
130
+ return result;
131
+ }, []);
115
132
  const result = {
133
+ annotations,
116
134
  points,
117
135
  markers,
118
136
  svgLabels: svgLabels,
@@ -122,11 +140,11 @@ export const prepareLineData = async (args) => {
122
140
  id: s.id,
123
141
  htmlLabels: htmlElements,
124
142
  color: s.color,
125
- lineWidth: (_c = (isRangeSlider ? s.rangeSlider.lineWidth : undefined)) !== null && _c !== void 0 ? _c : s.lineWidth,
143
+ lineWidth: (_f = (isRangeSlider ? s.rangeSlider.lineWidth : undefined)) !== null && _f !== void 0 ? _f : s.lineWidth,
126
144
  dashStyle: s.dashStyle,
127
145
  linecap: s.linecap,
128
146
  linejoin: s.linejoin,
129
- opacity: (_d = (isRangeSlider ? s.rangeSlider.opacity : undefined)) !== null && _d !== void 0 ? _d : s.opacity,
147
+ opacity: (_g = (isRangeSlider ? s.rangeSlider.opacity : undefined)) !== null && _g !== void 0 ? _g : s.opacity,
130
148
  };
131
149
  acc.push(result);
132
150
  }
@@ -1,11 +1,14 @@
1
1
  import type { DashStyle, LineCap, LineJoin } from '../../../core/constants';
2
+ import type { PreparedAnnotation } from '../../../core/series/types';
2
3
  import type { HtmlItem, LabelData, LineSeriesData, LineSeriesLineBaseStyle } from '../../../types';
3
4
  import type { PreparedLineSeries } from '../../useSeries/types';
5
+ import type { AnnotationAnchor } from '../annotation';
4
6
  export type PointData = {
5
7
  x: number | null;
6
8
  y: number | null;
7
9
  data: LineSeriesData;
8
10
  series: PreparedLineSeries;
11
+ annotation?: PreparedAnnotation;
9
12
  color?: string;
10
13
  };
11
14
  export type MarkerPointData = PointData & {
@@ -19,6 +22,7 @@ export type MarkerData = {
19
22
  clipped: boolean;
20
23
  };
21
24
  export type PreparedLineData = {
25
+ annotations: AnnotationAnchor[];
22
26
  id: string;
23
27
  points: PointData[];
24
28
  markers: MarkerData[];
@@ -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 {};