@gravity-ui/charts 1.44.0 → 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 (77) hide show
  1. package/dist/cjs/core/constants/defaults/annotation.d.ts +12 -0
  2. package/dist/cjs/core/constants/defaults/annotation.js +12 -0
  3. package/dist/cjs/core/constants/defaults/index.d.ts +1 -0
  4. package/dist/cjs/core/constants/defaults/index.js +1 -0
  5. package/dist/cjs/core/series/constants.d.ts +1 -1
  6. package/dist/cjs/core/series/constants.js +1 -1
  7. package/dist/cjs/core/series/prepare-annotation.d.ts +12 -0
  8. package/dist/cjs/core/series/prepare-annotation.js +31 -0
  9. package/dist/cjs/core/series/types.d.ts +16 -0
  10. package/dist/cjs/core/types/chart/annotation.d.ts +45 -0
  11. package/dist/cjs/core/types/chart/annotation.js +1 -0
  12. package/dist/cjs/core/types/chart/area.d.ts +8 -0
  13. package/dist/cjs/core/types/chart/bar-x.d.ts +6 -0
  14. package/dist/cjs/core/types/chart/line.d.ts +8 -0
  15. package/dist/cjs/core/types/chart/marker.d.ts +6 -4
  16. package/dist/cjs/core/types/chart/series.d.ts +7 -0
  17. package/dist/cjs/core/types/chart/tooltip.d.ts +1 -0
  18. package/dist/cjs/core/types/index.d.ts +1 -0
  19. package/dist/cjs/core/types/index.js +1 -0
  20. package/dist/cjs/core/utils/text.d.ts +8 -0
  21. package/dist/cjs/core/utils/text.js +9 -1
  22. package/dist/cjs/hooks/useShapes/annotation/index.d.ts +14 -0
  23. package/dist/cjs/hooks/useShapes/annotation/index.js +200 -0
  24. package/dist/cjs/hooks/useShapes/area/index.d.ts +2 -0
  25. package/dist/cjs/hooks/useShapes/area/index.js +21 -2
  26. package/dist/cjs/hooks/useShapes/area/prepare-data.d.ts +2 -1
  27. package/dist/cjs/hooks/useShapes/area/prepare-data.js +38 -20
  28. package/dist/cjs/hooks/useShapes/area/types.d.ts +4 -0
  29. package/dist/cjs/hooks/useShapes/bar-x/index.d.ts +2 -0
  30. package/dist/cjs/hooks/useShapes/bar-x/index.js +30 -2
  31. package/dist/cjs/hooks/useShapes/bar-x/prepare-data.js +10 -2
  32. package/dist/cjs/hooks/useShapes/bar-x/types.d.ts +2 -0
  33. package/dist/cjs/hooks/useShapes/index.js +5 -3
  34. package/dist/cjs/hooks/useShapes/line/index.d.ts +2 -0
  35. package/dist/cjs/hooks/useShapes/line/index.js +21 -7
  36. package/dist/cjs/hooks/useShapes/line/prepare-data.d.ts +2 -1
  37. package/dist/cjs/hooks/useShapes/line/prepare-data.js +28 -10
  38. package/dist/cjs/hooks/useShapes/line/types.d.ts +4 -0
  39. package/dist/esm/core/constants/defaults/annotation.d.ts +12 -0
  40. package/dist/esm/core/constants/defaults/annotation.js +12 -0
  41. package/dist/esm/core/constants/defaults/index.d.ts +1 -0
  42. package/dist/esm/core/constants/defaults/index.js +1 -0
  43. package/dist/esm/core/series/constants.d.ts +1 -1
  44. package/dist/esm/core/series/constants.js +1 -1
  45. package/dist/esm/core/series/prepare-annotation.d.ts +12 -0
  46. package/dist/esm/core/series/prepare-annotation.js +31 -0
  47. package/dist/esm/core/series/types.d.ts +16 -0
  48. package/dist/esm/core/types/chart/annotation.d.ts +45 -0
  49. package/dist/esm/core/types/chart/annotation.js +1 -0
  50. package/dist/esm/core/types/chart/area.d.ts +8 -0
  51. package/dist/esm/core/types/chart/bar-x.d.ts +6 -0
  52. package/dist/esm/core/types/chart/line.d.ts +8 -0
  53. package/dist/esm/core/types/chart/marker.d.ts +6 -4
  54. package/dist/esm/core/types/chart/series.d.ts +7 -0
  55. package/dist/esm/core/types/chart/tooltip.d.ts +1 -0
  56. package/dist/esm/core/types/index.d.ts +1 -0
  57. package/dist/esm/core/types/index.js +1 -0
  58. package/dist/esm/core/utils/text.d.ts +8 -0
  59. package/dist/esm/core/utils/text.js +9 -1
  60. package/dist/esm/hooks/useShapes/annotation/index.d.ts +14 -0
  61. package/dist/esm/hooks/useShapes/annotation/index.js +200 -0
  62. package/dist/esm/hooks/useShapes/area/index.d.ts +2 -0
  63. package/dist/esm/hooks/useShapes/area/index.js +21 -2
  64. package/dist/esm/hooks/useShapes/area/prepare-data.d.ts +2 -1
  65. package/dist/esm/hooks/useShapes/area/prepare-data.js +38 -20
  66. package/dist/esm/hooks/useShapes/area/types.d.ts +4 -0
  67. package/dist/esm/hooks/useShapes/bar-x/index.d.ts +2 -0
  68. package/dist/esm/hooks/useShapes/bar-x/index.js +30 -2
  69. package/dist/esm/hooks/useShapes/bar-x/prepare-data.js +10 -2
  70. package/dist/esm/hooks/useShapes/bar-x/types.d.ts +2 -0
  71. package/dist/esm/hooks/useShapes/index.js +5 -3
  72. package/dist/esm/hooks/useShapes/line/index.d.ts +2 -0
  73. package/dist/esm/hooks/useShapes/line/index.js +21 -7
  74. package/dist/esm/hooks/useShapes/line/prepare-data.d.ts +2 -1
  75. package/dist/esm/hooks/useShapes/line/prepare-data.js +28 -10
  76. package/dist/esm/hooks/useShapes/line/types.d.ts +4 -0
  77. package/package.json +2 -2
