@gravity-ui/charts 1.32.1 → 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 +2 -0
- package/dist/cjs/hooks/index.js +2 -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/x-axis.js +2 -0
- package/dist/cjs/hooks/useAxis/y-axis.d.ts +10 -0
- package/dist/cjs/hooks/useAxis/y-axis.js +32 -21
- package/dist/cjs/hooks/useAxisScales/index.d.ts +4 -20
- package/dist/cjs/hooks/useAxisScales/index.js +76 -436
- package/dist/cjs/hooks/useAxisScales/types.d.ts +6 -0
- package/dist/cjs/hooks/useAxisScales/types.js +1 -0
- package/dist/cjs/hooks/useAxisScales/utils.d.ts +12 -7
- package/dist/cjs/hooks/useAxisScales/utils.js +54 -14
- 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/useNormalizedOriginalData/index.d.ts +2 -0
- package/dist/cjs/hooks/useRangeSlider/index.js +1 -0
- package/dist/cjs/hooks/useRangeSlider/types.d.ts +1 -1
- package/dist/cjs/hooks/useRangeSlider/utils.d.ts +1 -1
- package/dist/cjs/hooks/useShapes/area/prepare-data.d.ts +1 -1
- package/dist/cjs/hooks/useShapes/area/prepare-data.js +25 -11
- package/dist/cjs/hooks/useShapes/bar-x/prepare-data.d.ts +1 -1
- package/dist/cjs/hooks/useShapes/bar-y/prepare-data.d.ts +1 -1
- package/dist/cjs/hooks/useShapes/heatmap/prepare-data.d.ts +1 -1
- package/dist/cjs/hooks/useShapes/index.d.ts +1 -1
- package/dist/cjs/hooks/useShapes/line/prepare-data.d.ts +1 -1
- package/dist/cjs/hooks/useShapes/scatter/prepare-data.d.ts +1 -1
- package/dist/cjs/hooks/useShapes/utils.d.ts +1 -1
- package/dist/cjs/hooks/useShapes/waterfall/prepare-data.d.ts +1 -1
- package/dist/cjs/hooks/useYAxisLabelWidth/index.d.ts +11 -0
- package/dist/cjs/hooks/useYAxisLabelWidth/index.js +48 -0
- package/dist/cjs/hooks/useZoom/index.d.ts +1 -1
- package/dist/cjs/hooks/useZoom/utils.d.ts +1 -1
- package/dist/cjs/hooks/utils/bar-x.d.ts +1 -1
- package/dist/cjs/hooks/utils/bar-x.js +1 -1
- package/dist/cjs/hooks/utils/bar-y.d.ts +1 -1
- package/dist/cjs/hooks/utils/bar-y.js +1 -1
- package/dist/cjs/i18n/keysets/en.json +2 -1
- package/dist/cjs/i18n/keysets/ru.json +2 -1
- package/dist/cjs/types/chart/axis.d.ts +25 -0
- 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/cjs/validation/validate-axes.js +35 -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 +2 -0
- package/dist/esm/hooks/index.js +2 -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/x-axis.js +2 -0
- package/dist/esm/hooks/useAxis/y-axis.d.ts +10 -0
- package/dist/esm/hooks/useAxis/y-axis.js +32 -21
- package/dist/esm/hooks/useAxisScales/index.d.ts +4 -20
- package/dist/esm/hooks/useAxisScales/index.js +76 -436
- package/dist/esm/hooks/useAxisScales/types.d.ts +6 -0
- package/dist/esm/hooks/useAxisScales/types.js +1 -0
- package/dist/esm/hooks/useAxisScales/utils.d.ts +12 -7
- package/dist/esm/hooks/useAxisScales/utils.js +54 -14
- 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/useNormalizedOriginalData/index.d.ts +2 -0
- package/dist/esm/hooks/useRangeSlider/index.js +1 -0
- package/dist/esm/hooks/useRangeSlider/types.d.ts +1 -1
- package/dist/esm/hooks/useRangeSlider/utils.d.ts +1 -1
- package/dist/esm/hooks/useShapes/area/prepare-data.d.ts +1 -1
- package/dist/esm/hooks/useShapes/area/prepare-data.js +25 -11
- package/dist/esm/hooks/useShapes/bar-x/prepare-data.d.ts +1 -1
- package/dist/esm/hooks/useShapes/bar-y/prepare-data.d.ts +1 -1
- package/dist/esm/hooks/useShapes/heatmap/prepare-data.d.ts +1 -1
- package/dist/esm/hooks/useShapes/index.d.ts +1 -1
- package/dist/esm/hooks/useShapes/line/prepare-data.d.ts +1 -1
- package/dist/esm/hooks/useShapes/scatter/prepare-data.d.ts +1 -1
- package/dist/esm/hooks/useShapes/utils.d.ts +1 -1
- package/dist/esm/hooks/useShapes/waterfall/prepare-data.d.ts +1 -1
- package/dist/esm/hooks/useYAxisLabelWidth/index.d.ts +11 -0
- package/dist/esm/hooks/useYAxisLabelWidth/index.js +48 -0
- package/dist/esm/hooks/useZoom/index.d.ts +1 -1
- package/dist/esm/hooks/useZoom/utils.d.ts +1 -1
- package/dist/esm/hooks/utils/bar-x.d.ts +1 -1
- package/dist/esm/hooks/utils/bar-x.js +1 -1
- package/dist/esm/hooks/utils/bar-y.d.ts +1 -1
- package/dist/esm/hooks/utils/bar-y.js +1 -1
- package/dist/esm/i18n/keysets/en.json +2 -1
- package/dist/esm/i18n/keysets/ru.json +2 -1
- package/dist/esm/types/chart/axis.d.ts +25 -0
- 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/dist/esm/validation/validate-axes.js +35 -0
- package/package.json +1 -1
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { PreparedAxis, PreparedSeries, RangeSliderState } from '../../hooks';
|
|
2
|
+
import type { ChartAxis, ChartSeries } from '../../types';
|
|
3
|
+
export declare function getXMaxDomainResult(args: {
|
|
4
|
+
xMaxDomain: number;
|
|
5
|
+
xMaxProps?: number;
|
|
6
|
+
xMaxRangeSlider?: number;
|
|
7
|
+
xMaxZoom?: number;
|
|
8
|
+
}): number;
|
|
9
|
+
export declare function createXScale(args: {
|
|
10
|
+
axis: PreparedAxis | ChartAxis;
|
|
11
|
+
boundsWidth: number;
|
|
12
|
+
series: (PreparedSeries | ChartSeries)[];
|
|
13
|
+
rangeSliderState?: RangeSliderState;
|
|
14
|
+
zoomStateX?: [number, number];
|
|
15
|
+
}): import("d3-scale").ScaleBand<string> | import("d3-scale").ScaleLinear<number, number, never> | import("d3-scale").ScaleTime<number, number, never> | undefined;
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { extent, scaleBand, scaleLinear, scaleLog, scaleUtc } from 'd3';
|
|
2
|
+
import get from 'lodash/get';
|
|
3
|
+
import { DEFAULT_AXIS_TYPE, SERIES_TYPE } from '../../constants';
|
|
4
|
+
import { getAxisCategories, getDefaultMaxXAxisValue, getDefaultMinXAxisValue, getDomainDataXBySeries, } from '../../utils';
|
|
5
|
+
import { getBandSize } from '../utils/get-band-size';
|
|
6
|
+
import { checkIsPointDomain, filterCategoriesByVisibleSeries, getMinMaxPropsOrState, hasOnlyMarkerSeries, validateArrayData, } from './utils';
|
|
7
|
+
const X_AXIS_ZOOM_PADDING = 0.02;
|
|
8
|
+
function calculateXAxisPadding(series) {
|
|
9
|
+
let result = 0;
|
|
10
|
+
series.forEach((s) => {
|
|
11
|
+
var _a, _b;
|
|
12
|
+
switch (s.type) {
|
|
13
|
+
case 'bar-y': {
|
|
14
|
+
// Since labels can be located to the right of the bar, need to add an additional space
|
|
15
|
+
const inside = get(s, 'dataLabels.inside');
|
|
16
|
+
if (!inside) {
|
|
17
|
+
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);
|
|
18
|
+
result = Math.max(result, labelsMaxWidth);
|
|
19
|
+
}
|
|
20
|
+
break;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
return result;
|
|
25
|
+
}
|
|
26
|
+
function isSeriesWithXAxisOffset(series) {
|
|
27
|
+
const types = [SERIES_TYPE.Heatmap, SERIES_TYPE.BarX];
|
|
28
|
+
return series.some((s) => types.includes(s.type));
|
|
29
|
+
}
|
|
30
|
+
export function getXMaxDomainResult(args) {
|
|
31
|
+
const { xMaxDomain, xMaxProps, xMaxRangeSlider, xMaxZoom } = args;
|
|
32
|
+
let xMaxDomainResult = xMaxDomain;
|
|
33
|
+
// When xMaxRangeSlider is provided, we use it directly without considering xMaxDomain.
|
|
34
|
+
// This is intentional: the range slider needs to display the chart's maxPadding area,
|
|
35
|
+
// which would be clipped if we constrained it to xMaxDomain.
|
|
36
|
+
if (typeof xMaxRangeSlider === 'number') {
|
|
37
|
+
xMaxDomainResult = xMaxRangeSlider;
|
|
38
|
+
}
|
|
39
|
+
else if (typeof xMaxZoom === 'number' && xMaxZoom < xMaxDomain) {
|
|
40
|
+
xMaxDomainResult = xMaxZoom;
|
|
41
|
+
}
|
|
42
|
+
else if (typeof xMaxProps === 'number' && xMaxProps < xMaxDomain) {
|
|
43
|
+
xMaxDomainResult = xMaxProps;
|
|
44
|
+
}
|
|
45
|
+
return xMaxDomainResult;
|
|
46
|
+
}
|
|
47
|
+
function getXScaleRange({ boundsWidth, hasZoomX }) {
|
|
48
|
+
const xAxisZoomPadding = boundsWidth * X_AXIS_ZOOM_PADDING;
|
|
49
|
+
const xRange = [0, boundsWidth];
|
|
50
|
+
const xRangeZoom = [0 + xAxisZoomPadding, boundsWidth - xAxisZoomPadding];
|
|
51
|
+
const range = hasZoomX ? xRangeZoom : xRange;
|
|
52
|
+
return range;
|
|
53
|
+
}
|
|
54
|
+
// eslint-disable-next-line complexity
|
|
55
|
+
export function createXScale(args) {
|
|
56
|
+
const { axis, boundsWidth, series, rangeSliderState, zoomStateX } = args;
|
|
57
|
+
const [xMinPropsOrState, xMaxPropsOrState] = getMinMaxPropsOrState({
|
|
58
|
+
axis,
|
|
59
|
+
maxValues: [zoomStateX === null || zoomStateX === void 0 ? void 0 : zoomStateX[1], rangeSliderState === null || rangeSliderState === void 0 ? void 0 : rangeSliderState.max],
|
|
60
|
+
minValues: [zoomStateX === null || zoomStateX === void 0 ? void 0 : zoomStateX[0], rangeSliderState === null || rangeSliderState === void 0 ? void 0 : rangeSliderState.min],
|
|
61
|
+
});
|
|
62
|
+
const xType = get(axis, 'type', DEFAULT_AXIS_TYPE);
|
|
63
|
+
const hasZoomX = Boolean(zoomStateX);
|
|
64
|
+
let xCategories = get(axis, 'categories');
|
|
65
|
+
if (rangeSliderState && xCategories) {
|
|
66
|
+
xCategories = getAxisCategories({
|
|
67
|
+
categories: xCategories,
|
|
68
|
+
min: rangeSliderState.min,
|
|
69
|
+
max: rangeSliderState.max,
|
|
70
|
+
order: axis.order,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
const maxPadding = get(axis, 'maxPadding', 0);
|
|
74
|
+
const xAxisMaxPadding = boundsWidth * maxPadding + calculateXAxisPadding(series);
|
|
75
|
+
const range = getXScaleRange({
|
|
76
|
+
boundsWidth,
|
|
77
|
+
hasZoomX,
|
|
78
|
+
});
|
|
79
|
+
switch (axis.order) {
|
|
80
|
+
case 'sortDesc':
|
|
81
|
+
case 'reverse': {
|
|
82
|
+
range.reverse();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
switch (xType) {
|
|
86
|
+
case 'linear':
|
|
87
|
+
case 'logarithmic': {
|
|
88
|
+
const domainData = getDomainDataXBySeries(series);
|
|
89
|
+
const { hasNumberAndNullValues, hasOnlyNullValues } = validateArrayData(domainData);
|
|
90
|
+
if (hasOnlyNullValues || domainData.length === 0) {
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
if (series.some((s) => s.type === 'bar-y' && s.stacking === 'percent')) {
|
|
94
|
+
return scaleLinear().domain([0, 100]).range(range);
|
|
95
|
+
}
|
|
96
|
+
if (hasNumberAndNullValues) {
|
|
97
|
+
const [xMinDomain, xMaxDomain] = extent(domainData);
|
|
98
|
+
const isPointDomain = hasOnlyMarkerSeries(series)
|
|
99
|
+
? checkIsPointDomain([xMinDomain, xMaxDomain])
|
|
100
|
+
: false;
|
|
101
|
+
let xMin;
|
|
102
|
+
let xMax;
|
|
103
|
+
if (typeof xMinPropsOrState === 'number' && !isPointDomain) {
|
|
104
|
+
xMin = xMinPropsOrState;
|
|
105
|
+
}
|
|
106
|
+
else if (xType === 'logarithmic') {
|
|
107
|
+
xMin = xMinDomain;
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
const xMinDefault = getDefaultMinXAxisValue(series);
|
|
111
|
+
xMin = xMinDefault !== null && xMinDefault !== void 0 ? xMinDefault : xMinDomain;
|
|
112
|
+
}
|
|
113
|
+
if (typeof xMaxPropsOrState === 'number' && !isPointDomain) {
|
|
114
|
+
xMax = xMaxPropsOrState;
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
const xMaxDefault = getDefaultMaxXAxisValue(series);
|
|
118
|
+
xMax =
|
|
119
|
+
typeof xMaxDefault === 'number'
|
|
120
|
+
? Math.max(xMaxDefault, xMaxDomain)
|
|
121
|
+
: xMaxDomain;
|
|
122
|
+
}
|
|
123
|
+
const scaleFn = xType === 'logarithmic' ? scaleLog : scaleLinear;
|
|
124
|
+
const scale = scaleFn().domain([xMin, xMax]).range(range);
|
|
125
|
+
let offsetMin = 0;
|
|
126
|
+
let offsetMax = xAxisMaxPadding;
|
|
127
|
+
const hasOffset = isSeriesWithXAxisOffset(series);
|
|
128
|
+
if (hasOffset) {
|
|
129
|
+
if (domainData.length > 1) {
|
|
130
|
+
const bandWidth = getBandSize({
|
|
131
|
+
scale: scale,
|
|
132
|
+
domain: domainData,
|
|
133
|
+
});
|
|
134
|
+
offsetMin += bandWidth / 2;
|
|
135
|
+
offsetMax += bandWidth / 2;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const isMinSpecified = typeof get(axis, 'min') === 'number' && !zoomStateX;
|
|
139
|
+
const isMaxSpecified = typeof get(axis, 'max') === 'number' && !zoomStateX;
|
|
140
|
+
const domainOffsetMin = isMinSpecified
|
|
141
|
+
? 0
|
|
142
|
+
: Math.abs(scale.invert(offsetMin) - scale.invert(0));
|
|
143
|
+
const domainOffsetMax = isMaxSpecified
|
|
144
|
+
? 0
|
|
145
|
+
: Math.abs(scale.invert(offsetMax) - scale.invert(0));
|
|
146
|
+
// 10 is the default value for the number of ticks. Here, to preserve the appearance of a series with a small number of points
|
|
147
|
+
const nicedDomain = scale.copy().nice(Math.max(10, domainData.length)).domain();
|
|
148
|
+
scale.domain([xMin - domainOffsetMin, xMax + domainOffsetMax]);
|
|
149
|
+
const startOnTick = get(axis, 'startOnTick', true);
|
|
150
|
+
const endOnTick = get(axis, 'endOnTick', true);
|
|
151
|
+
if (!hasZoomX && !hasOffset && nicedDomain.length === 2) {
|
|
152
|
+
const domainWithOffset = scale.domain();
|
|
153
|
+
scale.domain([
|
|
154
|
+
startOnTick
|
|
155
|
+
? Math.min(nicedDomain[0], domainWithOffset[0])
|
|
156
|
+
: domainWithOffset[0],
|
|
157
|
+
endOnTick
|
|
158
|
+
? Math.max(nicedDomain[1], domainWithOffset[1])
|
|
159
|
+
: domainWithOffset[1],
|
|
160
|
+
]);
|
|
161
|
+
}
|
|
162
|
+
return scale;
|
|
163
|
+
}
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
case 'category': {
|
|
167
|
+
if (xCategories) {
|
|
168
|
+
const filteredCategories = filterCategoriesByVisibleSeries({
|
|
169
|
+
axisDirection: 'x',
|
|
170
|
+
categories: xCategories,
|
|
171
|
+
series: series,
|
|
172
|
+
});
|
|
173
|
+
const xScale = scaleBand().domain(filteredCategories).range([0, boundsWidth]);
|
|
174
|
+
if (xScale.step() / 2 < xAxisMaxPadding) {
|
|
175
|
+
xScale.range(range);
|
|
176
|
+
}
|
|
177
|
+
return xScale;
|
|
178
|
+
}
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
case 'datetime': {
|
|
182
|
+
let domain = null;
|
|
183
|
+
const domainData = get(axis, 'timestamps') || getDomainDataXBySeries(series);
|
|
184
|
+
const { hasNumberAndNullValues, hasOnlyNullValues } = validateArrayData(domainData);
|
|
185
|
+
if (hasOnlyNullValues || domainData.length === 0) {
|
|
186
|
+
return undefined;
|
|
187
|
+
}
|
|
188
|
+
if (hasNumberAndNullValues) {
|
|
189
|
+
const [xMinTimestamp, xMaxTimestamp] = extent(domainData);
|
|
190
|
+
const isPointDomain = checkIsPointDomain([xMinTimestamp, xMaxTimestamp]);
|
|
191
|
+
const xMin = typeof xMinPropsOrState === 'number' &&
|
|
192
|
+
xMinPropsOrState > xMinTimestamp &&
|
|
193
|
+
!isPointDomain
|
|
194
|
+
? xMinPropsOrState
|
|
195
|
+
: xMinTimestamp;
|
|
196
|
+
const xMax = getXMaxDomainResult({
|
|
197
|
+
xMaxDomain: xMaxTimestamp,
|
|
198
|
+
xMaxProps: get(axis, 'max'),
|
|
199
|
+
xMaxRangeSlider: rangeSliderState === null || rangeSliderState === void 0 ? void 0 : rangeSliderState.max,
|
|
200
|
+
xMaxZoom: zoomStateX === null || zoomStateX === void 0 ? void 0 : zoomStateX[1],
|
|
201
|
+
});
|
|
202
|
+
domain = [xMin, xMax];
|
|
203
|
+
const scale = scaleUtc().domain(domain).range(range);
|
|
204
|
+
let offsetMin = 0;
|
|
205
|
+
let offsetMax = xAxisMaxPadding;
|
|
206
|
+
const hasOffset = isSeriesWithXAxisOffset(series);
|
|
207
|
+
if (hasOffset) {
|
|
208
|
+
if (domainData.length > 1) {
|
|
209
|
+
const bandWidth = getBandSize({
|
|
210
|
+
scale: scale,
|
|
211
|
+
domain: domainData,
|
|
212
|
+
});
|
|
213
|
+
offsetMin += bandWidth / 2;
|
|
214
|
+
offsetMax += bandWidth / 2;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
const isMinSpecified = typeof get(axis, 'min') === 'number' && !zoomStateX;
|
|
218
|
+
const isMaxSpecified = typeof get(axis, 'max') === 'number' && !zoomStateX;
|
|
219
|
+
const domainOffsetMin = isMinSpecified
|
|
220
|
+
? 0
|
|
221
|
+
: Math.abs(scale.invert(offsetMin).getTime() - scale.invert(0).getTime());
|
|
222
|
+
const domainOffsetMax = isMaxSpecified
|
|
223
|
+
? 0
|
|
224
|
+
: Math.abs(scale.invert(offsetMax).getTime() - scale.invert(0).getTime());
|
|
225
|
+
// 10 is the default value for the number of ticks. Here, to preserve the appearance of a series with a small number of points
|
|
226
|
+
const nicedDomain = scale.copy().nice(Math.max(10, domainData.length)).domain();
|
|
227
|
+
scale.domain([xMin - domainOffsetMin, xMax + domainOffsetMax]);
|
|
228
|
+
const startOnTick = get(axis, 'startOnTick', true);
|
|
229
|
+
const endOnTick = get(axis, 'endOnTick', true);
|
|
230
|
+
if (!hasZoomX && !hasOffset && nicedDomain.length === 2) {
|
|
231
|
+
const domainWithOffset = scale.domain();
|
|
232
|
+
scale.domain([
|
|
233
|
+
startOnTick
|
|
234
|
+
? Math.min(Number(nicedDomain[0]), Number(domainWithOffset[0]))
|
|
235
|
+
: Number(domainWithOffset[0]),
|
|
236
|
+
endOnTick
|
|
237
|
+
? Math.max(Number(nicedDomain[1]), Number(domainWithOffset[1]))
|
|
238
|
+
: Number(domainWithOffset[1]),
|
|
239
|
+
]);
|
|
240
|
+
}
|
|
241
|
+
return scale;
|
|
242
|
+
}
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
throw new Error('Failed to create xScale');
|
|
247
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { PreparedAxis, PreparedSeries } from '../../hooks';
|
|
2
|
+
import type { ChartSeries } from '../../types';
|
|
3
|
+
export declare function createYScale(args: {
|
|
4
|
+
axis: PreparedAxis;
|
|
5
|
+
boundsHeight: number;
|
|
6
|
+
series: PreparedSeries[] | ChartSeries[];
|
|
7
|
+
primaryAxis?: PreparedAxis;
|
|
8
|
+
primaryTicksCount?: number;
|
|
9
|
+
zoomStateY?: [number, number];
|
|
10
|
+
}): import("d3-scale").ScaleBand<string> | import("d3-scale").ScaleLinear<number, number, never> | import("d3-scale").ScaleTime<number, number, never> | undefined;
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { extent, scaleBand, scaleLinear, scaleLog, scaleUtc, tickStep, ticks } from 'd3';
|
|
2
|
+
import get from 'lodash/get';
|
|
3
|
+
import { getTickValues } from '../../components/AxisY/utils';
|
|
4
|
+
import { SERIES_TYPE } from '../../constants';
|
|
5
|
+
import { CHART_SERIES_WITH_VOLUME_ON_Y_AXIS, getDomainDataYBySeries, shouldSyncAxisWithPrimary, } from '../../utils';
|
|
6
|
+
import { getBandSize } from '../utils/get-band-size';
|
|
7
|
+
import { checkIsPointDomain, filterCategoriesByVisibleSeries, getMinMaxPropsOrState, hasOnlyMarkerSeries, validateArrayData, } from './utils';
|
|
8
|
+
// axis is validated in `validation/index.ts`, so the value of `axis.type` is definitely valid.
|
|
9
|
+
// eslint-disable-next-line consistent-return
|
|
10
|
+
function getYScaleRange(args) {
|
|
11
|
+
const { axis, boundsHeight } = args;
|
|
12
|
+
switch (axis.type) {
|
|
13
|
+
case 'datetime':
|
|
14
|
+
case 'linear':
|
|
15
|
+
case 'logarithmic': {
|
|
16
|
+
const range = [boundsHeight, 0];
|
|
17
|
+
switch (axis.order) {
|
|
18
|
+
case 'sortDesc':
|
|
19
|
+
case 'reverse': {
|
|
20
|
+
range.reverse();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return range;
|
|
24
|
+
}
|
|
25
|
+
case 'category': {
|
|
26
|
+
return [boundsHeight, 0];
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function isSeriesWithYAxisOffset(series) {
|
|
31
|
+
const types = [SERIES_TYPE.BarY, SERIES_TYPE.Heatmap];
|
|
32
|
+
return series.some((s) => types.includes(s.type));
|
|
33
|
+
}
|
|
34
|
+
function getDomainSyncedToPrimaryTicks(args) {
|
|
35
|
+
const { primaryTicksCount, scale, yMin, yMax } = args;
|
|
36
|
+
const [dMin, dMax] = scale.domain();
|
|
37
|
+
let secondaryTicks = ticks(dMin, dMax, primaryTicksCount);
|
|
38
|
+
let i = 1;
|
|
39
|
+
// Need to reduce the number of ticks to primaryTicksCount - 2, so that we can later
|
|
40
|
+
// add one tick each at the top and bottom edges of the chart
|
|
41
|
+
while (secondaryTicks.length > primaryTicksCount - 2) {
|
|
42
|
+
secondaryTicks = ticks(dMin, dMax, primaryTicksCount - i);
|
|
43
|
+
if (secondaryTicks.length === 0) {
|
|
44
|
+
secondaryTicks = ticks(dMin, dMax, primaryTicksCount - i + 1);
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
i += 1;
|
|
48
|
+
}
|
|
49
|
+
const step = tickStep(dMin, dMax, secondaryTicks.length);
|
|
50
|
+
let ticksCountDiff = primaryTicksCount - secondaryTicks.length;
|
|
51
|
+
let deltaMin = Math.abs(dMin - secondaryTicks[0]);
|
|
52
|
+
let deltaMax = Math.abs(dMax - secondaryTicks[secondaryTicks.length - 1]);
|
|
53
|
+
while (ticksCountDiff > 0) {
|
|
54
|
+
if (deltaMin > deltaMax) {
|
|
55
|
+
secondaryTicks.unshift(secondaryTicks[0] - step);
|
|
56
|
+
deltaMin -= step;
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
secondaryTicks.push(secondaryTicks[secondaryTicks.length - 1] + step);
|
|
60
|
+
deltaMax -= step;
|
|
61
|
+
}
|
|
62
|
+
ticksCountDiff -= 1;
|
|
63
|
+
}
|
|
64
|
+
if (secondaryTicks[secondaryTicks.length - 1] < yMax) {
|
|
65
|
+
secondaryTicks[secondaryTicks.length - 1] += step;
|
|
66
|
+
}
|
|
67
|
+
if (secondaryTicks[0] > yMin) {
|
|
68
|
+
secondaryTicks[0] -= step;
|
|
69
|
+
}
|
|
70
|
+
return [secondaryTicks[0], secondaryTicks[secondaryTicks.length - 1]];
|
|
71
|
+
}
|
|
72
|
+
function getDomainMinAlignedToStartTick(args) {
|
|
73
|
+
var _a, _b;
|
|
74
|
+
const { axis, range, scale, series } = args;
|
|
75
|
+
const [dMin, dMax] = scale.domain();
|
|
76
|
+
const tickValues = getTickValues({
|
|
77
|
+
axis,
|
|
78
|
+
scale,
|
|
79
|
+
labelLineHeight: axis.labels.lineHeight,
|
|
80
|
+
series,
|
|
81
|
+
});
|
|
82
|
+
const isStartOnTick = tickValues[0].y === range[0];
|
|
83
|
+
let dNewMin = dMin;
|
|
84
|
+
if (!isStartOnTick) {
|
|
85
|
+
let step;
|
|
86
|
+
if (typeof ((_a = tickValues[0]) === null || _a === void 0 ? void 0 : _a.value) === 'number' && typeof ((_b = tickValues[1]) === null || _b === void 0 ? void 0 : _b.value) === 'number') {
|
|
87
|
+
step = tickValues[1].value - tickValues[0].value;
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
step = tickStep(dMin, dMax, 1);
|
|
91
|
+
}
|
|
92
|
+
dNewMin = tickValues[0].value - step;
|
|
93
|
+
}
|
|
94
|
+
return dNewMin;
|
|
95
|
+
}
|
|
96
|
+
function getDomainMaxAlignedToEndTick(args) {
|
|
97
|
+
var _a, _b;
|
|
98
|
+
const { axis, range, scale, series } = args;
|
|
99
|
+
const [dMin, dMax] = scale.domain();
|
|
100
|
+
const tickValues = getTickValues({
|
|
101
|
+
axis,
|
|
102
|
+
scale,
|
|
103
|
+
labelLineHeight: axis.labels.lineHeight,
|
|
104
|
+
series,
|
|
105
|
+
});
|
|
106
|
+
const isEndOnTick = tickValues[tickValues.length - 1].y === range[1];
|
|
107
|
+
let dNewMax = dMax;
|
|
108
|
+
if (!isEndOnTick) {
|
|
109
|
+
let step;
|
|
110
|
+
if (typeof ((_a = tickValues[0]) === null || _a === void 0 ? void 0 : _a.value) === 'number' && typeof ((_b = tickValues[1]) === null || _b === void 0 ? void 0 : _b.value) === 'number') {
|
|
111
|
+
step = tickValues[1].value - tickValues[0].value;
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
step = tickStep(dMin, dMax, 1);
|
|
115
|
+
}
|
|
116
|
+
dNewMax = tickValues[tickValues.length - 1].value + step;
|
|
117
|
+
}
|
|
118
|
+
return dNewMax;
|
|
119
|
+
}
|
|
120
|
+
// eslint-disable-next-line complexity
|
|
121
|
+
export function createYScale(args) {
|
|
122
|
+
const { axis, boundsHeight, series, primaryAxis, primaryTicksCount, zoomStateY } = args;
|
|
123
|
+
const [yMinPropsOrState, yMaxPropsOrState] = getMinMaxPropsOrState({
|
|
124
|
+
axis,
|
|
125
|
+
maxValues: [zoomStateY === null || zoomStateY === void 0 ? void 0 : zoomStateY[1]],
|
|
126
|
+
minValues: [zoomStateY === null || zoomStateY === void 0 ? void 0 : zoomStateY[0]],
|
|
127
|
+
});
|
|
128
|
+
const yCategories = get(axis, 'categories');
|
|
129
|
+
const yTimestamps = get(axis, 'timestamps');
|
|
130
|
+
const range = getYScaleRange({ axis, boundsHeight });
|
|
131
|
+
switch (axis.type) {
|
|
132
|
+
case 'linear':
|
|
133
|
+
case 'logarithmic': {
|
|
134
|
+
const domain = getDomainDataYBySeries(series);
|
|
135
|
+
const { hasNumberAndNullValues, hasOnlyNullValues } = validateArrayData(domain);
|
|
136
|
+
if (hasOnlyNullValues || domain.length === 0) {
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
if (series.some((s) => (s.type === 'bar-x' || s.type === 'area') && s.stacking === 'percent')) {
|
|
140
|
+
return scaleLinear().domain([0, 100]).range(range);
|
|
141
|
+
}
|
|
142
|
+
if (hasNumberAndNullValues) {
|
|
143
|
+
const [yMinDomain, yMaxDomain] = extent(domain);
|
|
144
|
+
const isPointDomain = hasOnlyMarkerSeries(series)
|
|
145
|
+
? checkIsPointDomain([yMinDomain, yMaxDomain])
|
|
146
|
+
: false;
|
|
147
|
+
const yMin = typeof yMinPropsOrState === 'number' && !isPointDomain
|
|
148
|
+
? yMinPropsOrState
|
|
149
|
+
: yMinDomain;
|
|
150
|
+
let yMax;
|
|
151
|
+
if (typeof yMaxPropsOrState === 'number' && !isPointDomain) {
|
|
152
|
+
yMax = yMaxPropsOrState;
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
const hasSeriesWithVolumeOnYAxis = series.some((s) => CHART_SERIES_WITH_VOLUME_ON_Y_AXIS.includes(s.type));
|
|
156
|
+
yMax = hasSeriesWithVolumeOnYAxis ? Math.max(yMaxDomain, 0) : yMaxDomain;
|
|
157
|
+
}
|
|
158
|
+
const scaleFn = axis.type === 'logarithmic' ? scaleLog : scaleLinear;
|
|
159
|
+
let scale = scaleFn().domain([yMin, yMax]).range(range);
|
|
160
|
+
let offsetMin = 0;
|
|
161
|
+
// We should ignore padding if we are drawing only one point on the plot.
|
|
162
|
+
let offsetMax = yMin === yMax ? 0 : boundsHeight * axis.maxPadding;
|
|
163
|
+
if (isSeriesWithYAxisOffset(series)) {
|
|
164
|
+
if (domain.length > 1) {
|
|
165
|
+
const bandWidth = getBandSize({
|
|
166
|
+
scale: scale,
|
|
167
|
+
domain: domain,
|
|
168
|
+
});
|
|
169
|
+
offsetMin += bandWidth / 2;
|
|
170
|
+
offsetMax += bandWidth / 2;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
const isMinSpecified = typeof get(axis, 'min') === 'number' && !zoomStateY;
|
|
174
|
+
const isMaxSpecified = typeof get(axis, 'max') === 'number' && !zoomStateY;
|
|
175
|
+
const domainOffsetMin = isMinSpecified
|
|
176
|
+
? 0
|
|
177
|
+
: Math.abs(scale.invert(offsetMin) - scale.invert(0));
|
|
178
|
+
const domainOffsetMax = isMaxSpecified
|
|
179
|
+
? 0
|
|
180
|
+
: Math.abs(scale.invert(offsetMax) - scale.invert(0));
|
|
181
|
+
scale = scale.domain([yMin - domainOffsetMin, yMax + domainOffsetMax]);
|
|
182
|
+
const startOnTick = get(axis, 'startOnTick', false);
|
|
183
|
+
const endOnTick = get(axis, 'endOnTick', false);
|
|
184
|
+
const shouldBeSyncedWithPrimary = primaryAxis
|
|
185
|
+
? shouldSyncAxisWithPrimary(axis, primaryAxis)
|
|
186
|
+
: false;
|
|
187
|
+
if (shouldBeSyncedWithPrimary &&
|
|
188
|
+
typeof primaryTicksCount === 'number' &&
|
|
189
|
+
primaryTicksCount >= 2) {
|
|
190
|
+
const newDomain = getDomainSyncedToPrimaryTicks({
|
|
191
|
+
scale,
|
|
192
|
+
primaryTicksCount,
|
|
193
|
+
yMin,
|
|
194
|
+
yMax,
|
|
195
|
+
});
|
|
196
|
+
scale.domain(newDomain);
|
|
197
|
+
}
|
|
198
|
+
if (startOnTick && (!primaryAxis || !shouldBeSyncedWithPrimary)) {
|
|
199
|
+
const [_, dMax] = scale.domain();
|
|
200
|
+
const dNewMin = getDomainMinAlignedToStartTick({ axis, range, scale, series });
|
|
201
|
+
scale.domain([dNewMin, dMax]);
|
|
202
|
+
}
|
|
203
|
+
if (endOnTick && (!primaryAxis || !shouldBeSyncedWithPrimary)) {
|
|
204
|
+
const [dMin, _] = scale.domain();
|
|
205
|
+
const dNewMax = getDomainMaxAlignedToEndTick({ axis, range, scale, series });
|
|
206
|
+
scale.domain([dMin, dNewMax]);
|
|
207
|
+
}
|
|
208
|
+
return scale;
|
|
209
|
+
}
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
case 'category': {
|
|
213
|
+
if (yCategories) {
|
|
214
|
+
const filteredCategories = filterCategoriesByVisibleSeries({
|
|
215
|
+
axisDirection: 'y',
|
|
216
|
+
categories: yCategories,
|
|
217
|
+
series: series,
|
|
218
|
+
});
|
|
219
|
+
return scaleBand().domain(filteredCategories).range(range);
|
|
220
|
+
}
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
case 'datetime': {
|
|
224
|
+
if (yTimestamps) {
|
|
225
|
+
const [yMinTimestamp, yMaxTimestamp] = extent(yTimestamps);
|
|
226
|
+
const isPointDomain = hasOnlyMarkerSeries(series)
|
|
227
|
+
? checkIsPointDomain([yMinTimestamp, yMaxTimestamp])
|
|
228
|
+
: false;
|
|
229
|
+
const yMin = typeof yMinPropsOrState === 'number' &&
|
|
230
|
+
!isPointDomain &&
|
|
231
|
+
yMinPropsOrState > yMinTimestamp
|
|
232
|
+
? yMinPropsOrState
|
|
233
|
+
: yMinTimestamp;
|
|
234
|
+
const yMax = typeof yMaxPropsOrState === 'number' &&
|
|
235
|
+
!isPointDomain &&
|
|
236
|
+
yMaxPropsOrState < yMaxTimestamp
|
|
237
|
+
? yMaxPropsOrState
|
|
238
|
+
: yMaxTimestamp;
|
|
239
|
+
const scale = scaleUtc().domain([yMin, yMax]).range(range);
|
|
240
|
+
const startOnTick = get(axis, 'startOnTick', true);
|
|
241
|
+
const endOnTick = get(axis, 'endOnTick', true);
|
|
242
|
+
if (startOnTick || endOnTick) {
|
|
243
|
+
const nicedDomain = scale.copy().nice().domain();
|
|
244
|
+
return scale.domain([
|
|
245
|
+
startOnTick ? Number(nicedDomain[0]) : yMin,
|
|
246
|
+
endOnTick ? Number(nicedDomain[1]) : yMax,
|
|
247
|
+
]);
|
|
248
|
+
}
|
|
249
|
+
return scale;
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
const domain = getDomainDataYBySeries(series);
|
|
253
|
+
const { hasNumberAndNullValues, hasOnlyNullValues } = validateArrayData(domain);
|
|
254
|
+
if (hasOnlyNullValues || domain.length === 0) {
|
|
255
|
+
return undefined;
|
|
256
|
+
}
|
|
257
|
+
if (hasNumberAndNullValues) {
|
|
258
|
+
const [yMinTimestamp, yMaxTimestamp] = extent(domain);
|
|
259
|
+
const isPointDomain = hasOnlyMarkerSeries(series)
|
|
260
|
+
? checkIsPointDomain([yMinTimestamp, yMaxTimestamp])
|
|
261
|
+
: false;
|
|
262
|
+
const yMin = typeof yMinPropsOrState === 'number' &&
|
|
263
|
+
!isPointDomain &&
|
|
264
|
+
yMinPropsOrState > yMinTimestamp
|
|
265
|
+
? yMinPropsOrState
|
|
266
|
+
: yMinTimestamp;
|
|
267
|
+
const yMax = typeof yMaxPropsOrState === 'number' &&
|
|
268
|
+
!isPointDomain &&
|
|
269
|
+
yMaxPropsOrState < yMaxTimestamp
|
|
270
|
+
? yMaxPropsOrState
|
|
271
|
+
: yMaxTimestamp;
|
|
272
|
+
const scale = scaleUtc().domain([yMin, yMax]).range(range);
|
|
273
|
+
let offsetMin = 0;
|
|
274
|
+
let offsetMax = boundsHeight * axis.maxPadding;
|
|
275
|
+
if (isSeriesWithYAxisOffset(series)) {
|
|
276
|
+
if (Object.keys(domain).length > 1) {
|
|
277
|
+
const bandWidth = getBandSize({
|
|
278
|
+
scale: scale,
|
|
279
|
+
domain: domain,
|
|
280
|
+
});
|
|
281
|
+
offsetMin += bandWidth / 2;
|
|
282
|
+
offsetMax += bandWidth / 2;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
const isMinSpecified = typeof get(axis, 'min') === 'number' && !zoomStateY;
|
|
286
|
+
const isMaxSpecified = typeof get(axis, 'max') === 'number' && !zoomStateY;
|
|
287
|
+
const domainOffsetMin = isMinSpecified
|
|
288
|
+
? 0
|
|
289
|
+
: Math.abs(scale.invert(offsetMin).getTime() - scale.invert(0).getTime());
|
|
290
|
+
const domainOffsetMax = isMaxSpecified
|
|
291
|
+
? 0
|
|
292
|
+
: Math.abs(scale.invert(offsetMax).getTime() - scale.invert(0).getTime());
|
|
293
|
+
return scale.domain([yMin - domainOffsetMin, yMax + domainOffsetMax]);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
throw new Error('Failed to create yScale');
|
|
299
|
+
}
|
|
@@ -29,6 +29,8 @@ export declare function useNormalizedOriginalData(props: UseOriginalDataProps):
|
|
|
29
29
|
plotBands?: import("../../types").AxisPlotBand[];
|
|
30
30
|
visible?: boolean;
|
|
31
31
|
order?: "sortAsc" | "sortDesc" | "reverse";
|
|
32
|
+
startOnTick?: boolean;
|
|
33
|
+
endOnTick?: boolean;
|
|
32
34
|
};
|
|
33
35
|
normalizedYAxis: import("../../types").ChartYAxis[] | undefined;
|
|
34
36
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ChartXAxis, ChartYAxis } from '../../types';
|
|
2
2
|
import type { PreparedRangeSlider, PreparedXAxis, PreparedYAxis } from '../useAxis/types';
|
|
3
|
-
import type { ChartScale } from '../useAxisScales';
|
|
3
|
+
import type { ChartScale } from '../useAxisScales/types';
|
|
4
4
|
import type { BrushSelection, UseBrushProps } from '../useBrush/types';
|
|
5
5
|
import type { PreparedChart } from '../useChartOptions/types';
|
|
6
6
|
import type { PreparedLegend, PreparedSeries, PreparedSeriesOptions } from '../useSeries/types';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { PreparedRangeSlider } from '../useAxis/types';
|
|
2
|
-
import type { ChartScale } from '../useAxisScales';
|
|
2
|
+
import type { ChartScale } from '../useAxisScales/types';
|
|
3
3
|
import type { BrushSelection } from '../useBrush/types';
|
|
4
4
|
import type { PreparedChart } from '../useChartOptions/types';
|
|
5
5
|
import type { PreparedLegend } from '../useSeries/types';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { PreparedXAxis, PreparedYAxis } from '../../useAxis/types';
|
|
2
|
-
import type { ChartScale } from '../../useAxisScales';
|
|
2
|
+
import type { ChartScale } from '../../useAxisScales/types';
|
|
3
3
|
import type { PreparedAreaSeries } from '../../useSeries/types';
|
|
4
4
|
import type { PreparedSplit } from '../../useSplit/types';
|
|
5
5
|
import type { PreparedAreaData } from './types';
|