@gravity-ui/chartkit 5.15.0 → 5.16.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 (60) hide show
  1. package/build/plugins/d3/renderer/components/Legend.js +129 -66
  2. package/build/plugins/d3/renderer/components/styles.css +8 -0
  3. package/build/plugins/d3/renderer/constants/defaults/legend.d.ts +12 -4
  4. package/build/plugins/d3/renderer/constants/defaults/legend.js +4 -0
  5. package/build/plugins/d3/renderer/hooks/useSeries/prepare-area.js +1 -0
  6. package/build/plugins/d3/renderer/hooks/useSeries/prepare-bar-x.js +1 -0
  7. package/build/plugins/d3/renderer/hooks/useSeries/prepare-bar-y.js +8 -6
  8. package/build/plugins/d3/renderer/hooks/useSeries/prepare-legend.js +58 -11
  9. package/build/plugins/d3/renderer/hooks/useSeries/prepare-line.js +1 -0
  10. package/build/plugins/d3/renderer/hooks/useSeries/prepare-treemap.js +1 -0
  11. package/build/plugins/d3/renderer/hooks/useSeries/prepare-waterfall.js +1 -0
  12. package/build/plugins/d3/renderer/hooks/useSeries/types.d.ts +23 -1
  13. package/build/plugins/d3/renderer/hooks/useShapes/HtmlLayer.d.ts +8 -0
  14. package/build/plugins/d3/renderer/hooks/useShapes/HtmlLayer.js +22 -0
  15. package/build/plugins/d3/renderer/hooks/useShapes/area/index.d.ts +1 -0
  16. package/build/plugins/d3/renderer/hooks/useShapes/area/index.js +5 -2
  17. package/build/plugins/d3/renderer/hooks/useShapes/area/prepare-data.js +18 -3
  18. package/build/plugins/d3/renderer/hooks/useShapes/area/types.d.ts +2 -1
  19. package/build/plugins/d3/renderer/hooks/useShapes/bar-x/index.d.ts +1 -0
  20. package/build/plugins/d3/renderer/hooks/useShapes/bar-x/index.js +5 -2
  21. package/build/plugins/d3/renderer/hooks/useShapes/bar-x/prepare-data.js +21 -4
  22. package/build/plugins/d3/renderer/hooks/useShapes/bar-x/types.d.ts +2 -1
  23. package/build/plugins/d3/renderer/hooks/useShapes/bar-y/index.d.ts +1 -0
  24. package/build/plugins/d3/renderer/hooks/useShapes/bar-y/index.js +18 -23
  25. package/build/plugins/d3/renderer/hooks/useShapes/bar-y/prepare-data.js +44 -3
  26. package/build/plugins/d3/renderer/hooks/useShapes/bar-y/types.d.ts +3 -0
  27. package/build/plugins/d3/renderer/hooks/useShapes/index.js +7 -7
  28. package/build/plugins/d3/renderer/hooks/useShapes/line/index.d.ts +1 -0
  29. package/build/plugins/d3/renderer/hooks/useShapes/line/index.js +5 -2
  30. package/build/plugins/d3/renderer/hooks/useShapes/line/prepare-data.js +17 -1
  31. package/build/plugins/d3/renderer/hooks/useShapes/line/types.d.ts +2 -1
  32. package/build/plugins/d3/renderer/hooks/useShapes/pie/index.js +2 -10
  33. package/build/plugins/d3/renderer/hooks/useShapes/pie/prepare-data.js +0 -1
  34. package/build/plugins/d3/renderer/hooks/useShapes/scatter/index.d.ts +1 -0
  35. package/build/plugins/d3/renderer/hooks/useShapes/scatter/index.js +5 -2
  36. package/build/plugins/d3/renderer/hooks/useShapes/scatter/prepare-data.js +1 -0
  37. package/build/plugins/d3/renderer/hooks/useShapes/scatter/types.d.ts +2 -0
  38. package/build/plugins/d3/renderer/hooks/useShapes/treemap/index.d.ts +1 -0
  39. package/build/plugins/d3/renderer/hooks/useShapes/treemap/index.js +5 -2
  40. package/build/plugins/d3/renderer/hooks/useShapes/treemap/prepare-data.js +1 -1
  41. package/build/plugins/d3/renderer/hooks/useShapes/treemap/types.d.ts +2 -0
  42. package/build/plugins/d3/renderer/hooks/useShapes/waterfall/index.d.ts +1 -0
  43. package/build/plugins/d3/renderer/hooks/useShapes/waterfall/index.js +5 -2
  44. package/build/plugins/d3/renderer/hooks/useShapes/waterfall/prepare-data.js +1 -0
  45. package/build/plugins/d3/renderer/hooks/useShapes/waterfall/types.d.ts +2 -1
  46. package/build/plugins/d3/renderer/types/index.d.ts +3 -1
  47. package/build/plugins/d3/renderer/utils/axis-generators/bottom.d.ts +5 -4
  48. package/build/plugins/d3/renderer/utils/axis-generators/bottom.js +11 -7
  49. package/build/plugins/d3/renderer/utils/axis.d.ts +1 -1
  50. package/build/plugins/d3/renderer/utils/axis.js +1 -1
  51. package/build/plugins/d3/renderer/utils/color.d.ts +10 -0
  52. package/build/plugins/d3/renderer/utils/color.js +43 -0
  53. package/build/plugins/d3/renderer/utils/index.d.ts +2 -0
  54. package/build/plugins/d3/renderer/utils/index.js +2 -0
  55. package/build/plugins/d3/renderer/utils/legend.d.ts +8 -0
  56. package/build/plugins/d3/renderer/utils/legend.js +23 -0
  57. package/build/plugins/d3/renderer/utils/text.js +17 -10
  58. package/build/types/widget-data/bar-x.d.ts +1 -1
  59. package/build/types/widget-data/legend.d.ts +24 -0
  60. package/package.json +1 -1
