@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.
Files changed (49) hide show
  1. package/dist/cjs/components/ChartInner/useChartInnerProps.js +3 -2
  2. package/dist/cjs/components/Tooltip/DefaultTooltipContent/index.js +5 -1
  3. package/dist/cjs/hooks/index.d.ts +1 -0
  4. package/dist/cjs/hooks/index.js +1 -0
  5. package/dist/cjs/hooks/useAxis/index.d.ts +5 -3
  6. package/dist/cjs/hooks/useAxis/index.js +3 -3
  7. package/dist/cjs/hooks/useAxis/types.d.ts +6 -0
  8. package/dist/cjs/hooks/useAxis/y-axis.d.ts +10 -0
  9. package/dist/cjs/hooks/useAxis/y-axis.js +30 -21
  10. package/dist/cjs/hooks/useAxisScales/index.d.ts +2 -16
  11. package/dist/cjs/hooks/useAxisScales/index.js +17 -475
  12. package/dist/cjs/hooks/useAxisScales/utils.d.ts +10 -13
  13. package/dist/cjs/hooks/useAxisScales/utils.js +29 -63
  14. package/dist/cjs/hooks/useAxisScales/x-scale.d.ts +15 -0
  15. package/dist/cjs/hooks/useAxisScales/x-scale.js +247 -0
  16. package/dist/cjs/hooks/useAxisScales/y-scale.d.ts +10 -0
  17. package/dist/cjs/hooks/useAxisScales/y-scale.js +299 -0
  18. package/dist/cjs/hooks/useYAxisLabelWidth/index.d.ts +11 -0
  19. package/dist/cjs/hooks/useYAxisLabelWidth/index.js +48 -0
  20. package/dist/cjs/hooks/utils/bar-x.js +1 -1
  21. package/dist/cjs/hooks/utils/bar-y.js +1 -1
  22. package/dist/cjs/types/chart/tooltip.d.ts +3 -2
  23. package/dist/cjs/utils/chart/axis/common.d.ts +1 -0
  24. package/dist/cjs/utils/chart/axis/common.js +6 -0
  25. package/dist/esm/components/ChartInner/useChartInnerProps.js +3 -2
  26. package/dist/esm/components/Tooltip/DefaultTooltipContent/index.js +5 -1
  27. package/dist/esm/hooks/index.d.ts +1 -0
  28. package/dist/esm/hooks/index.js +1 -0
  29. package/dist/esm/hooks/useAxis/index.d.ts +5 -3
  30. package/dist/esm/hooks/useAxis/index.js +3 -3
  31. package/dist/esm/hooks/useAxis/types.d.ts +6 -0
  32. package/dist/esm/hooks/useAxis/y-axis.d.ts +10 -0
  33. package/dist/esm/hooks/useAxis/y-axis.js +30 -21
  34. package/dist/esm/hooks/useAxisScales/index.d.ts +2 -16
  35. package/dist/esm/hooks/useAxisScales/index.js +17 -475
  36. package/dist/esm/hooks/useAxisScales/utils.d.ts +10 -13
  37. package/dist/esm/hooks/useAxisScales/utils.js +29 -63
  38. package/dist/esm/hooks/useAxisScales/x-scale.d.ts +15 -0
  39. package/dist/esm/hooks/useAxisScales/x-scale.js +247 -0
  40. package/dist/esm/hooks/useAxisScales/y-scale.d.ts +10 -0
  41. package/dist/esm/hooks/useAxisScales/y-scale.js +299 -0
  42. package/dist/esm/hooks/useYAxisLabelWidth/index.d.ts +11 -0
  43. package/dist/esm/hooks/useYAxisLabelWidth/index.js +48 -0
  44. package/dist/esm/hooks/utils/bar-x.js +1 -1
  45. package/dist/esm/hooks/utils/bar-y.js +1 -1
  46. package/dist/esm/types/chart/tooltip.d.ts +3 -2
  47. package/dist/esm/utils/chart/axis/common.d.ts +1 -0
  48. package/dist/esm/utils/chart/axis/common.js +6 -0
  49. 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 { DEFAULT_AXIS_TYPE, SERIES_TYPE } from '../../constants';
6
- import { CHART_SERIES_WITH_VOLUME_ON_Y_AXIS, getAxisCategories, getAxisHeight, getDataCategoryValue, getDefaultMaxXAxisValue, getDefaultMinXAxisValue, getDomainDataXBySeries, getDomainDataYBySeries, getOnlyVisibleSeries, isAxisRelatedSeries, isSeriesWithCategoryValues, } from '../../utils';
7
- import { getBandSize } from '../utils/get-band-size';
8
- import { checkIsPointDomain, clusterYAxes, getDomainSyncedToPrimaryTicks, getMinMaxPropsOrState, getXMaxDomainResult, hasOnlyMarkerSeries, } from './utils';
9
- const X_AXIS_ZOOM_PADDING = 0.02;
10
- function validateArrayData(data) {
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 mainAxisSeries = series.filter((s) => {
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(mainAxisSeries);
25
+ const visiblePrimaryAxisSeries = getOnlyVisibleSeries(primaryAxisSeries);
487
26
  const primaryAxisScale = createYScale({
488
27
  axis: primaryAxis,
489
28
  boundsHeight: axisHeight,
490
- series: visiblePrimaryAxisSeries.length ? visiblePrimaryAxisSeries : mainAxisSeries,
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 primaryTickPositions;
36
+ let primaryTicksCount;
496
37
  if (primaryAxisScale && secondaryAxis && !isRangeSlider) {
497
- primaryTickPositions = getTickValues({
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
- : mainAxisSeries,
504
- }).map((t) => t.y);
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
- primaryTickPositions,
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 getDomainSyncedToPrimaryTicks(args: {
28
- primaryTickPositions: number[];
29
- range: [number, number];
30
- scaleFn: typeof scaleLinear;
31
- secondaryDomain: number[];
32
- }): [number, number];
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 getDomainSyncedToPrimaryTicks(args) {
79
- const { primaryTickPositions, range, scaleFn, secondaryDomain } = args;
80
- const [dMin, dMax] = secondaryDomain;
81
- const primaryPosBottom = primaryTickPositions[0];
82
- const primaryPosTop = primaryTickPositions[primaryTickPositions.length - 1];
83
- let secondaryTicks = ticks(dMin, dMax, primaryTickPositions.length);
84
- let originalStep = 0;
85
- if (typeof secondaryTicks[0] === 'number' && typeof secondaryTicks[1] === 'number') {
86
- originalStep = secondaryTicks[1] - secondaryTicks[0];
87
- }
88
- let i = 1;
89
- while (secondaryTicks.length > primaryTickPositions.length) {
90
- secondaryTicks = ticks(dMin, dMax, primaryTickPositions.length - i);
91
- i += 1;
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 [dNewMin, dNewMax];
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
  }