@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.
- package/dist/cjs/components/AxisX/AxisX.js +4 -4
- package/dist/cjs/components/AxisX/prepare-axis-data.js +17 -13
- package/dist/cjs/components/AxisY/AxisY.js +4 -4
- package/dist/cjs/components/AxisY/prepare-axis-data.js +27 -21
- package/dist/cjs/components/AxisY/prepare-axis-title.js +8 -3
- package/dist/cjs/components/AxisY/styles.css +1 -1
- package/dist/cjs/components/ChartInner/index.js +5 -15
- package/dist/cjs/components/ChartInner/useChartInnerProps.d.ts +4 -1
- package/dist/cjs/components/ChartInner/useChartInnerProps.js +22 -11
- package/dist/cjs/components/ChartInner/utils/title.d.ts +1 -1
- package/dist/cjs/components/ChartInner/utils/title.js +3 -3
- package/dist/cjs/components/Legend/index.js +19 -13
- package/dist/cjs/components/Legend/styles.css +1 -1
- package/dist/cjs/components/Tooltip/DefaultTooltipContent/Row.js +1 -1
- package/dist/cjs/components/utils/axis-title.js +1 -1
- package/dist/cjs/core/axes/x-axis.js +2 -2
- package/dist/cjs/core/axes/y-axis.js +2 -2
- package/dist/cjs/core/layout/split.d.ts +2 -2
- package/dist/cjs/core/layout/split.js +22 -19
- package/dist/cjs/core/series/prepare-legend.js +7 -7
- package/dist/cjs/core/series/types.d.ts +2 -0
- package/dist/cjs/core/utils/axis-generators/bottom.js +6 -16
- package/dist/cjs/core/utils/common.d.ts +0 -4
- package/dist/cjs/core/utils/common.js +0 -13
- package/dist/cjs/core/utils/labels.d.ts +1 -0
- package/dist/cjs/core/utils/labels.js +5 -5
- package/dist/cjs/core/utils/text.d.ts +1 -0
- package/dist/cjs/core/utils/text.js +4 -0
- package/dist/cjs/hooks/useShapes/area/prepare-data.js +1 -1
- package/dist/cjs/hooks/useShapes/bar-y/prepare-data.js +3 -2
- package/dist/cjs/hooks/useShapes/funnel/prepare-data.js +4 -1
- package/dist/cjs/hooks/useShapes/heatmap/prepare-data.js +1 -1
- package/dist/cjs/hooks/useShapes/line/prepare-data.js +4 -1
- package/dist/cjs/hooks/useShapes/pie/prepare-data.js +9 -2
- package/dist/cjs/hooks/useShapes/radar/prepare-data.js +17 -7
- package/dist/cjs/hooks/useShapes/sankey/prepare-data.js +1 -1
- package/dist/cjs/hooks/useShapes/sankey/sankey-layout.d.ts +49 -0
- package/dist/cjs/hooks/useShapes/sankey/sankey-layout.js +362 -0
- package/dist/cjs/hooks/useShapes/styles.css +4 -4
- package/dist/cjs/hooks/useShapes/treemap/prepare-data.js +3 -1
- package/dist/cjs/types/chart-ui.d.ts +1 -0
- package/dist/esm/components/AxisX/AxisX.js +4 -4
- package/dist/esm/components/AxisX/prepare-axis-data.js +17 -13
- package/dist/esm/components/AxisY/AxisY.js +4 -4
- package/dist/esm/components/AxisY/prepare-axis-data.js +27 -21
- package/dist/esm/components/AxisY/prepare-axis-title.js +8 -3
- package/dist/esm/components/AxisY/styles.css +1 -1
- package/dist/esm/components/ChartInner/index.js +5 -15
- package/dist/esm/components/ChartInner/useChartInnerProps.d.ts +4 -1
- package/dist/esm/components/ChartInner/useChartInnerProps.js +22 -11
- package/dist/esm/components/ChartInner/utils/title.d.ts +1 -1
- package/dist/esm/components/ChartInner/utils/title.js +3 -3
- package/dist/esm/components/Legend/index.js +19 -13
- package/dist/esm/components/Legend/styles.css +1 -1
- package/dist/esm/components/Tooltip/DefaultTooltipContent/Row.js +1 -1
- package/dist/esm/components/utils/axis-title.js +1 -1
- package/dist/esm/core/axes/x-axis.js +2 -2
- package/dist/esm/core/axes/y-axis.js +2 -2
- package/dist/esm/core/layout/split.d.ts +2 -2
- package/dist/esm/core/layout/split.js +22 -19
- package/dist/esm/core/series/prepare-legend.js +7 -7
- package/dist/esm/core/series/types.d.ts +2 -0
- package/dist/esm/core/utils/axis-generators/bottom.js +6 -16
- package/dist/esm/core/utils/common.d.ts +0 -4
- package/dist/esm/core/utils/common.js +0 -13
- package/dist/esm/core/utils/labels.d.ts +1 -0
- package/dist/esm/core/utils/labels.js +5 -5
- package/dist/esm/core/utils/text.d.ts +1 -0
- package/dist/esm/core/utils/text.js +4 -0
- package/dist/esm/hooks/useShapes/area/prepare-data.js +1 -1
- package/dist/esm/hooks/useShapes/bar-y/prepare-data.js +3 -2
- package/dist/esm/hooks/useShapes/funnel/prepare-data.js +4 -1
- package/dist/esm/hooks/useShapes/heatmap/prepare-data.js +1 -1
- package/dist/esm/hooks/useShapes/line/prepare-data.js +4 -1
- package/dist/esm/hooks/useShapes/pie/prepare-data.js +9 -2
- package/dist/esm/hooks/useShapes/radar/prepare-data.js +17 -7
- package/dist/esm/hooks/useShapes/sankey/prepare-data.js +1 -1
- package/dist/esm/hooks/useShapes/sankey/sankey-layout.d.ts +49 -0
- package/dist/esm/hooks/useShapes/sankey/sankey-layout.js +362 -0
- package/dist/esm/hooks/useShapes/styles.css +4 -4
- package/dist/esm/hooks/useShapes/treemap/prepare-data.js +3 -1
- package/dist/esm/types/chart-ui.d.ts +1 -0
- package/package.json +1 -3
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import get from 'lodash/get';
|
|
2
|
-
import { TIME_UNITS, calculateCos, calculateNumericProperty, calculateSin, formatAxisTickLabel, getDefaultDateFormat, getHorizontalHtmlTextHeight,
|
|
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
|
-
:
|
|
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,
|
|
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
|
-
:
|
|
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,
|
|
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
|
-
?
|
|
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:
|
|
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 {
|
|
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
|
|
25
|
-
const titleHeight = isTitleEnabled ?
|
|
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
|
|
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 {
|
|
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
|
-
.
|
|
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
|
|
157
|
-
|
|
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 =
|
|
169
|
-
|
|
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);
|
|
@@ -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 (
|
|
55
|
+
if (resultY < 0) {
|
|
56
56
|
resultY = 0;
|
|
57
57
|
}
|
|
58
|
-
if (
|
|
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
|
|
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) +
|
|
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 -
|
|
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
|
-
|
|
127
|
-
|
|
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
|
-
|
|
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:
|
|
139
|
-
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 {};
|