@gravity-ui/chartkit 5.14.1 → 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 (67) hide show
  1. package/build/plugins/d3/renderer/components/Chart.js +5 -0
  2. package/build/plugins/d3/renderer/components/Legend.js +129 -66
  3. package/build/plugins/d3/renderer/components/styles.css +16 -0
  4. package/build/plugins/d3/renderer/constants/defaults/legend.d.ts +12 -4
  5. package/build/plugins/d3/renderer/constants/defaults/legend.js +4 -0
  6. package/build/plugins/d3/renderer/hooks/useSeries/prepare-area.js +1 -0
  7. package/build/plugins/d3/renderer/hooks/useSeries/prepare-bar-x.js +1 -0
  8. package/build/plugins/d3/renderer/hooks/useSeries/prepare-bar-y.js +8 -6
  9. package/build/plugins/d3/renderer/hooks/useSeries/prepare-legend.js +58 -11
  10. package/build/plugins/d3/renderer/hooks/useSeries/prepare-line.js +1 -0
  11. package/build/plugins/d3/renderer/hooks/useSeries/prepare-pie.js +1 -0
  12. package/build/plugins/d3/renderer/hooks/useSeries/prepare-treemap.js +1 -0
  13. package/build/plugins/d3/renderer/hooks/useSeries/prepare-waterfall.js +1 -0
  14. package/build/plugins/d3/renderer/hooks/useSeries/types.d.ts +24 -1
  15. package/build/plugins/d3/renderer/hooks/useShapes/HtmlLayer.d.ts +8 -0
  16. package/build/plugins/d3/renderer/hooks/useShapes/HtmlLayer.js +22 -0
  17. package/build/plugins/d3/renderer/hooks/useShapes/area/index.d.ts +1 -0
  18. package/build/plugins/d3/renderer/hooks/useShapes/area/index.js +5 -2
  19. package/build/plugins/d3/renderer/hooks/useShapes/area/prepare-data.js +18 -3
  20. package/build/plugins/d3/renderer/hooks/useShapes/area/types.d.ts +2 -1
  21. package/build/plugins/d3/renderer/hooks/useShapes/bar-x/index.d.ts +1 -0
  22. package/build/plugins/d3/renderer/hooks/useShapes/bar-x/index.js +5 -2
  23. package/build/plugins/d3/renderer/hooks/useShapes/bar-x/prepare-data.js +21 -4
  24. package/build/plugins/d3/renderer/hooks/useShapes/bar-x/types.d.ts +2 -1
  25. package/build/plugins/d3/renderer/hooks/useShapes/bar-y/index.d.ts +1 -0
  26. package/build/plugins/d3/renderer/hooks/useShapes/bar-y/index.js +18 -23
  27. package/build/plugins/d3/renderer/hooks/useShapes/bar-y/prepare-data.js +44 -3
  28. package/build/plugins/d3/renderer/hooks/useShapes/bar-y/types.d.ts +3 -0
  29. package/build/plugins/d3/renderer/hooks/useShapes/index.d.ts +1 -0
  30. package/build/plugins/d3/renderer/hooks/useShapes/index.js +9 -9
  31. package/build/plugins/d3/renderer/hooks/useShapes/line/index.d.ts +1 -0
  32. package/build/plugins/d3/renderer/hooks/useShapes/line/index.js +5 -2
  33. package/build/plugins/d3/renderer/hooks/useShapes/line/prepare-data.js +17 -1
  34. package/build/plugins/d3/renderer/hooks/useShapes/line/types.d.ts +2 -1
  35. package/build/plugins/d3/renderer/hooks/useShapes/pie/index.d.ts +1 -0
  36. package/build/plugins/d3/renderer/hooks/useShapes/pie/index.js +10 -14
  37. package/build/plugins/d3/renderer/hooks/useShapes/pie/prepare-data.js +30 -12
  38. package/build/plugins/d3/renderer/hooks/useShapes/pie/types.d.ts +7 -5
  39. package/build/plugins/d3/renderer/hooks/useShapes/scatter/index.d.ts +1 -0
  40. package/build/plugins/d3/renderer/hooks/useShapes/scatter/index.js +5 -2
  41. package/build/plugins/d3/renderer/hooks/useShapes/scatter/prepare-data.js +1 -0
  42. package/build/plugins/d3/renderer/hooks/useShapes/scatter/types.d.ts +2 -0
  43. package/build/plugins/d3/renderer/hooks/useShapes/treemap/index.d.ts +1 -0
  44. package/build/plugins/d3/renderer/hooks/useShapes/treemap/index.js +5 -2
  45. package/build/plugins/d3/renderer/hooks/useShapes/treemap/prepare-data.js +1 -1
  46. package/build/plugins/d3/renderer/hooks/useShapes/treemap/types.d.ts +2 -0
  47. package/build/plugins/d3/renderer/hooks/useShapes/waterfall/index.d.ts +1 -0
  48. package/build/plugins/d3/renderer/hooks/useShapes/waterfall/index.js +5 -2
  49. package/build/plugins/d3/renderer/hooks/useShapes/waterfall/prepare-data.js +1 -0
  50. package/build/plugins/d3/renderer/hooks/useShapes/waterfall/types.d.ts +2 -1
  51. package/build/plugins/d3/renderer/types/index.d.ts +8 -0
  52. package/build/plugins/d3/renderer/utils/axis-generators/bottom.d.ts +5 -4
  53. package/build/plugins/d3/renderer/utils/axis-generators/bottom.js +11 -7
  54. package/build/plugins/d3/renderer/utils/axis.d.ts +1 -1
  55. package/build/plugins/d3/renderer/utils/axis.js +1 -1
  56. package/build/plugins/d3/renderer/utils/color.d.ts +10 -0
  57. package/build/plugins/d3/renderer/utils/color.js +43 -0
  58. package/build/plugins/d3/renderer/utils/index.d.ts +2 -0
  59. package/build/plugins/d3/renderer/utils/index.js +2 -0
  60. package/build/plugins/d3/renderer/utils/legend.d.ts +8 -0
  61. package/build/plugins/d3/renderer/utils/legend.js +23 -0
  62. package/build/plugins/d3/renderer/utils/text.d.ts +2 -1
  63. package/build/plugins/d3/renderer/utils/text.js +32 -10
  64. package/build/types/widget-data/bar-x.d.ts +1 -1
  65. package/build/types/widget-data/base.d.ts +7 -0
  66. package/build/types/widget-data/legend.d.ts +24 -0
  67. package/package.json +2 -2
