@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
|
@@ -95,6 +95,148 @@ const validateXYSeries = (args) => {
|
|
|
95
95
|
}
|
|
96
96
|
});
|
|
97
97
|
};
|
|
98
|
+
const validateAxisPlotValues = (args) => {
|
|
99
|
+
const { series, xAxis, yAxis = [] } = args;
|
|
100
|
+
const yAxisIndex = get(series, 'yAxis', 0);
|
|
101
|
+
const seriesYAxis = yAxis[yAxisIndex];
|
|
102
|
+
if (yAxisIndex !== 0 && typeof seriesYAxis === 'undefined') {
|
|
103
|
+
throw new ChartError({
|
|
104
|
+
code: CHART_ERROR_CODE.INVALID_DATA,
|
|
105
|
+
message: i18n('error', 'label_invalid-y-axis-index', {
|
|
106
|
+
index: yAxisIndex,
|
|
107
|
+
}),
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
const xPlotBands = get(xAxis, 'plotBands', []);
|
|
111
|
+
const yPlotBands = get(yAxis, 'plotBands', []);
|
|
112
|
+
if (!xPlotBands.length && !yPlotBands.length) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const xType = get(xAxis, 'type', DEFAULT_AXIS_TYPE);
|
|
116
|
+
const yType = get(seriesYAxis, 'type', DEFAULT_AXIS_TYPE);
|
|
117
|
+
xPlotBands.forEach(({ from = 0, to = 0 }) => {
|
|
118
|
+
const fromNotEqualTo = typeof to !== typeof from;
|
|
119
|
+
if (fromNotEqualTo) {
|
|
120
|
+
throw new ChartError({
|
|
121
|
+
code: CHART_ERROR_CODE.INVALID_OPTION_TYPE,
|
|
122
|
+
message: i18n('error', 'label_axis-plot-band-options-not-equal', {
|
|
123
|
+
axis: 'x',
|
|
124
|
+
option: 'from',
|
|
125
|
+
}),
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
switch (xType) {
|
|
129
|
+
case 'category': {
|
|
130
|
+
const invalidFrom = typeof from !== 'string' && typeof from !== 'number';
|
|
131
|
+
const invalidTo = typeof to !== 'string' && typeof to !== 'number';
|
|
132
|
+
if (invalidFrom) {
|
|
133
|
+
throw new ChartError({
|
|
134
|
+
code: CHART_ERROR_CODE.INVALID_OPTION_TYPE,
|
|
135
|
+
message: i18n('error', 'label_invalid-axis-plot-band-option', {
|
|
136
|
+
axis: 'x',
|
|
137
|
+
option: 'from',
|
|
138
|
+
}),
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
if (invalidTo) {
|
|
142
|
+
throw new ChartError({
|
|
143
|
+
code: CHART_ERROR_CODE.INVALID_OPTION_TYPE,
|
|
144
|
+
message: i18n('error', 'label_invalid-axis-plot-band-option', {
|
|
145
|
+
axis: 'x',
|
|
146
|
+
option: 'to',
|
|
147
|
+
}),
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
case 'linear':
|
|
153
|
+
case 'datetime': {
|
|
154
|
+
const invalidFrom = typeof from !== 'number';
|
|
155
|
+
const invalidTo = typeof to !== 'number';
|
|
156
|
+
if (invalidFrom) {
|
|
157
|
+
throw new ChartError({
|
|
158
|
+
code: CHART_ERROR_CODE.INVALID_OPTION_TYPE,
|
|
159
|
+
message: i18n('error', 'label_invalid-axis-plot-band-option', {
|
|
160
|
+
axis: 'x',
|
|
161
|
+
option: 'from',
|
|
162
|
+
}),
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
if (invalidTo) {
|
|
166
|
+
throw new ChartError({
|
|
167
|
+
code: CHART_ERROR_CODE.INVALID_OPTION_TYPE,
|
|
168
|
+
message: i18n('error', 'label_invalid-axis-plot-band-option', {
|
|
169
|
+
axis: 'x',
|
|
170
|
+
option: 'to',
|
|
171
|
+
}),
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
yPlotBands.forEach(({ from = 0, to = 0 }) => {
|
|
179
|
+
const fromNotEqualTo = typeof to !== typeof from;
|
|
180
|
+
if (fromNotEqualTo) {
|
|
181
|
+
throw new ChartError({
|
|
182
|
+
code: CHART_ERROR_CODE.INVALID_OPTION_TYPE,
|
|
183
|
+
message: i18n('error', 'label_axis-plot-band-options-not-equal', {
|
|
184
|
+
axis: 'x',
|
|
185
|
+
option: 'from',
|
|
186
|
+
}),
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
switch (yType) {
|
|
190
|
+
case 'category': {
|
|
191
|
+
const invalidFrom = typeof from !== 'string' && typeof from !== 'number';
|
|
192
|
+
const invalidTo = typeof to !== 'string' && typeof to !== 'number';
|
|
193
|
+
if (invalidFrom) {
|
|
194
|
+
throw new ChartError({
|
|
195
|
+
code: CHART_ERROR_CODE.INVALID_OPTION_TYPE,
|
|
196
|
+
message: i18n('error', 'label_invalid-axis-plot-band-option', {
|
|
197
|
+
axis: 'y',
|
|
198
|
+
option: 'from',
|
|
199
|
+
}),
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
if (invalidTo) {
|
|
203
|
+
throw new ChartError({
|
|
204
|
+
code: CHART_ERROR_CODE.INVALID_OPTION_TYPE,
|
|
205
|
+
message: i18n('error', 'label_invalid-axis-plot-band-option', {
|
|
206
|
+
axis: 'y',
|
|
207
|
+
option: 'to',
|
|
208
|
+
}),
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
case 'linear':
|
|
214
|
+
case 'datetime': {
|
|
215
|
+
const invalidFrom = typeof from !== 'number';
|
|
216
|
+
const invalidTo = typeof to !== 'number';
|
|
217
|
+
if (invalidFrom) {
|
|
218
|
+
throw new ChartError({
|
|
219
|
+
code: CHART_ERROR_CODE.INVALID_OPTION_TYPE,
|
|
220
|
+
message: i18n('error', 'label_invalid-axis-plot-band-option', {
|
|
221
|
+
axis: 'y',
|
|
222
|
+
option: 'from',
|
|
223
|
+
}),
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
if (invalidTo) {
|
|
227
|
+
throw new ChartError({
|
|
228
|
+
code: CHART_ERROR_CODE.INVALID_OPTION_TYPE,
|
|
229
|
+
message: i18n('error', 'label_invalid-axis-plot-band-option', {
|
|
230
|
+
axis: 'y',
|
|
231
|
+
option: 'to',
|
|
232
|
+
}),
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
};
|
|
98
240
|
const validatePieSeries = ({ series }) => {
|
|
99
241
|
series.data.forEach(({ value }) => {
|
|
100
242
|
if (typeof value !== 'number') {
|
|
@@ -163,12 +305,14 @@ const validateSeries = (args) => {
|
|
|
163
305
|
case 'area':
|
|
164
306
|
case 'bar-y':
|
|
165
307
|
case 'bar-x': {
|
|
308
|
+
validateAxisPlotValues({ series, xAxis, yAxis });
|
|
166
309
|
validateXYSeries({ series, xAxis, yAxis });
|
|
167
310
|
validateStacking({ series });
|
|
168
311
|
break;
|
|
169
312
|
}
|
|
170
313
|
case 'line':
|
|
171
314
|
case 'scatter': {
|
|
315
|
+
validateAxisPlotValues({ series, xAxis, yAxis });
|
|
172
316
|
validateXYSeries({ series, xAxis, yAxis });
|
|
173
317
|
break;
|
|
174
318
|
}
|
|
@@ -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
|
};
|