@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
@@ -6,15 +6,17 @@ import get from 'lodash/get';
6
6
  import { filterOverlappingLabels } 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('area');
12
13
  export const AreaSeriesShapes = (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
  const allowOverlapDataLabels = React.useMemo(() => {
19
21
  return preparedData.some((d) => d === null || d === void 0 ? void 0 : d.series.dataLabels.allowOverlap);
20
22
  }, [preparedData]);
@@ -83,6 +85,15 @@ export const AreaSeriesShapes = (args) => {
83
85
  .data(markers)
84
86
  .join('g')
85
87
  .call(renderMarker);
88
+ if (annotationsRef.current) {
89
+ const anchors = preparedData.flatMap((d) => d.annotations);
90
+ renderAnnotations({
91
+ anchors,
92
+ container: select(annotationsRef.current),
93
+ plotHeight: boundsHeight,
94
+ plotWidth: boundsWidth,
95
+ });
96
+ }
86
97
  const hoverEnabled = hoverOptions === null || hoverOptions === void 0 ? void 0 : hoverOptions.enabled;
87
98
  const inactiveEnabled = inactiveOptions === null || inactiveOptions === void 0 ? void 0 : inactiveOptions.enabled;
88
99
  function handleShapeHover(data) {
@@ -189,7 +200,14 @@ export const AreaSeriesShapes = (args) => {
189
200
  return () => {
190
201
  dispatcher === null || dispatcher === void 0 ? void 0 : dispatcher.on('hover-shape.area', null);
191
202
  };
192
- }, [allowOverlapDataLabels, dispatcher, preparedData, seriesOptions]);
203
+ }, [
204
+ allowOverlapDataLabels,
205
+ boundsHeight,
206
+ boundsWidth,
207
+ dispatcher,
208
+ preparedData,
209
+ seriesOptions,
210
+ ]);
193
211
  const htmlLayerData = React.useMemo(() => {
194
212
  const items = preparedData.map((d) => d === null || d === void 0 ? void 0 : d.htmlLabels).flat();
195
213
  if (allowOverlapDataLabels) {
@@ -201,5 +219,6 @@ export const AreaSeriesShapes = (args) => {
201
219
  React.createElement("g", { ref: plotRef, className: b(), clipPath: `url(#${clipPathId})` }),
202
220
  React.createElement("g", { ref: markersRef }),
203
221
  React.createElement("g", { ref: hoverMarkersRef }),
222
+ React.createElement("g", { ref: annotationsRef }),
204
223
  React.createElement(HtmlLayer, { preparedData: htmlLayerData, htmlLayout: htmlLayout })));
205
224
  };
@@ -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
- 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
+ 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';
@@ -27,7 +28,7 @@ function getXValues(series, xAxis, xScale) {
27
28
  return acc;
28
29
  }, []);
29
30
  }
30
- return Array.from(xValues);
31
+ return sort(Array.from(xValues), (d) => d[1]);
31
32
  }
32
33
  async function prepareDataLabels({ series, points, xMax, yAxisTop, isOutsideBounds, }) {
33
34
  var _a;
@@ -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,15 +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, _h;
170
- const d = (_a = seriesData.get(x)) !== null && _a !== void 0 ? _a : {
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];
173
+ const rawData = seriesData.get(x);
174
+ const d = rawData !== null && rawData !== void 0 ? rawData : {
171
175
  x,
172
176
  y: 0,
173
177
  };
174
- let yDataValue = (_b = d.y) !== null && _b !== void 0 ? _b : null;
175
- if (s.nullMode === 'connect' && yDataValue === null) {
176
- return pointsAcc;
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;
186
+ if (s.nullMode === 'connect' && (yDataValue === null || !rawData)) {
187
+ continue;
177
188
  }
178
189
  if (yDataValue && isPercentStacking) {
179
190
  yDataValue = Number(yDataValue) * ratio[x];
@@ -187,21 +198,23 @@ export const prepareAreaData = async (args) => {
187
198
  });
188
199
  if (typeof yDataValue === 'number' && yValue !== null) {
189
200
  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]);
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]);
192
203
  const currentPointStackHeight = Math.abs(yMin - yValue);