@@ -22,6 +22,7 @@ export const Chart = (props) => {
22
22
  var _a, _b;
23
23
  const { width, height, data } = props;
24
24
  const svgRef = React.useRef(null);
25
+ const htmlLayerRef = React.useRef(null);
25
26
  const dispatcher = React.useMemo(() => {
26
27
  return getD3Dispatcher();
27
28
  }, []);
@@ -71,6 +72,7 @@ export const Chart = (props) => {
71
72
  yAxis,
72
73
  yScale,
73
74
  split: preparedSplit,
75
+ htmlLayout: htmlLayerRef.current,
74
76
  });
75
77
  const clickHandler = (_b = (_a = data.chart) === null || _a === void 0 ? void 0 : _a.events) === null || _b === void 0 ? void 0 : _b.click;
76
78
  React.useEffect(() => {
@@ -136,5 +138,8 @@ export const Chart = (props) => {
136
138
  React.createElement(AxisX, { axis: xAxis, width: boundsWidth, height: boundsHeight, scale: xScale, split: preparedSplit })))),
137
139
  shapes),
138
140
  preparedLegend.enabled && (React.createElement(Legend, { chartSeries: preparedSeries, boundsWidth: boundsWidth, legend: preparedLegend, items: legendItems, config: legendConfig, onItemClick: handleLegendItemClick }))),
141
+ React.createElement("div", { className: b('html-layer'), ref: htmlLayerRef, style: {
142
+ transform: `translate(${boundsOffsetLeft}px, ${boundsOffsetTop}px)`,
143
+ } }),
139
144
  React.createElement(Tooltip, { dispatcher: dispatcher, tooltip: tooltip, svgContainer: svgRef.current, xAxis: xAxis, yAxis: yAxis[0] })));
140
145
  };
@@ -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
  };
@@ -2,6 +2,14 @@
2
2
  position: absolute;
3
3
  }
4
4
 
5
+ .chartkit-d3__html-layer {
6
+ display: contents;
7
+ }
8
+
9
+ .chartkit-d3__html-layer > * {
10
+ transform: inherit;
11
+ }
12
+
5
13
  .chartkit-d3-axis .domain {
6
14
  stroke: var(--g-color-line-generic-active);
7
15
  }
@@ -24,6 +32,14 @@
24
32
  alignment-baseline: after-edge;
25
33
  }
26
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
+
27
43
  .chartkit-d3-legend__item {
28
44
  cursor: pointer;
29
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,
@@ -24,6 +24,7 @@ export function preparePieSeries(args) {
24
24
  connectorShape: get(series, 'dataLabels.connectorShape', 'polyline'),
25
25
  distance: get(series, 'dataLabels.distance', 25),
26
26
  connectorCurve: get(series, 'dataLabels.connectorCurve', 'basic'),
27
+ html: get(series, 'dataLabels.html', false),
27
28
  },
28
29
  label: dataItem.label,
29
30
  value: dataItem.value,
@@ -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 = {
@@ -126,6 +144,7 @@ export type PreparedPieSeries = {
126
144
  connectorShape: ConnectorShape;
127
145
  distance: number;
128
146
  connectorCurve: ConnectorCurve;
147
+ html: boolean;
129
148
  };
130
149
  states: {
131
150
  hover: {
@@ -144,6 +163,7 @@ export type PreparedLineSeries = {
144
163
  style: BaseTextStyle;
145
164
  padding: number;
146
165
  allowOverlap: boolean;
166
+ html: boolean;
147
167
  };
148
168
  marker: {
149
169
  states: {
@@ -180,6 +200,7 @@ export type PreparedAreaSeries = {
180
200
  style: BaseTextStyle;
181
201
  padding: number;
182
202
  allowOverlap: boolean;
203
+ html: boolean;
183
204
  };
184
205
  marker: {
185
206
  states: {
@@ -209,6 +230,7 @@ export type PreparedTreemapSeries = {
209
230
  style: BaseTextStyle;
210
231
  padding: number;
211
232
  allowOverlap: boolean;
233
+ html: boolean;
212
234
  };
213
235
  layoutAlgorithm: `${LayoutAlgorithm}`;
214
236
  } & BasePreparedSeries & Omit<TreemapSeries, keyof BasePreparedSeries>;
@@ -220,6 +242,7 @@ export type PreparedWaterfallSeries = {
220
242
  style: BaseTextStyle;
221
243
  allowOverlap: boolean;
222
244
  padding: number;
245
+ html: boolean;
223
246
  };
224
247
  positiveColor: string;
225
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
  };