@gravity-ui/charts 1.42.2 → 1.42.4

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 (83) hide show
  1. package/dist/cjs/components/AxisX/AxisX.js +4 -4
  2. package/dist/cjs/components/AxisX/prepare-axis-data.js +17 -13
  3. package/dist/cjs/components/AxisY/AxisY.js +4 -4
  4. package/dist/cjs/components/AxisY/prepare-axis-data.js +27 -21
  5. package/dist/cjs/components/AxisY/prepare-axis-title.js +8 -3
  6. package/dist/cjs/components/AxisY/styles.css +1 -1
  7. package/dist/cjs/components/ChartInner/index.js +5 -15
  8. package/dist/cjs/components/ChartInner/useChartInnerProps.d.ts +4 -1
  9. package/dist/cjs/components/ChartInner/useChartInnerProps.js +22 -11
  10. package/dist/cjs/components/ChartInner/utils/title.d.ts +1 -1
  11. package/dist/cjs/components/ChartInner/utils/title.js +3 -3
  12. package/dist/cjs/components/Legend/index.js +19 -13
  13. package/dist/cjs/components/Legend/styles.css +1 -1
  14. package/dist/cjs/components/Tooltip/DefaultTooltipContent/Row.js +1 -1
  15. package/dist/cjs/components/utils/axis-title.js +1 -1
  16. package/dist/cjs/core/axes/x-axis.js +2 -2
  17. package/dist/cjs/core/axes/y-axis.js +2 -2
  18. package/dist/cjs/core/layout/split.d.ts +2 -2
  19. package/dist/cjs/core/layout/split.js +22 -19
  20. package/dist/cjs/core/series/prepare-legend.js +7 -7
  21. package/dist/cjs/core/series/types.d.ts +2 -0
  22. package/dist/cjs/core/utils/axis-generators/bottom.js +6 -16
  23. package/dist/cjs/core/utils/common.d.ts +0 -4
  24. package/dist/cjs/core/utils/common.js +0 -13
  25. package/dist/cjs/core/utils/labels.d.ts +1 -0
  26. package/dist/cjs/core/utils/labels.js +5 -5
  27. package/dist/cjs/core/utils/text.d.ts +1 -0
  28. package/dist/cjs/core/utils/text.js +4 -0
  29. package/dist/cjs/hooks/useShapes/area/prepare-data.js +1 -1
  30. package/dist/cjs/hooks/useShapes/bar-y/prepare-data.js +3 -2
  31. package/dist/cjs/hooks/useShapes/funnel/prepare-data.js +4 -1
  32. package/dist/cjs/hooks/useShapes/heatmap/prepare-data.js +1 -1
  33. package/dist/cjs/hooks/useShapes/line/prepare-data.js +4 -1
  34. package/dist/cjs/hooks/useShapes/pie/prepare-data.js +9 -2
  35. package/dist/cjs/hooks/useShapes/radar/prepare-data.js +17 -7
  36. package/dist/cjs/hooks/useShapes/sankey/prepare-data.js +1 -1
  37. package/dist/cjs/hooks/useShapes/sankey/sankey-layout.d.ts +49 -0
  38. package/dist/cjs/hooks/useShapes/sankey/sankey-layout.js +362 -0
  39. package/dist/cjs/hooks/useShapes/styles.css +4 -4
  40. package/dist/cjs/hooks/useShapes/treemap/prepare-data.js +3 -1
  41. package/dist/cjs/types/chart-ui.d.ts +1 -0
  42. package/dist/esm/components/AxisX/AxisX.js +4 -4
  43. package/dist/esm/components/AxisX/prepare-axis-data.js +17 -13
  44. package/dist/esm/components/AxisY/AxisY.js +4 -4
  45. package/dist/esm/components/AxisY/prepare-axis-data.js +27 -21
  46. package/dist/esm/components/AxisY/prepare-axis-title.js +8 -3
  47. package/dist/esm/components/AxisY/styles.css +1 -1
  48. package/dist/esm/components/ChartInner/index.js +5 -15
  49. package/dist/esm/components/ChartInner/useChartInnerProps.d.ts +4 -1
  50. package/dist/esm/components/ChartInner/useChartInnerProps.js +22 -11
  51. package/dist/esm/components/ChartInner/utils/title.d.ts +1 -1
  52. package/dist/esm/components/ChartInner/utils/title.js +3 -3
  53. package/dist/esm/components/Legend/index.js +19 -13
  54. package/dist/esm/components/Legend/styles.css +1 -1
  55. package/dist/esm/components/Tooltip/DefaultTooltipContent/Row.js +1 -1
  56. package/dist/esm/components/utils/axis-title.js +1 -1
  57. package/dist/esm/core/axes/x-axis.js +2 -2
  58. package/dist/esm/core/axes/y-axis.js +2 -2
  59. package/dist/esm/core/layout/split.d.ts +2 -2
  60. package/dist/esm/core/layout/split.js +22 -19
  61. package/dist/esm/core/series/prepare-legend.js +7 -7
  62. package/dist/esm/core/series/types.d.ts +2 -0
  63. package/dist/esm/core/utils/axis-generators/bottom.js +6 -16
  64. package/dist/esm/core/utils/common.d.ts +0 -4
  65. package/dist/esm/core/utils/common.js +0 -13
  66. package/dist/esm/core/utils/labels.d.ts +1 -0
  67. package/dist/esm/core/utils/labels.js +5 -5
  68. package/dist/esm/core/utils/text.d.ts +1 -0
  69. package/dist/esm/core/utils/text.js +4 -0
  70. package/dist/esm/hooks/useShapes/area/prepare-data.js +1 -1
  71. package/dist/esm/hooks/useShapes/bar-y/prepare-data.js +3 -2
  72. package/dist/esm/hooks/useShapes/funnel/prepare-data.js +4 -1
  73. package/dist/esm/hooks/useShapes/heatmap/prepare-data.js +1 -1
  74. package/dist/esm/hooks/useShapes/line/prepare-data.js +4 -1
  75. package/dist/esm/hooks/useShapes/pie/prepare-data.js +9 -2
  76. package/dist/esm/hooks/useShapes/radar/prepare-data.js +17 -7
  77. package/dist/esm/hooks/useShapes/sankey/prepare-data.js +1 -1
  78. package/dist/esm/hooks/useShapes/sankey/sankey-layout.d.ts +49 -0
  79. package/dist/esm/hooks/useShapes/sankey/sankey-layout.js +362 -0
  80. package/dist/esm/hooks/useShapes/styles.css +4 -4
  81. package/dist/esm/hooks/useShapes/treemap/prepare-data.js +3 -1
  82. package/dist/esm/types/chart-ui.d.ts +1 -0
  83. package/package.json +1 -3