@@ -1,10 +1,11 @@
1
1
  import type { PreparedSplit } from '../../../core/layout/split-types';
2
2
  import type { ChartScale } from '../../../core/scales/types';
3
3
  import type { PreparedXAxis, PreparedYAxis } from '../../useAxis/types';
4
- import type { PreparedAreaSeries } from '../../useSeries/types';
4
+ import type { PreparedAreaSeries, PreparedSeriesOptions } from '../../useSeries/types';
5
5
  import type { PreparedAreaData } from './types';
6
6
  export declare const prepareAreaData: (args: {
7
7
  series: PreparedAreaSeries[];
8
+ seriesOptions?: PreparedSeriesOptions;
8
9
  xAxis: PreparedXAxis;
9
10
  xScale: ChartScale;
10
11
  yAxis: PreparedYAxis[];
@@ -1,6 +1,7 @@
1
1
  import { group, min, sort } from 'd3-array';
2
2
  import isNil from 'lodash/isNil';
3
3
  import round from 'lodash/round';
4
+ import { prepareAnnotation } from '../../../core/series/prepare-annotation';
4
5
  import { getDataCategoryValue, getLabelsSize, getTextSizeFn } from '../../../core/utils';
5
6
  import { getFormattedValue } from '../../../core/utils/format';
6
7
  import { getXValue, getYValue } from '../utils';
@@ -76,8 +77,8 @@ async function prepareDataLabels({ series, points, xMax, yAxisTop, isOutsideBoun
76
77
  return { svgLabels, htmlLabels };
77
78
  }
78
79
  export const prepareAreaData = async (args) => {
79
- var _a, _b, _c, _d;
80
- const { series, xAxis, xScale, yAxis, yScale, split, isOutsideBounds, isRangeSlider } = args;
80
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
81
+ const { series, seriesOptions, xAxis, xScale, yAxis, yScale, split, isOutsideBounds, isRangeSlider, } = args;
81
82
  const [_xMin, xRangeMax] = xScale.range();
82
83
  const xMax = xRangeMax;
83
84
  const result = [];
@@ -165,16 +166,25 @@ export const prepareAreaData = async (args) => {
165
166
  : d.x);
166
167
  return m.set(key, d);
167
168
  }, new Map());
168
- const points = xValues.reduce((pointsAcc, [x, xValue], index) => {
169
- var _a, _b, _c, _d, _e, _f, _g;
169
+ const annotationOpts = (_d = seriesOptions === null || seriesOptions === void 0 ? void 0 : seriesOptions.area) === null || _d === void 0 ? void 0 : _d.annotation;
170
+ const points = [];
171
+ for (let xIdx = 0; xIdx < xValues.length; xIdx++) {
172
+ const [x, xValue] = xValues[xIdx];
170
173
  const rawData = seriesData.get(x);
171
174
  const d = rawData !== null && rawData !== void 0 ? rawData : {
172
175
  x,
173
176
  y: 0,
174
177
  };
175
- let yDataValue = (_a = d.y) !== null && _a !== void 0 ? _a : null;
178
+ let yDataValue = (_e = d.y) !== null && _e !== void 0 ? _e : null;
179
+ const pointAnnotation = d.annotation && !isRangeSlider
180
+ ? await prepareAnnotation({
181
+ annotation: d.annotation,
182
+ optionsLabel: annotationOpts === null || annotationOpts === void 0 ? void 0 : annotationOpts.label,
183
+ optionsPopup: annotationOpts === null || annotationOpts === void 0 ? void 0 : annotationOpts.popup,
184
+ })
185
+ : undefined;
176
186
  if (s.nullMode === 'connect' && (yDataValue === null || !rawData)) {
177
- return pointsAcc;
187
+ continue;
178
188
  }
179
189
  if (yDataValue && isPercentStacking) {
180
190
  yDataValue = Number(yDataValue) * ratio[x];
@@ -188,21 +198,23 @@ export const prepareAreaData = async (args) => {
188
198
  });
189
199
  if (typeof yDataValue === 'number' && yValue !== null) {
190
200
  yValue = round(yValue, 2);
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]);
201
+ const prevPoint = seriesData.get((_f = xValues[xIdx - 1]) === null || _f === void 0 ? void 0 : _f[0]);
202
+ const nextPoint = seriesData.get((_g = xValues[xIdx + 1]) === null || _g === void 0 ? void 0 : _g[0]);
193
203
  const currentPointStackHeight = Math.abs(yMin - yValue);
194
204
  if (yDataValue >= 0) {
195
205
  const positiveStackHeights = positiveStackValues.get(x);
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;
206
+ let prevSectionStackHeight = (_h = positiveStackHeights === null || positiveStackHeights === void 0 ? void 0 : positiveStackHeights.prev) !== null && _h !== void 0 ? _h : 0;
207
+ let nextSectionStackHeight = (_j = positiveStackHeights === null || positiveStackHeights === void 0 ? void 0 : positiveStackHeights.next) !== null && _j !== void 0 ? _j : 0;
198
208
  const point = {
199
209
  y0: yAxisTop + yMin - prevSectionStackHeight,
200
210
  x: xValue,
201
211
  y: yAxisTop + yValue - prevSectionStackHeight,
212
+ color: (_l = (_k = d.marker) === null || _k === void 0 ? void 0 : _k.color) !== null && _l !== void 0 ? _l : d.color,
202
213
  data: d,
203
214
  series: s,
215
+ annotation: pointAnnotation,
204
216
  };
205
- pointsAcc.push(point);
217
+ points.push(point);
206
218
  if (prevSectionStackHeight !== nextSectionStackHeight) {
207
219
  const point2 = {
208
220
  y0: yAxisTop + yMin - nextSectionStackHeight,
@@ -211,7 +223,7 @@ export const prepareAreaData = async (args) => {
211
223
  data: d,
212
224
  series: s,
213
225
  };
214
- pointsAcc.push(point2);
226
+ points.push(point2);
215
227
  if (isPercentStacking) {
216
228
  const newYValue = yAxisTop +
217
229
  yValue -
@@ -235,9 +247,9 @@ export const prepareAreaData = async (args) => {
235
247
  }
236
248
  else {
237
249
  const negativeStackHeights = negativeStackValues.get(x);
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;
240
- pointsAcc.push({
250
+ let prevSectionStackHeight = (_m = negativeStackHeights === null || negativeStackHeights === void 0 ? void 0 : negativeStackHeights.prev) !== null && _m !== void 0 ? _m : 0;
251
+ let nextSectionStackHeight = (_o = negativeStackHeights === null || negativeStackHeights === void 0 ? void 0 : negativeStackHeights.next) !== null && _o !== void 0 ? _o : 0;
252
+ points.push({
241
253
  y0: yAxisTop + yMin + prevSectionStackHeight,
242
254
  x: xValue,
243
255
  y: yAxisTop + yValue + prevSectionStackHeight,
@@ -245,7 +257,7 @@ export const prepareAreaData = async (args) => {
245
257
  series: s,
246
258
  });
247
259
  if (prevSectionStackHeight !== nextSectionStackHeight) {
248
- pointsAcc.push({
260
+ points.push({
249
261
  y0: yAxisTop + yMin + nextSectionStackHeight,
250
262
  x: xValue,
251
263
  y: yAxisTop + yValue + nextSectionStackHeight,
@@ -268,7 +280,7 @@ export const prepareAreaData = async (args) => {
268
280
  }
269
281
  }
270
282
  else {
271
- pointsAcc.push({
283
+ points.push({
272
284
  y0: yAxisTop + yMin,
273
285
  x: xValue,
274
286
  y: null,
@@ -276,8 +288,7 @@ export const prepareAreaData = async (args) => {
276
288
  series: s,
277
289
  });
278
290
  }
279
- return pointsAcc;
280
- }, []);
291
+ }
281
292
  let markers = [];
282
293
  const hasPerPointNormalMarkers = s.data.some((d) => { var _a, _b, _c; return (_c = (_b = (_a = d.marker) === null || _a === void 0 ? void 0 : _a.states) === null || _b === void 0 ? void 0 : _b.normal) === null || _c === void 0 ? void 0 : _c.enabled; });
283
294
  if (s.marker.states.normal.enabled || hasPerPointNormalMarkers) {
@@ -298,7 +309,14 @@ export const prepareAreaData = async (args) => {
298
309
  return markersAcc;
299
310
  }, []);
300
311
  }
312
+ const annotations = points.reduce((result, p) => {
313
+ if (p.annotation && p.y !== null) {
314
+ result.push({ annotation: p.annotation, x: p.x, y: p.y });
315
+ }
316
+ return result;
317
+ }, []);
301
318
  seriesStackData.push({
319
+ annotations,
302
320
  points,
303
321
  markers,
304
322
  svgLabels: [],
@@ -315,7 +333,7 @@ export const prepareAreaData = async (args) => {
315
333
  for (let itemIndex = 0; itemIndex < seriesStackData.length; itemIndex++) {
316
334
  const item = seriesStackData[itemIndex];
317
335
  const currentYAxis = yAxis[item.series.yAxis];
318
- const itemYAxisTop = ((_d = split.plots[currentYAxis.plotIndex]) === null || _d === void 0 ? void 0 : _d.top) || 0;
336
+ const itemYAxisTop = ((_p = split.plots[currentYAxis.plotIndex]) === null || _p === void 0 ? void 0 : _p.top) || 0;
319
337
  if (item.series.dataLabels.enabled && !isRangeSlider) {
320
338
  const labelsData = await prepareDataLabels({
321
339
  series: item.series,
@@ -1,11 +1,14 @@
1
+ import type { PreparedAnnotation } from '../../../core/series/types';
1
2
  import type { AreaSeriesData, HtmlItem, LabelData } from '../../../types';
2
3
  import type { PreparedAreaSeries } from '../../useSeries/types';
4
+ import type { AnnotationAnchor } from '../annotation';
3
5
  export type PointData = {
4
6
  y0: number;
5
7
  x: number;
6
8
  y: number | null;
7
9
  data: AreaSeriesData;
8
10
  series: PreparedAreaSeries;
11
+ annotation?: PreparedAnnotation;
9
12
  color?: string;
10
13
  };
11
14
  export type MarkerPointData = PointData & {
@@ -18,6 +21,7 @@ export type MarkerData = {
18
21
  clipped: boolean;
19
22
  };
20
23
  export type PreparedAreaData = {
24
+ annotations: AnnotationAnchor[];
21
25
  id: string;
22
26
  points: PointData[];
23
27
  markers: MarkerData[];
@@ -5,6 +5,8 @@ import type { PreparedBarXData } from './types';
5
5
  export { prepareBarXData } from './prepare-data';
6
6
  export * from './types';
7
7
  type Args = {
8
+ boundsHeight: number;
9
+ boundsWidth: number;
8
10
  clipPathId: string;
9
11
  htmlLayout: HTMLElement | null;
10
12
  preparedData: PreparedBarXData[];
@@ -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 : [];
@@ -178,6 +179,13 @@ export const prepareBarXData = async (args) => {
178
179
  continue;
179
180
  }
180
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,
181
189
  x,
182
190
  y: barPositionY,
183
191
  width: rectWidth,
@@ -220,7 +228,7 @@ export const prepareBarXData = async (args) => {
220
228
  barData.x >= xMax ||
221
229
  barData.y + barData.height <= 0 ||
222
230
  barData.y >= plotHeight;
223
- 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;
224
232
  if (barData.series.dataLabels.enabled &&
225
233
  !isRangeSlider &&
226
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;
@@ -62,7 +62,7 @@ export async function getShapes(args) {
62
62
  split,
63
63
  isRangeSlider,
64
64
  });
65
- 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 }));
66
66
  shapesData.splice(index, 0, ...preparedData);
67
67
  layers.push(...preparedData);
68
68
  }
@@ -104,6 +104,7 @@ export async function getShapes(args) {
104
104
  if (xAxis && xScale && (yScale === null || yScale === void 0 ? void 0 : yScale.length)) {
105
105
  const preparedData = await prepareLineData({
106
106
  series: chartSeries,
107
+ seriesOptions,
107
108
  xAxis,
108
109
  xScale,
109
110
  yAxis,
@@ -118,7 +119,7 @@ export async function getShapes(args) {
118
119
  yAxis,
119
120
  zoomState,
120
121
  });
121
- 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 }));
122
123
  shapesData.splice(index, 0, ...preparedData);
123
124
  layers.push(...preparedData);
124
125
  }
@@ -128,6 +129,7 @@ export async function getShapes(args) {
128
129
  if (xAxis && xScale && (yScale === null || yScale === void 0 ? void 0 : yScale.length)) {
129
130
  const preparedData = await prepareAreaData({
130
131
  series: chartSeries,
132
+ seriesOptions,
131
133
  xAxis,
132
134
  xScale,
133
135
  yAxis,
@@ -136,7 +138,7 @@ export async function getShapes(args) {
136
138
  isOutsideBounds,
137
139
  isRangeSlider,
138
140
  });
139
- 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 }));
140
142
  shapesData.splice(index, 0, ...preparedData);
141
143
  layers.push(...preparedData);
142
144
  }
@@ -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,12 @@
1
+ export declare const annotationLabelDefaults: {
2
+ style: {
3
+ fontSize: string;
4
+ fontColor: string;
5
+ };
6
+ };
7
+ export declare const annotationPopupDefaults: {
8
+ backgroundColor: string;
9
+ borderRadius: number;
10
+ offset: number;
11
+ padding: [number, number];
12
+ };
@@ -0,0 +1,12 @@
1
+ export const annotationLabelDefaults = {
2
+ style: {
3
+ fontSize: '13px',
4
+ fontColor: 'var(--g-color-text-light-primary)',
5
+ },
6
+ };
7
+ export const annotationPopupDefaults = {
8
+ backgroundColor: 'var(--g-color-base-float-heavy)',
9
+ borderRadius: 4,
10
+ offset: 5,
11
+ padding: [4, 8],
12
+ };
@@ -1,3 +1,4 @@
1
+ export * from './annotation';
1
2
  export * from './axis';
2
3
  export * from './brush';
3
4
  export * from './data-labels';
@@ -1,3 +1,4 @@
1
+ export * from './annotation';
1
2
  export * from './axis';
2
3
  export * from './brush';
3
4
  export * from './data-labels';
@@ -4,4 +4,4 @@ export declare const DEFAULT_LEGEND_SYMBOL_SIZE = 8;
4
4
  export declare const DEFAULT_LEGEND_SYMBOL_PADDING = 5;
5
5
  export declare const DEFAULT_DATALABELS_PADDING = 5;
6
6
  export declare const DEFAULT_HALO_OPTIONS: Required<Halo>;
7
- export declare const DEFAULT_POINT_MARKER_OPTIONS: Omit<Required<PointMarkerOptions>, 'enabled'>;
7
+ export declare const DEFAULT_POINT_MARKER_OPTIONS: Omit<Required<PointMarkerOptions>, 'color' | 'enabled'>;
@@ -7,8 +7,8 @@ export const DEFAULT_HALO_OPTIONS = {
7
7
  size: 6,
8
8
  };
9
9
  export const DEFAULT_POINT_MARKER_OPTIONS = {
10
- radius: 4,
11
10
  borderColor: '',
12
11
  borderWidth: 0,
12
+ radius: 4,
13
13
  symbol: 'circle',
14
14
  };