@gravity-ui/charts 1.42.3 → 1.43.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.
- package/dist/cjs/components/AxisX/AxisX.js +31 -4
- package/dist/cjs/components/AxisX/prepare-axis-data.js +58 -13
- package/dist/cjs/components/AxisX/types.d.ts +18 -1
- package/dist/cjs/components/AxisY/AxisY.js +31 -4
- package/dist/cjs/components/AxisY/prepare-axis-data.js +68 -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/AxisY/types.d.ts +18 -1
- package/dist/cjs/components/ChartInner/index.js +21 -15
- package/dist/cjs/components/ChartInner/useChartInnerHandlers.js +6 -5
- package/dist/cjs/components/ChartInner/useChartInnerProps.d.ts +3 -2
- package/dist/cjs/components/ChartInner/useChartInnerProps.js +22 -11
- package/dist/cjs/components/ChartInner/useDefaultState.js +4 -3
- package/dist/cjs/components/ChartInner/utils/chart.js +1 -1
- package/dist/cjs/components/ChartInner/utils/normalized-original-data.d.ts +1 -0
- package/dist/cjs/components/ChartInner/utils/title.d.ts +3 -2
- package/dist/cjs/components/ChartInner/utils/title.js +69 -11
- package/dist/cjs/components/Legend/index.js +8 -11
- package/dist/cjs/components/Legend/styles.css +1 -1
- package/dist/cjs/components/Title/index.js +3 -5
- package/dist/cjs/components/Tooltip/ChartTooltipContent.d.ts +2 -1
- package/dist/cjs/components/Tooltip/ChartTooltipContent.js +3 -2
- package/dist/cjs/components/Tooltip/index.js +2 -2
- package/dist/cjs/components/utils/axis-title.js +1 -1
- package/dist/cjs/core/axes/types.d.ts +26 -9
- package/dist/cjs/core/axes/x-axis.js +16 -3
- package/dist/cjs/core/axes/y-axis.js +21 -8
- package/dist/cjs/core/constants/defaults/axis.d.ts +1 -0
- package/dist/cjs/core/constants/defaults/axis.js +1 -0
- package/dist/cjs/core/layout/split.d.ts +2 -2
- package/dist/cjs/core/layout/split.js +22 -19
- package/dist/cjs/core/scales/y-scale.js +37 -13
- package/dist/cjs/core/series/prepare-legend.js +7 -7
- package/dist/cjs/core/series/types.d.ts +2 -0
- package/dist/cjs/core/types/chart/axis.d.ts +43 -1
- package/dist/cjs/core/types/chart/title.d.ts +10 -0
- package/dist/cjs/core/types/chart/tooltip.d.ts +3 -1
- 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 +1 -14
- package/dist/cjs/core/utils/get-hovered-plots.d.ts +3 -2
- package/dist/cjs/core/utils/get-hovered-plots.js +28 -4
- 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 +16 -2
- package/dist/cjs/hooks/types.d.ts +5 -2
- package/dist/cjs/hooks/useShapes/area/prepare-data.js +12 -7
- package/dist/cjs/hooks/useShapes/bar-x/prepare-data.js +12 -4
- 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/hooks/useTooltip/index.d.ts +3 -2
- package/dist/cjs/hooks/useTooltip/index.js +5 -3
- package/dist/cjs/types/chart-ui.d.ts +1 -0
- package/dist/esm/components/AxisX/AxisX.js +31 -4
- package/dist/esm/components/AxisX/prepare-axis-data.js +58 -13
- package/dist/esm/components/AxisX/types.d.ts +18 -1
- package/dist/esm/components/AxisY/AxisY.js +31 -4
- package/dist/esm/components/AxisY/prepare-axis-data.js +68 -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/AxisY/types.d.ts +18 -1
- package/dist/esm/components/ChartInner/index.js +21 -15
- package/dist/esm/components/ChartInner/useChartInnerHandlers.js +6 -5
- package/dist/esm/components/ChartInner/useChartInnerProps.d.ts +3 -2
- package/dist/esm/components/ChartInner/useChartInnerProps.js +22 -11
- package/dist/esm/components/ChartInner/useDefaultState.js +4 -3
- package/dist/esm/components/ChartInner/utils/chart.js +1 -1
- package/dist/esm/components/ChartInner/utils/normalized-original-data.d.ts +1 -0
- package/dist/esm/components/ChartInner/utils/title.d.ts +3 -2
- package/dist/esm/components/ChartInner/utils/title.js +69 -11
- package/dist/esm/components/Legend/index.js +8 -11
- package/dist/esm/components/Legend/styles.css +1 -1
- package/dist/esm/components/Title/index.js +3 -5
- package/dist/esm/components/Tooltip/ChartTooltipContent.d.ts +2 -1
- package/dist/esm/components/Tooltip/ChartTooltipContent.js +3 -2
- package/dist/esm/components/Tooltip/index.js +2 -2
- package/dist/esm/components/utils/axis-title.js +1 -1
- package/dist/esm/core/axes/types.d.ts +26 -9
- package/dist/esm/core/axes/x-axis.js +16 -3
- package/dist/esm/core/axes/y-axis.js +21 -8
- package/dist/esm/core/constants/defaults/axis.d.ts +1 -0
- package/dist/esm/core/constants/defaults/axis.js +1 -0
- package/dist/esm/core/layout/split.d.ts +2 -2
- package/dist/esm/core/layout/split.js +22 -19
- package/dist/esm/core/scales/y-scale.js +37 -13
- package/dist/esm/core/series/prepare-legend.js +7 -7
- package/dist/esm/core/series/types.d.ts +2 -0
- package/dist/esm/core/types/chart/axis.d.ts +43 -1
- package/dist/esm/core/types/chart/title.d.ts +10 -0
- package/dist/esm/core/types/chart/tooltip.d.ts +3 -1
- 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 +1 -14
- package/dist/esm/core/utils/get-hovered-plots.d.ts +3 -2
- package/dist/esm/core/utils/get-hovered-plots.js +28 -4
- 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 +16 -2
- package/dist/esm/hooks/types.d.ts +5 -2
- package/dist/esm/hooks/useShapes/area/prepare-data.js +12 -7
- package/dist/esm/hooks/useShapes/bar-x/prepare-data.js +12 -4
- 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/hooks/useTooltip/index.d.ts +3 -2
- package/dist/esm/hooks/useTooltip/index.js +5 -3
- package/dist/esm/types/chart-ui.d.ts +1 -0
- package/package.json +1 -3
|
@@ -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
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { extent, tickStep, ticks } from 'd3-array';
|
|
2
2
|
import { scaleBand, scaleLinear, scaleLog, scaleUtc } from 'd3-scale';
|
|
3
3
|
import get from 'lodash/get';
|
|
4
|
-
import { CHART_SERIES_WITH_VOLUME_ON_Y_AXIS, getDomainDataYBySeries, shouldSyncAxisWithPrimary, } from '../utils';
|
|
4
|
+
import { CHART_SERIES_WITH_VOLUME_ON_Y_AXIS, getDefaultMinYAxisValue, getDomainDataYBySeries, shouldSyncAxisWithPrimary, } from '../utils';
|
|
5
5
|
import { getTickValues } from '../../components/AxisY/utils';
|
|
6
6
|
import { getBandSize } from '../../hooks/utils/get-band-size';
|
|
7
7
|
import { SERIES_TYPE } from '../constants';
|
|
@@ -83,14 +83,21 @@ function getDomainMinAlignedToStartTick(args) {
|
|
|
83
83
|
const isStartOnTick = tickValues[0].y === range[0];
|
|
84
84
|
let dNewMin = dMin;
|
|
85
85
|
if (!isStartOnTick) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
86
|
+
if (axis.type === 'logarithmic') {
|
|
87
|
+
const [nicedMin, _nicedMax] = scale.copy().nice().domain();
|
|
88
|
+
dNewMin = nicedMin;
|
|
89
89
|
}
|
|
90
90
|
else {
|
|
91
|
-
step
|
|
91
|
+
let step;
|
|
92
|
+
if (typeof ((_a = tickValues[0]) === null || _a === void 0 ? void 0 : _a.value) === 'number' &&
|
|
93
|
+
typeof ((_b = tickValues[1]) === null || _b === void 0 ? void 0 : _b.value) === 'number') {
|
|
94
|
+
step = tickValues[1].value - tickValues[0].value;
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
step = tickStep(dMin, dMax, 1);
|
|
98
|
+
}
|
|
99
|
+
dNewMin = tickValues[0].value - step;
|
|
92
100
|
}
|
|
93
|
-
dNewMin = tickValues[0].value - step;
|
|
94
101
|
}
|
|
95
102
|
return dNewMin;
|
|
96
103
|
}
|
|
@@ -104,17 +111,24 @@ function getDomainMaxAlignedToEndTick(args) {
|
|
|
104
111
|
labelLineHeight: axis.labels.lineHeight,
|
|
105
112
|
series,
|
|
106
113
|
});
|
|
107
|
-
const isEndOnTick = tickValues[tickValues.length - 1].y === range[1];
|
|
108
114
|
let dNewMax = dMax;
|
|
115
|
+
const isEndOnTick = tickValues[tickValues.length - 1].y === range[1];
|
|
109
116
|
if (!isEndOnTick) {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
117
|
+
if (axis.type === 'logarithmic') {
|
|
118
|
+
const [_nicedMin, nicedMax] = scale.copy().nice().domain();
|
|
119
|
+
dNewMax = nicedMax;
|
|
113
120
|
}
|
|
114
121
|
else {
|
|
115
|
-
step
|
|
122
|
+
let step;
|
|
123
|
+
if (typeof ((_a = tickValues[0]) === null || _a === void 0 ? void 0 : _a.value) === 'number' &&
|
|
124
|
+
typeof ((_b = tickValues[1]) === null || _b === void 0 ? void 0 : _b.value) === 'number') {
|
|
125
|
+
step = tickValues[1].value - tickValues[0].value;
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
step = tickStep(dMin, dMax, 1);
|
|
129
|
+
}
|
|
130
|
+
dNewMax = tickValues[tickValues.length - 1].value + step;
|
|
116
131
|
}
|
|
117
|
-
dNewMax = tickValues[tickValues.length - 1].value + step;
|
|
118
132
|
}
|
|
119
133
|
return dNewMax;
|
|
120
134
|
}
|
|
@@ -141,7 +155,17 @@ export function createYScale(args) {
|
|
|
141
155
|
}
|
|
142
156
|
if (hasNumberAndNullValues) {
|
|
143
157
|
const [yMinDomain, yMaxDomain] = extent(domain);
|
|
144
|
-
|
|
158
|
+
let yMin;
|
|
159
|
+
if (typeof yMinPropsOrState === 'number') {
|
|
160
|
+
yMin = yMinPropsOrState;
|
|
161
|
+
}
|
|
162
|
+
else if (axis.type === 'logarithmic') {
|
|
163
|
+
yMin = yMinDomain;
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
const yMinDefault = getDefaultMinYAxisValue(series);
|
|
167
|
+
yMin = typeof yMinDefault === 'number' ? yMinDefault : yMinDomain;
|
|
168
|
+
}
|
|
145
169
|
let yMax;
|
|
146
170
|
if (typeof yMaxPropsOrState === 'number') {
|
|
147
171
|
yMax = yMaxPropsOrState;
|
|
@@ -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;
|
|
@@ -187,6 +187,8 @@ export interface ChartAxis {
|
|
|
187
187
|
plotLines?: AxisPlotLine[];
|
|
188
188
|
/** An array of colored bands stretching across the plot area marking an interval on the axis. */
|
|
189
189
|
plotBands?: AxisPlotBand[];
|
|
190
|
+
/** An array of custom SVG elements placed at specific axis values. */
|
|
191
|
+
plotShapes?: AxisPlotShape[];
|
|
190
192
|
/**
|
|
191
193
|
* Small perpendicular marks on the axis line at each tick position.
|
|
192
194
|
*
|
|
@@ -275,6 +277,46 @@ export interface AxisPlotLine extends AxisPlot {
|
|
|
275
277
|
width?: number;
|
|
276
278
|
/** Option for line stroke style. */
|
|
277
279
|
dashStyle?: DashStyle;
|
|
280
|
+
/**
|
|
281
|
+
* Extra pixels added to each side of the line for hover detection.
|
|
282
|
+
* The total hoverable area equals `line.width + hoverThreshold * 2`.
|
|
283
|
+
* @default 4
|
|
284
|
+
*/
|
|
285
|
+
hoverThreshold?: number;
|
|
286
|
+
}
|
|
287
|
+
export interface AxisPlotShape extends AxisPlot {
|
|
288
|
+
/**
|
|
289
|
+
* The position of the shape in axis units.
|
|
290
|
+
*
|
|
291
|
+
* Can be a number or a string (e.g., a category).
|
|
292
|
+
* When representing a date, the value **must be a timestamp** (number of milliseconds since Unix epoch).
|
|
293
|
+
*/
|
|
294
|
+
value: number | string;
|
|
295
|
+
/**
|
|
296
|
+
* Custom SVG content renderer.
|
|
297
|
+
*
|
|
298
|
+
* Called with the pixel coordinates of the shape and the plot area dimensions.
|
|
299
|
+
* Must return a string of valid SVG markup that will be inserted inside a `<g>` container
|
|
300
|
+
* positioned at the shape's axis value.
|
|
301
|
+
*
|
|
302
|
+
* @example
|
|
303
|
+
* ```
|
|
304
|
+
* renderer: ({plotHeight}) =>
|
|
305
|
+
* `<circle cx="0" cy="${plotHeight}" r="4" fill="red"/>
|
|
306
|
+
* <line x1="0" y1="0" x2="0" y2="${plotHeight}" stroke="red" stroke-width="1"/>
|
|
307
|
+
* <text x="4" y="12" font-size="11" fill="currentColor">Label</text>`
|
|
308
|
+
* ```
|
|
309
|
+
*/
|
|
310
|
+
renderer: (args: {
|
|
311
|
+
/** Pixel X coordinate in the plot area coordinate system */
|
|
312
|
+
x: number;
|
|
313
|
+
/** Pixel Y coordinate in the plot area coordinate system */
|
|
314
|
+
y: number;
|
|
315
|
+
/** Width of the plot area in pixels */
|
|
316
|
+
plotWidth: number;
|
|
317
|
+
/** Height of the plot area in pixels */
|
|
318
|
+
plotHeight: number;
|
|
319
|
+
}) => string;
|
|
278
320
|
}
|
|
279
321
|
export interface AxisPlotBand extends AxisPlot {
|
|
280
322
|
/**
|
|
@@ -296,7 +338,7 @@ export interface AxisPlotBand extends AxisPlot {
|
|
|
296
338
|
*/
|
|
297
339
|
to: number | string | null;
|
|
298
340
|
}
|
|
299
|
-
export interface AxisCrosshair extends
|
|
341
|
+
export interface AxisCrosshair extends Pick<AxisPlotLine, 'color' | 'dashStyle' | 'opacity' | 'layerPlacement' | 'width'> {
|
|
300
342
|
/**
|
|
301
343
|
* Whether the crosshair should snap to the point or follow the pointer independent of points.
|
|
302
344
|
* @default true
|
|
@@ -2,6 +2,16 @@ import type { BaseTextStyle } from './base';
|
|
|
2
2
|
export interface ChartTitle {
|
|
3
3
|
text: string;
|
|
4
4
|
style?: Partial<BaseTextStyle>;
|
|
5
|
+
/**
|
|
6
|
+
* Maximum number of text rows. If the text exceeds this limit, it is truncated with an ellipsis.
|
|
7
|
+
* Default: 1
|
|
8
|
+
*/
|
|
9
|
+
maxRowCount?: number;
|
|
10
|
+
/**
|
|
11
|
+
* Space between the title and the chart area (in pixels).
|
|
12
|
+
* Default: 10
|
|
13
|
+
*/
|
|
14
|
+
margin?: number;
|
|
5
15
|
/**
|
|
6
16
|
* Can be used for the UI automated test.
|
|
7
17
|
* It is assigned as a data-qa attribute to an element.
|
|
@@ -2,7 +2,7 @@ import type { TOOLTIP_TOTALS_BUILT_IN_AGGREGATION } from '../../constants';
|
|
|
2
2
|
import type { DateTimeLabelFormats } from '../../utils/time';
|
|
3
3
|
import type { MeaningfulAny } from '../misc';
|
|
4
4
|
import type { AreaSeries, AreaSeriesData } from './area';
|
|
5
|
-
import type { AxisPlotBand, AxisPlotLine, ChartXAxis, ChartYAxis } from './axis';
|
|
5
|
+
import type { AxisPlotBand, AxisPlotLine, AxisPlotShape, ChartXAxis, ChartYAxis } from './axis';
|
|
6
6
|
import type { BarXSeries, BarXSeriesData } from './bar-x';
|
|
7
7
|
import type { BarYSeries, BarYSeriesData } from './bar-y';
|
|
8
8
|
import type { CustomFormat, ValueFormat } from './base';
|
|
@@ -96,6 +96,8 @@ export interface ChartTooltipRendererArgs<T = MeaningfulAny> {
|
|
|
96
96
|
hoveredPlotLines?: AxisPlotLine[];
|
|
97
97
|
/** Plot bands that contain the current pointer position. */
|
|
98
98
|
hoveredPlotBands?: AxisPlotBand[];
|
|
99
|
+
/** Plot shapes that contain the current pointer position. */
|
|
100
|
+
hoveredPlotShapes?: AxisPlotShape[];
|
|
99
101
|
xAxis?: ChartXAxis | null;
|
|
100
102
|
yAxis?: ChartYAxis;
|
|
101
103
|
/** Formatting settings for tooltip header row (includes computed default). */
|
|
@@ -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[];
|
|
@@ -88,7 +88,7 @@ export const getDomainDataYBySeries = (series) => {
|
|
|
88
88
|
switch (type) {
|
|
89
89
|
case 'area':
|
|
90
90
|
case 'bar-x': {
|
|
91
|
-
acc.push(
|
|
91
|
+
acc.push(...getDomainDataForStackedSeries(seriesList));
|
|
92
92
|
break;
|
|
93
93
|
}
|
|
94
94
|
case 'waterfall': {
|
|
@@ -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);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AxisPlotBand, AxisPlotLine } from '../../types';
|
|
1
|
+
import type { AxisPlotBand, AxisPlotLine, AxisPlotShape } from '../../types';
|
|
2
2
|
import type { PreparedXAxis, PreparedYAxis } from '../axes/types';
|
|
3
3
|
import type { ChartScale } from '../scales/types';
|
|
4
4
|
export declare function getHoveredPlots(args: {
|
|
@@ -9,6 +9,7 @@ export declare function getHoveredPlots(args: {
|
|
|
9
9
|
xScale?: ChartScale;
|
|
10
10
|
yScale?: (ChartScale | undefined)[];
|
|
11
11
|
}): {
|
|
12
|
-
plotLines: AxisPlotLine[];
|
|
13
12
|
plotBands: AxisPlotBand[];
|
|
13
|
+
plotLines: AxisPlotLine[];
|
|
14
|
+
plotShapes: AxisPlotShape[];
|
|
14
15
|
};
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { getBandsPosition, isBandScale } from './axis/common';
|
|
2
|
-
const PLOT_LINE_HIT_THRESHOLD_PX = 4;
|
|
3
2
|
function getHoveredAxisPlotBands(args) {
|
|
4
3
|
const { pointerPx, plotBands, scale, axis } = args;
|
|
5
4
|
const axisScale = scale;
|
|
@@ -22,16 +21,31 @@ function getHoveredAxisPlotLines(args) {
|
|
|
22
21
|
}
|
|
23
22
|
for (const line of plotLines) {
|
|
24
23
|
const linePx = Number(scale(line.value));
|
|
25
|
-
if (Math.abs(pointerPx - linePx) <=
|
|
24
|
+
if (Math.abs(pointerPx - linePx) <= line.hoverThreshold + line.width / 2) {
|
|
26
25
|
result.push(line);
|
|
27
26
|
}
|
|
28
27
|
}
|
|
29
28
|
return result;
|
|
30
29
|
}
|
|
30
|
+
function getHoveredAxisPlotShapes(args) {
|
|
31
|
+
const { pointerX, pointerY, plotShapes } = args;
|
|
32
|
+
const result = [];
|
|
33
|
+
for (const shape of plotShapes) {
|
|
34
|
+
const left = shape.x + shape.hitbox.x;
|
|
35
|
+
const top = shape.y + shape.hitbox.y;
|
|
36
|
+
const inX = pointerX >= left && pointerX <= left + shape.hitbox.width;
|
|
37
|
+
const inY = pointerY >= top && pointerY <= top + shape.hitbox.height;
|
|
38
|
+
if (inX && inY) {
|
|
39
|
+
result.push(shape);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
31
44
|
export function getHoveredPlots(args) {
|
|
32
45
|
const { pointerX, pointerY, xAxis, yAxis, xScale, yScale } = args;
|
|
33
|
-
const plotLines = [];
|
|
34
46
|
const plotBands = [];
|
|
47
|
+
const plotLines = [];
|
|
48
|
+
const plotShapes = [];
|
|
35
49
|
if (xAxis && xScale) {
|
|
36
50
|
plotBands.push(...getHoveredAxisPlotBands({
|
|
37
51
|
pointerPx: pointerX,
|
|
@@ -44,6 +58,11 @@ export function getHoveredPlots(args) {
|
|
|
44
58
|
plotLines: xAxis.plotLines,
|
|
45
59
|
scale: xScale,
|
|
46
60
|
}));
|
|
61
|
+
plotShapes.push(...getHoveredAxisPlotShapes({
|
|
62
|
+
pointerX,
|
|
63
|
+
pointerY,
|
|
64
|
+
plotShapes: xAxis.plotShapes,
|
|
65
|
+
}));
|
|
47
66
|
}
|
|
48
67
|
for (let i = 0; i < yAxis.length; i++) {
|
|
49
68
|
const yAxisItem = yAxis[i];
|
|
@@ -62,6 +81,11 @@ export function getHoveredPlots(args) {
|
|
|
62
81
|
plotLines: yAxisItem.plotLines,
|
|
63
82
|
scale: yScaleItem,
|
|
64
83
|
}));
|
|
84
|
+
plotShapes.push(...getHoveredAxisPlotShapes({
|
|
85
|
+
pointerX,
|
|
86
|
+
pointerY,
|
|
87
|
+
plotShapes: yAxisItem.plotShapes,
|
|
88
|
+
}));
|
|
65
89
|
}
|
|
66
|
-
return { plotLines,
|
|
90
|
+
return { plotBands, plotLines, plotShapes };
|
|
67
91
|
}
|
|
@@ -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;
|
|
@@ -189,14 +189,28 @@ export function getTextSizeFn({ style }) {
|
|
|
189
189
|
const defaultFontFamily = computedStyle.getPropertyValue('font-family');
|
|
190
190
|
const defaultFontSize = computedStyle.getPropertyValue('font-size');
|
|
191
191
|
const defaultFontWeight = computedStyle.getPropertyValue('font-weight');
|
|
192
|
+
const resolveCSSVar = (value) => {
|
|
193
|
+
const match = value.match(/^var\(\s*([\w-]+)/);
|
|
194
|
+
if (match) {
|
|
195
|
+
return computedStyle.getPropertyValue(match[1]).trim() || value;
|
|
196
|
+
}
|
|
197
|
+
return value;
|
|
198
|
+
};
|
|
192
199
|
return async (str) => {
|
|
193
|
-
var _a, _b;
|
|
194
200
|
await document.fonts.ready;
|
|
195
|
-
|
|
201
|
+
const fontWeight = (style === null || style === void 0 ? void 0 : style.fontWeight)
|
|
202
|
+
? resolveCSSVar(String(style.fontWeight))
|
|
203
|
+
: defaultFontWeight;
|
|
204
|
+
const fontSize = (style === null || style === void 0 ? void 0 : style.fontSize) ? resolveCSSVar(style.fontSize) : defaultFontSize;
|
|
205
|
+
context.font = `${fontWeight} ${fontSize} ${defaultFontFamily}`;
|
|
196
206
|
const textMetric = context.measureText(unescapeHtml(str));
|
|
207
|
+
// we calculate hanging based on an approximate algorithm from chromium
|
|
208
|
+
// 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
|
|
209
|
+
// it would be possible to use native, but the browsers are not working in harmony right now
|
|
197
210
|
return {
|
|
198
211
|
width: textMetric.width,
|
|
199
212
|
height: textMetric.fontBoundingBoxDescent + textMetric.fontBoundingBoxAscent,
|
|
213
|
+
hangingOffset: textMetric.fontBoundingBoxAscent * 0.2,
|
|
200
214
|
};
|
|
201
215
|
};
|
|
202
216
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { TextRowData } from '../components/types';
|
|
2
|
+
import type { ChartBrush, ChartData, ChartMargin, ChartTitle, ChartZoom, DeepRequired } from '../types';
|
|
2
3
|
export type PreparedZoom = DeepRequired<Omit<ChartZoom, 'enabled' | 'brush'>> & DeepRequired<{
|
|
3
4
|
brush: ChartBrush;
|
|
4
5
|
}>;
|
|
@@ -6,8 +7,10 @@ export type PreparedChart = {
|
|
|
6
7
|
margin: ChartMargin;
|
|
7
8
|
zoom: PreparedZoom | null;
|
|
8
9
|
};
|
|
9
|
-
export type PreparedTitle =
|
|
10
|
+
export type PreparedTitle = Omit<ChartTitle, 'margin'> & {
|
|
10
11
|
height: number;
|
|
12
|
+
margin: number;
|
|
13
|
+
contentRows: TextRowData[];
|
|
11
14
|
};
|
|
12
15
|
export type PreparedTooltip = ChartData['tooltip'] & {
|
|
13
16
|
enabled: boolean;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { group } from 'd3-array';
|
|
1
|
+
import { group, min } from 'd3-array';
|
|
2
2
|
import isNil from 'lodash/isNil';
|
|
3
3
|
import round from 'lodash/round';
|
|
4
4
|
import { getDataCategoryValue, getLabelsSize, getTextSizeFn } from '../../../core/utils';
|
|
@@ -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,
|
|
@@ -76,7 +76,7 @@ async function prepareDataLabels({ series, points, xMax, yAxisTop, isOutsideBoun
|
|
|
76
76
|
return { svgLabels, htmlLabels };
|
|
77
77
|
}
|
|
78
78
|
export const prepareAreaData = async (args) => {
|
|
79
|
-
var _a, _b, _c;
|
|
79
|
+
var _a, _b, _c, _d;
|
|
80
80
|
const { series, xAxis, xScale, yAxis, yScale, split, isOutsideBounds, isRangeSlider } = args;
|
|
81
81
|
const [_xMin, xRangeMax] = xScale.range();
|
|
82
82
|
const xMax = xRangeMax;
|
|
@@ -144,12 +144,17 @@ export const prepareAreaData = async (args) => {
|
|
|
144
144
|
continue;
|
|
145
145
|
}
|
|
146
146
|
const yAxisTop = ((_a = split.plots[plotIndex]) === null || _a === void 0 ? void 0 : _a.top) || 0;
|
|
147
|
-
|
|
148
|
-
|
|
147
|
+
let base = 0;
|
|
148
|
+
if (seriesYAxis.type === 'logarithmic') {
|
|
149
|
+
const domainData = seriesYScale.domain();
|
|
150
|
+
base = (_b = min(domainData)) !== null && _b !== void 0 ? _b : 0;
|
|
151
|
+
}
|
|
152
|
+
const yMin = (_c = getYValue({
|
|
153
|
+
point: { y: base },
|
|
149
154
|
points: s.data,
|
|
150
155
|
yAxis: seriesYAxis,
|
|
151
156
|
yScale: seriesYScale,
|
|
152
|
-
})) !== null &&
|
|
157
|
+
})) !== null && _c !== void 0 ? _c : 0;
|
|
153
158
|
const seriesData = s.data.reduce((m, d) => {
|
|
154
159
|
const key = String(xAxis.type === 'category'
|
|
155
160
|
? getDataCategoryValue({
|
|
@@ -309,7 +314,7 @@ export const prepareAreaData = async (args) => {
|
|
|
309
314
|
for (let itemIndex = 0; itemIndex < seriesStackData.length; itemIndex++) {
|
|
310
315
|
const item = seriesStackData[itemIndex];
|
|
311
316
|
const currentYAxis = yAxis[item.series.yAxis];
|
|
312
|
-
const itemYAxisTop = ((
|
|
317
|
+
const itemYAxisTop = ((_d = split.plots[currentYAxis.plotIndex]) === null || _d === void 0 ? void 0 : _d.top) || 0;
|
|
313
318
|
if (item.series.dataLabels.enabled && !isRangeSlider) {
|
|
314
319
|
const labelsData = await prepareDataLabels({
|
|
315
320
|
series: item.series,
|