@gravity-ui/charts 1.33.0 → 1.34.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/ChartInner/useChartInnerProps.js +3 -2
- package/dist/cjs/components/Tooltip/DefaultTooltipContent/index.js +5 -1
- package/dist/cjs/hooks/index.d.ts +1 -0
- package/dist/cjs/hooks/index.js +1 -0
- package/dist/cjs/hooks/useAxis/index.d.ts +5 -3
- package/dist/cjs/hooks/useAxis/index.js +3 -3
- package/dist/cjs/hooks/useAxis/types.d.ts +6 -0
- package/dist/cjs/hooks/useAxis/y-axis.d.ts +10 -0
- package/dist/cjs/hooks/useAxis/y-axis.js +30 -21
- package/dist/cjs/hooks/useAxisScales/index.d.ts +2 -16
- package/dist/cjs/hooks/useAxisScales/index.js +17 -475
- package/dist/cjs/hooks/useAxisScales/utils.d.ts +10 -13
- package/dist/cjs/hooks/useAxisScales/utils.js +29 -63
- package/dist/cjs/hooks/useAxisScales/x-scale.d.ts +15 -0
- package/dist/cjs/hooks/useAxisScales/x-scale.js +247 -0
- package/dist/cjs/hooks/useAxisScales/y-scale.d.ts +10 -0
- package/dist/cjs/hooks/useAxisScales/y-scale.js +299 -0
- package/dist/cjs/hooks/useYAxisLabelWidth/index.d.ts +11 -0
- package/dist/cjs/hooks/useYAxisLabelWidth/index.js +48 -0
- package/dist/cjs/hooks/utils/bar-x.js +1 -1
- package/dist/cjs/hooks/utils/bar-y.js +1 -1
- package/dist/cjs/types/chart/tooltip.d.ts +3 -2
- package/dist/cjs/utils/chart/axis/common.d.ts +1 -0
- package/dist/cjs/utils/chart/axis/common.js +6 -0
- package/dist/esm/components/ChartInner/useChartInnerProps.js +3 -2
- package/dist/esm/components/Tooltip/DefaultTooltipContent/index.js +5 -1
- package/dist/esm/hooks/index.d.ts +1 -0
- package/dist/esm/hooks/index.js +1 -0
- package/dist/esm/hooks/useAxis/index.d.ts +5 -3
- package/dist/esm/hooks/useAxis/index.js +3 -3
- package/dist/esm/hooks/useAxis/types.d.ts +6 -0
- package/dist/esm/hooks/useAxis/y-axis.d.ts +10 -0
- package/dist/esm/hooks/useAxis/y-axis.js +30 -21
- package/dist/esm/hooks/useAxisScales/index.d.ts +2 -16
- package/dist/esm/hooks/useAxisScales/index.js +17 -475
- package/dist/esm/hooks/useAxisScales/utils.d.ts +10 -13
- package/dist/esm/hooks/useAxisScales/utils.js +29 -63
- package/dist/esm/hooks/useAxisScales/x-scale.d.ts +15 -0
- package/dist/esm/hooks/useAxisScales/x-scale.js +247 -0
- package/dist/esm/hooks/useAxisScales/y-scale.d.ts +10 -0
- package/dist/esm/hooks/useAxisScales/y-scale.js +299 -0
- package/dist/esm/hooks/useYAxisLabelWidth/index.d.ts +11 -0
- package/dist/esm/hooks/useYAxisLabelWidth/index.js +48 -0
- package/dist/esm/hooks/utils/bar-x.js +1 -1
- package/dist/esm/hooks/utils/bar-y.js +1 -1
- package/dist/esm/types/chart/tooltip.d.ts +3 -2
- package/dist/esm/utils/chart/axis/common.d.ts +1 -0
- package/dist/esm/utils/chart/axis/common.js +6 -0
- package/package.json +1 -1
|
@@ -1,473 +1,12 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { extent, scaleBand, scaleLinear, scaleLog, scaleUtc } from 'd3';
|
|
3
2
|
import get from 'lodash/get';
|
|
4
3
|
import { getTickValues } from '../../components/AxisY/utils';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
let hasNumberAndNullValues;
|
|
12
|
-
let hasOnlyNullValues;
|
|
13
|
-
for (const d of data) {
|
|
14
|
-
const isNumber = typeof d === 'number';
|
|
15
|
-
const isNull = d === null;
|
|
16
|
-
hasNumberAndNullValues =
|
|
17
|
-
typeof hasNumberAndNullValues === 'undefined'
|
|
18
|
-
? isNumber || isNull
|
|
19
|
-
: hasNumberAndNullValues && (isNumber || isNull);
|
|
20
|
-
hasOnlyNullValues =
|
|
21
|
-
typeof hasOnlyNullValues === 'undefined' ? isNull : hasOnlyNullValues && isNull;
|
|
22
|
-
if (!hasNumberAndNullValues) {
|
|
23
|
-
break;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
return { hasNumberAndNullValues, hasOnlyNullValues };
|
|
27
|
-
}
|
|
28
|
-
function filterCategoriesByVisibleSeries(args) {
|
|
29
|
-
const { axisDirection, categories, series } = args;
|
|
30
|
-
const visibleCategories = new Set();
|
|
31
|
-
series.forEach((s) => {
|
|
32
|
-
if (isSeriesWithCategoryValues(s)) {
|
|
33
|
-
s.data.forEach((d) => {
|
|
34
|
-
visibleCategories.add(getDataCategoryValue({ axisDirection, categories, data: d }));
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
});
|
|
38
|
-
const filteredCategories = categories.filter((c) => visibleCategories.has(c));
|
|
39
|
-
return filteredCategories.length > 0 ? filteredCategories : categories;
|
|
40
|
-
}
|
|
41
|
-
// axis is validated in `validation/index.ts`, so the value of `axis.type` is definitely valid.
|
|
42
|
-
// eslint-disable-next-line consistent-return
|
|
43
|
-
function getYScaleRange(args) {
|
|
44
|
-
const { axis, boundsHeight } = args;
|
|
45
|
-
switch (axis.type) {
|
|
46
|
-
case 'datetime':
|
|
47
|
-
case 'linear':
|
|
48
|
-
case 'logarithmic': {
|
|
49
|
-
const range = [boundsHeight, 0];
|
|
50
|
-
switch (axis.order) {
|
|
51
|
-
case 'sortDesc':
|
|
52
|
-
case 'reverse': {
|
|
53
|
-
range.reverse();
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
return range;
|
|
57
|
-
}
|
|
58
|
-
case 'category': {
|
|
59
|
-
return [boundsHeight, 0];
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
function isSeriesWithYAxisOffset(series) {
|
|
64
|
-
const types = [SERIES_TYPE.BarY, SERIES_TYPE.Heatmap];
|
|
65
|
-
return series.some((s) => types.includes(s.type));
|
|
66
|
-
}
|
|
67
|
-
// eslint-disable-next-line complexity
|
|
68
|
-
export function createYScale(args) {
|
|
69
|
-
const { axis, boundsHeight, series, primaryTickPositions, zoomStateY } = args;
|
|
70
|
-
const [yMinPropsOrState, yMaxPropsOrState] = getMinMaxPropsOrState({
|
|
71
|
-
axis,
|
|
72
|
-
maxValues: [zoomStateY === null || zoomStateY === void 0 ? void 0 : zoomStateY[1]],
|
|
73
|
-
minValues: [zoomStateY === null || zoomStateY === void 0 ? void 0 : zoomStateY[0]],
|
|
74
|
-
});
|
|
75
|
-
const yCategories = get(axis, 'categories');
|
|
76
|
-
const yTimestamps = get(axis, 'timestamps');
|
|
77
|
-
const range = getYScaleRange({ axis, boundsHeight });
|
|
78
|
-
switch (axis.type) {
|
|
79
|
-
case 'linear':
|
|
80
|
-
case 'logarithmic': {
|
|
81
|
-
const domain = getDomainDataYBySeries(series);
|
|
82
|
-
const { hasNumberAndNullValues, hasOnlyNullValues } = validateArrayData(domain);
|
|
83
|
-
if (hasOnlyNullValues || domain.length === 0) {
|
|
84
|
-
return undefined;
|
|
85
|
-
}
|
|
86
|
-
if (series.some((s) => (s.type === 'bar-x' || s.type === 'area') && s.stacking === 'percent')) {
|
|
87
|
-
return scaleLinear().domain([0, 100]).range(range);
|
|
88
|
-
}
|
|
89
|
-
if (hasNumberAndNullValues) {
|
|
90
|
-
const [yMinDomain, yMaxDomain] = extent(domain);
|
|
91
|
-
const isPointDomain = hasOnlyMarkerSeries(series)
|
|
92
|
-
? checkIsPointDomain([yMinDomain, yMaxDomain])
|
|
93
|
-
: false;
|
|
94
|
-
const yMin = typeof yMinPropsOrState === 'number' && !isPointDomain
|
|
95
|
-
? yMinPropsOrState
|
|
96
|
-
: yMinDomain;
|
|
97
|
-
let yMax;
|
|
98
|
-
if (typeof yMaxPropsOrState === 'number' && !isPointDomain) {
|
|
99
|
-
yMax = yMaxPropsOrState;
|
|
100
|
-
}
|
|
101
|
-
else {
|
|
102
|
-
const hasSeriesWithVolumeOnYAxis = series.some((s) => CHART_SERIES_WITH_VOLUME_ON_Y_AXIS.includes(s.type));
|
|
103
|
-
yMax = hasSeriesWithVolumeOnYAxis ? Math.max(yMaxDomain, 0) : yMaxDomain;
|
|
104
|
-
}
|
|
105
|
-
const scaleFn = axis.type === 'logarithmic' ? scaleLog : scaleLinear;
|
|
106
|
-
let scale = scaleFn().domain([yMin, yMax]).range(range);
|
|
107
|
-
if (primaryTickPositions) {
|
|
108
|
-
const syncedDomain = getDomainSyncedToPrimaryTicks({
|
|
109
|
-
primaryTickPositions,
|
|
110
|
-
range,
|
|
111
|
-
scaleFn,
|
|
112
|
-
secondaryDomain: scale.domain(),
|
|
113
|
-
});
|
|
114
|
-
scale.domain(syncedDomain);
|
|
115
|
-
}
|
|
116
|
-
else {
|
|
117
|
-
let offsetMin = 0;
|
|
118
|
-
// We should ignore padding if we are drawing only one point on the plot.
|
|
119
|
-
let offsetMax = yMin === yMax ? 0 : boundsHeight * axis.maxPadding;
|
|
120
|
-
if (isSeriesWithYAxisOffset(series)) {
|
|
121
|
-
if (domain.length > 1) {
|
|
122
|
-
const bandWidth = getBandSize({
|
|
123
|
-
scale: scale,
|
|
124
|
-
domain: domain,
|
|
125
|
-
});
|
|
126
|
-
offsetMin += bandWidth / 2;
|
|
127
|
-
offsetMax += bandWidth / 2;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
const isMinSpecified = typeof get(axis, 'min') === 'number' && !zoomStateY;
|
|
131
|
-
const isMaxSpecified = typeof get(axis, 'max') === 'number' && !zoomStateY;
|
|
132
|
-
const domainOffsetMin = isMinSpecified
|
|
133
|
-
? 0
|
|
134
|
-
: Math.abs(scale.invert(offsetMin) - scale.invert(0));
|
|
135
|
-
const domainOffsetMax = isMaxSpecified
|
|
136
|
-
? 0
|
|
137
|
-
: Math.abs(scale.invert(offsetMax) - scale.invert(0));
|
|
138
|
-
scale = scale.domain([yMin - domainOffsetMin, yMax + domainOffsetMax]);
|
|
139
|
-
}
|
|
140
|
-
// 10 is the default value for the number of ticks. Here, to preserve the appearance of a series with a small number of points
|
|
141
|
-
const nicedDomain = scale.copy().nice(Math.max(10, domain.length)).domain();
|
|
142
|
-
const startOnTick = get(axis, 'startOnTick', false);
|
|
143
|
-
const endOnTick = get(axis, 'endOnTick', false);
|
|
144
|
-
const hasOffset = isSeriesWithYAxisOffset(series);
|
|
145
|
-
if (!zoomStateY && !hasOffset && nicedDomain.length === 2) {
|
|
146
|
-
const domainWithOffset = scale.domain();
|
|
147
|
-
scale.domain([
|
|
148
|
-
startOnTick
|
|
149
|
-
? Math.min(nicedDomain[0], domainWithOffset[0])
|
|
150
|
-
: domainWithOffset[0],
|
|
151
|
-
endOnTick
|
|
152
|
-
? Math.max(nicedDomain[1], domainWithOffset[1])
|
|
153
|
-
: domainWithOffset[1],
|
|
154
|
-
]);
|
|
155
|
-
}
|
|
156
|
-
return scale;
|
|
157
|
-
}
|
|
158
|
-
break;
|
|
159
|
-
}
|
|
160
|
-
case 'category': {
|
|
161
|
-
if (yCategories) {
|
|
162
|
-
const filteredCategories = filterCategoriesByVisibleSeries({
|
|
163
|
-
axisDirection: 'y',
|
|
164
|
-
categories: yCategories,
|
|
165
|
-
series: series,
|
|
166
|
-
});
|
|
167
|
-
return scaleBand().domain(filteredCategories).range(range);
|
|
168
|
-
}
|
|
169
|
-
break;
|
|
170
|
-
}
|
|
171
|
-
case 'datetime': {
|
|
172
|
-
if (yTimestamps) {
|
|
173
|
-
const [yMinTimestamp, yMaxTimestamp] = extent(yTimestamps);
|
|
174
|
-
const isPointDomain = hasOnlyMarkerSeries(series)
|
|
175
|
-
? checkIsPointDomain([yMinTimestamp, yMaxTimestamp])
|
|
176
|
-
: false;
|
|
177
|
-
const yMin = typeof yMinPropsOrState === 'number' &&
|
|
178
|
-
!isPointDomain &&
|
|
179
|
-
yMinPropsOrState > yMinTimestamp
|
|
180
|
-
? yMinPropsOrState
|
|
181
|
-
: yMinTimestamp;
|
|
182
|
-
const yMax = typeof yMaxPropsOrState === 'number' &&
|
|
183
|
-
!isPointDomain &&
|
|
184
|
-
yMaxPropsOrState < yMaxTimestamp
|
|
185
|
-
? yMaxPropsOrState
|
|
186
|
-
: yMaxTimestamp;
|
|
187
|
-
const scale = scaleUtc().domain([yMin, yMax]).range(range);
|
|
188
|
-
const startOnTick = get(axis, 'startOnTick', true);
|
|
189
|
-
const endOnTick = get(axis, 'endOnTick', true);
|
|
190
|
-
if (startOnTick || endOnTick) {
|
|
191
|
-
const nicedDomain = scale.copy().nice().domain();
|
|
192
|
-
return scale.domain([
|
|
193
|
-
startOnTick ? Number(nicedDomain[0]) : yMin,
|
|
194
|
-
endOnTick ? Number(nicedDomain[1]) : yMax,
|
|
195
|
-
]);
|
|
196
|
-
}
|
|
197
|
-
return scale;
|
|
198
|
-
}
|
|
199
|
-
else {
|
|
200
|
-
const domain = getDomainDataYBySeries(series);
|
|
201
|
-
const { hasNumberAndNullValues, hasOnlyNullValues } = validateArrayData(domain);
|
|
202
|
-
if (hasOnlyNullValues || domain.length === 0) {
|
|
203
|
-
return undefined;
|
|
204
|
-
}
|
|
205
|
-
if (hasNumberAndNullValues) {
|
|
206
|
-
const [yMinTimestamp, yMaxTimestamp] = extent(domain);
|
|
207
|
-
const isPointDomain = hasOnlyMarkerSeries(series)
|
|
208
|
-
? checkIsPointDomain([yMinTimestamp, yMaxTimestamp])
|
|
209
|
-
: false;
|
|
210
|
-
const yMin = typeof yMinPropsOrState === 'number' &&
|
|
211
|
-
!isPointDomain &&
|
|
212
|
-
yMinPropsOrState > yMinTimestamp
|
|
213
|
-
? yMinPropsOrState
|
|
214
|
-
: yMinTimestamp;
|
|
215
|
-
const yMax = typeof yMaxPropsOrState === 'number' &&
|
|
216
|
-
!isPointDomain &&
|
|
217
|
-
yMaxPropsOrState < yMaxTimestamp
|
|
218
|
-
? yMaxPropsOrState
|
|
219
|
-
: yMaxTimestamp;
|
|
220
|
-
const scale = scaleUtc().domain([yMin, yMax]).range(range);
|
|
221
|
-
let offsetMin = 0;
|
|
222
|
-
let offsetMax = boundsHeight * axis.maxPadding;
|
|
223
|
-
if (isSeriesWithYAxisOffset(series)) {
|
|
224
|
-
if (Object.keys(domain).length > 1) {
|
|
225
|
-
const bandWidth = getBandSize({
|
|
226
|
-
scale: scale,
|
|
227
|
-
domain: domain,
|
|
228
|
-
});
|
|
229
|
-
offsetMin += bandWidth / 2;
|
|
230
|
-
offsetMax += bandWidth / 2;
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
const isMinSpecified = typeof get(axis, 'min') === 'number' && !zoomStateY;
|
|
234
|
-
const isMaxSpecified = typeof get(axis, 'max') === 'number' && !zoomStateY;
|
|
235
|
-
const domainOffsetMin = isMinSpecified
|
|
236
|
-
? 0
|
|
237
|
-
: Math.abs(scale.invert(offsetMin).getTime() - scale.invert(0).getTime());
|
|
238
|
-
const domainOffsetMax = isMaxSpecified
|
|
239
|
-
? 0
|
|
240
|
-
: Math.abs(scale.invert(offsetMax).getTime() - scale.invert(0).getTime());
|
|
241
|
-
return scale.domain([yMin - domainOffsetMin, yMax + domainOffsetMax]);
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
throw new Error('Failed to create yScale');
|
|
247
|
-
}
|
|
248
|
-
function calculateXAxisPadding(series) {
|
|
249
|
-
let result = 0;
|
|
250
|
-
series.forEach((s) => {
|
|
251
|
-
var _a, _b;
|
|
252
|
-
switch (s.type) {
|
|
253
|
-
case 'bar-y': {
|
|
254
|
-
// Since labels can be located to the right of the bar, need to add an additional space
|
|
255
|
-
const inside = get(s, 'dataLabels.inside');
|
|
256
|
-
if (!inside) {
|
|
257
|
-
const labelsMaxWidth = get(s, 'dataLabels.maxWidth', 0) + ((_b = (_a = s.dataLabels) === null || _a === void 0 ? void 0 : _a.padding) !== null && _b !== void 0 ? _b : 0);
|
|
258
|
-
result = Math.max(result, labelsMaxWidth);
|
|
259
|
-
}
|
|
260
|
-
break;
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
});
|
|
264
|
-
return result;
|
|
265
|
-
}
|
|
266
|
-
function isSeriesWithXAxisOffset(series) {
|
|
267
|
-
const types = [SERIES_TYPE.Heatmap, SERIES_TYPE.BarX];
|
|
268
|
-
return series.some((s) => types.includes(s.type));
|
|
269
|
-
}
|
|
270
|
-
function getXScaleRange({ boundsWidth, hasZoomX }) {
|
|
271
|
-
const xAxisZoomPadding = boundsWidth * X_AXIS_ZOOM_PADDING;
|
|
272
|
-
const xRange = [0, boundsWidth];
|
|
273
|
-
const xRangeZoom = [0 + xAxisZoomPadding, boundsWidth - xAxisZoomPadding];
|
|
274
|
-
const range = hasZoomX ? xRangeZoom : xRange;
|
|
275
|
-
return range;
|
|
276
|
-
}
|
|
277
|
-
// eslint-disable-next-line complexity
|
|
278
|
-
export function createXScale(args) {
|
|
279
|
-
const { axis, boundsWidth, series, rangeSliderState, zoomStateX } = args;
|
|
280
|
-
const [xMinPropsOrState, xMaxPropsOrState] = getMinMaxPropsOrState({
|
|
281
|
-
axis,
|
|
282
|
-
maxValues: [zoomStateX === null || zoomStateX === void 0 ? void 0 : zoomStateX[1], rangeSliderState === null || rangeSliderState === void 0 ? void 0 : rangeSliderState.max],
|
|
283
|
-
minValues: [zoomStateX === null || zoomStateX === void 0 ? void 0 : zoomStateX[0], rangeSliderState === null || rangeSliderState === void 0 ? void 0 : rangeSliderState.min],
|
|
284
|
-
});
|
|
285
|
-
const xType = get(axis, 'type', DEFAULT_AXIS_TYPE);
|
|
286
|
-
const hasZoomX = Boolean(zoomStateX);
|
|
287
|
-
let xCategories = get(axis, 'categories');
|
|
288
|
-
if (rangeSliderState && xCategories) {
|
|
289
|
-
xCategories = getAxisCategories({
|
|
290
|
-
categories: xCategories,
|
|
291
|
-
min: rangeSliderState.min,
|
|
292
|
-
max: rangeSliderState.max,
|
|
293
|
-
order: axis.order,
|
|
294
|
-
});
|
|
295
|
-
}
|
|
296
|
-
const maxPadding = get(axis, 'maxPadding', 0);
|
|
297
|
-
const xAxisMaxPadding = boundsWidth * maxPadding + calculateXAxisPadding(series);
|
|
298
|
-
const range = getXScaleRange({
|
|
299
|
-
boundsWidth,
|
|
300
|
-
hasZoomX,
|
|
301
|
-
});
|
|
302
|
-
switch (axis.order) {
|
|
303
|
-
case 'sortDesc':
|
|
304
|
-
case 'reverse': {
|
|
305
|
-
range.reverse();
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
switch (xType) {
|
|
309
|
-
case 'linear':
|
|
310
|
-
case 'logarithmic': {
|
|
311
|
-
const domainData = getDomainDataXBySeries(series);
|
|
312
|
-
const { hasNumberAndNullValues, hasOnlyNullValues } = validateArrayData(domainData);
|
|
313
|
-
if (hasOnlyNullValues || domainData.length === 0) {
|
|
314
|
-
return undefined;
|
|
315
|
-
}
|
|
316
|
-
if (series.some((s) => s.type === 'bar-y' && s.stacking === 'percent')) {
|
|
317
|
-
return scaleLinear().domain([0, 100]).range(range);
|
|
318
|
-
}
|
|
319
|
-
if (hasNumberAndNullValues) {
|
|
320
|
-
const [xMinDomain, xMaxDomain] = extent(domainData);
|
|
321
|
-
const isPointDomain = hasOnlyMarkerSeries(series)
|
|
322
|
-
? checkIsPointDomain([xMinDomain, xMaxDomain])
|
|
323
|
-
: false;
|
|
324
|
-
let xMin;
|
|
325
|
-
let xMax;
|
|
326
|
-
if (typeof xMinPropsOrState === 'number' && !isPointDomain) {
|
|
327
|
-
xMin = xMinPropsOrState;
|
|
328
|
-
}
|
|
329
|
-
else if (xType === 'logarithmic') {
|
|
330
|
-
xMin = xMinDomain;
|
|
331
|
-
}
|
|
332
|
-
else {
|
|
333
|
-
const xMinDefault = getDefaultMinXAxisValue(series);
|
|
334
|
-
xMin = xMinDefault !== null && xMinDefault !== void 0 ? xMinDefault : xMinDomain;
|
|
335
|
-
}
|
|
336
|
-
if (typeof xMaxPropsOrState === 'number' && !isPointDomain) {
|
|
337
|
-
xMax = xMaxPropsOrState;
|
|
338
|
-
}
|
|
339
|
-
else {
|
|
340
|
-
const xMaxDefault = getDefaultMaxXAxisValue(series);
|
|
341
|
-
xMax =
|
|
342
|
-
typeof xMaxDefault === 'number'
|
|
343
|
-
? Math.max(xMaxDefault, xMaxDomain)
|
|
344
|
-
: xMaxDomain;
|
|
345
|
-
}
|
|
346
|
-
const scaleFn = xType === 'logarithmic' ? scaleLog : scaleLinear;
|
|
347
|
-
const scale = scaleFn().domain([xMin, xMax]).range(range);
|
|
348
|
-
let offsetMin = 0;
|
|
349
|
-
let offsetMax = xAxisMaxPadding;
|
|
350
|
-
const hasOffset = isSeriesWithXAxisOffset(series);
|
|
351
|
-
if (hasOffset) {
|
|
352
|
-
if (domainData.length > 1) {
|
|
353
|
-
const bandWidth = getBandSize({
|
|
354
|
-
scale: scale,
|
|
355
|
-
domain: domainData,
|
|
356
|
-
});
|
|
357
|
-
offsetMin += bandWidth / 2;
|
|
358
|
-
offsetMax += bandWidth / 2;
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
const isMinSpecified = typeof get(axis, 'min') === 'number' && !zoomStateX;
|
|
362
|
-
const isMaxSpecified = typeof get(axis, 'max') === 'number' && !zoomStateX;
|
|
363
|
-
const domainOffsetMin = isMinSpecified
|
|
364
|
-
? 0
|
|
365
|
-
: Math.abs(scale.invert(offsetMin) - scale.invert(0));
|
|
366
|
-
const domainOffsetMax = isMaxSpecified
|
|
367
|
-
? 0
|
|
368
|
-
: Math.abs(scale.invert(offsetMax) - scale.invert(0));
|
|
369
|
-
// 10 is the default value for the number of ticks. Here, to preserve the appearance of a series with a small number of points
|
|
370
|
-
const nicedDomain = scale.copy().nice(Math.max(10, domainData.length)).domain();
|
|
371
|
-
scale.domain([xMin - domainOffsetMin, xMax + domainOffsetMax]);
|
|
372
|
-
const startOnTick = get(axis, 'startOnTick', true);
|
|
373
|
-
const endOnTick = get(axis, 'endOnTick', true);
|
|
374
|
-
if (!hasZoomX && !hasOffset && nicedDomain.length === 2) {
|
|
375
|
-
const domainWithOffset = scale.domain();
|
|
376
|
-
scale.domain([
|
|
377
|
-
startOnTick
|
|
378
|
-
? Math.min(nicedDomain[0], domainWithOffset[0])
|
|
379
|
-
: domainWithOffset[0],
|
|
380
|
-
endOnTick
|
|
381
|
-
? Math.max(nicedDomain[1], domainWithOffset[1])
|
|
382
|
-
: domainWithOffset[1],
|
|
383
|
-
]);
|
|
384
|
-
}
|
|
385
|
-
return scale;
|
|
386
|
-
}
|
|
387
|
-
break;
|
|
388
|
-
}
|
|
389
|
-
case 'category': {
|
|
390
|
-
if (xCategories) {
|
|
391
|
-
const filteredCategories = filterCategoriesByVisibleSeries({
|
|
392
|
-
axisDirection: 'x',
|
|
393
|
-
categories: xCategories,
|
|
394
|
-
series: series,
|
|
395
|
-
});
|
|
396
|
-
const xScale = scaleBand().domain(filteredCategories).range([0, boundsWidth]);
|
|
397
|
-
if (xScale.step() / 2 < xAxisMaxPadding) {
|
|
398
|
-
xScale.range(range);
|
|
399
|
-
}
|
|
400
|
-
return xScale;
|
|
401
|
-
}
|
|
402
|
-
break;
|
|
403
|
-
}
|
|
404
|
-
case 'datetime': {
|
|
405
|
-
let domain = null;
|
|
406
|
-
const domainData = get(axis, 'timestamps') || getDomainDataXBySeries(series);
|
|
407
|
-
const { hasNumberAndNullValues, hasOnlyNullValues } = validateArrayData(domainData);
|
|
408
|
-
if (hasOnlyNullValues || domainData.length === 0) {
|
|
409
|
-
return undefined;
|
|
410
|
-
}
|
|
411
|
-
if (hasNumberAndNullValues) {
|
|
412
|
-
const [xMinTimestamp, xMaxTimestamp] = extent(domainData);
|
|
413
|
-
const isPointDomain = checkIsPointDomain([xMinTimestamp, xMaxTimestamp]);
|
|
414
|
-
const xMin = typeof xMinPropsOrState === 'number' &&
|
|
415
|
-
xMinPropsOrState > xMinTimestamp &&
|
|
416
|
-
!isPointDomain
|
|
417
|
-
? xMinPropsOrState
|
|
418
|
-
: xMinTimestamp;
|
|
419
|
-
const xMax = getXMaxDomainResult({
|
|
420
|
-
xMaxDomain: xMaxTimestamp,
|
|
421
|
-
xMaxProps: get(axis, 'max'),
|
|
422
|
-
xMaxRangeSlider: rangeSliderState === null || rangeSliderState === void 0 ? void 0 : rangeSliderState.max,
|
|
423
|
-
xMaxZoom: zoomStateX === null || zoomStateX === void 0 ? void 0 : zoomStateX[1],
|
|
424
|
-
});
|
|
425
|
-
domain = [xMin, xMax];
|
|
426
|
-
const scale = scaleUtc().domain(domain).range(range);
|
|
427
|
-
let offsetMin = 0;
|
|
428
|
-
let offsetMax = xAxisMaxPadding;
|
|
429
|
-
const hasOffset = isSeriesWithXAxisOffset(series);
|
|
430
|
-
if (hasOffset) {
|
|
431
|
-
if (domainData.length > 1) {
|
|
432
|
-
const bandWidth = getBandSize({
|
|
433
|
-
scale: scale,
|
|
434
|
-
domain: domainData,
|
|
435
|
-
});
|
|
436
|
-
offsetMin += bandWidth / 2;
|
|
437
|
-
offsetMax += bandWidth / 2;
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
const isMinSpecified = typeof get(axis, 'min') === 'number' && !zoomStateX;
|
|
441
|
-
const isMaxSpecified = typeof get(axis, 'max') === 'number' && !zoomStateX;
|
|
442
|
-
const domainOffsetMin = isMinSpecified
|
|
443
|
-
? 0
|
|
444
|
-
: Math.abs(scale.invert(offsetMin).getTime() - scale.invert(0).getTime());
|
|
445
|
-
const domainOffsetMax = isMaxSpecified
|
|
446
|
-
? 0
|
|
447
|
-
: Math.abs(scale.invert(offsetMax).getTime() - scale.invert(0).getTime());
|
|
448
|
-
// 10 is the default value for the number of ticks. Here, to preserve the appearance of a series with a small number of points
|
|
449
|
-
const nicedDomain = scale.copy().nice(Math.max(10, domainData.length)).domain();
|
|
450
|
-
scale.domain([xMin - domainOffsetMin, xMax + domainOffsetMax]);
|
|
451
|
-
const startOnTick = get(axis, 'startOnTick', true);
|
|
452
|
-
const endOnTick = get(axis, 'endOnTick', true);
|
|
453
|
-
if (!hasZoomX && !hasOffset && nicedDomain.length === 2) {
|
|
454
|
-
const domainWithOffset = scale.domain();
|
|
455
|
-
scale.domain([
|
|
456
|
-
startOnTick
|
|
457
|
-
? Math.min(Number(nicedDomain[0]), Number(domainWithOffset[0]))
|
|
458
|
-
: Number(domainWithOffset[0]),
|
|
459
|
-
endOnTick
|
|
460
|
-
? Math.max(Number(nicedDomain[1]), Number(domainWithOffset[1]))
|
|
461
|
-
: Number(domainWithOffset[1]),
|
|
462
|
-
]);
|
|
463
|
-
}
|
|
464
|
-
return scale;
|
|
465
|
-
}
|
|
466
|
-
break;
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
throw new Error('Failed to create xScale');
|
|
470
|
-
}
|
|
4
|
+
import { getAxisHeight, getOnlyVisibleSeries, isAxisRelatedSeries } from '../../utils';
|
|
5
|
+
import { clusterYAxes } from './utils';
|
|
6
|
+
import { createXScale } from './x-scale';
|
|
7
|
+
import { createYScale } from './y-scale';
|
|
8
|
+
export { createXScale } from './x-scale';
|
|
9
|
+
export { createYScale } from './y-scale';
|
|
471
10
|
const createScales = (args) => {
|
|
472
11
|
const { boundsWidth, boundsHeight, isRangeSlider, rangeSliderState, series, split, xAxis, yAxis, zoomState, } = args;
|
|
473
12
|
let visibleSeries = getOnlyVisibleSeries(series);
|
|
@@ -479,29 +18,31 @@ const createScales = (args) => {
|
|
|
479
18
|
const yScale = clusterYAxes(yAxis).reduce((acc, cluster) => {
|
|
480
19
|
var _a, _b;
|
|
481
20
|
const [primaryAxis, secondaryAxis] = cluster;
|
|
482
|
-
const
|
|
21
|
+
const primaryAxisSeries = series.filter((s) => {
|
|
483
22
|
const seriesAxisIndex = get(s, 'yAxis', 0);
|
|
484
23
|
return seriesAxisIndex === index;
|
|
485
24
|
});
|
|
486
|
-
const visiblePrimaryAxisSeries = getOnlyVisibleSeries(
|
|
25
|
+
const visiblePrimaryAxisSeries = getOnlyVisibleSeries(primaryAxisSeries);
|
|
487
26
|
const primaryAxisScale = createYScale({
|
|
488
27
|
axis: primaryAxis,
|
|
489
28
|
boundsHeight: axisHeight,
|
|
490
|
-
series: visiblePrimaryAxisSeries.length
|
|
29
|
+
series: visiblePrimaryAxisSeries.length
|
|
30
|
+
? visiblePrimaryAxisSeries
|
|
31
|
+
: primaryAxisSeries,
|
|
491
32
|
zoomStateY: (_a = zoomState === null || zoomState === void 0 ? void 0 : zoomState.y) === null || _a === void 0 ? void 0 : _a[index],
|
|
492
33
|
});
|
|
493
34
|
acc.push(primaryAxisScale);
|
|
494
35
|
index += 1;
|
|
495
|
-
let
|
|
36
|
+
let primaryTicksCount;
|
|
496
37
|
if (primaryAxisScale && secondaryAxis && !isRangeSlider) {
|
|
497
|
-
|
|
38
|
+
primaryTicksCount = getTickValues({
|
|
498
39
|
axis: primaryAxis,
|
|
499
40
|
scale: primaryAxisScale,
|
|
500
41
|
labelLineHeight: primaryAxis.labels.lineHeight,
|
|
501
42
|
series: visiblePrimaryAxisSeries.length
|
|
502
43
|
? visiblePrimaryAxisSeries
|
|
503
|
-
:
|
|
504
|
-
}).
|
|
44
|
+
: primaryAxisSeries,
|
|
45
|
+
}).length;
|
|
505
46
|
}
|
|
506
47
|
const secondAxisSeries = series.filter((s) => {
|
|
507
48
|
const seriesAxisIndex = get(s, 'yAxis', 0);
|
|
@@ -512,7 +53,8 @@ const createScales = (args) => {
|
|
|
512
53
|
? createYScale({
|
|
513
54
|
axis: secondaryAxis,
|
|
514
55
|
boundsHeight: axisHeight,
|
|
515
|
-
|
|
56
|
+
primaryAxis,
|
|
57
|
+
primaryTicksCount,
|
|
516
58
|
series: visibleSecondAxisSeries.length
|
|
517
59
|
? visibleSecondAxisSeries
|
|
518
60
|
: secondAxisSeries,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { scaleLinear } from 'd3';
|
|
2
1
|
import type { PreparedAxis, PreparedSeries, PreparedYAxis } from '../../hooks';
|
|
3
2
|
import type { ChartAxis, ChartSeries } from '../../types';
|
|
3
|
+
import type { AxisDirection } from '../../utils';
|
|
4
4
|
type OptionalNumber = number | undefined;
|
|
5
5
|
export declare function getMinMaxPropsOrState(args: {
|
|
6
6
|
axis: PreparedAxis | ChartAxis;
|
|
@@ -17,17 +17,14 @@ export declare function getMinMaxPropsOrState(args: {
|
|
|
17
17
|
*/
|
|
18
18
|
export declare function checkIsPointDomain(domain: [number, number]): boolean;
|
|
19
19
|
export declare function hasOnlyMarkerSeries(series: (PreparedSeries | ChartSeries)[]): boolean;
|
|
20
|
-
export declare function getXMaxDomainResult(args: {
|
|
21
|
-
xMaxDomain: number;
|
|
22
|
-
xMaxProps?: number;
|
|
23
|
-
xMaxRangeSlider?: number;
|
|
24
|
-
xMaxZoom?: number;
|
|
25
|
-
}): number;
|
|
26
20
|
export declare function clusterYAxes(yAxes: PreparedYAxis[]): [PreparedYAxis, PreparedYAxis?][];
|
|
27
|
-
export declare function
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
21
|
+
export declare function validateArrayData(data: unknown[]): {
|
|
22
|
+
hasNumberAndNullValues: boolean | undefined;
|
|
23
|
+
hasOnlyNullValues: boolean | undefined;
|
|
24
|
+
};
|
|
25
|
+
export declare function filterCategoriesByVisibleSeries(args: {
|
|
26
|
+
axisDirection: AxisDirection;
|
|
27
|
+
categories: string[];
|
|
28
|
+
series: (PreparedSeries | ChartSeries)[];
|
|
29
|
+
}): string[];
|
|
33
30
|
export {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { ticks } from 'd3';
|
|
2
1
|
import get from 'lodash/get';
|
|
3
2
|
import { SERIES_TYPE } from '../../constants';
|
|
3
|
+
import { getDataCategoryValue, isSeriesWithCategoryValues } from '../../utils';
|
|
4
4
|
const MARKER_SERIES_TYPES = [SERIES_TYPE.Area, SERIES_TYPE.Line, SERIES_TYPE.Scatter];
|
|
5
5
|
function getNormilizedMinMax(args) {
|
|
6
6
|
const { maxValues, minValues } = args;
|
|
@@ -33,23 +33,6 @@ export function checkIsPointDomain(domain) {
|
|
|
33
33
|
export function hasOnlyMarkerSeries(series) {
|
|
34
34
|
return series.every((s) => MARKER_SERIES_TYPES.includes(s.type));
|
|
35
35
|
}
|
|
36
|
-
export function getXMaxDomainResult(args) {
|
|
37
|
-
const { xMaxDomain, xMaxProps, xMaxRangeSlider, xMaxZoom } = args;
|
|
38
|
-
let xMaxDomainResult = xMaxDomain;
|
|
39
|
-
// When xMaxRangeSlider is provided, we use it directly without considering xMaxDomain.
|
|
40
|
-
// This is intentional: the range slider needs to display the chart's maxPadding area,
|
|
41
|
-
// which would be clipped if we constrained it to xMaxDomain.
|
|
42
|
-
if (typeof xMaxRangeSlider === 'number') {
|
|
43
|
-
xMaxDomainResult = xMaxRangeSlider;
|
|
44
|
-
}
|
|
45
|
-
else if (typeof xMaxZoom === 'number' && xMaxZoom < xMaxDomain) {
|
|
46
|
-
xMaxDomainResult = xMaxZoom;
|
|
47
|
-
}
|
|
48
|
-
else if (typeof xMaxProps === 'number' && xMaxProps < xMaxDomain) {
|
|
49
|
-
xMaxDomainResult = xMaxProps;
|
|
50
|
-
}
|
|
51
|
-
return xMaxDomainResult;
|
|
52
|
-
}
|
|
53
36
|
export function clusterYAxes(yAxes) {
|
|
54
37
|
if (yAxes.length <= 1) {
|
|
55
38
|
return yAxes.map((axis) => [axis]);
|
|
@@ -75,51 +58,34 @@ export function clusterYAxes(yAxes) {
|
|
|
75
58
|
return [cluster[0], cluster[1]];
|
|
76
59
|
});
|
|
77
60
|
}
|
|
78
|
-
export function
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
let step = originalStep;
|
|
94
|
-
if (typeof secondaryTicks[0] === 'number' && typeof secondaryTicks[1] === 'number') {
|
|
95
|
-
step = secondaryTicks[1] - secondaryTicks[0];
|
|
96
|
-
}
|
|
97
|
-
let ticksCountDiff = primaryTickPositions.length - secondaryTicks.length;
|
|
98
|
-
let deltaMin = Math.abs(dMin - secondaryTicks[0]);
|
|
99
|
-
let deltaMax = Math.abs(dMax - secondaryTicks[secondaryTicks.length - 1]);
|
|
100
|
-
while (ticksCountDiff > 0) {
|
|
101
|
-
if (deltaMin > deltaMax) {
|
|
102
|
-
secondaryTicks.unshift(secondaryTicks[0] - step);
|
|
103
|
-
deltaMin -= step;
|
|
104
|
-
}
|
|
105
|
-
else {
|
|
106
|
-
secondaryTicks.push(secondaryTicks[secondaryTicks.length - 1] + step);
|
|
107
|
-
deltaMax -= step;
|
|
61
|
+
export function validateArrayData(data) {
|
|
62
|
+
let hasNumberAndNullValues;
|
|
63
|
+
let hasOnlyNullValues;
|
|
64
|
+
for (const d of data) {
|
|
65
|
+
const isNumber = typeof d === 'number';
|
|
66
|
+
const isNull = d === null;
|
|
67
|
+
hasNumberAndNullValues =
|
|
68
|
+
typeof hasNumberAndNullValues === 'undefined'
|
|
69
|
+
? isNumber || isNull
|
|
70
|
+
: hasNumberAndNullValues && (isNumber || isNull);
|
|
71
|
+
hasOnlyNullValues =
|
|
72
|
+
typeof hasOnlyNullValues === 'undefined' ? isNull : hasOnlyNullValues && isNull;
|
|
73
|
+
if (!hasNumberAndNullValues) {
|
|
74
|
+
break;
|
|
108
75
|
}
|
|
109
|
-
ticksCountDiff -= 1;
|
|
110
|
-
}
|
|
111
|
-
let tmpScale = scaleFn()
|
|
112
|
-
.domain([secondaryTicks[0], secondaryTicks[secondaryTicks.length - 1]])
|
|
113
|
-
.range([primaryPosBottom, primaryPosTop]);
|
|
114
|
-
let dNewMin = tmpScale.invert(range[0]);
|
|
115
|
-
let dNewMax = tmpScale.invert(range[1]);
|
|
116
|
-
if (dNewMin < dMin) {
|
|
117
|
-
secondaryTicks = secondaryTicks.map((st) => st + step);
|
|
118
|
-
tmpScale = scaleFn()
|
|
119
|
-
.domain([secondaryTicks[0], secondaryTicks[secondaryTicks.length - 1]])
|
|
120
|
-
.range([primaryPosBottom, primaryPosTop]);
|
|
121
|
-
dNewMin = tmpScale.invert(range[0]);
|
|
122
|
-
dNewMax = tmpScale.invert(range[1]);
|
|
123
76
|
}
|
|
124
|
-
return
|
|
77
|
+
return { hasNumberAndNullValues, hasOnlyNullValues };
|
|
78
|
+
}
|
|
79
|
+
export function filterCategoriesByVisibleSeries(args) {
|
|
80
|
+
const { axisDirection, categories, series } = args;
|
|
81
|
+
const visibleCategories = new Set();
|
|
82
|
+
series.forEach((s) => {
|
|
83
|
+
if (isSeriesWithCategoryValues(s)) {
|
|
84
|
+
s.data.forEach((d) => {
|
|
85
|
+
visibleCategories.add(getDataCategoryValue({ axisDirection, categories, data: d }));
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
const filteredCategories = categories.filter((c) => visibleCategories.has(c));
|
|
90
|
+
return filteredCategories.length > 0 ? filteredCategories : categories;
|
|
125
91
|
}
|