@gravity-ui/charts 1.0.1 → 1.2.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 +1 -0
- package/dist/cjs/components/Axis/AxisX.js +57 -12
- package/dist/cjs/components/Axis/AxisY.d.ts +1 -0
- package/dist/cjs/components/Axis/AxisY.js +83 -12
- package/dist/cjs/components/ChartInner/index.js +3 -3
- package/dist/cjs/components/ChartInner/styles.css +2 -0
- package/dist/cjs/components/ChartInner/useChartInnerProps.d.ts +3 -0
- package/dist/cjs/components/ChartInner/useChartInnerProps.js +14 -3
- package/dist/cjs/components/Legend/index.js +2 -2
- package/dist/cjs/components/Title/index.js +1 -1
- package/dist/cjs/components/Tooltip/DefaultContent.js +41 -23
- package/dist/cjs/hooks/useChartDimensions/index.js +3 -0
- package/dist/cjs/hooks/useChartDimensions/utils.js +3 -0
- package/dist/cjs/hooks/useChartOptions/types.d.ts +5 -3
- package/dist/cjs/hooks/useChartOptions/x-axis.js +8 -0
- package/dist/cjs/hooks/useChartOptions/y-axis.js +8 -0
- package/dist/cjs/hooks/useSeries/prepare-waterfall.js +40 -28
- package/dist/cjs/hooks/useSeries/types.d.ts +4 -3
- package/dist/cjs/hooks/useShapes/HtmlLayer.js +2 -1
- package/dist/cjs/hooks/useShapes/area/prepare-data.js +1 -0
- package/dist/cjs/hooks/useShapes/bar-x/prepare-data.js +1 -0
- package/dist/cjs/hooks/useShapes/bar-y/prepare-data.js +1 -0
- package/dist/cjs/hooks/useShapes/line/prepare-data.js +1 -0
- package/dist/cjs/hooks/useShapes/pie/index.js +1 -1
- package/dist/cjs/hooks/useShapes/pie/prepare-data.js +1 -0
- package/dist/cjs/hooks/useShapes/radar/prepare-data.js +1 -0
- package/dist/cjs/hooks/useShapes/treemap/index.js +1 -1
- package/dist/cjs/hooks/useShapes/treemap/prepare-data.js +3 -2
- package/dist/cjs/hooks/useShapes/waterfall/prepare-data.js +4 -2
- package/dist/cjs/i18n/keysets/en.json +3 -1
- package/dist/cjs/i18n/keysets/ru.json +3 -1
- package/dist/cjs/libs/chart-error/index.d.ts +1 -0
- package/dist/cjs/libs/chart-error/index.js +1 -0
- package/dist/cjs/types/chart/axis.d.ts +32 -7
- package/dist/cjs/types/chart/waterfall.d.ts +9 -0
- package/dist/cjs/types/chart-ui.d.ts +1 -0
- package/dist/cjs/utils/chart/axis-generators/bottom.d.ts +1 -0
- package/dist/cjs/utils/chart/axis-generators/bottom.js +16 -1
- package/dist/cjs/utils/chart/axis.d.ts +12 -1
- package/dist/cjs/utils/chart/axis.js +35 -0
- package/dist/cjs/utils/chart/get-closest-data.js +23 -13
- package/dist/cjs/utils/chart/index.d.ts +2 -1
- package/dist/cjs/utils/chart/index.js +5 -5
- package/dist/cjs/utils/chart/series/waterfall.d.ts +2 -2
- package/dist/cjs/utils/chart/series/waterfall.js +1 -7
- package/dist/cjs/utils/chart/types.d.ts +1 -0
- package/dist/cjs/utils/chart/types.js +1 -0
- package/dist/cjs/utils/chart-ui/pie-center-text.d.ts +1 -0
- package/dist/cjs/utils/chart-ui/pie-center-text.js +3 -1
- package/dist/cjs/validation/index.js +144 -0
- package/dist/esm/components/Axis/AxisX.d.ts +1 -0
- package/dist/esm/components/Axis/AxisX.js +57 -12
- package/dist/esm/components/Axis/AxisY.d.ts +1 -0
- package/dist/esm/components/Axis/AxisY.js +83 -12
- package/dist/esm/components/ChartInner/index.js +3 -3
- package/dist/esm/components/ChartInner/styles.css +2 -0
- package/dist/esm/components/ChartInner/useChartInnerProps.d.ts +3 -0
- package/dist/esm/components/ChartInner/useChartInnerProps.js +14 -3
- package/dist/esm/components/Legend/index.js +2 -2
- package/dist/esm/components/Title/index.js +1 -1
- package/dist/esm/components/Tooltip/DefaultContent.js +41 -23
- package/dist/esm/hooks/useChartDimensions/index.js +3 -0
- package/dist/esm/hooks/useChartDimensions/utils.js +3 -0
- package/dist/esm/hooks/useChartOptions/types.d.ts +5 -3
- package/dist/esm/hooks/useChartOptions/x-axis.js +8 -0
- package/dist/esm/hooks/useChartOptions/y-axis.js +8 -0
- package/dist/esm/hooks/useSeries/prepare-waterfall.js +40 -28
- package/dist/esm/hooks/useSeries/types.d.ts +4 -3
- package/dist/esm/hooks/useShapes/HtmlLayer.js +2 -1
- package/dist/esm/hooks/useShapes/area/prepare-data.js +1 -0
- package/dist/esm/hooks/useShapes/bar-x/prepare-data.js +1 -0
- package/dist/esm/hooks/useShapes/bar-y/prepare-data.js +1 -0
- package/dist/esm/hooks/useShapes/line/prepare-data.js +1 -0
- package/dist/esm/hooks/useShapes/pie/index.js +1 -1
- package/dist/esm/hooks/useShapes/pie/prepare-data.js +1 -0
- package/dist/esm/hooks/useShapes/radar/prepare-data.js +1 -0
- package/dist/esm/hooks/useShapes/treemap/index.js +1 -1
- package/dist/esm/hooks/useShapes/treemap/prepare-data.js +3 -2
- package/dist/esm/hooks/useShapes/waterfall/prepare-data.js +4 -2
- package/dist/esm/i18n/keysets/en.json +3 -1
- package/dist/esm/i18n/keysets/ru.json +3 -1
- package/dist/esm/libs/chart-error/index.d.ts +1 -0
- package/dist/esm/libs/chart-error/index.js +1 -0
- package/dist/esm/types/chart/axis.d.ts +32 -7
- package/dist/esm/types/chart/waterfall.d.ts +9 -0
- package/dist/esm/types/chart-ui.d.ts +1 -0
- package/dist/esm/utils/chart/axis-generators/bottom.d.ts +1 -0
- package/dist/esm/utils/chart/axis-generators/bottom.js +16 -1
- package/dist/esm/utils/chart/axis.d.ts +12 -1
- package/dist/esm/utils/chart/axis.js +35 -0
- package/dist/esm/utils/chart/get-closest-data.js +23 -13
- package/dist/esm/utils/chart/index.d.ts +2 -1
- package/dist/esm/utils/chart/index.js +5 -5
- package/dist/esm/utils/chart/series/waterfall.d.ts +2 -2
- package/dist/esm/utils/chart/series/waterfall.js +1 -7
- package/dist/esm/utils/chart/types.d.ts +1 -0
- package/dist/esm/utils/chart/types.js +1 -0
- package/dist/esm/utils/chart-ui/pie-center-text.d.ts +1 -0
- package/dist/esm/utils/chart-ui/pie-center-text.js +3 -1
- package/dist/esm/validation/index.js +144 -0
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { line, select } from 'd3';
|
|
3
|
-
import { block, formatAxisTickLabel, getAxisTitleRows, getClosestPointsRange, getLineDashArray, getMaxTickCount, getScaleTicks, getTicksCount, handleOverflowingText, } from '../../utils';
|
|
3
|
+
import { block, formatAxisTickLabel, getAxisTitleRows, getBandsPosition, getClosestPointsRange, getLineDashArray, getMaxTickCount, getScaleTicks, getTicksCount, handleOverflowingText, } from '../../utils';
|
|
4
4
|
import { axisBottom } from '../../utils/chart/axis-generators';
|
|
5
5
|
import './styles.css';
|
|
6
6
|
const b = block('axis');
|
|
@@ -42,12 +42,23 @@ export function getTitlePosition(args) {
|
|
|
42
42
|
return { x, y };
|
|
43
43
|
}
|
|
44
44
|
export const AxisX = React.memo(function AxisX(props) {
|
|
45
|
-
const { axis, width, height: totalHeight, scale, split, plotRef } = props;
|
|
45
|
+
const { axis, width, height: totalHeight, scale, split, plotRef, leftmostLimit } = props;
|
|
46
46
|
const ref = React.useRef(null);
|
|
47
47
|
React.useEffect(() => {
|
|
48
48
|
if (!ref.current) {
|
|
49
49
|
return;
|
|
50
50
|
}
|
|
51
|
+
const svgElement = select(ref.current);
|
|
52
|
+
svgElement.selectAll('*').remove();
|
|
53
|
+
const plotClassName = b('plot-x');
|
|
54
|
+
let plotContainer = null;
|
|
55
|
+
if (plotRef === null || plotRef === void 0 ? void 0 : plotRef.current) {
|
|
56
|
+
plotContainer = select(plotRef.current);
|
|
57
|
+
plotContainer.selectAll(`.${plotClassName}`).remove();
|
|
58
|
+
}
|
|
59
|
+
if (!axis.visible) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
51
62
|
let tickItems = [];
|
|
52
63
|
if (axis.grid.enabled) {
|
|
53
64
|
tickItems = new Array(split.plots.length || 1).fill(null).map((_, index) => {
|
|
@@ -59,6 +70,7 @@ export const AxisX = React.memo(function AxisX(props) {
|
|
|
59
70
|
}
|
|
60
71
|
const axisScale = scale;
|
|
61
72
|
const xAxisGenerator = axisBottom({
|
|
73
|
+
leftmostLimit,
|
|
62
74
|
scale: axisScale,
|
|
63
75
|
ticks: {
|
|
64
76
|
items: tickItems,
|
|
@@ -77,8 +89,6 @@ export const AxisX = React.memo(function AxisX(props) {
|
|
|
77
89
|
color: axis.lineColor,
|
|
78
90
|
},
|
|
79
91
|
});
|
|
80
|
-
const svgElement = select(ref.current);
|
|
81
|
-
svgElement.selectAll('*').remove();
|
|
82
92
|
svgElement.call(xAxisGenerator).attr('class', b());
|
|
83
93
|
// add an axis header if necessary
|
|
84
94
|
if (axis.title.text) {
|
|
@@ -104,16 +114,51 @@ export const AxisX = React.memo(function AxisX(props) {
|
|
|
104
114
|
}
|
|
105
115
|
});
|
|
106
116
|
}
|
|
117
|
+
// add plot bands
|
|
118
|
+
if (plotContainer && axis.plotBands.length > 0) {
|
|
119
|
+
const plotBandClassName = b('plot-x-band');
|
|
120
|
+
const plotBandsSelection = plotContainer
|
|
121
|
+
.selectAll(`.${plotBandClassName}-x`)
|
|
122
|
+
.data(axis.plotBands)
|
|
123
|
+
.join('g')
|
|
124
|
+
.attr('class', `${plotClassName} ${plotBandClassName}-x`);
|
|
125
|
+
plotBandsSelection
|
|
126
|
+
.append('rect')
|
|
127
|
+
.attr('x', (band) => {
|
|
128
|
+
var _a, _b;
|
|
129
|
+
const { from, to } = getBandsPosition({ band, axisScale, axis: 'x' });
|
|
130
|
+
const halfBandwidth = ((_b = (_a = axisScale.bandwidth) === null || _a === void 0 ? void 0 : _a.call(axisScale)) !== null && _b !== void 0 ? _b : 0) / 2;
|
|
131
|
+
const startPos = halfBandwidth + Math.min(from, to);
|
|
132
|
+
return Math.max(0, startPos);
|
|
133
|
+
})
|
|
134
|
+
.attr('width', (band) => {
|
|
135
|
+
const { from, to } = getBandsPosition({ band, axisScale, axis: 'x' });
|
|
136
|
+
const startPos = width - Math.min(from, to);
|
|
137
|
+
const endPos = Math.min(Math.abs(to - from), startPos);
|
|
138
|
+
return Math.min(endPos, width);
|
|
139
|
+
})
|
|
140
|
+
.attr('y', 0)
|
|
141
|
+
.attr('height', totalHeight)
|
|
142
|
+
.attr('fill', (band) => band.color)
|
|
143
|
+
.attr('opacity', (band) => band.opacity);
|
|
144
|
+
plotBandsSelection.each((plotBandData, i, nodes) => {
|
|
145
|
+
const plotLineSelection = select(nodes[i]);
|
|
146
|
+
if (plotBandData.layerPlacement === 'before') {
|
|
147
|
+
plotLineSelection.lower();
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
plotLineSelection.raise();
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
}
|
|
107
154
|
// add plot lines
|
|
108
|
-
if (
|
|
109
|
-
const plotLineClassName = b('
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
const plotLinesSelection = plotLineContainer
|
|
113
|
-
.selectAll(`.${plotLineClassName}-x`)
|
|
155
|
+
if (plotContainer && axis.plotLines.length > 0) {
|
|
156
|
+
const plotLineClassName = b('plot-x-line');
|
|
157
|
+
const plotLinesSelection = plotContainer
|
|
158
|
+
.selectAll(`.${plotLineClassName}`)
|
|
114
159
|
.data(axis.plotLines)
|
|
115
160
|
.join('g')
|
|
116
|
-
.attr('class', `${plotLineClassName}
|
|
161
|
+
.attr('class', `${plotClassName} ${plotLineClassName}`);
|
|
117
162
|
const lineGenerator = line();
|
|
118
163
|
plotLinesSelection
|
|
119
164
|
.append('path')
|
|
@@ -140,6 +185,6 @@ export const AxisX = React.memo(function AxisX(props) {
|
|
|
140
185
|
}
|
|
141
186
|
});
|
|
142
187
|
}
|
|
143
|
-
}, [axis, width, totalHeight, scale, split, plotRef]);
|
|
188
|
+
}, [axis, width, totalHeight, scale, split, plotRef, leftmostLimit]);
|
|
144
189
|
return React.createElement("g", { ref: ref });
|
|
145
190
|
});
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { axisLeft, axisRight, line, select } from 'd3';
|
|
3
|
-
import { block, calculateCos, calculateSin, formatAxisTickLabel, getAxisHeight, getAxisTitleRows, getClosestPointsRange, getLineDashArray, getScaleTicks, getTicksCount, handleOverflowingText, parseTransformStyle, setEllipsisForOverflowTexts, wrapText, } from '../../utils';
|
|
3
|
+
import { block, calculateCos, calculateSin, formatAxisTickLabel, getAxisHeight, getAxisTitleRows, getBandsPosition, getClosestPointsRange, getLineDashArray, getScaleTicks, getTicksCount, handleOverflowingText, parseTransformStyle, setEllipsisForOverflowTexts, wrapText, } from '../../utils';
|
|
4
4
|
import './styles.css';
|
|
5
5
|
const b = block('axis');
|
|
6
6
|
function transformLabel(args) {
|
|
7
|
-
const { node, axis } = args;
|
|
7
|
+
const { node, axis, isTopOffsetOverload = false } = args;
|
|
8
8
|
let topOffset = axis.labels.lineHeight / 2;
|
|
9
|
+
if (isTopOffsetOverload) {
|
|
10
|
+
topOffset = 0;
|
|
11
|
+
}
|
|
9
12
|
let leftOffset = axis.labels.margin;
|
|
10
13
|
if (axis.position === 'left') {
|
|
11
14
|
leftOffset = leftOffset * -1;
|
|
@@ -82,7 +85,7 @@ function getTitlePosition(args) {
|
|
|
82
85
|
return { x, y };
|
|
83
86
|
}
|
|
84
87
|
export const AxisY = (props) => {
|
|
85
|
-
const { axes, width, height: totalHeight, scale, split, plotRef } = props;
|
|
88
|
+
const { axes: allAxes, width, height: totalHeight, scale, split, plotRef, bottomLimit = 0, } = props;
|
|
86
89
|
const height = getAxisHeight({ split, boundsHeight: totalHeight });
|
|
87
90
|
const ref = React.useRef(null);
|
|
88
91
|
const lineGenerator = line();
|
|
@@ -90,8 +93,15 @@ export const AxisY = (props) => {
|
|
|
90
93
|
if (!ref.current) {
|
|
91
94
|
return;
|
|
92
95
|
}
|
|
96
|
+
const axes = allAxes.filter((a) => a.visible);
|
|
93
97
|
const svgElement = select(ref.current);
|
|
94
98
|
svgElement.selectAll('*').remove();
|
|
99
|
+
let plotContainer = null;
|
|
100
|
+
const plotClassName = b('plot-y');
|
|
101
|
+
if (plotRef === null || plotRef === void 0 ? void 0 : plotRef.current) {
|
|
102
|
+
plotContainer = select(plotRef.current);
|
|
103
|
+
plotContainer.selectAll(`.${plotClassName}`).remove();
|
|
104
|
+
}
|
|
95
105
|
const getAxisPosition = (axis) => {
|
|
96
106
|
var _a;
|
|
97
107
|
const top = ((_a = split.plots[axis.plotIndex]) === null || _a === void 0 ? void 0 : _a.top) || 0;
|
|
@@ -108,6 +118,12 @@ export const AxisY = (props) => {
|
|
|
108
118
|
}
|
|
109
119
|
return acc;
|
|
110
120
|
}, []);
|
|
121
|
+
const plotBands = axes.reduce((acc, axis) => {
|
|
122
|
+
if (axis.plotBands.length) {
|
|
123
|
+
acc.push(...axis.plotBands.map((plotBand) => (Object.assign(Object.assign({}, plotBand), { transform: getAxisPosition(axis) }))));
|
|
124
|
+
}
|
|
125
|
+
return acc;
|
|
126
|
+
}, []);
|
|
111
127
|
const axisSelection = svgElement
|
|
112
128
|
.selectAll('axis')
|
|
113
129
|
.data(axes)
|
|
@@ -127,8 +143,8 @@ export const AxisY = (props) => {
|
|
|
127
143
|
});
|
|
128
144
|
yAxisGenerator(axisItem);
|
|
129
145
|
if (d.labels.enabled) {
|
|
130
|
-
const
|
|
131
|
-
|
|
146
|
+
const labels = axisItem.selectAll('.tick text');
|
|
147
|
+
const tickTexts = labels
|
|
132
148
|
// The offset must be applied before the labels are rotated.
|
|
133
149
|
// Therefore, we reset the values and make an offset in transform attribute.
|
|
134
150
|
// FIXME: give up axisLeft(d3) and switch to our own generation method
|
|
@@ -138,6 +154,26 @@ export const AxisY = (props) => {
|
|
|
138
154
|
.style('transform', function () {
|
|
139
155
|
return transformLabel({ node: this, axis: d });
|
|
140
156
|
});
|
|
157
|
+
labels.each(function (_d, i) {
|
|
158
|
+
if (i === 0) {
|
|
159
|
+
const currentElement = this;
|
|
160
|
+
const currentElementPosition = currentElement.getBoundingClientRect();
|
|
161
|
+
const text = select(currentElement);
|
|
162
|
+
if (currentElementPosition.bottom > bottomLimit) {
|
|
163
|
+
const transform = transformLabel({
|
|
164
|
+
node: this,
|
|
165
|
+
axis: d,
|
|
166
|
+
isTopOffsetOverload: true,
|
|
167
|
+
});
|
|
168
|
+
text.style('transform', transform);
|
|
169
|
+
if (d.labels.rotation) {
|
|
170
|
+
text.attr('text-anchor', () => {
|
|
171
|
+
return d.labels.rotation < 0 ? 'start' : 'end';
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
});
|
|
141
177
|
const textMaxWidth = !d.labels.rotation || Math.abs(d.labels.rotation) % 360 !== 90
|
|
142
178
|
? d.labels.maxWidth
|
|
143
179
|
: (height - d.labels.padding * (tickTexts.size() - 1)) / tickTexts.size();
|
|
@@ -160,15 +196,50 @@ export const AxisY = (props) => {
|
|
|
160
196
|
})
|
|
161
197
|
.remove();
|
|
162
198
|
}
|
|
163
|
-
if (
|
|
164
|
-
const
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
199
|
+
if (plotContainer && d.plotBands.length > 0) {
|
|
200
|
+
const plotBandClassName = b('plot-y-band');
|
|
201
|
+
const plotBandsSelection = plotContainer
|
|
202
|
+
.selectAll(`.${plotBandClassName}`)
|
|
203
|
+
.data(plotBands)
|
|
204
|
+
.join('g')
|
|
205
|
+
.attr('class', `${plotClassName} ${plotBandClassName}`)
|
|
206
|
+
.style('transform', (plotBand) => plotBand.transform);
|
|
207
|
+
plotBandsSelection
|
|
208
|
+
.append('rect')
|
|
209
|
+
.attr('x', 0)
|
|
210
|
+
.attr('width', width)
|
|
211
|
+
.attr('y', (band) => {
|
|
212
|
+
var _a, _b;
|
|
213
|
+
const { from, to } = getBandsPosition({ band, axisScale, axis: 'y' });
|
|
214
|
+
const halfBandwidth = ((_b = (_a = axisScale.bandwidth) === null || _a === void 0 ? void 0 : _a.call(axisScale)) !== null && _b !== void 0 ? _b : 0) / 2;
|
|
215
|
+
const startPos = halfBandwidth + Math.min(from, to);
|
|
216
|
+
return Math.max(0, startPos);
|
|
217
|
+
})
|
|
218
|
+
.attr('height', (band) => {
|
|
219
|
+
const { from, to } = getBandsPosition({ band, axisScale, axis: 'y' });
|
|
220
|
+
const startPos = height - Math.min(from, to);
|
|
221
|
+
const endPos = Math.min(Math.abs(to - from), startPos);
|
|
222
|
+
return Math.min(endPos, height);
|
|
223
|
+
})
|
|
224
|
+
.attr('fill', (band) => band.color)
|
|
225
|
+
.attr('opacity', (band) => band.opacity);
|
|
226
|
+
plotBandsSelection.each((plotBandData, i, nodes) => {
|
|
227
|
+
const plotLineSelection = select(nodes[i]);
|
|
228
|
+
if (plotBandData.layerPlacement === 'before') {
|
|
229
|
+
plotLineSelection.lower();
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
plotLineSelection.raise();
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
if (plotContainer && d.plotLines.length > 0) {
|
|
237
|
+
const plotLineClassName = b('plot-y-line');
|
|
238
|
+
const plotLinesSelection = plotContainer
|
|
168
239
|
.selectAll(`.${plotLineClassName}`)
|
|
169
240
|
.data(plotLines)
|
|
170
241
|
.join('g')
|
|
171
|
-
.attr('class', plotLineClassName)
|
|
242
|
+
.attr('class', `${plotClassName} ${plotLineClassName}`)
|
|
172
243
|
.style('transform', (plotLine) => plotLine.transform);
|
|
173
244
|
plotLinesSelection
|
|
174
245
|
.append('path')
|
|
@@ -240,6 +311,6 @@ export const AxisY = (props) => {
|
|
|
240
311
|
handleOverflowingText(nodes[index], height);
|
|
241
312
|
}
|
|
242
313
|
});
|
|
243
|
-
}, [
|
|
314
|
+
}, [allAxes, width, height, scale, split, bottomLimit]);
|
|
244
315
|
return React.createElement("g", { ref: ref, className: b('container') });
|
|
245
316
|
};
|
|
@@ -17,7 +17,7 @@ export const ChartInner = (props) => {
|
|
|
17
17
|
const htmlLayerRef = React.useRef(null);
|
|
18
18
|
const plotRef = React.useRef(null);
|
|
19
19
|
const dispatcher = React.useMemo(() => getDispatcher(), []);
|
|
20
|
-
const { boundsHeight, boundsOffsetLeft, boundsOffsetTop, boundsWidth, handleLegendItemClick, legendConfig, legendItems, preparedSeries, preparedSplit, preparedLegend, prevHeight, prevWidth, shapes, shapesData, title, tooltip, xAxis, xScale, yAxis, yScale, } = useChartInnerProps(Object.assign(Object.assign({}, props), { dispatcher, htmlLayout: htmlLayerRef.current }));
|
|
20
|
+
const { boundsHeight, boundsOffsetLeft, boundsOffsetTop, boundsWidth, handleLegendItemClick, legendConfig, legendItems, preparedSeries, preparedSplit, preparedLegend, prevHeight, prevWidth, shapes, shapesData, title, tooltip, xAxis, xScale, yAxis, yScale, svgXPos, svgBottomPos, } = useChartInnerProps(Object.assign(Object.assign({}, props), { dispatcher, htmlLayout: htmlLayerRef.current, svgContainer: svgRef.current }));
|
|
21
21
|
const { tooltipPinned, togglePinTooltip, unpinTooltip } = useChartInnerState({
|
|
22
22
|
dispatcher,
|
|
23
23
|
tooltip,
|
|
@@ -67,9 +67,9 @@ export const ChartInner = (props) => {
|
|
|
67
67
|
})),
|
|
68
68
|
React.createElement("g", { width: boundsWidth, height: boundsHeight, transform: `translate(${[boundsOffsetLeft, boundsOffsetTop].join(',')})`, ref: plotRef },
|
|
69
69
|
xScale && (yScale === null || yScale === void 0 ? void 0 : yScale.length) && (React.createElement(React.Fragment, null,
|
|
70
|
-
React.createElement(AxisY, { axes: yAxis, width: boundsWidth, height: boundsHeight, scale: yScale, split: preparedSplit, plotRef: plotRef }),
|
|
70
|
+
React.createElement(AxisY, { bottomLimit: svgBottomPos, axes: yAxis, width: boundsWidth, height: boundsHeight, scale: yScale, split: preparedSplit, plotRef: plotRef }),
|
|
71
71
|
React.createElement("g", { transform: `translate(0, ${boundsHeight})` },
|
|
72
|
-
React.createElement(AxisX, { axis: xAxis, width: boundsWidth, height: boundsHeight, scale: xScale, split: preparedSplit, plotRef: plotRef })))),
|
|
72
|
+
React.createElement(AxisX, { leftmostLimit: svgXPos, axis: xAxis, width: boundsWidth, height: boundsHeight, scale: xScale, split: preparedSplit, plotRef: plotRef })))),
|
|
73
73
|
shapes),
|
|
74
74
|
preparedLegend.enabled && (React.createElement(Legend, { chartSeries: preparedSeries, boundsWidth: boundsWidth, legend: preparedLegend, items: legendItems, config: legendConfig, onItemClick: handleLegendItemClick, onUpdate: unpinTooltip }))),
|
|
75
75
|
React.createElement("div", { className: b('html-layer'), ref: htmlLayerRef, style: {
|
|
@@ -4,8 +4,11 @@ import type { ChartInnerProps } from './types';
|
|
|
4
4
|
type Props = ChartInnerProps & {
|
|
5
5
|
dispatcher: Dispatch<object>;
|
|
6
6
|
htmlLayout: HTMLElement | null;
|
|
7
|
+
svgContainer: SVGGElement | null;
|
|
7
8
|
};
|
|
8
9
|
export declare function useChartInnerProps(props: Props): {
|
|
10
|
+
svgBottomPos: number | undefined;
|
|
11
|
+
svgXPos: number | undefined;
|
|
9
12
|
boundsHeight: number;
|
|
10
13
|
boundsOffsetLeft: number;
|
|
11
14
|
boundsOffsetTop: number;
|
|
@@ -4,7 +4,8 @@ import { getYAxisWidth } from '../../hooks/useChartDimensions/utils';
|
|
|
4
4
|
import { getPreparedXAxis } from '../../hooks/useChartOptions/x-axis';
|
|
5
5
|
import { getPreparedYAxis } from '../../hooks/useChartOptions/y-axis';
|
|
6
6
|
export function useChartInnerProps(props) {
|
|
7
|
-
|
|
7
|
+
var _a;
|
|
8
|
+
const { width, height, data, dispatcher, htmlLayout, svgContainer } = props;
|
|
8
9
|
const prevWidth = usePrevious(width);
|
|
9
10
|
const prevHeight = usePrevious(height);
|
|
10
11
|
const { chart, title, tooltip } = useChartOptions({ data });
|
|
@@ -54,9 +55,19 @@ export function useChartInnerProps(props) {
|
|
|
54
55
|
htmlLayout,
|
|
55
56
|
});
|
|
56
57
|
const boundsOffsetTop = chart.margin.top;
|
|
57
|
-
// We
|
|
58
|
-
const boundsOffsetLeft = chart.margin.left +
|
|
58
|
+
// We need to calculate the width of each axis because the first axis can be hidden
|
|
59
|
+
const boundsOffsetLeft = chart.margin.left +
|
|
60
|
+
yAxis.reduce((acc, axis) => {
|
|
61
|
+
const axisWidth = getYAxisWidth(axis);
|
|
62
|
+
if (acc < axisWidth) {
|
|
63
|
+
acc = axisWidth;
|
|
64
|
+
}
|
|
65
|
+
return acc;
|
|
66
|
+
}, 0);
|
|
67
|
+
const { x, bottom } = (_a = svgContainer === null || svgContainer === void 0 ? void 0 : svgContainer.getBoundingClientRect()) !== null && _a !== void 0 ? _a : {};
|
|
59
68
|
return {
|
|
69
|
+
svgBottomPos: bottom,
|
|
70
|
+
svgXPos: x,
|
|
60
71
|
boundsHeight,
|
|
61
72
|
boundsOffsetLeft,
|
|
62
73
|
boundsOffsetTop,
|
|
@@ -185,7 +185,7 @@ export const Legend = (props) => {
|
|
|
185
185
|
const mods = { selected: d.visible, unselected: !d.visible };
|
|
186
186
|
return b('item-text', mods);
|
|
187
187
|
})
|
|
188
|
-
.
|
|
188
|
+
.html(function (d) {
|
|
189
189
|
return ('name' in d && d.name);
|
|
190
190
|
})
|
|
191
191
|
.style('font-size', legend.itemStyle.fontSize);
|
|
@@ -283,7 +283,7 @@ export const Legend = (props) => {
|
|
|
283
283
|
.attr('font-size', (_d = legend.title.style.fontSize) !== null && _d !== void 0 ? _d : null)
|
|
284
284
|
.attr('fill', (_e = legend.title.style.fontColor) !== null && _e !== void 0 ? _e : null)
|
|
285
285
|
.style('alignment-baseline', 'before-edge')
|
|
286
|
-
.
|
|
286
|
+
.html(legend.title.text);
|
|
287
287
|
}
|
|
288
288
|
const { left } = getLegendPosition({
|
|
289
289
|
align: legend.align,
|
|
@@ -5,5 +5,5 @@ const b = block('title');
|
|
|
5
5
|
export const Title = (props) => {
|
|
6
6
|
const { chartWidth, text, height, style } = props;
|
|
7
7
|
return (React.createElement("text", { className: b(), dx: chartWidth / 2, dy: height / 2, dominantBaseline: "middle", textAnchor: "middle", style: Object.assign({ lineHeight: `${height}px` }, style) },
|
|
8
|
-
React.createElement("tspan",
|
|
8
|
+
React.createElement("tspan", { dangerouslySetInnerHTML: { __html: text } })));
|
|
9
9
|
};
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { dateTime } from '@gravity-ui/date-utils';
|
|
3
2
|
import get from 'lodash/get';
|
|
4
|
-
import { formatNumber } from '../../libs';
|
|
5
3
|
import { block, getDataCategoryValue, getWaterfallPointSubtotal } from '../../utils';
|
|
6
4
|
import { getFormattedValue } from '../../utils/chart/format';
|
|
7
5
|
const b = block('tooltip');
|
|
@@ -12,23 +10,14 @@ const getRowData = (fieldName, data, axis) => {
|
|
|
12
10
|
const categories = get(axis, 'categories', []);
|
|
13
11
|
return getDataCategoryValue({ axisDirection: fieldName, categories, data });
|
|
14
12
|
}
|
|
15
|
-
case 'datetime': {
|
|
16
|
-
const value = get(data, fieldName);
|
|
17
|
-
if (!value) {
|
|
18
|
-
return undefined;
|
|
19
|
-
}
|
|
20
|
-
return dateTime({ input: value }).format(DEFAULT_DATE_FORMAT);
|
|
21
|
-
}
|
|
22
|
-
case 'linear':
|
|
23
13
|
default: {
|
|
24
|
-
|
|
25
|
-
return formatNumber(value);
|
|
14
|
+
return get(data, fieldName);
|
|
26
15
|
}
|
|
27
16
|
}
|
|
28
17
|
};
|
|
29
18
|
const getXRowData = (data, xAxis) => getRowData('x', data, xAxis);
|
|
30
19
|
const getYRowData = (data, yAxis) => getRowData('y', data, yAxis);
|
|
31
|
-
const getMeasureValue = (data, xAxis, yAxis) => {
|
|
20
|
+
const getMeasureValue = ({ data, xAxis, yAxis, valueFormat, }) => {
|
|
32
21
|
var _a, _b, _c, _d;
|
|
33
22
|
if (data.every((item) => ['pie', 'treemap', 'waterfall', 'sankey'].includes(item.series.type))) {
|
|
34
23
|
return null;
|
|
@@ -37,12 +26,38 @@ const getMeasureValue = (data, xAxis, yAxis) => {
|
|
|
37
26
|
return (_b = (_a = data[0].category) === null || _a === void 0 ? void 0 : _a.key) !== null && _b !== void 0 ? _b : null;
|
|
38
27
|
}
|
|
39
28
|
if (data.some((item) => item.series.type === 'bar-y')) {
|
|
40
|
-
|
|
29
|
+
const format = valueFormat !== null && valueFormat !== void 0 ? valueFormat : getDefaultValueFormat({ axis: yAxis });
|
|
30
|
+
return getFormattedValue({
|
|
31
|
+
value: getYRowData((_c = data[0]) === null || _c === void 0 ? void 0 : _c.data, yAxis),
|
|
32
|
+
format,
|
|
33
|
+
});
|
|
41
34
|
}
|
|
42
|
-
|
|
35
|
+
const format = valueFormat !== null && valueFormat !== void 0 ? valueFormat : getDefaultValueFormat({ axis: xAxis });
|
|
36
|
+
return getFormattedValue({
|
|
37
|
+
value: getXRowData((_d = data[0]) === null || _d === void 0 ? void 0 : _d.data, xAxis),
|
|
38
|
+
format,
|
|
39
|
+
});
|
|
43
40
|
};
|
|
41
|
+
function getDefaultValueFormat({ axis }) {
|
|
42
|
+
switch (axis === null || axis === void 0 ? void 0 : axis.type) {
|
|
43
|
+
case 'linear':
|
|
44
|
+
case 'logarithmic': {
|
|
45
|
+
return {
|
|
46
|
+
type: 'number',
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
case 'datetime': {
|
|
50
|
+
return {
|
|
51
|
+
type: 'date',
|
|
52
|
+
format: DEFAULT_DATE_FORMAT,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
default:
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
44
59
|
export const DefaultContent = ({ hovered, xAxis, yAxis, valueFormat }) => {
|
|
45
|
-
const measureValue = getMeasureValue(hovered, xAxis, yAxis);
|
|
60
|
+
const measureValue = getMeasureValue({ data: hovered, xAxis, yAxis });
|
|
46
61
|
return (React.createElement(React.Fragment, null,
|
|
47
62
|
measureValue && React.createElement("div", null, measureValue),
|
|
48
63
|
hovered.map((seriesItem, i) => {
|
|
@@ -55,9 +70,10 @@ export const DefaultContent = ({ hovered, xAxis, yAxis, valueFormat }) => {
|
|
|
55
70
|
case 'line':
|
|
56
71
|
case 'area':
|
|
57
72
|
case 'bar-x': {
|
|
73
|
+
const format = valueFormat !== null && valueFormat !== void 0 ? valueFormat : getDefaultValueFormat({ axis: yAxis });
|
|
58
74
|
const formattedValue = getFormattedValue({
|
|
59
75
|
value: getYRowData(data, yAxis),
|
|
60
|
-
format
|
|
76
|
+
format,
|
|
61
77
|
});
|
|
62
78
|
const value = (React.createElement(React.Fragment, null,
|
|
63
79
|
series.name,
|
|
@@ -70,13 +86,14 @@ export const DefaultContent = ({ hovered, xAxis, yAxis, valueFormat }) => {
|
|
|
70
86
|
case 'waterfall': {
|
|
71
87
|
const isTotal = get(data, 'total', false);
|
|
72
88
|
const subTotalValue = getWaterfallPointSubtotal(data, series);
|
|
89
|
+
const format = valueFormat !== null && valueFormat !== void 0 ? valueFormat : getDefaultValueFormat({ axis: yAxis });
|
|
73
90
|
const subTotal = getFormattedValue({
|
|
74
91
|
value: subTotalValue,
|
|
75
|
-
format
|
|
92
|
+
format,
|
|
76
93
|
});
|
|
77
94
|
const formattedValue = getFormattedValue({
|
|
78
95
|
value: getYRowData(data, yAxis),
|
|
79
|
-
format
|
|
96
|
+
format,
|
|
80
97
|
});
|
|
81
98
|
return (React.createElement("div", { key: `${id}_${get(data, 'x')}` },
|
|
82
99
|
!isTotal && (React.createElement(React.Fragment, null,
|
|
@@ -93,9 +110,10 @@ export const DefaultContent = ({ hovered, xAxis, yAxis, valueFormat }) => {
|
|
|
93
110
|
subTotal)));
|
|
94
111
|
}
|
|
95
112
|
case 'bar-y': {
|
|
113
|
+
const format = valueFormat !== null && valueFormat !== void 0 ? valueFormat : getDefaultValueFormat({ axis: xAxis });
|
|
96
114
|
const formattedValue = getFormattedValue({
|
|
97
115
|
value: getXRowData(data, xAxis),
|
|
98
|
-
format
|
|
116
|
+
format,
|
|
99
117
|
});
|
|
100
118
|
const value = (React.createElement(React.Fragment, null,
|
|
101
119
|
series.name,
|
|
@@ -110,7 +128,7 @@ export const DefaultContent = ({ hovered, xAxis, yAxis, valueFormat }) => {
|
|
|
110
128
|
const seriesData = data;
|
|
111
129
|
const formattedValue = getFormattedValue({
|
|
112
130
|
value: seriesData.value,
|
|
113
|
-
format: valueFormat,
|
|
131
|
+
format: valueFormat !== null && valueFormat !== void 0 ? valueFormat : { type: 'number' },
|
|
114
132
|
});
|
|
115
133
|
return (React.createElement("div", { key: id, className: b('content-row') },
|
|
116
134
|
React.createElement("div", { className: b('color'), style: { backgroundColor: color } }),
|
|
@@ -124,7 +142,7 @@ export const DefaultContent = ({ hovered, xAxis, yAxis, valueFormat }) => {
|
|
|
124
142
|
const value = (_a = source.links.find((d) => d.name === (target === null || target === void 0 ? void 0 : target.name))) === null || _a === void 0 ? void 0 : _a.value;
|
|
125
143
|
const formattedValue = getFormattedValue({
|
|
126
144
|
value,
|
|
127
|
-
format: valueFormat,
|
|
145
|
+
format: valueFormat !== null && valueFormat !== void 0 ? valueFormat : { type: 'number' },
|
|
128
146
|
});
|
|
129
147
|
return (React.createElement("div", { key: id, className: b('content-row') },
|
|
130
148
|
React.createElement("div", { className: b('color'), style: { backgroundColor: source.color } }),
|
|
@@ -142,7 +160,7 @@ export const DefaultContent = ({ hovered, xAxis, yAxis, valueFormat }) => {
|
|
|
142
160
|
const seriesData = data;
|
|
143
161
|
const formattedValue = getFormattedValue({
|
|
144
162
|
value: seriesData.value,
|
|
145
|
-
format: valueFormat,
|
|
163
|
+
format: valueFormat !== null && valueFormat !== void 0 ? valueFormat : { type: 'number' },
|
|
146
164
|
});
|
|
147
165
|
const value = (React.createElement(React.Fragment, null,
|
|
148
166
|
React.createElement("span", null,
|
|
@@ -8,6 +8,9 @@ const getBottomOffset = (args) => {
|
|
|
8
8
|
if (preparedLegend.enabled) {
|
|
9
9
|
result += preparedLegend.height + preparedLegend.margin;
|
|
10
10
|
}
|
|
11
|
+
if (!preparedXAxis.visible) {
|
|
12
|
+
return result;
|
|
13
|
+
}
|
|
11
14
|
if (hasAxisRelatedSeries) {
|
|
12
15
|
if (preparedXAxis.title.text) {
|
|
13
16
|
result += preparedXAxis.title.height + preparedXAxis.title.margin;
|
|
@@ -6,6 +6,9 @@ export const getBoundsWidth = (args) => {
|
|
|
6
6
|
getWidthOccupiedByYAxis({ preparedAxis: preparedYAxis }));
|
|
7
7
|
};
|
|
8
8
|
export function getYAxisWidth(axis) {
|
|
9
|
+
if (!(axis === null || axis === void 0 ? void 0 : axis.visible)) {
|
|
10
|
+
return 0;
|
|
11
|
+
}
|
|
9
12
|
let result = 0;
|
|
10
13
|
if (axis === null || axis === void 0 ? void 0 : axis.title.text) {
|
|
11
14
|
result += axis.title.height + axis.title.margin;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { DashStyle } from 'src/constants';
|
|
2
|
-
import type {
|
|
2
|
+
import type { AxisPlotBand, BaseTextStyle, ChartAxis, ChartAxisLabels, ChartAxisTitleAlignment, ChartAxisType, ChartData, ChartMargin, PlotLayerPlacement } from '../../types';
|
|
3
3
|
type PreparedAxisLabels = Omit<ChartAxisLabels, 'enabled' | 'padding' | 'style' | 'autoRotation'> & Required<Pick<ChartAxisLabels, 'enabled' | 'padding' | 'margin' | 'rotation'>> & {
|
|
4
4
|
style: BaseTextStyle;
|
|
5
5
|
rotation: number;
|
|
@@ -11,15 +11,16 @@ type PreparedAxisLabels = Omit<ChartAxisLabels, 'enabled' | 'padding' | 'style'
|
|
|
11
11
|
export type PreparedChart = {
|
|
12
12
|
margin: ChartMargin;
|
|
13
13
|
};
|
|
14
|
+
export type PreparedAxisPlotBand = Required<AxisPlotBand>;
|
|
14
15
|
export type PreparedAxisPlotLine = {
|
|
15
16
|
value: number;
|
|
16
17
|
color: string;
|
|
17
18
|
width: number;
|
|
18
19
|
dashStyle: DashStyle;
|
|
19
20
|
opacity: number;
|
|
20
|
-
layerPlacement:
|
|
21
|
+
layerPlacement: PlotLayerPlacement;
|
|
21
22
|
};
|
|
22
|
-
export type PreparedAxis = Omit<ChartAxis, 'type' | 'labels' | 'plotLines'> & {
|
|
23
|
+
export type PreparedAxis = Omit<ChartAxis, 'type' | 'labels' | 'plotLines' | 'plotBands'> & {
|
|
23
24
|
type: ChartAxisType;
|
|
24
25
|
labels: PreparedAxisLabels;
|
|
25
26
|
title: {
|
|
@@ -42,6 +43,7 @@ export type PreparedAxis = Omit<ChartAxis, 'type' | 'labels' | 'plotLines'> & {
|
|
|
42
43
|
position: 'left' | 'right' | 'top' | 'bottom';
|
|
43
44
|
plotIndex: number;
|
|
44
45
|
plotLines: PreparedAxisPlotLine[];
|
|
46
|
+
plotBands: PreparedAxisPlotBand[];
|
|
45
47
|
};
|
|
46
48
|
export type PreparedTitle = ChartData['title'] & {
|
|
47
49
|
height: number;
|
|
@@ -110,6 +110,14 @@ export const getPreparedXAxis = ({ xAxis, series, width, }) => {
|
|
|
110
110
|
opacity: get(d, 'opacity', 1),
|
|
111
111
|
layerPlacement: get(d, 'layerPlacement', 'before'),
|
|
112
112
|
})),
|
|
113
|
+
plotBands: get(xAxis, 'plotBands', []).map((d) => ({
|
|
114
|
+
color: get(d, 'color', 'var(--g-color-base-brand)'),
|
|
115
|
+
opacity: get(d, 'opacity', 1),
|
|
116
|
+
from: get(d, 'from', 0),
|
|
117
|
+
to: get(d, 'to', 0),
|
|
118
|
+
layerPlacement: get(d, 'layerPlacement', 'before'),
|
|
119
|
+
})),
|
|
120
|
+
visible: get(xAxis, 'visible', true),
|
|
113
121
|
};
|
|
114
122
|
const { height, rotation } = getLabelSettings({
|
|
115
123
|
axis: preparedXAxis,
|
|
@@ -116,6 +116,14 @@ export const getPreparedYAxis = ({ series, yAxis, height, }) => {
|
|
|
116
116
|
opacity: get(d, 'opacity', 1),
|
|
117
117
|
layerPlacement: get(d, 'layerPlacement', 'before'),
|
|
118
118
|
})),
|
|
119
|
+
plotBands: get(axisItem, 'plotBands', []).map((d) => ({
|
|
120
|
+
color: get(d, 'color', 'var(--g-color-base-brand)'),
|
|
121
|
+
opacity: get(d, 'opacity', 1),
|
|
122
|
+
from: get(d, 'from', 0),
|
|
123
|
+
to: get(d, 'to', 0),
|
|
124
|
+
layerPlacement: get(d, 'layerPlacement', 'before'),
|
|
125
|
+
})),
|
|
126
|
+
visible: get(axisItem, 'visible', true),
|
|
119
127
|
};
|
|
120
128
|
if (labelsEnabled) {
|
|
121
129
|
preparedAxis.labels.width = getAxisLabelMaxWidth({ axis: preparedAxis, series });
|