@gravity-ui/charts 1.13.1 → 1.14.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/Axis/AxisX.d.ts +5 -2
- package/dist/cjs/components/Axis/AxisX.js +28 -11
- package/dist/cjs/components/Axis/AxisY.d.ts +5 -2
- package/dist/cjs/components/Axis/AxisY.js +67 -9
- package/dist/cjs/components/ChartInner/index.js +2 -2
- package/dist/cjs/components/Legend/index.js +5 -4
- package/dist/cjs/components/Tooltip/DefaultTooltipContent/index.js +3 -3
- package/dist/cjs/hooks/useAxisScales/index.js +13 -27
- package/dist/cjs/hooks/useChartOptions/title.js +2 -2
- package/dist/cjs/hooks/useChartOptions/types.d.ts +1 -1
- package/dist/cjs/hooks/useChartOptions/x-axis.js +18 -10
- package/dist/cjs/hooks/useChartOptions/y-axis.js +17 -6
- package/dist/cjs/hooks/useShapes/bar-y/prepare-data.js +4 -3
- package/dist/cjs/hooks/useSplit/index.js +2 -2
- package/dist/cjs/hooks/utils/bar-y.d.ts +0 -1
- package/dist/cjs/hooks/utils/bar-y.js +5 -4
- package/dist/cjs/i18n/keysets/en.json +3 -1
- package/dist/cjs/i18n/keysets/ru.json +3 -1
- package/dist/cjs/types/chart/axis.d.ts +11 -1
- package/dist/cjs/utils/chart/axis-generators/bottom.d.ts +18 -13
- package/dist/cjs/utils/chart/axis-generators/bottom.js +173 -73
- package/dist/cjs/utils/chart/index.d.ts +5 -9
- package/dist/cjs/utils/chart/index.js +18 -9
- package/dist/cjs/validation/index.js +2 -22
- package/dist/cjs/validation/validate-axes.d.ts +5 -0
- package/dist/cjs/validation/validate-axes.js +46 -0
- package/dist/esm/components/Axis/AxisX.d.ts +5 -2
- package/dist/esm/components/Axis/AxisX.js +28 -11
- package/dist/esm/components/Axis/AxisY.d.ts +5 -2
- package/dist/esm/components/Axis/AxisY.js +67 -9
- package/dist/esm/components/ChartInner/index.js +2 -2
- package/dist/esm/components/Legend/index.js +5 -4
- package/dist/esm/components/Tooltip/DefaultTooltipContent/index.js +3 -3
- package/dist/esm/hooks/useAxisScales/index.js +13 -27
- package/dist/esm/hooks/useChartOptions/title.js +2 -2
- package/dist/esm/hooks/useChartOptions/types.d.ts +1 -1
- package/dist/esm/hooks/useChartOptions/x-axis.js +18 -10
- package/dist/esm/hooks/useChartOptions/y-axis.js +17 -6
- package/dist/esm/hooks/useShapes/bar-y/prepare-data.js +4 -3
- package/dist/esm/hooks/useSplit/index.js +2 -2
- package/dist/esm/hooks/utils/bar-y.d.ts +0 -1
- package/dist/esm/hooks/utils/bar-y.js +5 -4
- package/dist/esm/i18n/keysets/en.json +3 -1
- package/dist/esm/i18n/keysets/ru.json +3 -1
- package/dist/esm/types/chart/axis.d.ts +11 -1
- package/dist/esm/utils/chart/axis-generators/bottom.d.ts +18 -13
- package/dist/esm/utils/chart/axis-generators/bottom.js +173 -73
- package/dist/esm/utils/chart/index.d.ts +5 -9
- package/dist/esm/utils/chart/index.js +18 -9
- package/dist/esm/validation/index.js +2 -22
- package/dist/esm/validation/validate-axes.d.ts +5 -0
- package/dist/esm/validation/validate-axes.js +46 -0
- package/package.json +1 -1
|
@@ -3,13 +3,16 @@ import type { ChartScale, PreparedAxis, PreparedSplit } from '../../hooks';
|
|
|
3
3
|
import './styles.css';
|
|
4
4
|
type Props = {
|
|
5
5
|
axis: PreparedAxis;
|
|
6
|
-
|
|
6
|
+
boundsOffsetLeft: number;
|
|
7
|
+
boundsOffsetTop: number;
|
|
7
8
|
height: number;
|
|
9
|
+
htmlLayout: HTMLElement | null;
|
|
8
10
|
scale: ChartScale;
|
|
9
11
|
split: PreparedSplit;
|
|
12
|
+
width: number;
|
|
13
|
+
leftmostLimit?: number;
|
|
10
14
|
plotBeforeRef?: React.MutableRefObject<SVGGElement | null>;
|
|
11
15
|
plotAfterRef?: React.MutableRefObject<SVGGElement | null>;
|
|
12
|
-
leftmostLimit?: number;
|
|
13
16
|
};
|
|
14
17
|
export declare function getTitlePosition(args: {
|
|
15
18
|
axis: PreparedAxis;
|
|
@@ -42,11 +42,11 @@ export function getTitlePosition(args) {
|
|
|
42
42
|
return { x, y };
|
|
43
43
|
}
|
|
44
44
|
export const AxisX = React.memo(function AxisX(props) {
|
|
45
|
-
const { axis,
|
|
45
|
+
const { axis, boundsOffsetLeft, boundsOffsetTop, height: totalHeight, htmlLayout, leftmostLimit, plotAfterRef, plotBeforeRef, split, scale, width, } = props;
|
|
46
46
|
const ref = React.useRef(null);
|
|
47
47
|
React.useEffect(() => {
|
|
48
48
|
(async () => {
|
|
49
|
-
if (!ref.current) {
|
|
49
|
+
if (!ref.current || !htmlLayout) {
|
|
50
50
|
return;
|
|
51
51
|
}
|
|
52
52
|
const svgElement = select(ref.current);
|
|
@@ -65,24 +65,29 @@ export const AxisX = React.memo(function AxisX(props) {
|
|
|
65
65
|
}
|
|
66
66
|
const axisScale = scale;
|
|
67
67
|
const xAxisGenerator = await axisBottom({
|
|
68
|
+
boundsOffsetLeft,
|
|
69
|
+
boundsOffsetTop,
|
|
70
|
+
domain: {
|
|
71
|
+
size: width,
|
|
72
|
+
color: axis.lineColor,
|
|
73
|
+
},
|
|
74
|
+
htmlLayout,
|
|
68
75
|
leftmostLimit,
|
|
69
76
|
scale: axisScale,
|
|
70
77
|
ticks: {
|
|
78
|
+
count: getTicksCount({ axis, range: width }),
|
|
79
|
+
labelsHtml: axis.labels.html,
|
|
71
80
|
items: tickItems,
|
|
72
81
|
labelFormat: getLabelFormatter({ axis, scale }),
|
|
73
|
-
|
|
82
|
+
labelsHeight: axis.labels.height,
|
|
83
|
+
labelsLineHeight: axis.labels.lineHeight,
|
|
74
84
|
labelsMargin: axis.labels.margin,
|
|
75
|
-
labelsStyle: axis.labels.style,
|
|
76
85
|
labelsMaxWidth: axis.labels.maxWidth,
|
|
77
|
-
|
|
78
|
-
|
|
86
|
+
labelsPaddings: axis.labels.padding,
|
|
87
|
+
labelsStyle: axis.labels.style,
|
|
79
88
|
maxTickCount: getMaxTickCount({ axis, width }),
|
|
80
89
|
rotation: axis.labels.rotation,
|
|
81
90
|
},
|
|
82
|
-
domain: {
|
|
83
|
-
size: width,
|
|
84
|
-
color: axis.lineColor,
|
|
85
|
-
},
|
|
86
91
|
});
|
|
87
92
|
svgElement.call(xAxisGenerator).attr('class', b());
|
|
88
93
|
// add an axis header if necessary
|
|
@@ -219,6 +224,18 @@ export const AxisX = React.memo(function AxisX(props) {
|
|
|
219
224
|
setPlotLines(plotAfterContainer, axis.plotLines.filter((d) => d.layerPlacement === 'after'));
|
|
220
225
|
}
|
|
221
226
|
})();
|
|
222
|
-
}, [
|
|
227
|
+
}, [
|
|
228
|
+
axis,
|
|
229
|
+
boundsOffsetLeft,
|
|
230
|
+
boundsOffsetTop,
|
|
231
|
+
htmlLayout,
|
|
232
|
+
leftmostLimit,
|
|
233
|
+
plotAfterRef,
|
|
234
|
+
plotBeforeRef,
|
|
235
|
+
scale,
|
|
236
|
+
split,
|
|
237
|
+
totalHeight,
|
|
238
|
+
width,
|
|
239
|
+
]);
|
|
223
240
|
return React.createElement("g", { ref: ref });
|
|
224
241
|
});
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { ChartScale, PreparedAxis, PreparedSplit } from '../../hooks';
|
|
3
3
|
import './styles.css';
|
|
4
|
-
|
|
4
|
+
interface Props {
|
|
5
5
|
axes: PreparedAxis[];
|
|
6
|
+
boundsOffsetTop: number;
|
|
7
|
+
boundsOffsetLeft: number;
|
|
6
8
|
scale: ChartScale[];
|
|
7
9
|
width: number;
|
|
8
10
|
height: number;
|
|
11
|
+
htmlLayout: HTMLElement | null;
|
|
9
12
|
split: PreparedSplit;
|
|
10
13
|
plotBeforeRef?: React.MutableRefObject<SVGGElement | null>;
|
|
11
14
|
plotAfterRef?: React.MutableRefObject<SVGGElement | null>;
|
|
12
15
|
bottomLimit?: number;
|
|
13
16
|
topLimit?: number;
|
|
14
|
-
}
|
|
17
|
+
}
|
|
15
18
|
export declare const AxisY: (props: Props) => React.JSX.Element;
|
|
16
19
|
export {};
|
|
@@ -3,6 +3,8 @@ import { axisLeft, axisRight, line, select } from 'd3';
|
|
|
3
3
|
import { block, calculateCos, calculateSin, formatAxisTickLabel, getAxisHeight, getAxisPlotsPosition, getAxisTitleRows, getBandsPosition, getClosestPointsRange, getLineDashArray, getScaleTicks, getTicksCount, handleOverflowingText, parseTransformStyle, setEllipsisForOverflowTexts, wrapText, } from '../../utils';
|
|
4
4
|
import './styles.css';
|
|
5
5
|
const b = block('axis');
|
|
6
|
+
const AXIS_LEFT_HTML_LABELS_DATA_ATTR = 'data-axis-left-html-labels';
|
|
7
|
+
const AXIS_RIGHT_HTML_LABELS_DATA_ATTR = 'data-axis-right-html-labels';
|
|
6
8
|
function transformLabel(args) {
|
|
7
9
|
const { node, axis, startTopOffset } = args;
|
|
8
10
|
let topOffset = startTopOffset !== null && startTopOffset !== void 0 ? startTopOffset : axis.labels.lineHeight / 2;
|
|
@@ -36,7 +38,7 @@ function getAxisGenerator(args) {
|
|
|
36
38
|
.tickSize(tickSize)
|
|
37
39
|
.tickPadding(preparedAxis.labels.margin)
|
|
38
40
|
.tickFormat((value) => {
|
|
39
|
-
if (!preparedAxis.labels.enabled) {
|
|
41
|
+
if (!preparedAxis.labels.enabled || preparedAxis.labels.html) {
|
|
40
42
|
return '';
|
|
41
43
|
}
|
|
42
44
|
return formatAxisTickLabel({
|
|
@@ -82,17 +84,20 @@ function getTitlePosition(args) {
|
|
|
82
84
|
return { x, y };
|
|
83
85
|
}
|
|
84
86
|
export const AxisY = (props) => {
|
|
85
|
-
const { axes: allAxes,
|
|
87
|
+
const { axes: allAxes, bottomLimit = 0, boundsOffsetLeft, boundsOffsetTop, height: totalHeight, htmlLayout, plotAfterRef, plotBeforeRef, scale, split, topLimit = 0, width, } = props;
|
|
86
88
|
const height = getAxisHeight({ split, boundsHeight: totalHeight });
|
|
87
89
|
const ref = React.useRef(null);
|
|
88
90
|
const lineGenerator = line();
|
|
89
91
|
React.useEffect(() => {
|
|
90
|
-
if (!ref.current) {
|
|
92
|
+
if (!ref.current || !htmlLayout) {
|
|
91
93
|
return;
|
|
92
94
|
}
|
|
93
95
|
const axes = allAxes.filter((a) => a.visible);
|
|
94
96
|
const svgElement = select(ref.current);
|
|
95
97
|
svgElement.selectAll('*').remove();
|
|
98
|
+
const htmlSelection = select(htmlLayout);
|
|
99
|
+
htmlSelection.selectAll(`[${AXIS_LEFT_HTML_LABELS_DATA_ATTR}]`).remove();
|
|
100
|
+
htmlSelection.selectAll(`[${AXIS_RIGHT_HTML_LABELS_DATA_ATTR}]`).remove();
|
|
96
101
|
let plotBeforeContainer = null;
|
|
97
102
|
let plotAfterContainer = null;
|
|
98
103
|
const plotDataAttr = 'data-plot-y';
|
|
@@ -111,6 +116,7 @@ export const AxisY = (props) => {
|
|
|
111
116
|
.attr('class', b())
|
|
112
117
|
.style('transform', (d) => getAxisPlotsPosition(d, split, width));
|
|
113
118
|
axisSelection.each((d, index, node) => {
|
|
119
|
+
var _a, _b;
|
|
114
120
|
const seriesScale = scale[index];
|
|
115
121
|
const axisItem = select(node[index]);
|
|
116
122
|
const axisScale = seriesScale;
|
|
@@ -125,7 +131,56 @@ export const AxisY = (props) => {
|
|
|
125
131
|
// because the standard generator interrupts the desired font
|
|
126
132
|
// https://github.com/d3/d3-axis/blob/main/src/axis.js#L110
|
|
127
133
|
axisItem.attr('font-family', null);
|
|
128
|
-
if (d.labels.enabled) {
|
|
134
|
+
if (d.labels.enabled && d.labels.html) {
|
|
135
|
+
const offsetTop = ((_a = svgElement.node()) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect().top) || 0;
|
|
136
|
+
const offsetLeft = ((_b = svgElement.node()) === null || _b === void 0 ? void 0 : _b.getBoundingClientRect().left) || 0;
|
|
137
|
+
const htmlLabelsData = [];
|
|
138
|
+
axisItem
|
|
139
|
+
.selectAll('.tick')
|
|
140
|
+
.data(axisScale.domain())
|
|
141
|
+
.each(function (tickContent) {
|
|
142
|
+
const rect = this.getBoundingClientRect();
|
|
143
|
+
htmlLabelsData.push({
|
|
144
|
+
content: tickContent,
|
|
145
|
+
right: rect.right,
|
|
146
|
+
top: rect.top,
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
const dataAttr = d.position === 'left'
|
|
150
|
+
? AXIS_LEFT_HTML_LABELS_DATA_ATTR
|
|
151
|
+
: AXIS_RIGHT_HTML_LABELS_DATA_ATTR;
|
|
152
|
+
htmlSelection.append('div').attr(dataAttr, 1).style('position', 'absolute');
|
|
153
|
+
htmlLabelsData.forEach((labelData) => {
|
|
154
|
+
htmlSelection
|
|
155
|
+
.selectAll(`[${dataAttr}]`)
|
|
156
|
+
.data([labelData])
|
|
157
|
+
.append('div')
|
|
158
|
+
.html(function (l) {
|
|
159
|
+
return l.content;
|
|
160
|
+
})
|
|
161
|
+
.style('font-size', d.labels.style.fontSize || '')
|
|
162
|
+
.style('position', 'absolute')
|
|
163
|
+
.style('white-space', 'nowrap')
|
|
164
|
+
.style('color', 'var(--g-color-text-secondary)')
|
|
165
|
+
.style('overflow', 'hidden')
|
|
166
|
+
.style('text-overflow', 'ellipsis')
|
|
167
|
+
.style('height', `${d.labels.height}px`)
|
|
168
|
+
.style('display', 'inline-flex')
|
|
169
|
+
.style('align-items', 'center')
|
|
170
|
+
.style('left', function (l) {
|
|
171
|
+
if (d.position === 'right') {
|
|
172
|
+
return `${l.right - offsetLeft + d.labels.margin + boundsOffsetLeft}px`;
|
|
173
|
+
}
|
|
174
|
+
const rect = this.getBoundingClientRect();
|
|
175
|
+
return `${boundsOffsetLeft - rect.width - d.labels.margin}px`;
|
|
176
|
+
})
|
|
177
|
+
.style('top', function (l) {
|
|
178
|
+
const rect = this.getBoundingClientRect();
|
|
179
|
+
return `${l.top + boundsOffsetTop - offsetTop - rect.height / 2}px`;
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
else if (d.labels.enabled) {
|
|
129
184
|
const labels = axisItem.selectAll('.tick text');
|
|
130
185
|
const tickTexts = labels
|
|
131
186
|
// The offset must be applied before the labels are rotated.
|
|
@@ -344,15 +399,18 @@ export const AxisY = (props) => {
|
|
|
344
399
|
});
|
|
345
400
|
}, [
|
|
346
401
|
allAxes,
|
|
347
|
-
width,
|
|
348
|
-
height,
|
|
349
|
-
scale,
|
|
350
|
-
split,
|
|
351
402
|
bottomLimit,
|
|
403
|
+
boundsOffsetLeft,
|
|
404
|
+
boundsOffsetTop,
|
|
405
|
+
height,
|
|
406
|
+
htmlLayout,
|
|
352
407
|
lineGenerator,
|
|
353
|
-
plotBeforeRef,
|
|
354
408
|
plotAfterRef,
|
|
409
|
+
plotBeforeRef,
|
|
410
|
+
scale,
|
|
411
|
+
split,
|
|
355
412
|
topLimit,
|
|
413
|
+
width,
|
|
356
414
|
]);
|
|
357
415
|
return React.createElement("g", { ref: ref, className: b('container') });
|
|
358
416
|
};
|
|
@@ -94,9 +94,9 @@ export const ChartInner = (props) => {
|
|
|
94
94
|
})),
|
|
95
95
|
React.createElement("g", { width: boundsWidth, height: boundsHeight, transform: `translate(${[boundsOffsetLeft, boundsOffsetTop].join(',')})`, ref: plotRef },
|
|
96
96
|
xScale && (yScale === null || yScale === void 0 ? void 0 : yScale.length) && (React.createElement(React.Fragment, null,
|
|
97
|
-
React.createElement(AxisY, {
|
|
97
|
+
React.createElement(AxisY, { axes: yAxis, bottomLimit: svgBottomPos, boundsOffsetLeft: boundsOffsetLeft, boundsOffsetTop: boundsOffsetTop, height: boundsHeight, htmlLayout: htmlLayout, plotAfterRef: plotAfterRef, plotBeforeRef: plotBeforeRef, scale: yScale, split: preparedSplit, topLimit: svgTopPos, width: boundsWidth }),
|
|
98
98
|
xAxis && (React.createElement("g", { transform: `translate(0, ${boundsHeight})` },
|
|
99
|
-
React.createElement(AxisX, {
|
|
99
|
+
React.createElement(AxisX, { axis: xAxis, boundsOffsetLeft: boundsOffsetLeft, boundsOffsetTop: boundsOffsetTop, height: boundsHeight, htmlLayout: htmlLayout, leftmostLimit: svgXPos, plotAfterRef: plotAfterRef, plotBeforeRef: plotBeforeRef, scale: xScale, split: preparedSplit, width: boundsWidth }))))),
|
|
100
100
|
React.createElement("g", { ref: plotBeforeRef }),
|
|
101
101
|
shapes,
|
|
102
102
|
React.createElement("g", { ref: plotAfterRef })),
|
|
@@ -297,6 +297,11 @@ export const Legend = (props) => {
|
|
|
297
297
|
// ticks
|
|
298
298
|
const scale = scaleLinear(domain, [0, legend.width]);
|
|
299
299
|
const xAxisGenerator = await axisBottom({
|
|
300
|
+
domain: {
|
|
301
|
+
size: legend.width,
|
|
302
|
+
color: 'transparent',
|
|
303
|
+
},
|
|
304
|
+
htmlLayout,
|
|
300
305
|
scale,
|
|
301
306
|
ticks: {
|
|
302
307
|
items: [[0, -rectHeight]],
|
|
@@ -307,10 +312,6 @@ export const Legend = (props) => {
|
|
|
307
312
|
labelFormat: (value) => formatNumber(value, { unit: 'auto' }),
|
|
308
313
|
labelsStyle: legend.ticks.style,
|
|
309
314
|
},
|
|
310
|
-
domain: {
|
|
311
|
-
size: legend.width,
|
|
312
|
-
color: 'transparent',
|
|
313
|
-
},
|
|
314
315
|
});
|
|
315
316
|
const tickTop = legend.title.height + legend.title.margin + rectHeight;
|
|
316
317
|
const legendAxisClassname = b('axis');
|
|
@@ -11,7 +11,7 @@ export const DefaultTooltipContent = ({ hovered, xAxis, yAxis, valueFormat, tota
|
|
|
11
11
|
const measureValue = getMeasureValue({ data: hovered, xAxis, yAxis });
|
|
12
12
|
const hoveredValues = getHoveredValues({ hovered, xAxis, yAxis });
|
|
13
13
|
return (React.createElement("div", { className: b('content') },
|
|
14
|
-
measureValue && React.createElement("div", { className: b('series-name') }
|
|
14
|
+
measureValue && (React.createElement("div", { className: b('series-name'), dangerouslySetInnerHTML: { __html: measureValue } })),
|
|
15
15
|
// eslint-disable-next-line complexity
|
|
16
16
|
hovered.map((seriesItem, i) => {
|
|
17
17
|
var _a;
|
|
@@ -30,7 +30,7 @@ export const DefaultTooltipContent = ({ hovered, xAxis, yAxis, valueFormat, tota
|
|
|
30
30
|
value: hoveredValues[i],
|
|
31
31
|
format,
|
|
32
32
|
});
|
|
33
|
-
return (React.createElement(Row, { key: id, active: active, color: color, label: series.name, striped: striped, value: formattedValue }));
|
|
33
|
+
return (React.createElement(Row, { key: id, active: active, color: color, label: React.createElement("span", { dangerouslySetInnerHTML: { __html: series.name } }), striped: striped, value: formattedValue }));
|
|
34
34
|
}
|
|
35
35
|
case 'waterfall': {
|
|
36
36
|
const isTotal = get(data, 'total', false);
|
|
@@ -56,7 +56,7 @@ export const DefaultTooltipContent = ({ hovered, xAxis, yAxis, valueFormat, tota
|
|
|
56
56
|
value: hoveredValues[i],
|
|
57
57
|
format,
|
|
58
58
|
});
|
|
59
|
-
return (React.createElement(Row, { key: id, active: active, color: color, label: series.name, striped: striped, value: formattedValue }));
|
|
59
|
+
return (React.createElement(Row, { key: id, active: active, color: color, label: React.createElement("span", { dangerouslySetInnerHTML: { __html: series.name } }), striped: striped, value: formattedValue }));
|
|
60
60
|
}
|
|
61
61
|
case 'pie':
|
|
62
62
|
case 'treemap': {
|
|
@@ -29,39 +29,25 @@ function getYScaleRange(args) {
|
|
|
29
29
|
case 'linear':
|
|
30
30
|
case 'logarithmic': {
|
|
31
31
|
let range = [boundsHeight, boundsHeight * axis.maxPadding];
|
|
32
|
-
switch (axis.order) {
|
|
33
|
-
case 'sortDesc':
|
|
34
|
-
case 'reverse': {
|
|
35
|
-
range.reverse();
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
32
|
const barYSeries = series.filter((s) => s.type === SeriesType.BarY);
|
|
39
33
|
if (barYSeries.length) {
|
|
40
34
|
const groupedData = groupBarYDataByYValue(barYSeries, [axis]);
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
const offsetMultiplier = barYSeries.reduce((acc, s) => {
|
|
49
|
-
let count = 0;
|
|
50
|
-
if (s.stackId) {
|
|
51
|
-
if (!alreadyCountedStackingIds.has(s.stackId)) {
|
|
52
|
-
alreadyCountedStackingIds.add(s.stackId);
|
|
53
|
-
count = 1;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
else {
|
|
57
|
-
count = 1;
|
|
58
|
-
}
|
|
59
|
-
return acc + count;
|
|
60
|
-
}, 0);
|
|
61
|
-
const offset = (barSize * Math.max(offsetMultiplier, 1)) / 2;
|
|
35
|
+
if (Object.keys(groupedData).length > 1) {
|
|
36
|
+
const { bandSize } = getBarYLayoutForNumericScale({
|
|
37
|
+
plotHeight: boundsHeight - boundsHeight * axis.maxPadding,
|
|
38
|
+
groupedData,
|
|
39
|
+
seriesOptions: seriesOptions,
|
|
40
|
+
});
|
|
41
|
+
const offset = bandSize / 2;
|
|
62
42
|
range = [range[0] - offset, range[1] + offset];
|
|
63
43
|
}
|
|
64
44
|
}
|
|
45
|
+
switch (axis.order) {
|
|
46
|
+
case 'sortDesc':
|
|
47
|
+
case 'reverse': {
|
|
48
|
+
range.reverse();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
65
51
|
return range;
|
|
66
52
|
}
|
|
67
53
|
case 'category': {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import get from 'lodash/get';
|
|
2
|
-
import {
|
|
2
|
+
import { getHorizontalSvgTextHeight } from '../../utils';
|
|
3
3
|
const DEFAULT_TITLE_FONT_SIZE = '15px';
|
|
4
4
|
const TITLE_PADDINGS = 8 * 2;
|
|
5
5
|
export const getPreparedTitle = ({ title, }) => {
|
|
@@ -9,7 +9,7 @@ export const getPreparedTitle = ({ title, }) => {
|
|
|
9
9
|
fontWeight: get(title, 'style.fontWeight'),
|
|
10
10
|
};
|
|
11
11
|
const titleHeight = titleText
|
|
12
|
-
?
|
|
12
|
+
? getHorizontalSvgTextHeight({ text: titleText, style: titleStyle }) + TITLE_PADDINGS
|
|
13
13
|
: 0;
|
|
14
14
|
const preparedTitle = titleText
|
|
15
15
|
? { text: titleText, style: titleStyle, height: titleHeight }
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { DashStyle } from '../../constants';
|
|
2
2
|
import type { AxisCrosshair, AxisPlotBand, BaseTextStyle, ChartAxis, ChartAxisLabels, ChartAxisTitleAlignment, ChartAxisType, ChartData, ChartMargin, ChartZoom, DeepRequired, PlotLayerPlacement } from '../../types';
|
|
3
|
-
type PreparedAxisLabels = Omit<ChartAxisLabels, 'enabled' | 'padding' | 'style' | 'autoRotation'> & Required<Pick<ChartAxisLabels, 'enabled' | 'padding' | 'margin' | 'rotation'>> & {
|
|
3
|
+
type PreparedAxisLabels = Omit<ChartAxisLabels, 'enabled' | 'padding' | 'style' | 'autoRotation'> & Required<Pick<ChartAxisLabels, 'enabled' | 'padding' | 'margin' | 'rotation' | 'html'>> & {
|
|
4
4
|
style: BaseTextStyle;
|
|
5
5
|
rotation: number;
|
|
6
6
|
height: number;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import get from 'lodash/get';
|
|
2
2
|
import { DASH_STYLE, DEFAULT_AXIS_LABEL_FONT_SIZE, axisCrosshairDefaults, axisLabelsDefaults, xAxisTitleDefaults, } from '../../constants';
|
|
3
|
-
import { calculateCos, formatAxisTickLabel, getClosestPointsRange,
|
|
3
|
+
import { calculateCos, formatAxisTickLabel, getClosestPointsRange, getHorizontalHtmlTextHeight, getHorizontalSvgTextHeight, getLabelsSize, getMaxTickCount, getTicksCount, getXAxisItems, hasOverlappingLabels, wrapText, } from '../../utils';
|
|
4
4
|
import { createXScale } from '../useAxisScales';
|
|
5
5
|
import { getAxisCategories, prepareAxisPlotLabel } from './utils';
|
|
6
6
|
async function getLabelSettings({ axis, seriesData, width, autoRotation = true, }) {
|
|
@@ -19,19 +19,22 @@ async function getLabelSettings({ axis, seriesData, width, autoRotation = true,
|
|
|
19
19
|
step,
|
|
20
20
|
});
|
|
21
21
|
});
|
|
22
|
-
const overlapping =
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
const overlapping = axis.labels.html
|
|
23
|
+
? false
|
|
24
|
+
: hasOverlappingLabels({
|
|
25
|
+
width,
|
|
26
|
+
labels,
|
|
27
|
+
padding: axis.labels.padding,
|
|
28
|
+
style: axis.labels.style,
|
|
29
|
+
});
|
|
28
30
|
const defaultRotation = overlapping && autoRotation ? -45 : 0;
|
|
29
|
-
const rotation = axis.labels.rotation || defaultRotation;
|
|
30
|
-
const labelsHeight = rotation
|
|
31
|
+
const rotation = axis.labels.html ? 0 : axis.labels.rotation || defaultRotation;
|
|
32
|
+
const labelsHeight = rotation || axis.labels.html
|
|
31
33
|
? (await getLabelsSize({
|
|
32
34
|
labels,
|
|
33
35
|
style: axis.labels.style,
|
|
34
36
|
rotation,
|
|
37
|
+
html: axis.labels.html,
|
|
35
38
|
})).maxHeight
|
|
36
39
|
: axis.labels.lineHeight;
|
|
37
40
|
const maxHeight = rotation ? calculateCos(rotation) * axis.labels.maxWidth : labelsHeight;
|
|
@@ -54,6 +57,10 @@ export const getPreparedXAxis = async ({ xAxis, seriesData, width, }) => {
|
|
|
54
57
|
const labelsStyle = {
|
|
55
58
|
fontSize: get(xAxis, 'labels.style.fontSize', DEFAULT_AXIS_LABEL_FONT_SIZE),
|
|
56
59
|
};
|
|
60
|
+
const labelsHtml = get(xAxis, 'labels.html', false);
|
|
61
|
+
const labelsLineHeight = labelsHtml
|
|
62
|
+
? getHorizontalHtmlTextHeight({ text: 'Tmp', style: labelsStyle })
|
|
63
|
+
: getHorizontalSvgTextHeight({ text: 'Tmp', style: labelsStyle });
|
|
57
64
|
const preparedXAxis = {
|
|
58
65
|
type: get(xAxis, 'type', 'linear'),
|
|
59
66
|
labels: {
|
|
@@ -66,8 +73,9 @@ export const getPreparedXAxis = async ({ xAxis, seriesData, width, }) => {
|
|
|
66
73
|
style: labelsStyle,
|
|
67
74
|
width: 0,
|
|
68
75
|
height: 0,
|
|
69
|
-
lineHeight:
|
|
76
|
+
lineHeight: labelsLineHeight,
|
|
70
77
|
maxWidth: get(xAxis, 'labels.maxWidth', axisLabelsDefaults.maxWidth),
|
|
78
|
+
html: labelsHtml,
|
|
71
79
|
},
|
|
72
80
|
lineColor: get(xAxis, 'lineColor'),
|
|
73
81
|
categories: getAxisCategories(xAxis),
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import get from 'lodash/get';
|
|
2
2
|
import { DASH_STYLE, DEFAULT_AXIS_LABEL_FONT_SIZE, DEFAULT_AXIS_TYPE, axisCrosshairDefaults, axisLabelsDefaults, yAxisTitleDefaults, } from '../../constants';
|
|
3
|
-
import { formatAxisTickLabel, getClosestPointsRange, getDefaultMinYAxisValue,
|
|
3
|
+
import { formatAxisTickLabel, getClosestPointsRange, getDefaultMinYAxisValue, getHorizontalHtmlTextHeight, getHorizontalSvgTextHeight, getLabelsSize, getScaleTicks, isAxisRelatedSeries, wrapText, } from '../../utils';
|
|
4
4
|
import { createYScale } from '../useAxisScales';
|
|
5
5
|
import { getAxisCategories, prepareAxisPlotLabel } from './utils';
|
|
6
6
|
const getAxisLabelMaxWidth = async (args) => {
|
|
7
7
|
const { axis, seriesData, seriesOptions } = args;
|
|
8
8
|
if (!axis.labels.enabled) {
|
|
9
|
-
return 0;
|
|
9
|
+
return { height: 0, width: 0 };
|
|
10
10
|
}
|
|
11
11
|
const scale = createYScale({
|
|
12
12
|
axis,
|
|
@@ -26,8 +26,9 @@ const getAxisLabelMaxWidth = async (args) => {
|
|
|
26
26
|
labels,
|
|
27
27
|
style: axis.labels.style,
|
|
28
28
|
rotation: axis.labels.rotation,
|
|
29
|
+
html: axis.labels.html,
|
|
29
30
|
});
|
|
30
|
-
return size.maxWidth;
|
|
31
|
+
return { height: size.maxHeight, width: size.maxWidth };
|
|
31
32
|
};
|
|
32
33
|
export const getPreparedYAxis = ({ height, seriesData, seriesOptions, yAxis, }) => {
|
|
33
34
|
const axisByPlot = [];
|
|
@@ -49,6 +50,10 @@ export const getPreparedYAxis = ({ height, seriesData, seriesOptions, yAxis, })
|
|
|
49
50
|
const labelsStyle = {
|
|
50
51
|
fontSize: get(axisItem, 'labels.style.fontSize', DEFAULT_AXIS_LABEL_FONT_SIZE),
|
|
51
52
|
};
|
|
53
|
+
const labelsHtml = get(axisItem, 'labels.html', false);
|
|
54
|
+
const labelsLineHeight = labelsHtml
|
|
55
|
+
? getHorizontalHtmlTextHeight({ text: 'Tmp', style: labelsStyle })
|
|
56
|
+
: getHorizontalSvgTextHeight({ text: 'Tmp', style: labelsStyle });
|
|
52
57
|
const titleText = get(axisItem, 'title.text', '');
|
|
53
58
|
const titleStyle = Object.assign(Object.assign({}, yAxisTitleDefaults.style), get(axisItem, 'title.style'));
|
|
54
59
|
const titleMaxRowsCount = get(axisItem, 'title.maxRowCount', yAxisTitleDefaults.maxRowCount);
|
|
@@ -72,11 +77,12 @@ export const getPreparedYAxis = ({ height, seriesData, seriesOptions, yAxis, })
|
|
|
72
77
|
dateFormat: get(axisItem, 'labels.dateFormat'),
|
|
73
78
|
numberFormat: get(axisItem, 'labels.numberFormat'),
|
|
74
79
|
style: labelsStyle,
|
|
75
|
-
rotation: get(axisItem, 'labels.rotation', 0),
|
|
80
|
+
rotation: labelsHtml ? 0 : get(axisItem, 'labels.rotation', 0),
|
|
76
81
|
width: 0,
|
|
77
82
|
height: 0,
|
|
78
|
-
lineHeight:
|
|
83
|
+
lineHeight: labelsLineHeight,
|
|
79
84
|
maxWidth: get(axisItem, 'labels.maxWidth', axisLabelsDefaults.maxWidth),
|
|
85
|
+
html: labelsHtml,
|
|
80
86
|
},
|
|
81
87
|
lineColor: get(axisItem, 'lineColor'),
|
|
82
88
|
categories: getAxisCategories(axisItem),
|
|
@@ -132,11 +138,16 @@ export const getPreparedYAxis = ({ height, seriesData, seriesOptions, yAxis, })
|
|
|
132
138
|
order: axisItem.order,
|
|
133
139
|
};
|
|
134
140
|
if (labelsEnabled) {
|
|
135
|
-
|
|
141
|
+
const { height: labelsHeight, width: labelsWidth } = await getAxisLabelMaxWidth({
|
|
136
142
|
axis: preparedAxis,
|
|
137
143
|
seriesData,
|
|
138
144
|
seriesOptions,
|
|
139
145
|
});
|
|
146
|
+
preparedAxis.labels.height = labelsHeight;
|
|
147
|
+
preparedAxis.labels.width =
|
|
148
|
+
labelsWidth > preparedAxis.labels.maxWidth
|
|
149
|
+
? preparedAxis.labels.maxWidth
|
|
150
|
+
: labelsWidth;
|
|
140
151
|
}
|
|
141
152
|
return preparedAxis;
|
|
142
153
|
}));
|
|
@@ -10,7 +10,8 @@ export const prepareBarYData = async (args) => {
|
|
|
10
10
|
const stackGap = seriesOptions['bar-y'].stackGap;
|
|
11
11
|
const xLinearScale = xScale;
|
|
12
12
|
const yLinearScale = yScale;
|
|
13
|
-
const
|
|
13
|
+
const yScaleRange = yLinearScale.range();
|
|
14
|
+
const plotHeight = Math.abs(yScaleRange[0] - yScaleRange[1]);
|
|
14
15
|
const sortingOptions = get(seriesOptions, 'bar-y.dataSorting');
|
|
15
16
|
const comparator = (sortingOptions === null || sortingOptions === void 0 ? void 0 : sortingOptions.direction) === 'desc' ? descending : ascending;
|
|
16
17
|
const sortKey = (() => {
|
|
@@ -40,7 +41,7 @@ export const prepareBarYData = async (args) => {
|
|
|
40
41
|
const stacks = Object.values(val);
|
|
41
42
|
const currentBarHeight = barSize * stacks.length + barGap * (stacks.length - 1);
|
|
42
43
|
stacks.forEach((measureValues, groupItemIndex) => {
|
|
43
|
-
const base = xLinearScale(0 - measureValues[0].series.borderWidth
|
|
44
|
+
const base = xLinearScale(0) - measureValues[0].series.borderWidth;
|
|
44
45
|
let stackSum = base;
|
|
45
46
|
const stackItems = [];
|
|
46
47
|
const sortedData = sortKey
|
|
@@ -85,7 +86,7 @@ export const prepareBarYData = async (args) => {
|
|
|
85
86
|
height: barSize,
|
|
86
87
|
color: data.color || s.color,
|
|
87
88
|
borderColor: s.borderColor,
|
|
88
|
-
borderWidth: s.borderWidth,
|
|
89
|
+
borderWidth: barSize > s.borderWidth * 2 ? s.borderWidth : 0,
|
|
89
90
|
opacity: get(data, 'opacity', null),
|
|
90
91
|
data,
|
|
91
92
|
series: s,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import get from 'lodash/get';
|
|
3
|
-
import { calculateNumericProperty,
|
|
3
|
+
import { calculateNumericProperty, getHorizontalSvgTextHeight } from '../../utils';
|
|
4
4
|
const DEFAULT_TITLE_FONT_SIZE = '15px';
|
|
5
5
|
const TITLE_TOP_BOTTOM_PADDING = 8;
|
|
6
6
|
function preparePlotTitle(args) {
|
|
@@ -11,7 +11,7 @@ function preparePlotTitle(args) {
|
|
|
11
11
|
fontWeight: get(title, 'style.fontWeight'),
|
|
12
12
|
};
|
|
13
13
|
const titleHeight = titleText
|
|
14
|
-
?
|
|
14
|
+
? getHorizontalSvgTextHeight({ text: titleText, style: titleStyle }) +
|
|
15
15
|
TITLE_TOP_BOTTOM_PADDING * 2
|
|
16
16
|
: 0;
|
|
17
17
|
const top = plotIndex * (plotHeight + gap);
|
|
@@ -14,7 +14,6 @@ export declare function getBarYLayoutForNumericScale(args: {
|
|
|
14
14
|
bandSize: number;
|
|
15
15
|
barGap: number;
|
|
16
16
|
barSize: number;
|
|
17
|
-
dataLength: number;
|
|
18
17
|
};
|
|
19
18
|
export declare function getBarYLayoutForCategoryScale(args: {
|
|
20
19
|
groupedData: ReturnType<typeof groupBarYDataByYValue>;
|
|
@@ -32,13 +32,14 @@ export function getBarYLayoutForNumericScale(args) {
|
|
|
32
32
|
const barMaxWidth = get(seriesOptions, 'bar-y.barMaxWidth');
|
|
33
33
|
const barPadding = get(seriesOptions, 'bar-y.barPadding');
|
|
34
34
|
const groupPadding = get(seriesOptions, 'bar-y.groupPadding');
|
|
35
|
-
const
|
|
36
|
-
const
|
|
35
|
+
const groups = Object.values(groupedData);
|
|
36
|
+
const maxGroupItemCount = groups.reduce((acc, items) => Math.max(acc, Object.keys(items).length), 0);
|
|
37
|
+
const bandSize = plotHeight / groups.length;
|
|
37
38
|
const groupGap = Math.max(bandSize * groupPadding, MIN_BAR_GROUP_GAP);
|
|
38
39
|
const groupSize = bandSize - groupGap;
|
|
39
40
|
const barGap = Math.max(bandSize * barPadding, MIN_BAR_GAP);
|
|
40
|
-
const barSize = Math.max(MIN_BAR_WIDTH, Math.min(groupSize - barGap, barMaxWidth));
|
|
41
|
-
return { bandSize, barGap, barSize
|
|
41
|
+
const barSize = Math.max(MIN_BAR_WIDTH, Math.min((groupSize - barGap) / maxGroupItemCount, barMaxWidth));
|
|
42
|
+
return { bandSize, barGap, barSize };
|
|
42
43
|
}
|
|
43
44
|
export function getBarYLayoutForCategoryScale(args) {
|
|
44
45
|
const { groupedData, seriesOptions, yScale } = args;
|
|
@@ -16,7 +16,9 @@
|
|
|
16
16
|
"label_axis-plot-band-options-not-equal": "It seems you are trying to use different type for \"{{axis}}\" axis plot band options",
|
|
17
17
|
"label_invalid-tooltip-totals-aggregation-type": "It seems you are trying to use inappropriate data type for \"tooltip.totals.aggregation\". Available types: string, function.",
|
|
18
18
|
"label_invalid-tooltip-totals-aggregation-type-str": "It seems you are trying to use inappropriate value for built-in \"tooltip.totals.aggregation\". Available values: [{{values}}].",
|
|
19
|
-
"label_invalid-axis-type": "It seems you are trying to use inappropriate type for \"{{key}}\" axis. Available types: [{{values}}]."
|
|
19
|
+
"label_invalid-axis-type": "It seems you are trying to use inappropriate type for \"{{key}}\" axis. Available types: [{{values}}].",
|
|
20
|
+
"label_invalid-axis-labels-html-type": "It seems you are trying to use inappropriate type for \"labels.html\" property. Only boolean is allowed.",
|
|
21
|
+
"label_invalid-axis-labels-html-not-supported-axis-type": "It seems you are trying to use \"labels.html\" property for an axis with an unsupported type. This property is supported only for \"category\" axis."
|
|
20
22
|
},
|
|
21
23
|
"tooltip": {
|
|
22
24
|
"label_totals_sum": "Sum"
|
|
@@ -16,7 +16,9 @@
|
|
|
16
16
|
"label_axis-plot-band-options-not-equal": "Похоже, что вы пытаетесь использовать разные типы для для параметра полосы для оси \"{{axis}}\"",
|
|
17
17
|
"label_invalid-tooltip-totals-aggregation-type": "Похоже, что вы пытаетесь использовать некорректный тип данных для \"tooltip.totals.aggregation\". Доступные типы: string, function.",
|
|
18
18
|
"label_invalid-tooltip-totals-aggregation-type-str": "Похоже, что вы пытаетесь использовать некорректное значение для встроенной агрегации \"tooltip.totals.aggregation\". Доступные значения: [{{values}}].",
|
|
19
|
-
"label_invalid-axis-type": "Похоже, что вы пытаетесь использовать некорректный тип для оси \"{{key}}\". Доступные типы: [{{values}}]."
|
|
19
|
+
"label_invalid-axis-type": "Похоже, что вы пытаетесь использовать некорректный тип для оси \"{{key}}\". Доступные типы: [{{values}}].",
|
|
20
|
+
"label_invalid-axis-labels-html-type": "Похоже, что вы пытаетесь использовать некорректный тип для свойства \"labels.html\". Допускается только использование булевых значений.",
|
|
21
|
+
"label_invalid-axis-labels-html-not-supported-axis-type": "Похоже, что вы пытаетесь использовать свойство \"labels.html\" для оси с неподдерживаемым типом. Это свойство поддерживается только для оси типа \"category\"."
|
|
20
22
|
},
|
|
21
23
|
"tooltip": {
|
|
22
24
|
"label_totals_sum": "Сумма"
|
|
@@ -19,12 +19,22 @@ export interface ChartAxisLabels {
|
|
|
19
19
|
style?: Partial<BaseTextStyle>;
|
|
20
20
|
/** For horizontal axes, enable label rotation to prevent overlapping labels.
|
|
21
21
|
* If there is enough space, labels are not rotated.
|
|
22
|
-
* As the chart gets narrower, it will start rotating the labels -45 degrees.
|
|
22
|
+
* As the chart gets narrower, it will start rotating the labels -45 degrees.
|
|
23
|
+
*
|
|
24
|
+
* Does not apply to html labels.
|
|
25
|
+
*/
|
|
23
26
|
autoRotation?: boolean;
|
|
24
27
|
/** Rotation of the labels in degrees.
|
|
28
|
+
*
|
|
29
|
+
* Does not apply to html labels.
|
|
25
30
|
* @default 0
|
|
26
31
|
*/
|
|
27
32
|
rotation?: number;
|
|
33
|
+
/**
|
|
34
|
+
* Allows to use any html-tags to display labels content. Supports only for axis with type "category".
|
|
35
|
+
* @default false
|
|
36
|
+
* */
|
|
37
|
+
html?: boolean;
|
|
28
38
|
}
|
|
29
39
|
export interface ChartAxis {
|
|
30
40
|
categories?: string[];
|