@@ -31,7 +31,7 @@ export async function getMultilineTitleContentRows({ axis, titleMaxWidth, }) {
31
31
  titleContent.push({
32
32
  text: textRowContent,
33
33
  x: 0,
34
- y: textRow.y,
34
+ y: textRow.y + textRowSize.hangingOffset,
35
35
  size: textRowSize,
36
36
  });
37
37
  }
@@ -1,5 +1,5 @@
1
1
  import get from 'lodash/get';
2
- import { TIME_UNITS, calculateCos, calculateNumericProperty, calculateSin, formatAxisTickLabel, getDefaultDateFormat, getHorizontalHtmlTextHeight, getHorizontalSvgTextHeight, getLabelsSize, getMinSpaceBetween, getTextSizeFn, isAxisRelatedSeries, wrapText, } from '../utils';
2
+ import { TIME_UNITS, calculateCos, calculateNumericProperty, calculateSin, formatAxisTickLabel, getDefaultDateFormat, getHorizontalHtmlTextHeight, getLabelsSize, getMinSpaceBetween, getTextSizeFn, isAxisRelatedSeries, wrapText, } from '../utils';
3
3
  import { DASH_STYLE, DEFAULT_AXIS_LABEL_FONT_SIZE, SERIES_TYPE, axisCrosshairDefaults, axisLabelsDefaults, axisTickMarksDefaults, xAxisTitleDefaults, } from '../constants';
4
4
  import { createXScale } from '../scales/x-scale';
5
5
  import { getXAxisTickValues } from '../utils/axis/x-axis';