193
204
  if (yDataValue >= 0) {
194
205
  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;
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;
197
208
  const point = {
198
209
  y0: yAxisTop + yMin - prevSectionStackHeight,
199
210
  x: xValue,
200
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,
201
213
  data: d,
202
214
  series: s,
215
+ annotation: pointAnnotation,
203
216
  };
204
- pointsAcc.push(point);
217
+ points.push(point);
205
218
  if (prevSectionStackHeight !== nextSectionStackHeight) {
206
219
  const point2 = {
207
220
  y0: yAxisTop + yMin - nextSectionStackHeight,
@@ -210,7 +223,7 @@ export const prepareAreaData = async (args) => {
210
223
  data: d,
211
224
  series: s,
212
225
  };
213
- pointsAcc.push(point2);
226
+ points.push(point2);
214
227
  if (isPercentStacking) {
215
228
  const newYValue = yAxisTop +
216
229
  yValue -
@@ -219,11 +232,11 @@ export const prepareAreaData = async (args) => {
219
232
  point2.y = newYValue;
220
233
  }
221
234
  }
222
- if ((prevPoint === null || prevPoint === void 0 ? void 0 : prevPoint.y) !== null) {
235
+ if ((prevPoint === null || prevPoint === void 0 ? void 0 : prevPoint.y) !== null || s.nullMode === 'zero') {
223
236
  prevSectionStackHeight =
224
237
  prevSectionStackHeight + currentPointStackHeight;
225
238
  }
226
- if ((nextPoint === null || nextPoint === void 0 ? void 0 : nextPoint.y) !== null) {
239
+ if ((nextPoint === null || nextPoint === void 0 ? void 0 : nextPoint.y) !== null || s.nullMode === 'zero') {
227
240
  nextSectionStackHeight =
228
241
  nextSectionStackHeight + currentPointStackHeight;
229
242
  }
@@ -234,9 +247,9 @@ export const prepareAreaData = async (args) => {
234
247
  }
235
248
  else {
236
249
  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;
239
- 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({
240
253
  y0: yAxisTop + yMin + prevSectionStackHeight,
241
254
  x: xValue,
242
255
  y: yAxisTop + yValue + prevSectionStackHeight,
@@ -244,7 +257,7 @@ export const prepareAreaData = async (args) => {
244
257
  series: s,
245
258
  });
246
259
  if (prevSectionStackHeight !== nextSectionStackHeight) {
247
- pointsAcc.push({
260
+ points.push({
248
261
  y0: yAxisTop + yMin + nextSectionStackHeight,
249
262
  x: xValue,
250
263
  y: yAxisTop + yValue + nextSectionStackHeight,
@@ -267,7 +280,7 @@ export const prepareAreaData = async (args) => {
267
280
  }
268
281
  }
269
282
  else {
270
- pointsAcc.push({
283
+ points.push({
271
284
  y0: yAxisTop + yMin,
272
285
  x: xValue,
273
286
  y: null,
@@ -275,8 +288,7 @@ export const prepareAreaData = async (args) => {
275
288
  series: s,
276
289
  });
277
290
  }
278
- return pointsAcc;
279
- }, []);
291
+ }
280
292
  let markers = [];
281
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; });
282
294
  if (s.marker.states.normal.enabled || hasPerPointNormalMarkers) {
@@ -297,7 +309,14 @@ export const prepareAreaData = async (args) => {
297
309
  return markersAcc;
298
310
  }, []);
299
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
+ }, []);
300
318
  seriesStackData.push({
319
+ annotations,
301
320
  points,
302
321
  markers,
303
322
  svgLabels: [],
@@ -314,7 +333,7 @@ export const prepareAreaData = async (args) => {
314
333
  for (let itemIndex = 0; itemIndex < seriesStackData.length; itemIndex++) {
315
334
  const item = seriesStackData[itemIndex];
316
335
  const currentYAxis = yAxis[item.series.yAxis];
317
- 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;
318
337
  if (item.series.dataLabels.enabled && !isRangeSlider) {
319
338
  const labelsData = await prepareDataLabels({
320
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 : [];
@@ -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
  };