@@ -1,8 +1,10 @@
1
1
  import React from 'react';
2
- import { line as lineGenerator, select, symbol } from 'd3';
2
+ import { line as lineGenerator, scaleLinear, select, symbol } from 'd3';
3
3
  import { block } from '../../../../utils/cn';
4
+ import { CONTINUOUS_LEGEND_SIZE } from '../constants';
4
5
  import { getLineDashArray } from '../hooks/useShapes/utils';
5
- import { getSymbol } from '../utils';
6
+ import { createGradientRect, getContinuesColorFn, getLabelsSize, getSymbol } from '../utils';
7
+ import { axisBottom } from '../utils/axis-generators';
6
8
  const b = block('d3-legend');
7
9
  const getLegendPosition = (args) => {
8
10
  const { align, offsetWidth, width, contentWidth } = args;
@@ -138,78 +140,139 @@ export const Legend = (props) => {
138
140
  setPaginationOffset(0);
139
141
  }, [boundsWidth]);
140
142
  React.useEffect(() => {
141
- var _a;
143
+ var _a, _b, _c, _d, _e;
142
144
  if (!ref.current) {
143
145
  return;
144
146
  }
145
147
  const svgElement = select(ref.current);
146
148
  svgElement.selectAll('*').remove();
147
- const limit = (_a = config.pagination) === null || _a === void 0 ? void 0 : _a.limit;
148
- const pageItems = typeof limit === 'number'
149
- ? items.slice(paginationOffset * limit, paginationOffset * limit + limit)
150
- : items;
151
- pageItems.forEach((line, lineIndex) => {
152
- var _a;
153
- const legendLine = svgElement.append('g').attr('class', b('line'));
154
- const legendItemTemplate = legendLine
155
- .selectAll('legend-history')
156
- .data(line)
157
- .enter()
158
- .append('g')
159
- .attr('class', b('item'))
160
- .on('click', function (e, d) {
161
- onItemClick({ name: d.name, metaKey: e.metaKey });
149
+ let legendWidth = 0;
150
+ if (legend.type === 'discrete') {
151
+ const limit = (_a = config.pagination) === null || _a === void 0 ? void 0 : _a.limit;
152
+ const pageItems = typeof limit === 'number'
153
+ ? items.slice(paginationOffset * limit, paginationOffset * limit + limit)
154
+ : items;
155
+ pageItems.forEach((line, lineIndex) => {
156
+ var _a;
157
+ const legendLine = svgElement.append('g').attr('class', b('line'));
158
+ const legendItemTemplate = legendLine
159
+ .selectAll('legend-history')
160
+ .data(line)
161
+ .enter()
162
+ .append('g')
163
+ .attr('class', b('item'))
164
+ .on('click', function (e, d) {
165
+ onItemClick({ name: d.name, metaKey: e.metaKey });
166
+ });
167
+ const getXPosition = (i) => {
168
+ return line.slice(0, i).reduce((acc, legendItem) => {
169
+ return (acc +
170
+ legendItem.symbol.width +
171
+ legendItem.symbol.padding +
172
+ legendItem.textWidth +
173
+ legend.itemDistance);
174
+ }, 0);
175
+ };
176
+ renderLegendSymbol({ selection: legendItemTemplate, legend });
177
+ legendItemTemplate
178
+ .append('text')
179
+ .attr('x', function (legendItem, i) {
180
+ return (getXPosition(i) + legendItem.symbol.width + legendItem.symbol.padding);
181
+ })
182
+ .attr('height', legend.lineHeight)
183
+ .attr('class', function (d) {
184
+ const mods = { selected: d.visible, unselected: !d.visible };
185
+ return b('item-text', mods);
186
+ })
187
+ .text(function (d) {
188
+ return ('name' in d && d.name);
189
+ })
190
+ .style('font-size', legend.itemStyle.fontSize);
191
+ const contentWidth = ((_a = legendLine.node()) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect().width) || 0;
192
+ const { left } = getLegendPosition({
193
+ align: legend.align,
194
+ width: boundsWidth,
195
+ offsetWidth: 0,
196
+ contentWidth,
197
+ });
198
+ const top = legend.lineHeight * lineIndex;
199
+ legendLine.attr('transform', `translate(${[left, top].join(',')})`);
162
200
  });
163
- const getXPosition = (i) => {
164
- return line.slice(0, i).reduce((acc, legendItem) => {
165
- return (acc +
166
- legendItem.symbol.width +
167
- legendItem.symbol.padding +
168
- legendItem.textWidth +
169
- legend.itemDistance);
170
- }, 0);
171
- };
172
- renderLegendSymbol({ selection: legendItemTemplate, legend });
173
- legendItemTemplate
174
- .append('text')
175
- .attr('x', function (legendItem, i) {
176
- return getXPosition(i) + legendItem.symbol.width + legendItem.symbol.padding;
177
- })
178
- .attr('height', legend.lineHeight)
179
- .attr('class', function (d) {
180
- const mods = { selected: d.visible, unselected: !d.visible };
181
- return b('item-text', mods);
182
- })
183
- .text(function (d) {
184
- return ('name' in d && d.name);
185
- })
186
- .style('font-size', legend.itemStyle.fontSize);
187
- const contentWidth = ((_a = legendLine.node()) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect().width) || 0;
188
- const { left } = getLegendPosition({
189
- align: legend.align,
190
- width: boundsWidth,
191
- offsetWidth: config.offset.left,
192
- contentWidth,
201
+ legendWidth = boundsWidth;
202
+ if (config.pagination) {
203
+ const transform = `translate(${[
204
+ 0,
205
+ legend.lineHeight * config.pagination.limit + legend.lineHeight / 2,
206
+ ].join(',')})`;
207
+ appendPaginator({
208
+ container: svgElement,
209
+ offset: paginationOffset,
210
+ maxPage: config.pagination.maxPage,
211
+ legend,
212
+ transform,
213
+ onArrowClick: setPaginationOffset,
214
+ });
215
+ }
216
+ }
217
+ else {
218
+ // gradient rect
219
+ const domain = (_b = legend.colorScale.domain) !== null && _b !== void 0 ? _b : [];
220
+ const rectHeight = CONTINUOUS_LEGEND_SIZE.height;
221
+ svgElement.call(createGradientRect, {
222
+ y: legend.title.height + legend.title.margin,
223
+ height: rectHeight,
224
+ width: legend.width,
225
+ interpolator: getContinuesColorFn({
226
+ values: [0, 1],
227
+ colors: legend.colorScale.colors,
228
+ stops: legend.colorScale.stops,
229
+ }),
193
230
  });
194
- const top = config.offset.top + legend.lineHeight * lineIndex;
195
- legendLine.attr('transform', `translate(${[left, top].join(',')})`);
196
- });
197
- if (config.pagination) {
198
- const transform = `translate(${[
199
- config.offset.left,
200
- config.offset.top +
201
- legend.lineHeight * config.pagination.limit +
202
- legend.lineHeight / 2,
203
- ].join(',')})`;
204
- appendPaginator({
205
- container: svgElement,
206
- offset: paginationOffset,
207
- maxPage: config.pagination.maxPage,
208
- legend,
209
- transform,
210
- onArrowClick: setPaginationOffset,
231
+ // ticks
232
+ const xAxisGenerator = axisBottom({
233
+ scale: scaleLinear(domain, [0, legend.width]),
234
+ ticks: {
235
+ items: [[0, -rectHeight]],
236
+ labelsMargin: legend.ticks.labelsMargin,
237
+ labelsLineHeight: legend.ticks.labelsLineHeight,
238
+ maxTickCount: 4,
239
+ tickColor: '#fff',
240
+ },
241
+ domain: {
242
+ size: legend.width,
243
+ color: 'transparent',
244
+ },
211
245
  });
246
+ const tickTop = legend.title.height + legend.title.margin + rectHeight;
247
+ svgElement
248
+ .append('g')
249
+ .attr('transform', `translate(0, ${tickTop})`)
250
+ .call(xAxisGenerator);
251
+ legendWidth = legend.width;
212
252
  }
253
+ if (legend.title.enable) {
254
+ const { maxWidth: labelWidth } = getLabelsSize({
255
+ labels: [legend.title.text],
256
+ style: legend.title.style,
257
+ });
258
+ svgElement
259
+ .append('g')
260
+ .attr('class', b('title'))
261
+ .append('text')
262
+ .attr('dx', legend.width / 2 - labelWidth / 2)
263
+ .attr('font-weight', (_c = legend.title.style.fontWeight) !== null && _c !== void 0 ? _c : null)
264
+ .attr('font-size', (_d = legend.title.style.fontSize) !== null && _d !== void 0 ? _d : null)
265
+ .attr('fill', (_e = legend.title.style.fontColor) !== null && _e !== void 0 ? _e : null)
266
+ .style('alignment-baseline', 'before-edge')
267
+ .text(legend.title.text);
268
+ }
269
+ const { left } = getLegendPosition({
270
+ align: legend.align,
271
+ width: boundsWidth,
272
+ offsetWidth: config.offset.left,
273
+ contentWidth: legendWidth,
274
+ });
275
+ svgElement.attr('transform', `translate(${[left, config.offset.top].join(',')})`);
213
276
  }, [boundsWidth, chartSeries, onItemClick, legend, items, config, paginationOffset]);
214
- return React.createElement("g", { ref: ref, width: boundsWidth, height: legend.height });
277
+ return React.createElement("g", { className: b(), ref: ref, width: boundsWidth, height: legend.height });
215
278
  };
@@ -32,6 +32,14 @@
32
32
  alignment-baseline: after-edge;
33
33
  }
34
34
 
35
+ .chartkit-d3-legend {
36
+ color: var(--g-color-text-secondary);
37
+ }
38
+
39
+ .chartkit-d3-legend__title {
40
+ fill: var(--g-color-text-secondary);
41
+ }
42
+
35
43
  .chartkit-d3-legend__item {
36
44
  cursor: pointer;
37
45
  user-select: none;
@@ -1,4 +1,12 @@
1
- import type { ChartKitWidgetLegend } from '../../../../../types';
2
- type LegendDefaults = Required<Omit<ChartKitWidgetLegend, 'enabled'>> & Pick<ChartKitWidgetLegend, 'enabled'>;
3
- export declare const legendDefaults: LegendDefaults;
4
- export {};
1
+ export declare const legendDefaults: {
2
+ align: "left" | "center" | "right";
3
+ itemDistance: number;
4
+ margin: number;
5
+ itemStyle: {
6
+ fontSize: string;
7
+ };
8
+ };
9
+ export declare const CONTINUOUS_LEGEND_SIZE: {
10
+ height: number;
11
+ width: number;
12
+ };
@@ -6,3 +6,7 @@ export const legendDefaults = {
6
6
  fontSize: '12px',
7
7
  },
8
8
  };
9
+ export const CONTINUOUS_LEGEND_SIZE = {
10
+ height: 12,
11
+ width: 200,
12
+ };
@@ -52,6 +52,7 @@ export function prepareArea(args) {
52
52
  style: Object.assign({}, DEFAULT_DATALABELS_STYLE, (_b = series.dataLabels) === null || _b === void 0 ? void 0 : _b.style),
53
53
  padding: get(series, 'dataLabels.padding', DEFAULT_DATALABELS_PADDING),
54
54
  allowOverlap: get(series, 'dataLabels.allowOverlap', false),
55
+ html: get(series, 'dataLabels.html', false),
55
56
  },
56
57
  marker: prepareMarker(series, seriesOptions),
57
58
  cursor: get(series, 'cursor', null),
@@ -29,6 +29,7 @@ export function prepareBarXSeries(args) {
29
29
  style: Object.assign({}, DEFAULT_DATALABELS_STYLE, (_d = series.dataLabels) === null || _d === void 0 ? void 0 : _d.style),
30
30
  allowOverlap: ((_e = series.dataLabels) === null || _e === void 0 ? void 0 : _e.allowOverlap) || false,
31
31
  padding: get(series, 'dataLabels.padding', DEFAULT_DATALABELS_PADDING),
32
+ html: get(series, 'dataLabels.html', false),
32
33
  },
33
34
  cursor: get(series, 'cursor', null),
34
35
  yAxis: get(series, 'yAxis', 0),
@@ -7,12 +7,13 @@ function prepareDataLabels(series) {
7
7
  var _a;
8
8
  const enabled = get(series, 'dataLabels.enabled', false);
9
9
  const style = Object.assign({}, DEFAULT_DATALABELS_STYLE, (_a = series.dataLabels) === null || _a === void 0 ? void 0 : _a.style);
10
- const { maxHeight = 0, maxWidth = 0 } = enabled
11
- ? getLabelsSize({
12
- labels: series.data.map((d) => String(d.label || d.x)),
13
- style,
14
- })
15
- : {};
10
+ const html = get(series, 'dataLabels.html', false);
11
+ const labels = enabled ? series.data.map((d) => String(d.label || d.x)) : [];
12
+ const { maxHeight = 0, maxWidth = 0 } = getLabelsSize({
13
+ labels,
14
+ style,
15
+ html,
16
+ });
16
17
  const inside = series.stacking === 'percent' ? true : get(series, 'dataLabels.inside', false);
17
18
  return {
18
19
  enabled,
@@ -20,6 +21,7 @@ function prepareDataLabels(series) {
20
21
  style,
21
22
  maxHeight,
22
23
  maxWidth,
24
+ html,
23
25
  };
24
26
  }
25
27
  export function prepareBarYSeries(args) {
@@ -2,18 +2,52 @@ import { select } from 'd3';
2
2
  import clone from 'lodash/clone';
3
3
  import get from 'lodash/get';
4
4
  import merge from 'lodash/merge';
5
- import { legendDefaults } from '../../constants';
6
- import { getHorisontalSvgTextHeight } from '../../utils';
5
+ import { CONTINUOUS_LEGEND_SIZE, legendDefaults } from '../../constants';
6
+ import { getDefaultColorStops, getDomainForContinuousColorScale, getHorisontalSvgTextHeight, getLabelsSize, } from '../../utils';
7
7
  import { getBoundsWidth } from '../useChartDimensions';
8
8
  import { getYAxisWidth } from '../useChartDimensions/utils';
9
9
  export const getPreparedLegend = (args) => {
10
+ var _a, _b, _c, _d, _e, _f, _g;
10
11
  const { legend, series } = args;
11
12
  const enabled = Boolean(typeof (legend === null || legend === void 0 ? void 0 : legend.enabled) === 'boolean' ? legend === null || legend === void 0 ? void 0 : legend.enabled : series.length > 1);
12
13
  const defaultItemStyle = clone(legendDefaults.itemStyle);
13
14
  const itemStyle = get(legend, 'itemStyle');
14
15
  const computedItemStyle = merge(defaultItemStyle, itemStyle);
15
16
  const lineHeight = getHorisontalSvgTextHeight({ text: 'Tmp', style: computedItemStyle });
16
- const height = enabled ? lineHeight : 0;
17
+ const legendType = get(legend, 'type', 'discrete');
18
+ const isTitleEnabled = Boolean((_a = legend === null || legend === void 0 ? void 0 : legend.title) === null || _a === void 0 ? void 0 : _a.text);
19
+ const titleMargin = isTitleEnabled ? get(legend, 'title.margin', 4) : 0;
20
+ const titleStyle = Object.assign({ fontSize: '12px', fontWeight: 'bold' }, get(legend, 'title.style'));
21
+ const titleText = isTitleEnabled ? get(legend, 'title.text', '') : '';
22
+ const titleHeight = isTitleEnabled
23
+ ? getLabelsSize({ labels: [titleText], style: titleStyle }).maxHeight
24
+ : 0;
25
+ const ticks = {
26
+ labelsMargin: 4,
27
+ labelsLineHeight: 12,
28
+ };
29
+ const colorScale = {
30
+ colors: [],
31
+ domain: [],
32
+ stops: [],
33
+ };
34
+ let height = 0;
35
+ if (enabled) {
36
+ height += titleHeight + titleMargin;
37
+ if (legendType === 'continuous') {
38
+ height += CONTINUOUS_LEGEND_SIZE.height;
39
+ height += ticks.labelsLineHeight + ticks.labelsMargin;
40
+ colorScale.colors = (_c = (_b = legend === null || legend === void 0 ? void 0 : legend.colorScale) === null || _b === void 0 ? void 0 : _b.colors) !== null && _c !== void 0 ? _c : [];
41
+ colorScale.stops =
42
+ (_e = (_d = legend === null || legend === void 0 ? void 0 : legend.colorScale) === null || _d === void 0 ? void 0 : _d.stops) !== null && _e !== void 0 ? _e : getDefaultColorStops(colorScale.colors.length);
43
+ colorScale.domain =
44
+ (_g = (_f = legend === null || legend === void 0 ? void 0 : legend.colorScale) === null || _f === void 0 ? void 0 : _f.domain) !== null && _g !== void 0 ? _g : getDomainForContinuousColorScale({ series });
45
+ }
46
+ else {
47
+ height += lineHeight;
48
+ }
49
+ }
50
+ const legendWidth = get(legend, 'width', CONTINUOUS_LEGEND_SIZE.width);
17
51
  return {
18
52
  align: get(legend, 'align', legendDefaults.align),
19
53
  enabled,
@@ -22,6 +56,17 @@ export const getPreparedLegend = (args) => {
22
56
  itemStyle: computedItemStyle,
23
57
  lineHeight,
24
58
  margin: get(legend, 'margin', legendDefaults.margin),
59
+ type: legendType,
60
+ title: {
61
+ enable: isTitleEnabled,
62
+ text: titleText,
63
+ margin: titleMargin,
64
+ style: titleStyle,
65
+ height: titleHeight,
66
+ },
67
+ width: legendWidth,
68
+ ticks,
69
+ colorScale,
25
70
  };
26
71
  };
27
72
  const getFlattenLegendItems = (series) => {
@@ -78,16 +123,18 @@ export const getLegendComponents = (args) => {
78
123
  items: flattenLegendItems,
79
124
  preparedLegend,
80
125
  });
81
- let legendHeight = preparedLegend.lineHeight * items.length;
82
126
  let pagination;
83
- if (maxLegendHeight < legendHeight) {
84
- // extra line for paginator
85
- const limit = Math.floor(maxLegendHeight / preparedLegend.lineHeight) - 1;
86
- const maxPage = Math.ceil(items.length / limit);
87
- pagination = { limit, maxPage };
88
- legendHeight = maxLegendHeight;
127
+ if (preparedLegend.type === 'discrete') {
128
+ let legendHeight = preparedLegend.lineHeight * items.length;
129
+ if (maxLegendHeight < legendHeight) {
130
+ // extra line for paginator
131
+ const limit = Math.floor(maxLegendHeight / preparedLegend.lineHeight) - 1;
132
+ const maxPage = Math.ceil(items.length / limit);
133
+ pagination = { limit, maxPage };
134
+ legendHeight = maxLegendHeight;
135
+ }
136
+ preparedLegend.height = legendHeight;
89
137
  }
90
- preparedLegend.height = legendHeight;
91
138
  const top = chartHeight - chartMargin.bottom - preparedLegend.height;
92
139
  const offset = {
93
140
  left: chartMargin.left + getYAxisWidth(preparedYAxis[0]),
@@ -68,6 +68,7 @@ export function prepareLineSeries(args) {
68
68
  style: Object.assign({}, DEFAULT_DATALABELS_STYLE, (_b = series.dataLabels) === null || _b === void 0 ? void 0 : _b.style),
69
69
  padding: get(series, 'dataLabels.padding', DEFAULT_DATALABELS_PADDING),
70
70
  allowOverlap: get(series, 'dataLabels.allowOverlap', false),
71
+ html: get(series, 'dataLabels.html', false),
71
72
  },
72
73
  marker: prepareMarker(series, seriesOptions),
73
74
  dashStyle: dashStyle,
@@ -18,6 +18,7 @@ export function prepareTreemap(args) {
18
18
  style: Object.assign({}, DEFAULT_DATALABELS_STYLE, (_a = s.dataLabels) === null || _a === void 0 ? void 0 : _a.style),
19
19
  padding: get(s, 'dataLabels.padding', DEFAULT_DATALABELS_PADDING),
20
20
  allowOverlap: get(s, 'dataLabels.allowOverlap', false),
21
+ html: get(series, 'dataLabels.html', false),
21
22
  },
22
23
  id,
23
24
  type: s.type,
@@ -28,6 +28,7 @@ export function prepareWaterfallSeries(args) {
28
28
  style: Object.assign({}, DEFAULT_DATALABELS_STYLE, (_b = series.dataLabels) === null || _b === void 0 ? void 0 : _b.style),
29
29
  allowOverlap: ((_c = series.dataLabels) === null || _c === void 0 ? void 0 : _c.allowOverlap) || false,
30
30
  padding: get(series, 'dataLabels.padding', DEFAULT_DATALABELS_PADDING),
31
+ html: get(series, 'dataLabels.html', false),
31
32
  },
32
33
  cursor: get(series, 'cursor', null),
33
34
  };
@@ -13,9 +13,25 @@ export type SymbolLegendSymbol = {
13
13
  symbolType: `${SymbolType}`;
14
14
  } & Required<SymbolLegendSymbolOptions>;
15
15
  export type PreparedLegendSymbol = RectLegendSymbol | PathLegendSymbol | SymbolLegendSymbol;
16
- export type PreparedLegend = Required<ChartKitWidgetLegend> & {
16
+ export type PreparedLegend = Required<Omit<ChartKitWidgetLegend, 'title' | 'colorScale'>> & {
17
17
  height: number;
18
18
  lineHeight: number;
19
+ title: {
20
+ enable: boolean;
21
+ text: string;
22
+ margin: number;
23
+ style: BaseTextStyle;
24
+ height: number;
25
+ };
26
+ ticks: {
27
+ labelsMargin: number;
28
+ labelsLineHeight: number;
29
+ };
30
+ colorScale: {
31
+ colors: string[];
32
+ domain: number[];
33
+ stops: number[];
34
+ };
19
35
  };
20
36
  export type OnLegendItemClick = (data: {
21
37
  name: string;
@@ -89,6 +105,7 @@ export type PreparedBarXSeries = {
89
105
  style: BaseTextStyle;
90
106
  allowOverlap: boolean;
91
107
  padding: number;
108
+ html: boolean;
92
109
  };
93
110
  yAxis: number;
94
111
  } & BasePreparedSeries;
@@ -103,6 +120,7 @@ export type PreparedBarYSeries = {
103
120
  style: BaseTextStyle;
104
121
  maxHeight: number;
105
122
  maxWidth: number;
123
+ html: boolean;
106
124
  };
107
125
  } & BasePreparedSeries;
108
126
  export type PreparedPieSeries = {
@@ -145,6 +163,7 @@ export type PreparedLineSeries = {
145
163
  style: BaseTextStyle;
146
164
  padding: number;
147
165
  allowOverlap: boolean;
166
+ html: boolean;
148
167
  };
149
168
  marker: {
150
169
  states: {
@@ -181,6 +200,7 @@ export type PreparedAreaSeries = {
181
200
  style: BaseTextStyle;
182
201
  padding: number;
183
202
  allowOverlap: boolean;
203
+ html: boolean;
184
204
  };
185
205
  marker: {
186
206
  states: {
@@ -210,6 +230,7 @@ export type PreparedTreemapSeries = {
210
230
  style: BaseTextStyle;
211
231
  padding: number;
212
232
  allowOverlap: boolean;
233
+ html: boolean;
213
234
  };
214
235
  layoutAlgorithm: `${LayoutAlgorithm}`;
215
236
  } & BasePreparedSeries & Omit<TreemapSeries, keyof BasePreparedSeries>;
@@ -221,6 +242,7 @@ export type PreparedWaterfallSeries = {
221
242
  style: BaseTextStyle;
222
243
  allowOverlap: boolean;
223
244
  padding: number;
245
+ html: boolean;
224
246
  };
225
247
  positiveColor: string;
226
248
  negativeColor: string;
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ import { ShapeDataWithHtmlItems } from '../../types';
3
+ type Props = {
4
+ htmlLayout: HTMLElement | null;
5
+ preparedData: ShapeDataWithHtmlItems | ShapeDataWithHtmlItems[];
6
+ };
7
+ export declare const HtmlLayer: (props: Props) => React.JSX.Element | null;
8
+ export {};
@@ -0,0 +1,22 @@
1
+ import React from 'react';
2
+ import { Portal } from '@gravity-ui/uikit';
3
+ export const HtmlLayer = (props) => {
4
+ const { htmlLayout, preparedData } = props;
5
+ const items = React.useMemo(() => {
6
+ if (Array.isArray(preparedData)) {
7
+ return preparedData.reduce((result, d) => {
8
+ result.push(...d.htmlElements);
9
+ return result;
10
+ }, []);
11
+ }
12
+ else {
13
+ return preparedData.htmlElements;
14
+ }
15
+ }, [preparedData]);
16
+ if (!htmlLayout) {
17
+ return null;
18
+ }
19
+ return (React.createElement(Portal, { container: htmlLayout }, items.map((item, index) => {
20
+ return (React.createElement("div", { key: index, dangerouslySetInnerHTML: { __html: item.content }, style: { position: 'absolute', left: item.x, top: item.y } }));
21
+ })));
22
+ };
@@ -6,6 +6,7 @@ type Args = {
6
6
  dispatcher: Dispatch<object>;
7
7
  preparedData: PreparedAreaData[];
8
8
  seriesOptions: PreparedSeriesOptions;
9
+ htmlLayout: HTMLElement | null;
9
10
  };
10
11
  export declare const AreaSeriesShapes: (args: Args) => React.JSX.Element;
11
12
  export {};
@@ -3,11 +3,12 @@ import { area as areaGenerator, color, line as lineGenerator, select } from 'd3'
3
3
  import get from 'lodash/get';
4
4
  import { block } from '../../../../../../utils/cn';
5
5
  import { filterOverlappingLabels } from '../../../utils';
6
+ import { HtmlLayer } from '../HtmlLayer';
6
7
  import { getMarkerHaloVisibility, getMarkerVisibility, renderMarker, selectMarkerHalo, selectMarkerSymbol, setMarker, } from '../marker';
7
8
  import { setActiveState } from '../utils';
8
9
  const b = block('d3-area');
9
10
  export const AreaSeriesShapes = (args) => {
10
- const { dispatcher, preparedData, seriesOptions } = args;
11
+ const { dispatcher, preparedData, seriesOptions, htmlLayout } = args;
11
12
  const ref = React.useRef(null);
12
13
  React.useEffect(() => {
13
14
  var _a;
@@ -137,5 +138,7 @@ export const AreaSeriesShapes = (args) => {
137
138
  dispatcher.on('hover-shape.area', null);
138
139
  };
139
140
  }, [dispatcher, preparedData, seriesOptions]);
140
- return React.createElement("g", { ref: ref, className: b() });
141
+ return (React.createElement(React.Fragment, null,
142
+ React.createElement("g", { ref: ref, className: b() }),
143
+ React.createElement(HtmlLayer, { preparedData: preparedData, htmlLayout: htmlLayout })));
141
144
  };
@@ -4,7 +4,7 @@ import { getXValue, getYValue } from '../utils';
4
4
  function getLabelData(point, series, xMax) {
5
5
  const text = String(point.data.label || point.data.y);
6
6
  const style = series.dataLabels.style;
7
- const size = getLabelsSize({ labels: [text], style });
7
+ const size = getLabelsSize({ labels: [text], style, html: series.dataLabels.html });
8
8
  const labelData = {
9
9
  text,
10
10
  x: point.x,
@@ -22,7 +22,7 @@ function getLabelData(point, series, xMax) {
22
22
  else {
23
23
  const right = left + labelData.size.width;
24
24
  if (right > xMax) {
25
- labelData.x = labelData.x - xMax - right;
25
+ labelData.x = labelData.x - (right - xMax);
26
26
  }
27
27
  }
28
28
  return labelData;
@@ -97,8 +97,22 @@ export const prepareAreaData = (args) => {
97
97
  return pointsAcc;
98
98
  }, []);
99
99
  let labels = [];
100
+ const htmlElements = [];
100
101
  if (s.dataLabels.enabled) {
101
- labels = points.map((p) => getLabelData(p, s, xMax));
102
+ const labelItems = points.map((p) => getLabelData(p, s, xMax));
103
+ if (s.dataLabels.html) {
104
+ const htmlLabels = labelItems.map((l) => {
105
+ return {
106
+ x: l.x - l.size.width / 2,
107
+ y: l.y,
108
+ content: l.text,
109
+ };
110
+ });
111
+ htmlElements.push(...htmlLabels);
112
+ }
113
+ else {
114
+ labels = labelItems;
115
+ }
102
116
  }
103
117
  let markers = [];
104
118
  if (s.marker.states.normal.enabled || s.marker.states.hover.enabled) {
@@ -119,6 +133,7 @@ export const prepareAreaData = (args) => {
119
133
  hovered: false,
120
134
  active: true,
121
135
  id: s.id,
136
+ htmlElements,
122
137
  });
123
138
  return acc;
124
139
  }, []);
@@ -1,5 +1,5 @@
1
1
  import { AreaSeriesData } from '../../../../../../types';
2
- import { LabelData } from '../../../types';
2
+ import { HtmlItem, LabelData } from '../../../types';
3
3
  import { PreparedAreaSeries } from '../../useSeries/types';
4
4
  export type PointData = {
5
5
  y0: number;
@@ -24,4 +24,5 @@ export type PreparedAreaData = {
24
24
  hovered: boolean;
25
25
  active: boolean;
26
26
  labels: LabelData[];
27
+ htmlElements: HtmlItem[];
27
28
  };
@@ -8,5 +8,6 @@ type Args = {
8
8
  dispatcher: Dispatch<object>;
9
9
  preparedData: PreparedBarXData[];
10
10
  seriesOptions: PreparedSeriesOptions;
11
+ htmlLayout: HTMLElement | null;
11
12
  };
12
13
  export declare const BarXSeriesShapes: (args: Args) => React.JSX.Element;