@@ -90,7 +90,7 @@ export const getPreparedXAxis = async ({ xAxis, seriesData, width, boundsWidth,
90
90
  if (isLabelsEnabled) {
91
91
  labelsLineHeight = labelsHtml
92
92
  ? getHorizontalHtmlTextHeight({ text: 'Tmp', style: labelsStyle })
93
- : getHorizontalSvgTextHeight({ text: 'Tmp', style: labelsStyle });
93
+ : (await getTextSizeFn({ style: labelsStyle })('Tmp')).height;
94
94
  }
95
95
  const shouldHideGrid = isAxisVisible === false || seriesData.some((s) => s.type === SERIES_TYPE.Heatmap);
96
96
  const preparedRangeSlider = getPreparedRangeSlider({ xAxis });
@@ -1,5 +1,5 @@
1
1
  import get from 'lodash/get';
2
- import { calculateNumericProperty, formatAxisTickLabel, getDefaultDateFormat, getDefaultMinYAxisValue, getHorizontalHtmlTextHeight, getHorizontalSvgTextHeight, getLabelsSize, getMinSpaceBetween, getTextSizeFn, isAxisRelatedSeries, shouldSyncAxisWithPrimary, wrapText, } from '../utils';
2
+ import { calculateNumericProperty, formatAxisTickLabel, getDefaultDateFormat, getDefaultMinYAxisValue, getHorizontalHtmlTextHeight, getLabelsSize, getMinSpaceBetween, getTextSizeFn, isAxisRelatedSeries, shouldSyncAxisWithPrimary, wrapText, } from '../utils';
3
3
  import { getTickValues } from '../../components/AxisY/utils';
4
4
  import { DASH_STYLE, DEFAULT_AXIS_LABEL_FONT_SIZE, DEFAULT_AXIS_TYPE, SERIES_TYPE, axisCrosshairDefaults, axisLabelsDefaults, axisTickMarksDefaults, yAxisTitleDefaults, } from '../constants';
5
5
  import { createYScale } from '../scales/y-scale';
@@ -68,7 +68,7 @@ export const getPreparedYAxis = ({ height, boundsHeight, width, seriesData, yAxi
68
68
  const labelsHtml = get(axisItem, 'labels.html', false);
69
69
  const labelsLineHeight = labelsHtml
70
70
  ? getHorizontalHtmlTextHeight({ text: 'Tmp', style: labelsStyle })
71
- : getHorizontalSvgTextHeight({ text: 'Tmp', style: labelsStyle });
71
+ : (await getTextSizeFn({ style: labelsStyle })('Tmp')).height;
72
72
  const titleText = isAxisVisible ? get(axisItem, 'title.text', '') : '';
73
73
  const titleStyle = Object.assign(Object.assign({}, yAxisTitleDefaults.style), get(axisItem, 'title.style'));
74
74
  const titleMaxRowsCount = get(axisItem, 'title.maxRowCount', yAxisTitleDefaults.maxRowCount);
@@ -10,8 +10,8 @@ export declare function getPlotHeight(args: {
10
10
  boundsHeight: number;
11
11
  gap: number;
12
12
  }): number;
13
- export declare function getSplit(args: UseSplitArgs): {
13
+ export declare function getSplit(args: UseSplitArgs): Promise<{
14
14
  plots: PreparedPlot[];
15
15
  gap: number;
16
- };
16
+ }>;
17
17
  export {};
@@ -1,9 +1,9 @@
1
1
  import get from 'lodash/get';
2
2
  import isEmpty from 'lodash/isEmpty';
3
- import { calculateNumericProperty, getHorizontalSvgTextHeight } from '../utils';
3
+ import { calculateNumericProperty, getTextSizeFn } from '../utils';
4
4
  const DEFAULT_TITLE_FONT_SIZE = '15px';
5
5
  const TITLE_TOP_BOTTOM_PADDING = 8;
6
- function preparePlotTitle(args) {
6
+ async function preparePlotTitle(args) {
7
7
  const { title, plotIndex, plotHeight, chartWidth, gap } = args;
8
8
  const titleText = (title === null || title === void 0 ? void 0 : title.text) || '';
9
9
  const titleStyle = {
@@ -11,7 +11,7 @@ function preparePlotTitle(args) {
11
11
  fontWeight: get(title, 'style.fontWeight'),
12
12
  };
13
13
  const titleHeight = titleText
14
- ? getHorizontalSvgTextHeight({ text: titleText, style: titleStyle }) +
14
+ ? (await getTextSizeFn({ style: titleStyle })(titleText)).height +
15
15
  TITLE_TOP_BOTTOM_PADDING * 2
16
16
  : 0;
17
17
  const top = plotIndex * (plotHeight + gap);
@@ -31,7 +31,7 @@ export function getPlotHeight(args) {
31
31
  }
32
32
  return boundsHeight;
33
33
  }
34
- export function getSplit(args) {
34
+ export async function getSplit(args) {
35
35
  var _a, _b;
36
36
  const { split, boundsHeight, chartWidth } = args;
37
37
  const splitGap = (_a = calculateNumericProperty({ value: split === null || split === void 0 ? void 0 : split.gap, base: boundsHeight })) !== null && _a !== void 0 ? _a : 0;
@@ -40,22 +40,25 @@ export function getSplit(args) {
40
40
  if (isEmpty(plots)) {
41
41
  plots.push({});
42
42
  }
43
+ const items = [];
44
+ for (let index = 0; index < plots.length; index++) {
45
+ const p = plots[index];
46
+ const title = await preparePlotTitle({
47
+ title: p.title,
48
+ plotIndex: index,
49
+ gap: splitGap,
50
+ plotHeight,
51
+ chartWidth,
52
+ });
53
+ const top = index * (plotHeight + splitGap);
54
+ items.push({
55
+ top: top + title.height,
56
+ height: plotHeight - title.height,
57
+ title,
58
+ });
59
+ }
43
60
  return {
44
- plots: plots.map((p, index) => {
45
- const title = preparePlotTitle({
46
- title: p.title,
47
- plotIndex: index,
48
- gap: splitGap,
49
- plotHeight,
50
- chartWidth,
51
- });
52
- const top = index * (plotHeight + splitGap);
53
- return {
54
- top: top + title.height,
55
- height: plotHeight - title.height,
56
- title,
57
- };
58
- }),
61
+ plots: items,
59
62
  gap: splitGap,
60
63
  };
61
64
  }
@@ -12,23 +12,21 @@ export async function getPreparedLegend(args) {
12
12
  const defaultItemStyle = clone(legendDefaults.itemStyle);
13
13
  const itemStyle = get(legend, 'itemStyle');
14
14
  const computedItemStyle = merge(defaultItemStyle, itemStyle);
15
- const { maxHeight: lineHeight, maxWidth: lineWidth } = await getLabelsSize({
16
- labels: ['Tmp'],
17
- style: computedItemStyle,
18
- });
15
+ const { width: lineWidth, height: lineHeight, hangingOffset: itemHangingOffset, } = await getTextSizeFn({ style: computedItemStyle })('Tmp');
19
16
  const legendType = get(legend, 'type', 'discrete');
20
17
  const isTitleEnabled = Boolean((_a = legend === null || legend === void 0 ? void 0 : legend.title) === null || _a === void 0 ? void 0 : _a.text);
21
18
  const titleMargin = isTitleEnabled ? get(legend, 'title.margin', 4) : 0;
22
19
  const titleStyle = Object.assign({ fontSize: '12px', fontWeight: 'bold' }, get(legend, 'title.style'));
23
20
  const titleText = isTitleEnabled ? get(legend, 'title.text', '') : '';
24
- const titleSize = await getLabelsSize({ labels: [titleText], style: titleStyle });
25
- const titleHeight = isTitleEnabled ? titleSize.maxHeight : 0;
21
+ const titleTextSize = await getTextSizeFn({ style: titleStyle })(titleText);
22
+ const titleHeight = isTitleEnabled ? titleTextSize.height : 0;
23
+ const titleHangingOffset = titleTextSize.hangingOffset;
26
24
  const tickStyle = {
27
25
  fontSize: '12px',
28
26
  };
29
27
  const ticks = {
30
28
  labelsMargin: 4,
31
- labelsLineHeight: (await getLabelsSize({ labels: ['Tmp'], style: tickStyle })).maxHeight,
29
+ labelsLineHeight: (await getTextSizeFn({ style: tickStyle })('Tmp')).height,
32
30
  style: tickStyle,
33
31
  };
34
32
  const colorScale = {
@@ -60,6 +58,7 @@ export async function getPreparedLegend(args) {
60
58
  verticalAlign: get(legend, 'verticalAlign', legendDefaults.verticalAlign),
61
59
  justifyContent: get(legend, 'justifyContent', legendDefaults.justifyContent),
62
60
  enabled,
61
+ hangingOffset: itemHangingOffset,
63
62
  height,
64
63
  itemDistance: get(legend, 'itemDistance', legendDefaults.itemDistance),
65
64
  itemStyle: computedItemStyle,
@@ -68,6 +67,7 @@ export async function getPreparedLegend(args) {
68
67
  type: legendType,
69
68
  title: {
70
69
  enable: isTitleEnabled,
70
+ hangingOffset: titleHangingOffset,
71
71
  text: titleText,
72
72
  margin: titleMargin,
73
73
  style: titleStyle,
@@ -15,10 +15,12 @@ export type PreparedLegendSymbol = (RectLegendSymbol | PathLegendSymbol | Symbol
15
15
  bboxWidth: number;
16
16
  };
17
17
  export type PreparedLegend = Required<Omit<ChartLegend, 'title' | 'colorScale'>> & {
18
+ hangingOffset: number;
18
19
  height: number;
19
20
  lineHeight: number;
20
21
  title: {
21
22
  enable: boolean;
23
+ hangingOffset: number;
22
24
  text: string;
23
25
  margin: number;
24
26
  style: BaseTextStyle;
@@ -2,7 +2,7 @@ import { path } from 'd3-path';
2
2
  import { select } from 'd3-selection';
3
3
  import { getAxisItems, getXAxisOffset, getXTickPosition } from '../axis/common';
4
4
  import { calculateCos, calculateSin } from '../math';
5
- import { getLabelsSize, setEllipsisForOverflowText } from '../text';
5
+ import { getTextSizeFn, setEllipsisForOverflowText } from '../text';
6
6
  const AXIS_BOTTOM_HTML_LABELS_DATA_ATTR = 'data-axis-bottom-html-labels';
7
7
  function addDomain(selection, options) {
8
8
  const { size, color } = options;
@@ -32,7 +32,7 @@ function appendSvgLabels(args) {
32
32
  return 'middle';
33
33
  })
34
34
  .style('transform', transform)
35
- .style('dominant-baseline', 'text-after-edge');
35
+ .attr('dominant-baseline', 'hanging');
36
36
  const labels = ticksSelection.selectAll('.tick text');
37
37
  // FIXME: handle rotated overlapping labels (with a smarter approach)
38
38
  if (ticks.rotation) {
@@ -153,10 +153,8 @@ export async function axisBottom(args) {
153
153
  const offset = getXAxisOffset();
154
154
  const position = getXTickPosition({ scale, offset });
155
155
  const values = getAxisItems({ scale, count: ticksCount, maxCount: maxTickCount });
156
- const labelHeight = (await getLabelsSize({
157
- labels: values.map(labelFormat),
158
- style: labelsStyle,
159
- })).maxHeight;
156
+ const getTextSize = getTextSizeFn({ style: labelsStyle });
157
+ const labelSize = await getTextSize('Tmp');
160
158
  return function (selection) {
161
159
  var _a, _b, _c;
162
160
  selection.selectAll('.tick, .domain').remove();
@@ -165,16 +163,8 @@ export async function axisBottom(args) {
165
163
  const x = (rect === null || rect === void 0 ? void 0 : rect.x) || 0;
166
164
  const right = x + domain.size;
167
165
  const top = -((_c = (_b = tickItems === null || tickItems === void 0 ? void 0 : tickItems[0]) === null || _b === void 0 ? void 0 : _b[0]) !== null && _c !== void 0 ? _c : 0);
168
- const translateY = labelHeight + labelsMargin - top;
169
- let transform = `translate(0, ${translateY}px)`;
170
- if (rotation) {
171
- const labelsOffsetTop = labelHeight * calculateCos(rotation) + labelsMargin - top;
172
- let labelsOffsetLeft = calculateSin(rotation) * labelHeight;
173
- if (Math.abs(rotation) % 360 === 90) {
174
- labelsOffsetLeft += ((rotation > 0 ? -1 : 1) * labelHeight) / 2;
175
- }
176
- transform = `translate(${-labelsOffsetLeft}px, ${labelsOffsetTop}px) rotate(${rotation}deg)`;
177
- }
166
+ const translateY = labelsMargin - top + labelSize.hangingOffset;
167
+ const transform = `translate(0, ${translateY}px)`;
178
168
  const tickPath = path();
179
169
  tickItems === null || tickItems === void 0 ? void 0 : tickItems.forEach(([start, end]) => {
180
170
  tickPath.moveTo(0, start);
@@ -16,10 +16,6 @@ export declare const getHorizontalHtmlTextHeight: (args: {
16
16
  text: string;
17
17
  style?: Partial<BaseTextStyle>;
18
18
  }) => number;
19
- export declare const getHorizontalSvgTextHeight: (args: {
20
- text: string;
21
- style?: Partial<BaseTextStyle>;
22
- }) => number;
23
19
  export declare const getDataCategoryValue: (args: {
24
20
  axisDirection: AxisDirection;
25
21
  categories: string[];
@@ -155,19 +155,6 @@ export const getHorizontalHtmlTextHeight = (args) => {
155
155
  container.remove();
156
156
  return height;
157
157
  };
158
- export const getHorizontalSvgTextHeight = (args) => {
159
- var _a;
160
- const { text, style } = args;
161
- const container = select(document.body).append('svg');
162
- const textSelection = container.append('text').text(text);
163
- const fontSize = get(style, 'fontSize', DEFAULT_AXIS_LABEL_FONT_SIZE);
164
- if (fontSize) {
165
- textSelection.style('font-size', fontSize).style('dominant-baseline', 'text-after-edge');
166
- }
167
- const height = ((_a = textSelection.node()) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect().height) || 0;
168
- container.remove();
169
- return height;
170
- };
171
158
  const extractCategoryValue = (args) => {
172
159
  const { axisDirection, categories, data } = args;
173
160
  const dataCategory = get(data, axisDirection);
@@ -11,6 +11,7 @@ export declare function getSvgLabelConstraintedPosition(args: {
11
11
  width: number;
12
12
  x: number;
13
13
  y: number;
14
+ hangingOffset: number;
14
15
  }): {
15
16
  x: number;
16
17
  y: number;
@@ -43,20 +43,20 @@ export function filterOverlappingLabels(labels) {
43
43
  return result;
44
44
  }
45
45
  export function getSvgLabelConstraintedPosition(args) {
46
- const { boundsHeight, boundsWidth, height, width, x, y } = args;
46
+ const { boundsHeight, boundsWidth, height, width, x, y, hangingOffset } = args;
47
47
  let resultX = x;
48
- let resultY = y;
48
+ let resultY = y - height / 2 + hangingOffset;
49
49
  if (x < 0) {
50
50
  resultX = 0;
51
51
  }
52
52
  if (x + width > boundsWidth) {
53
53
  resultX = boundsWidth - width;
54
54
  }
55
- if (y - height < 0) {
55
+ if (resultY < 0) {
56
56
  resultY = 0;
57
57
  }
58
- if (y > boundsHeight) {
59
- resultY = boundsHeight;
58
+ if (resultY + height > boundsHeight) {
59
+ resultY = boundsHeight - height + hangingOffset;
60
60
  }
61
61
  return { x: resultX, y: resultY };
62
62
  }
@@ -27,6 +27,7 @@ export declare function getTextSizeFn({ style }: {
27
27
  }): (str: string) => Promise<{
28
28
  width: number;
29
29
  height: number;
30
+ hangingOffset: number;
30
31
  }>;
31
32
  export declare function getTextWithElipsis({ text: originalText, getTextWidth, maxWidth, }: {
32
33
  text: string;
@@ -194,9 +194,13 @@ export function getTextSizeFn({ style }) {
194
194
  await document.fonts.ready;
195
195
  context.font = `${(_a = style === null || style === void 0 ? void 0 : style.fontWeight) !== null && _a !== void 0 ? _a : defaultFontWeight} ${(_b = style === null || style === void 0 ? void 0 : style.fontSize) !== null && _b !== void 0 ? _b : defaultFontSize} ${defaultFontFamily}`;
196
196
  const textMetric = context.measureText(unescapeHtml(str));
197
+ // we calculate hanging based on an approximate algorithm from chromium
198
+ // https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/html/canvas/text_metrics.cc;l=32;drc=7cf6ac3dd6dca800fbc0d28e80a7732d4ea90340?q=member_hanging_&ss=chromium%2Fchromium%2Fsrc
199
+ // it would be possible to use native, but the browsers are not working in harmony right now
197
200
  return {
198
201
  width: textMetric.width,
199
202
  height: textMetric.fontBoundingBoxDescent + textMetric.fontBoundingBoxAscent,
203
+ hangingOffset: textMetric.fontBoundingBoxAscent * 0.2,
200
204
  };
201
205
  };
202
206
  }
@@ -60,7 +60,7 @@ async function prepareDataLabels({ series, points, xMax, yAxisTop, isOutsideBoun
60
60
  else {
61
61
  const labelSize = await getTextSize(text);
62
62
  const x = Math.min(xMax - labelSize.width, Math.max(0, point.x - labelSize.width / 2));
63
- const y = Math.max(yAxisTop, point.y - series.dataLabels.padding - labelSize.height);
63
+ const y = Math.max(yAxisTop, point.y - series.dataLabels.padding - labelSize.height + labelSize.hangingOffset);
64
64
  svgLabels.push({
65
65
  text,
66
66
  x,
@@ -171,7 +171,7 @@ export async function prepareBarYData(args) {
171
171
  map.set(dataLabels.style, getTextSizeFn({ style: dataLabels.style }));
172
172
  }
173
173
  const getTextSize = map.get(dataLabels.style);
174
- const { width, height } = await getTextSize(content);
174
+ const { width, height, hangingOffset } = await getTextSize(content);
175
175
  const x = dataLabels.inside
176
176
  ? prepared.x + prepared.width / 2 - width / 2
177
177
  : prepared.x + prepared.width + dataLabels.padding;
@@ -181,7 +181,8 @@ export async function prepareBarYData(args) {
181
181
  height,
182
182
  width,
183
183
  x,
184
- y: y + height / 2,
184
+ y,
185
+ hangingOffset,
185
186
  });
186
187
  labels.push({
187
188
  size: { width, height },
@@ -62,7 +62,10 @@ export async function prepareFunnelData(args) {
62
62
  }
63
63
  svgLabels.push({
64
64
  x,
65
- y: getSegmentY(index) + itemHeight / 2 - labelSize.height / 2,
65
+ y: getSegmentY(index) +
66
+ itemHeight / 2 -
67
+ labelSize.height / 2 +
68
+ labelSize.hangingOffset,
66
69
  text: labelContent,
67
70
  style: s.dataLabels.style,
68
71
  size: labelSize,
@@ -82,7 +82,7 @@ export async function prepareHeatmapData({ series, xAxis, xScale, yAxis, yScale,
82
82
  if (text) {
83
83
  svgDataLabels.push({
84
84
  x: item.x + item.width / 2 - size.width / 2,
85
- y: item.y + item.height / 2 - size.height / 2,
85
+ y: item.y + item.height / 2 - size.height / 2 + size.hangingOffset,
86
86
  text,
87
87
  style: series.dataLabels.style,
88
88
  });
@@ -68,7 +68,10 @@ export const prepareLineData = async (args) => {
68
68
  const text = getFormattedValue(Object.assign({ value: labelValue }, s.dataLabels));
69
69
  const labelSize = await getTextSize(text);
70
70
  const style = s.dataLabels.style;
71
- const y = Math.max(yAxisTop, point.y - s.dataLabels.padding - labelSize.height);
71
+ const y = Math.max(yAxisTop, point.y -
72
+ s.dataLabels.padding -
73
+ labelSize.height +
74
+ labelSize.hangingOffset);
72
75
  const x = Math.min(xMax - labelSize.width, Math.max(0, point.x - labelSize.width / 2));
73
76
  const labelData = {
74
77
  text,
@@ -95,6 +95,7 @@ export function preparePieData(args) {
95
95
  const text = getFormattedValue(Object.assign({ value: (_a = d.data.label) !== null && _a !== void 0 ? _a : d.data.value }, d.dataLabels));
96
96
  let labelWidth = 0;
97
97
  let labelHeight = 0;
98
+ let labelHangingOffset = 0;
98
99
  if (dataLabels.html) {
99
100
  const size = await getLabelsSize({
100
101
  labels: [text],
@@ -108,10 +109,11 @@ export function preparePieData(args) {
108
109
  const size = await getTextSize(text);
109
110
  labelWidth = size.width;
110
111
  labelHeight = size.height;
112
+ labelHangingOffset = size.hangingOffset;
111
113
  }
112
114
  const label = {
113
115
  text,
114
- size: { width: labelWidth, height: labelHeight },
116
+ size: { width: labelWidth, height: labelHeight, hangingOffset: labelHangingOffset },
115
117
  };
116
118
  acc[d.id] = label;
117
119
  }
@@ -165,11 +167,16 @@ export function preparePieData(args) {
165
167
  * @returns {[number, number]} A tuple [x, y] relative to the pie center.
166
168
  */
167
169
  const getLabelPosition = (angle) => {
170
+ var _a;
168
171
  let [x, y] = labelArcGenerator.centroid(Object.assign(Object.assign({}, relatedSegment), { startAngle: angle, endAngle: angle }));
169
172
  if (shouldUseHtml) {
170
173
  x = x < 0 ? x - labelWidth : x;
174
+ y = y < 0 ? y - labelHeight : y;
175
+ }
176
+ else {
177
+ const hangingOffset = (_a = labelSize === null || labelSize === void 0 ? void 0 : labelSize.hangingOffset) !== null && _a !== void 0 ? _a : 0;
178
+ y = y < 0 ? y - labelHeight + hangingOffset : y + hangingOffset;
171
179
  }
172
- y = y < 0 ? y - labelHeight : y;
173
180
  return [x, y];
174
181
  };
175
182
  const getConnectorPoints = (angle) => {
@@ -1,7 +1,7 @@
1
1
  import { range } from 'd3-array';
2
2
  import { scaleLinear } from 'd3-scale';
3
3
  import { curveLinearClosed, line } from 'd3-shape';
4
- import { getLabelsSize } from '../../../core/utils';
4
+ import { getLabelsSize, getTextSizeFn } from '../../../core/utils';
5
5
  import { getFormattedValue } from '../../../core/utils/format';
6
6
  export async function prepareRadarData(args) {
7
7
  const { series: preparedSeries, boundsWidth, boundsHeight } = args;
@@ -116,18 +116,28 @@ export async function prepareRadarData(args) {
116
116
  const shouldUseHtml = dataLabels.html;
117
117
  data.labels = await Promise.all(categories.map(async (category, index) => {
118
118
  const text = getFormattedValue(Object.assign({ value: category.key }, dataLabels));
119
- const labelSize = await getLabelsSize({ labels: [text], style });
120
119
  const angle = index * angleStep - Math.PI / 2;
121
120
  // Position label slightly outside the point
122
121
  const labelRadius = data.radius + 10;
123
122
  let x = center[0] + Math.cos(angle) * labelRadius;
124
123
  let y = center[1] + Math.sin(angle) * labelRadius;
124
+ let labelWidth = 0;
125
+ let labelHeight = 0;
125
126
  if (shouldUseHtml) {
126
- x = x < center[0] ? x - labelSize.maxWidth : x;
127
- y = y - labelSize.maxHeight;
127
+ const labelSize = await getLabelsSize({ labels: [text], style });
128
+ labelWidth = labelSize.maxWidth;
129
+ labelHeight = labelSize.maxHeight;
130
+ x = x < center[0] ? x - labelWidth : x;
131
+ y = y - labelHeight;
128
132
  }
129
133
  else {
130
- y = y < center[1] ? y - labelSize.maxHeight : y;
134
+ const labelSize = await getTextSizeFn({ style })(text);
135
+ labelWidth = labelSize.width;
136
+ labelHeight = labelSize.height;
137
+ y =
138
+ y < center[1]
139
+ ? y - labelHeight + labelSize.hangingOffset
140
+ : y + labelSize.hangingOffset;
131
141
  }
132
142
  x = Math.max(-boundsWidth / 2, x);
133
143
  return {
@@ -135,8 +145,8 @@ export async function prepareRadarData(args) {
135
145
  x,
136
146
  y,
137
147
  style,
138
- size: { width: labelSize.maxWidth, height: labelSize.maxHeight },
139
- maxWidth: labelSize.maxWidth,
148
+ size: { width: labelWidth, height: labelHeight },
149
+ maxWidth: labelWidth,
140
150
  textAnchor: angle > Math.PI / 2 && angle < (3 * Math.PI) / 2 ? 'end' : 'start',
141
151
  series: { id: series.id },
142
152
  };
@@ -1,5 +1,5 @@
1
- import { sankey, sankeyLinkHorizontal } from 'd3-sankey';
2
1
  import { getFormattedValue } from '../../../core/utils/format';
2
+ import { sankey, sankeyLinkHorizontal } from './sankey-layout';
3
3
  export function prepareSankeyData(args) {
4
4
  const { series, width, height } = args;
5
5
  const htmlElements = [];
@@ -0,0 +1,49 @@
1
+ type SankeyLayoutNodeProps = {
2
+ index: number;
3
+ depth: number;
4
+ height: number;
5
+ layer: number;
6
+ value: number;
7
+ fixedValue?: number;
8
+ x0: number;
9
+ x1: number;
10
+ y0: number;
11
+ y1: number;
12
+ };
13
+ type SankeyLayoutLinkProps = {
14
+ index: number;
15
+ value: number;
16
+ width: number;
17
+ y0: number;
18
+ y1: number;
19
+ };
20
+ export type SankeyComputedNode<N, L> = N & SankeyLayoutNodeProps & {
21
+ sourceLinks: SankeyComputedLink<N, L>[];
22
+ targetLinks: SankeyComputedLink<N, L>[];
23
+ };
24
+ export type SankeyComputedLink<N, L> = L & SankeyLayoutLinkProps & {
25
+ source: SankeyComputedNode<N, L>;
26
+ target: SankeyComputedNode<N, L>;
27
+ };
28
+ export type SankeyGraph<N, L> = {
29
+ nodes: SankeyComputedNode<N, L>[];
30
+ links: SankeyComputedLink<N, L>[];
31
+ };
32
+ type AlignFn<N, L> = (node: SankeyComputedNode<N, L>, n: number) => number;
33
+ type SortFn<T> = (a: T, b: T) => number;
34
+ export declare function sankey<N, L>(): {
35
+ (input: {
36
+ nodes: N[];
37
+ links: L[];
38
+ }): SankeyGraph<N, L>;
39
+ nodeId(fn: (d: N, i: number) => string | number): /*elided*/ any;
40
+ nodeAlign(fn: AlignFn<N, L>): /*elided*/ any;
41
+ nodeSort(fn: SortFn<SankeyComputedNode<N, L>> | undefined): /*elided*/ any;
42
+ nodeWidth(value: number): /*elided*/ any;
43
+ nodePadding(value: number): /*elided*/ any;
44
+ linkSort(fn: SortFn<SankeyComputedLink<N, L>> | undefined): /*elided*/ any;
45
+ extent([[left, top], [right, bottom]]: [[number, number], [number, number]]): /*elided*/ any;
46
+ iterations(value: number): /*elided*/ any;
47
+ };
48
+ export declare function sankeyLinkHorizontal<N, L>(): import("d3-shape").Link<any, SankeyComputedLink<N, L>, [number, number]>;
49
+ export {};