@gravity-ui/charts 1.48.2 → 1.49.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/AxisX/prepare-axis-data.js +11 -13
- package/dist/cjs/components/AxisY/prepare-axis-data.js +1 -1
- package/dist/cjs/components/ChartInner/utils/normalized-original-data.d.ts +1 -0
- package/dist/cjs/components/ChartInner/utils/zoom.js +3 -1
- package/dist/cjs/core/axes/x-axis.js +3 -1
- package/dist/cjs/core/axes/y-axis.js +1 -0
- package/dist/cjs/core/constants/defaults/series-options.js +5 -5
- package/dist/cjs/core/scales/y-scale.js +1 -1
- package/dist/cjs/core/shapes/bar-x/prepare-data.js +3 -2
- package/dist/cjs/core/types/chart/axis.d.ts +38 -0
- package/dist/cjs/core/types/chart/series.d.ts +8 -8
- package/dist/cjs/core/types/chart/zoom.d.ts +2 -1
- package/dist/cjs/core/utils/axis/x-axis.js +7 -2
- package/dist/cjs/core/utils/axis-generators/bottom.d.ts +1 -0
- package/dist/cjs/core/utils/axis-generators/bottom.js +14 -10
- package/dist/cjs/core/utils/bar-y.js +3 -2
- package/dist/cjs/core/utils/format.js +39 -2
- package/dist/cjs/core/utils/ticks/datetime.d.ts +2 -1
- package/dist/cjs/core/utils/ticks/datetime.js +27 -7
- package/dist/cjs/core/utils/ticks/index.d.ts +3 -1
- package/dist/cjs/core/utils/ticks/index.js +2 -2
- package/dist/cjs/core/utils/time.d.ts +2 -0
- package/dist/cjs/core/utils/time.js +4 -0
- package/dist/esm/components/AxisX/prepare-axis-data.js +11 -13
- package/dist/esm/components/AxisY/prepare-axis-data.js +1 -1
- package/dist/esm/components/ChartInner/utils/normalized-original-data.d.ts +1 -0
- package/dist/esm/components/ChartInner/utils/zoom.js +3 -1
- package/dist/esm/core/axes/x-axis.js +3 -1
- package/dist/esm/core/axes/y-axis.js +1 -0
- package/dist/esm/core/constants/defaults/series-options.js +5 -5
- package/dist/esm/core/scales/y-scale.js +1 -1
- package/dist/esm/core/shapes/bar-x/prepare-data.js +3 -2
- package/dist/esm/core/types/chart/axis.d.ts +38 -0
- package/dist/esm/core/types/chart/series.d.ts +8 -8
- package/dist/esm/core/types/chart/zoom.d.ts +2 -1
- package/dist/esm/core/utils/axis/x-axis.js +7 -2
- package/dist/esm/core/utils/axis-generators/bottom.d.ts +1 -0
- package/dist/esm/core/utils/axis-generators/bottom.js +14 -10
- package/dist/esm/core/utils/bar-y.js +3 -2
- package/dist/esm/core/utils/format.js +39 -2
- package/dist/esm/core/utils/ticks/datetime.d.ts +2 -1
- package/dist/esm/core/utils/ticks/datetime.js +27 -7
- package/dist/esm/core/utils/ticks/index.d.ts +3 -1
- package/dist/esm/core/utils/ticks/index.js +2 -2
- package/dist/esm/core/utils/time.d.ts +2 -0
- package/dist/esm/core/utils/time.js +4 -0
- package/package.json +1 -1
|
@@ -74,7 +74,7 @@ async function getSvgAxisLabel({ getTextSize, text, axis, top, left, labelMaxWid
|
|
|
74
74
|
return svgLabel;
|
|
75
75
|
}
|
|
76
76
|
export async function prepareXAxisData({ axis, boundsOffsetLeft, boundsOffsetRight, boundsWidth, chartMarginLeft, chartMarginRight, height, scale, series, split, yAxis, }) {
|
|
77
|
-
var _a, _b, _c, _d, _e, _f, _g
|
|
77
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
78
78
|
const xAxisItems = [];
|
|
79
79
|
const splitPlots = (_a = split === null || split === void 0 ? void 0 : split.plots) !== null && _a !== void 0 ? _a : [];
|
|
80
80
|
for (let plotIndex = 0; plotIndex < splitPlots.length; plotIndex++) {
|
|
@@ -84,18 +84,16 @@ export async function prepareXAxisData({ axis, boundsOffsetLeft, boundsOffsetRig
|
|
|
84
84
|
const axisWidth = boundsWidth;
|
|
85
85
|
const isBottomPlot = plotIndex === splitPlots.length - 1;
|
|
86
86
|
const plotYAxes = yAxis.filter((a) => a.plotIndex === plotIndex);
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const yDomainRightPosition = (
|
|
91
|
-
? axisWidth
|
|
92
|
-
: null;
|
|
87
|
+
const leftYAxis = plotYAxes.find((a) => a.position === 'left');
|
|
88
|
+
const yDomainLeftPosition = (leftYAxis === null || leftYAxis === void 0 ? void 0 : leftYAxis.visible) && (leftYAxis === null || leftYAxis === void 0 ? void 0 : leftYAxis.lineVisible) !== false ? 0 : null;
|
|
89
|
+
const rightYAxis = plotYAxes.find((a) => a.position === 'right');
|
|
90
|
+
const yDomainRightPosition = (rightYAxis === null || rightYAxis === void 0 ? void 0 : rightYAxis.visible) && (rightYAxis === null || rightYAxis === void 0 ? void 0 : rightYAxis.lineVisible) !== false ? axisWidth : null;
|
|
93
91
|
let domain = null;
|
|
94
|
-
if (isBottomPlot && axis.visible) {
|
|
92
|
+
if (isBottomPlot && axis.visible && axis.lineVisible !== false) {
|
|
95
93
|
domain = {
|
|
96
94
|
start: [0, axisTop + axisHeight],
|
|
97
95
|
end: [axisWidth, axisTop + axisHeight],
|
|
98
|
-
lineColor: (
|
|
96
|
+
lineColor: (_b = axis.lineColor) !== null && _b !== void 0 ? _b : '',
|
|
99
97
|
};
|
|
100
98
|
}
|
|
101
99
|
const ticks = [];
|
|
@@ -264,14 +262,14 @@ export async function prepareXAxisData({ axis, boundsOffsetLeft, boundsOffsetRig
|
|
|
264
262
|
axisScale,
|
|
265
263
|
axis: 'x',
|
|
266
264
|
});
|
|
267
|
-
const halfBandwidth = ((
|
|
265
|
+
const halfBandwidth = ((_d = (_c = axisScale.bandwidth) === null || _c === void 0 ? void 0 : _c.call(axisScale)) !== null && _d !== void 0 ? _d : 0) / 2;
|
|
268
266
|
const startPos = halfBandwidth + Math.min(from, to);
|
|
269
267
|
const endPos = Math.min(Math.abs(to - from), axisWidth - Math.min(from, to));
|
|
270
268
|
const plotBandWidth = Math.min(endPos, axisWidth);
|
|
271
269
|
if (plotBandWidth < 0) {
|
|
272
270
|
continue;
|
|
273
271
|
}
|
|
274
|
-
const perpExtent = (
|
|
272
|
+
const perpExtent = (_e = calculateNumericProperty({ value: plotBand.size, base: axisHeight })) !== null && _e !== void 0 ? _e : axisHeight;
|
|
275
273
|
// X axis is positioned at the bottom of the plot area, so 'start' = bottom edge.
|
|
276
274
|
const bandY = plotBand.align === 'end' ? axisTop : axisTop + axisHeight - perpExtent;
|
|
277
275
|
const getPlotLabelSize = getTextSizeFn({ style: plotBand.label.style });
|
|
@@ -290,8 +288,8 @@ export async function prepareXAxisData({ axis, boundsOffsetLeft, boundsOffsetRig
|
|
|
290
288
|
? {
|
|
291
289
|
text: plotBand.label.text,
|
|
292
290
|
style: plotBand.label.style,
|
|
293
|
-
x: plotBand.label.padding + ((
|
|
294
|
-
y: plotBand.label.padding + ((
|
|
291
|
+
x: plotBand.label.padding + ((_f = labelSize === null || labelSize === void 0 ? void 0 : labelSize.hangingOffset) !== null && _f !== void 0 ? _f : 0),
|
|
292
|
+
y: plotBand.label.padding + ((_g = labelSize === null || labelSize === void 0 ? void 0 : labelSize.width) !== null && _g !== void 0 ? _g : 0),
|
|
295
293
|
rotate: -90,
|
|
296
294
|
qa: plotBand.label.qa,
|
|
297
295
|
}
|
|
@@ -118,7 +118,7 @@ export async function prepareYAxisData({ axis, split, scale, top: topOffset, wid
|
|
|
118
118
|
const axisHeight = ((_b = split === null || split === void 0 ? void 0 : split.plots[axis.plotIndex]) === null || _b === void 0 ? void 0 : _b.height) || height;
|
|
119
119
|
const domainX = axis.position === 'left' ? 0 : width;
|
|
120
120
|
let domain = null;
|
|
121
|
-
if (axis.visible) {
|
|
121
|
+
if (axis.visible && axis.lineVisible !== false) {
|
|
122
122
|
domain = {
|
|
123
123
|
start: [domainX, axisPlotTopPosition],
|
|
124
124
|
end: [domainX, axisPlotTopPosition + axisHeight],
|
|
@@ -9,6 +9,7 @@ export declare function getNormalizedXAxis(props: {
|
|
|
9
9
|
type?: import("../../../types").ChartAxisType;
|
|
10
10
|
labels?: import("../../../types").ChartAxisLabels;
|
|
11
11
|
lineColor?: string;
|
|
12
|
+
lineVisible?: boolean;
|
|
12
13
|
title?: import("../../../types").ChartAxisTitle;
|
|
13
14
|
min?: number;
|
|
14
15
|
max?: number;
|
|
@@ -6,7 +6,9 @@ function mapSeriesTypeToZoomType(seriesType) {
|
|
|
6
6
|
case SERIES_TYPE.Area: {
|
|
7
7
|
return [ZOOM_TYPE.X, ZOOM_TYPE.XY, ZOOM_TYPE.Y];
|
|
8
8
|
}
|
|
9
|
-
case SERIES_TYPE.BarX:
|
|
9
|
+
case SERIES_TYPE.BarX: {
|
|
10
|
+
return [ZOOM_TYPE.X, ZOOM_TYPE.XY];
|
|
11
|
+
}
|
|
10
12
|
case SERIES_TYPE.XRange: {
|
|
11
13
|
return [ZOOM_TYPE.X];
|
|
12
14
|
}
|
|
@@ -18,7 +18,7 @@ async function setLabelSettings({ axis, seriesData, width, axisLabels, }) {
|
|
|
18
18
|
const tickValues = getXAxisTickValues({ axis, scale, labelLineHeight, series: seriesData });
|
|
19
19
|
const tickStep = getMinSpaceBetween(tickValues, (d) => Number(d.value));
|
|
20
20
|
if (axis.type === 'datetime' && !(axisLabels === null || axisLabels === void 0 ? void 0 : axisLabels.dateFormat) && tickStep >= TIME_UNITS.day) {
|
|
21
|
-
axis.labels.dateFormat = getDefaultDateFormat(tickStep);
|
|
21
|
+
axis.labels.dateFormat = getDefaultDateFormat(tickStep, axisLabels === null || axisLabels === void 0 ? void 0 : axisLabels.dateTimeLabelFormats);
|
|
22
22
|
}
|
|
23
23
|
const labels = tickValues.map((tick) => formatAxisTickLabel({
|
|
24
24
|
axis,
|
|
@@ -104,6 +104,7 @@ export const getPreparedXAxis = async ({ xAxis, seriesData, width, boundsWidth,
|
|
|
104
104
|
margin: isLabelsEnabled ? get(xAxis, 'labels.margin', axisLabelsDefaults.margin) : 0,
|
|
105
105
|
padding: get(xAxis, 'labels.padding', axisLabelsDefaults.padding),
|
|
106
106
|
dateFormat: get(xAxis, 'labels.dateFormat'),
|
|
107
|
+
dateTimeLabelFormats: get(xAxis, 'labels.dateTimeLabelFormats'),
|
|
107
108
|
numberFormat: get(xAxis, 'labels.numberFormat'),
|
|
108
109
|
rotation: get(xAxis, 'labels.rotation', 0),
|
|
109
110
|
style: labelsStyle,
|
|
@@ -114,6 +115,7 @@ export const getPreparedXAxis = async ({ xAxis, seriesData, width, boundsWidth,
|
|
|
114
115
|
html: labelsHtml,
|
|
115
116
|
},
|
|
116
117
|
lineColor: get(xAxis, 'lineColor'),
|
|
118
|
+
lineVisible: get(xAxis, 'lineVisible', true),
|
|
117
119
|
categories: xAxis === null || xAxis === void 0 ? void 0 : xAxis.categories,
|
|
118
120
|
timestamps: get(xAxis, 'timestamps'),
|
|
119
121
|
title: {
|
|
@@ -128,6 +128,7 @@ export const getPreparedYAxis = ({ height, boundsHeight, width, seriesData, yAxi
|
|
|
128
128
|
html: labelsHtml,
|
|
129
129
|
},
|
|
130
130
|
lineColor: get(axisItem, 'lineColor'),
|
|
131
|
+
lineVisible: get(axisItem, 'lineVisible', true),
|
|
131
132
|
categories: axisItem.categories,
|
|
132
133
|
timestamps: get(axisItem, 'timestamps'),
|
|
133
134
|
title: {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export const seriesOptionsDefaults = {
|
|
2
2
|
'bar-x': {
|
|
3
3
|
barMaxWidth: 50,
|
|
4
|
-
barPadding: 0.
|
|
5
|
-
groupPadding: 0.
|
|
4
|
+
barPadding: 0.2,
|
|
5
|
+
groupPadding: 0.1,
|
|
6
6
|
stackGap: 1,
|
|
7
7
|
states: {
|
|
8
8
|
hover: {
|
|
@@ -17,8 +17,8 @@ export const seriesOptionsDefaults = {
|
|
|
17
17
|
},
|
|
18
18
|
'bar-y': {
|
|
19
19
|
barMaxWidth: 50,
|
|
20
|
-
barPadding: 0.
|
|
21
|
-
groupPadding: 0.
|
|
20
|
+
barPadding: 0.2,
|
|
21
|
+
groupPadding: 0.1,
|
|
22
22
|
stackGap: 1,
|
|
23
23
|
states: {
|
|
24
24
|
hover: {
|
|
@@ -105,7 +105,7 @@ export const seriesOptionsDefaults = {
|
|
|
105
105
|
},
|
|
106
106
|
waterfall: {
|
|
107
107
|
barMaxWidth: 50,
|
|
108
|
-
barPadding: 0.
|
|
108
|
+
barPadding: 0.2,
|
|
109
109
|
states: {
|
|
110
110
|
hover: {
|
|
111
111
|
enabled: true,
|
|
@@ -121,8 +121,9 @@ export const prepareBarXData = async (args) => {
|
|
|
121
121
|
});
|
|
122
122
|
const groupGap = Math.max(bandSize * groupPadding, MIN_BAR_GROUP_GAP);
|
|
123
123
|
const groupSize = bandSize - groupGap;
|
|
124
|
-
const
|
|
125
|
-
const
|
|
124
|
+
const barSlotSize = groupSize / maxGroupSize;
|
|
125
|
+
const rectGap = Math.max(barSlotSize * barPadding, MIN_BAR_GAP);
|
|
126
|
+
const rectWidth = Math.max(MIN_BAR_WIDTH, Math.min(barSlotSize - rectGap, barMaxWidth));
|
|
126
127
|
const plotIndexes = Array.from(dataByPlots.keys());
|
|
127
128
|
for (let plotDataIndex = 0; plotDataIndex < plotIndexes.length; plotDataIndex++) {
|
|
128
129
|
const data = (_b = dataByPlots.get(plotIndexes[plotDataIndex])) !== null && _b !== void 0 ? _b : {};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { DurationInput } from '@gravity-ui/date-utils';
|
|
2
2
|
import type { AXIS_TYPE, DashStyle } from '../../constants';
|
|
3
|
+
import type { DateTimeLabelFormats } from '../../utils/time';
|
|
3
4
|
import type { FormatNumberOptions } from '../formatter';
|
|
4
5
|
import type { MeaningfulAny } from '../misc';
|
|
5
6
|
import type { BaseTextStyle } from './base';
|
|
@@ -21,6 +22,38 @@ export interface ChartAxisLabels {
|
|
|
21
22
|
*/
|
|
22
23
|
padding?: number;
|
|
23
24
|
dateFormat?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Per-granularity display formats for the x-axis datetime labels.
|
|
27
|
+
* Ignored when `dateFormat` is set.
|
|
28
|
+
*
|
|
29
|
+
* Each value is a format string in the same form as the `format` argument to
|
|
30
|
+
* [`DateTime#format`](https://gravity-ui.github.io/date-utils/pages/api/DateTime/overview.html) in `@gravity-ui/date-utils`
|
|
31
|
+
* (see the **`FormatInput`** type there): Day.js–style tokens, e.g. `YYYY`, `MM`, `DD`, `HH`, `mm`, `ss`, `SSS`, `MMM`.
|
|
32
|
+
*
|
|
33
|
+
* Additional custom token: `B` expands to the half-year digit (`1` or `2`) — `B` stands for *bi*-annual since `H` and `h` are reserved by Day.js for clock hours.
|
|
34
|
+
* Use Day.js escape syntax for the label prefix, e.g. `'YYYY [H]B'` → `"2024 H1"`.
|
|
35
|
+
* Mirrors the quarter pattern: `'YYYY [Q]Q'` → `"2024 Q2"`.
|
|
36
|
+
*
|
|
37
|
+
* Partial objects are merged with the built-in `DATETIME_LABEL_FORMATS`; omitted keys keep defaults.
|
|
38
|
+
* @example ISO-like date and time
|
|
39
|
+
* ```ts
|
|
40
|
+
* dateTimeLabelFormats: { day: 'YYYY-MM-DD', hour: 'YYYY-MM-DD HH:mm', minute: 'YYYY-MM-DD HH:mm' }
|
|
41
|
+
* ```
|
|
42
|
+
* @example US-style calendar date
|
|
43
|
+
* ```ts
|
|
44
|
+
* dateTimeLabelFormats: { day: 'MM/DD/YYYY', week: 'MM/DD/YYYY' }
|
|
45
|
+
* ```
|
|
46
|
+
* @example Quarter and half-year labels
|
|
47
|
+
* ```ts
|
|
48
|
+
* dateTimeLabelFormats: { quarter: 'YYYY [Q]Q', halfYear: 'YYYY [H]B' }
|
|
49
|
+
* ```
|
|
50
|
+
* @example Coarse ranges: short month and full year
|
|
51
|
+
* ```ts
|
|
52
|
+
* dateTimeLabelFormats: { month: 'YYYY-MM', year: 'YYYY' }
|
|
53
|
+
* ```
|
|
54
|
+
* @see https://gravity-ui.github.io/date-utils/pages/api/DateTime/overview.html
|
|
55
|
+
*/
|
|
56
|
+
dateTimeLabelFormats?: DateTimeLabelFormats;
|
|
24
57
|
numberFormat?: FormatNumberOptions;
|
|
25
58
|
style?: Partial<BaseTextStyle>;
|
|
26
59
|
/**
|
|
@@ -125,6 +158,11 @@ export interface ChartAxis {
|
|
|
125
158
|
labels?: ChartAxisLabels;
|
|
126
159
|
/** The color of the line marking the axis itself. */
|
|
127
160
|
lineColor?: string;
|
|
161
|
+
/**
|
|
162
|
+
* Whether to display the line marking the axis itself.
|
|
163
|
+
* @default true
|
|
164
|
+
*/
|
|
165
|
+
lineVisible?: boolean;
|
|
128
166
|
title?: ChartAxisTitle;
|
|
129
167
|
/**
|
|
130
168
|
* The minimum value of the axis. If undefined the min value is automatically calculated.
|
|
@@ -65,13 +65,13 @@ export interface ChartSeriesOptions {
|
|
|
65
65
|
*/
|
|
66
66
|
barMaxWidth?: number;
|
|
67
67
|
/**
|
|
68
|
-
* Padding between each
|
|
69
|
-
* @default 0.
|
|
68
|
+
* Padding between each bar as a fraction of the space allocated per bar.
|
|
69
|
+
* @default 0.2
|
|
70
70
|
*/
|
|
71
71
|
barPadding?: number;
|
|
72
72
|
/**
|
|
73
73
|
* Padding between each value groups, in x axis units
|
|
74
|
-
* @default 0.
|
|
74
|
+
* @default 0.1
|
|
75
75
|
*/
|
|
76
76
|
groupPadding?: number;
|
|
77
77
|
/**
|
|
@@ -113,13 +113,13 @@ export interface ChartSeriesOptions {
|
|
|
113
113
|
*/
|
|
114
114
|
barMaxWidth?: number;
|
|
115
115
|
/**
|
|
116
|
-
* Padding between each
|
|
117
|
-
* @default 0.
|
|
116
|
+
* Padding between each bar as a fraction of the space allocated per bar.
|
|
117
|
+
* @default 0.2
|
|
118
118
|
*/
|
|
119
119
|
barPadding?: number;
|
|
120
120
|
/**
|
|
121
121
|
* Padding between each value groups, in x axis units
|
|
122
|
-
* @default 0.
|
|
122
|
+
* @default 0.1
|
|
123
123
|
*/
|
|
124
124
|
groupPadding?: number;
|
|
125
125
|
/**
|
|
@@ -254,8 +254,8 @@ export interface ChartSeriesOptions {
|
|
|
254
254
|
*/
|
|
255
255
|
barMaxWidth?: number;
|
|
256
256
|
/**
|
|
257
|
-
* Padding between each
|
|
258
|
-
* @default 0.
|
|
257
|
+
* Padding between each bar as a fraction of the space allocated per bar.
|
|
258
|
+
* @default 0.2
|
|
259
259
|
*/
|
|
260
260
|
barPadding?: number;
|
|
261
261
|
/** Options for the series states that provide additional styling information to the series. */
|
|
@@ -18,7 +18,8 @@ export interface ChartZoom {
|
|
|
18
18
|
*
|
|
19
19
|
* Supported zoom types by series type:
|
|
20
20
|
* - `Area`, `Line`, `Scatter`: `x`, `y`, `xy`
|
|
21
|
-
* - `BarX
|
|
21
|
+
* - `BarX`: `x`, `xy`
|
|
22
|
+
* - `XRange`: `x`
|
|
22
23
|
* - `BarY`: `y`
|
|
23
24
|
*
|
|
24
25
|
* Default zoom type by series type:
|
|
@@ -38,7 +38,12 @@ export function getXAxisTickValues({ axis, labelLineHeight, scale, series, }) {
|
|
|
38
38
|
return [];
|
|
39
39
|
}
|
|
40
40
|
const scaleTicksCount = getTicksCount({ axis, axisWidth, series });
|
|
41
|
-
const
|
|
41
|
+
const dateTimeLabelFormats = axis.type === 'datetime' ? axis.labels.dateTimeLabelFormats : undefined;
|
|
42
|
+
const scaleTicks = getScaleTicks({
|
|
43
|
+
scale,
|
|
44
|
+
ticksCount: scaleTicksCount,
|
|
45
|
+
dateTimeLabelFormats,
|
|
46
|
+
});
|
|
42
47
|
const originalTickValues = scaleTicks.map((t) => ({
|
|
43
48
|
x: scale(t),
|
|
44
49
|
value: t,
|
|
@@ -52,7 +57,7 @@ export function getXAxisTickValues({ axis, labelLineHeight, scale, series, }) {
|
|
|
52
57
|
let ticksCount = result.length - 1;
|
|
53
58
|
while (availableSpaceForLabel < labelLineHeight && result.length > 1) {
|
|
54
59
|
ticksCount = ticksCount ? ticksCount - 1 : result.length - 1;
|
|
55
|
-
const newScaleTicks = getScaleTicks({ scale, ticksCount });
|
|
60
|
+
const newScaleTicks = getScaleTicks({ scale, ticksCount, dateTimeLabelFormats });
|
|
56
61
|
result = newScaleTicks.map((t) => ({
|
|
57
62
|
x: scale(t),
|
|
58
63
|
value: t,
|
|
@@ -194,14 +194,16 @@ export async function axisBottom(args) {
|
|
|
194
194
|
.append('path')
|
|
195
195
|
.attr('d', tickPath.toString())
|
|
196
196
|
.attr('stroke', tickColor !== null && tickColor !== void 0 ? tickColor : 'currentColor');
|
|
197
|
-
// Remove tick that has the same x coordinate like domain
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
197
|
+
// Remove tick that has the same x coordinate like domain (only when domain line is visible)
|
|
198
|
+
if (domain.visible !== false) {
|
|
199
|
+
selection
|
|
200
|
+
.selectAll('.tick')
|
|
201
|
+
.filter((d) => {
|
|
202
|
+
return position(d) === 0;
|
|
203
|
+
})
|
|
204
|
+
.select('path')
|
|
205
|
+
.remove();
|
|
206
|
+
}
|
|
205
207
|
if (labelsHtml) {
|
|
206
208
|
appendHtmlLabels({
|
|
207
209
|
htmlSelection,
|
|
@@ -236,7 +238,9 @@ export async function axisBottom(args) {
|
|
|
236
238
|
x,
|
|
237
239
|
});
|
|
238
240
|
}
|
|
239
|
-
const { size: domainSize, color: domainColor } = domain;
|
|
240
|
-
|
|
241
|
+
const { size: domainSize, color: domainColor, visible: domainVisible } = domain;
|
|
242
|
+
if (domainVisible !== false) {
|
|
243
|
+
selection.call(addDomain, { size: domainSize, color: domainColor });
|
|
244
|
+
}
|
|
241
245
|
};
|
|
242
246
|
}
|
|
@@ -42,7 +42,8 @@ export function getBarYLayout(args) {
|
|
|
42
42
|
const groupGap = Math.max(bandSize * groupPadding, MIN_BAR_GROUP_GAP);
|
|
43
43
|
const maxGroupSize = max(Object.values(groupedData), (d) => Object.values(d).length) || 1;
|
|
44
44
|
const groupSize = bandSize - groupGap;
|
|
45
|
-
const
|
|
46
|
-
const
|
|
45
|
+
const barSlotSize = groupSize / maxGroupSize;
|
|
46
|
+
const barGap = Math.max(barSlotSize * barPadding, MIN_BAR_GAP);
|
|
47
|
+
const barSize = Math.max(MIN_BAR_WIDTH, Math.min(barSlotSize - barGap, barMaxWidth));
|
|
47
48
|
return { bandSize, barGap, barSize };
|
|
48
49
|
}
|
|
@@ -4,11 +4,48 @@ import { formatNumber, getDefaultUnit } from '../../libs';
|
|
|
4
4
|
import { DEFAULT_DATE_FORMAT } from '../constants';
|
|
5
5
|
import { DATETIME_LABEL_FORMATS, TIME_UNITS, getDefaultDateFormat, getDefaultTimeOnlyFormat, } from './time';
|
|
6
6
|
const LETTER_MOUNTH_AT_START_FORMAT_REGEXP = /^M{3,}/;
|
|
7
|
+
/**
|
|
8
|
+
* Expands the custom `B` token before passing the format string to `date.format()`.
|
|
9
|
+
*
|
|
10
|
+
* - `B` — half-year (*B*i-annual) digit: expands to `1` or `2`.
|
|
11
|
+
* `H` and `h` are already reserved by Day.js (24 h and 12 h clock hours respectively),
|
|
12
|
+
* so `B` (from Latin *bi*, "twice a year") is used as the token for half-year.
|
|
13
|
+
* No major date library defines a native half-year token, so this is a custom convention.
|
|
14
|
+
* Use Day.js escape syntax for the label prefix, e.g. `'YYYY [H]B'` → `"2024 H1"`.
|
|
15
|
+
* Mirrors the quarter pattern: `'YYYY [Q]Q'` → `"2024 Q2"`.
|
|
16
|
+
*
|
|
17
|
+
* Tokens inside `[…]` Day.js escape blocks are passed through unchanged.
|
|
18
|
+
*/
|
|
19
|
+
function processCustomDateTokens(format, date) {
|
|
20
|
+
let result = '';
|
|
21
|
+
let i = 0;
|
|
22
|
+
while (i < format.length) {
|
|
23
|
+
if (format[i] === '[') {
|
|
24
|
+
const end = format.indexOf(']', i + 1);
|
|
25
|
+
if (end === -1) {
|
|
26
|
+
result += format[i++];
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
result += format.slice(i, end + 1);
|
|
30
|
+
i = end + 1;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
else if (format[i] === 'B') {
|
|
34
|
+
result += date.month() < 6 ? '1' : '2';
|
|
35
|
+
i++;
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
result += format[i++];
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
7
43
|
function getFormattedDate(args) {
|
|
8
44
|
const { value, format = DEFAULT_DATE_FORMAT } = args;
|
|
9
45
|
const date = dateTimeUtc({ input: value });
|
|
10
46
|
if (date === null || date === void 0 ? void 0 : date.isValid()) {
|
|
11
|
-
const
|
|
47
|
+
const processedFormat = processCustomDateTokens(format, date);
|
|
48
|
+
const formattedDate = date.format(processedFormat);
|
|
12
49
|
if (LETTER_MOUNTH_AT_START_FORMAT_REGEXP.test(format)) {
|
|
13
50
|
return capitalize(formattedDate);
|
|
14
51
|
}
|
|
@@ -57,7 +94,7 @@ export function formatAxisTickLabel(args) {
|
|
|
57
94
|
format = isMidnight ? DATETIME_LABEL_FORMATS.day : getDefaultTimeOnlyFormat(step);
|
|
58
95
|
}
|
|
59
96
|
else {
|
|
60
|
-
format = getDefaultDateFormat(step);
|
|
97
|
+
format = getDefaultDateFormat(step, axis.labels.dateTimeLabelFormats);
|
|
61
98
|
}
|
|
62
99
|
return getFormattedDate({ value: date, format });
|
|
63
100
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { DateTimeLabelFormats } from '../time';
|
|
1
2
|
/**
|
|
2
3
|
* Generates time ticks for the given interval.
|
|
3
4
|
*
|
|
@@ -5,4 +6,4 @@
|
|
|
5
6
|
* weeks are considered to start on Monday (ISO 8601 standard)
|
|
6
7
|
* instead of Sunday (d3 default).
|
|
7
8
|
*/
|
|
8
|
-
export declare function getDateTimeTicks(start: Date, stop: Date, count?: number): Date[];
|
|
9
|
+
export declare function getDateTimeTicks(start: Date, stop: Date, count?: number, formats?: DateTimeLabelFormats): Date[];
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { bisector, tickStep } from 'd3-array';
|
|
2
2
|
import { utcDay, utcHour, utcMillisecond, utcMinute, utcMonth, utcSecond, utcMonday as utcWeek, utcYear, } from 'd3-time';
|
|
3
3
|
import { DAY, HOUR, MINUTE, MONTH, SECOND, WEEK, YEAR } from '../../constants';
|
|
4
|
-
|
|
4
|
+
// Base tick intervals matching the original D3 utcTicks algorithm
|
|
5
|
+
// (with Monday-start weeks). Extra granularities live in optionalTickIntervals
|
|
6
|
+
// and are injected only when the matching dateTimeLabelFormats key is provided.
|
|
7
|
+
const baseTickIntervals = [
|
|
5
8
|
[utcSecond, 1, SECOND],
|
|
6
9
|
[utcSecond, 5, 5 * SECOND],
|
|
7
10
|
[utcSecond, 15, 15 * SECOND],
|
|
@@ -21,20 +24,37 @@ const tickIntervals = [
|
|
|
21
24
|
[utcMonth, 3, 3 * MONTH],
|
|
22
25
|
[utcYear, 1, YEAR],
|
|
23
26
|
];
|
|
27
|
+
const optionalTickIntervals = {
|
|
28
|
+
halfYear: [utcMonth, 6, 6 * MONTH],
|
|
29
|
+
};
|
|
24
30
|
// utcDay.every(2) resets its day counter at the start of each month (field = getUTCDate() - 1),
|
|
25
31
|
// so in a 31-day month the last tick lands on day 31 and the next tick is day 1 of the following
|
|
26
32
|
// month — only 1 day apart. Filtering by absolute Unix day number avoids the monthly reset.
|
|
27
33
|
const utcEvery2Days = utcDay.filter((d) => Math.floor(d.getTime() / DAY) % 2 === 0);
|
|
28
|
-
function
|
|
34
|
+
function buildTickIntervals(formats) {
|
|
35
|
+
if (!formats) {
|
|
36
|
+
return baseTickIntervals;
|
|
37
|
+
}
|
|
38
|
+
const extras = Object.keys(formats).flatMap((key) => {
|
|
39
|
+
const entry = optionalTickIntervals[key];
|
|
40
|
+
return entry ? [entry] : [];
|
|
41
|
+
});
|
|
42
|
+
if (extras.length === 0) {
|
|
43
|
+
return baseTickIntervals;
|
|
44
|
+
}
|
|
45
|
+
return [...baseTickIntervals, ...extras].sort((a, b) => a[2] - b[2]);
|
|
46
|
+
}
|
|
47
|
+
function getDateTimeTickInterval(start, stop, count, formats) {
|
|
48
|
+
const intervals = buildTickIntervals(formats);
|
|
29
49
|
const target = Math.abs(stop - start) / count;
|
|
30
|
-
const i = bisector(([, , step]) => step).right(
|
|
31
|
-
if (i ===
|
|
50
|
+
const i = bisector(([, , step]) => step).right(intervals, target);
|
|
51
|
+
if (i === intervals.length) {
|
|
32
52
|
return utcYear.every(tickStep(start / YEAR, stop / YEAR, count));
|
|
33
53
|
}
|
|
34
54
|
if (i === 0) {
|
|
35
55
|
return utcMillisecond.every(Math.max(tickStep(start, stop, count), 1));
|
|
36
56
|
}
|
|
37
|
-
const [t, step] =
|
|
57
|
+
const [t, step] = intervals[target / intervals[i - 1][2] < intervals[i][2] / target ? i - 1 : i];
|
|
38
58
|
if (t === utcDay && step === 2) {
|
|
39
59
|
return utcEvery2Days;
|
|
40
60
|
}
|
|
@@ -47,12 +67,12 @@ function getDateTimeTickInterval(start, stop, count) {
|
|
|
47
67
|
* weeks are considered to start on Monday (ISO 8601 standard)
|
|
48
68
|
* instead of Sunday (d3 default).
|
|
49
69
|
*/
|
|
50
|
-
export function getDateTimeTicks(start, stop, count = 10) {
|
|
70
|
+
export function getDateTimeTicks(start, stop, count = 10, formats) {
|
|
51
71
|
const reverse = stop < start;
|
|
52
72
|
if (reverse) {
|
|
53
73
|
[start, stop] = [stop, start];
|
|
54
74
|
}
|
|
55
|
-
const interval = getDateTimeTickInterval(start.getTime(), stop.getTime(), count);
|
|
75
|
+
const interval = getDateTimeTickInterval(start.getTime(), stop.getTime(), count, formats);
|
|
56
76
|
const ticks = interval ? interval.range(start, new Date(Number(stop) + 1)) : [];
|
|
57
77
|
return reverse ? ticks.reverse() : ticks;
|
|
58
78
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { AxisDomain, AxisScale } from 'd3-axis';
|
|
2
2
|
import type { ChartScale } from '../../../hooks';
|
|
3
|
-
|
|
3
|
+
import { getDateTimeTicks } from './datetime';
|
|
4
|
+
export declare function getScaleTicks({ scale, ticksCount, dateTimeLabelFormats, }: {
|
|
4
5
|
scale: ChartScale | AxisScale<AxisDomain>;
|
|
5
6
|
ticksCount?: number;
|
|
7
|
+
dateTimeLabelFormats?: Parameters<typeof getDateTimeTicks>[3];
|
|
6
8
|
}): string[] | number[] | Date[];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getDateTimeTicks } from './datetime';
|
|
2
|
-
export function getScaleTicks({ scale, ticksCount, }) {
|
|
2
|
+
export function getScaleTicks({ scale, ticksCount, dateTimeLabelFormats, }) {
|
|
3
3
|
const scaleDomain = scale.domain();
|
|
4
4
|
switch (typeof scaleDomain[0]) {
|
|
5
5
|
case 'number': {
|
|
@@ -7,7 +7,7 @@ export function getScaleTicks({ scale, ticksCount, }) {
|
|
|
7
7
|
}
|
|
8
8
|
// datetime scale
|
|
9
9
|
case 'object': {
|
|
10
|
-
return getDateTimeTicks(scaleDomain[0], scaleDomain[scaleDomain.length - 1], ticksCount);
|
|
10
|
+
return getDateTimeTicks(scaleDomain[0], scaleDomain[scaleDomain.length - 1], ticksCount, dateTimeLabelFormats);
|
|
11
11
|
}
|
|
12
12
|
case 'string': {
|
|
13
13
|
return scaleDomain;
|
|
@@ -6,6 +6,8 @@ export const TIME_UNITS = {
|
|
|
6
6
|
day: 24 * 3600000,
|
|
7
7
|
week: 7 * 24 * 3600000,
|
|
8
8
|
month: 28 * 24 * 3600000,
|
|
9
|
+
quarter: 62 * 24 * 3600000,
|
|
10
|
+
halfYear: 160 * 24 * 3600000,
|
|
9
11
|
year: 364 * 24 * 3600000,
|
|
10
12
|
};
|
|
11
13
|
export const DATETIME_LABEL_FORMATS = {
|
|
@@ -16,6 +18,8 @@ export const DATETIME_LABEL_FORMATS = {
|
|
|
16
18
|
day: 'DD.MM.YY',
|
|
17
19
|
week: 'DD.MM.YY',
|
|
18
20
|
month: "MMM 'YY",
|
|
21
|
+
quarter: "MMM 'YY",
|
|
22
|
+
halfYear: "MMM 'YY",
|
|
19
23
|
year: 'YYYY',
|
|
20
24
|
};
|
|
21
25
|
function getTimeUnit(range) {
|
|
@@ -74,7 +74,7 @@ async function getSvgAxisLabel({ getTextSize, text, axis, top, left, labelMaxWid
|
|
|
74
74
|
return svgLabel;
|
|
75
75
|
}
|
|
76
76
|
export async function prepareXAxisData({ axis, boundsOffsetLeft, boundsOffsetRight, boundsWidth, chartMarginLeft, chartMarginRight, height, scale, series, split, yAxis, }) {
|
|
77
|
-
var _a, _b, _c, _d, _e, _f, _g
|
|
77
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
78
78
|
const xAxisItems = [];
|
|
79
79
|
const splitPlots = (_a = split === null || split === void 0 ? void 0 : split.plots) !== null && _a !== void 0 ? _a : [];
|
|
80
80
|
for (let plotIndex = 0; plotIndex < splitPlots.length; plotIndex++) {
|
|
@@ -84,18 +84,16 @@ export async function prepareXAxisData({ axis, boundsOffsetLeft, boundsOffsetRig
|
|
|
84
84
|
const axisWidth = boundsWidth;
|
|
85
85
|
const isBottomPlot = plotIndex === splitPlots.length - 1;
|
|
86
86
|
const plotYAxes = yAxis.filter((a) => a.plotIndex === plotIndex);
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const yDomainRightPosition = (
|
|
91
|
-
? axisWidth
|
|
92
|
-
: null;
|
|
87
|
+
const leftYAxis = plotYAxes.find((a) => a.position === 'left');
|
|
88
|
+
const yDomainLeftPosition = (leftYAxis === null || leftYAxis === void 0 ? void 0 : leftYAxis.visible) && (leftYAxis === null || leftYAxis === void 0 ? void 0 : leftYAxis.lineVisible) !== false ? 0 : null;
|
|
89
|
+
const rightYAxis = plotYAxes.find((a) => a.position === 'right');
|
|
90
|
+
const yDomainRightPosition = (rightYAxis === null || rightYAxis === void 0 ? void 0 : rightYAxis.visible) && (rightYAxis === null || rightYAxis === void 0 ? void 0 : rightYAxis.lineVisible) !== false ? axisWidth : null;
|
|
93
91
|
let domain = null;
|
|
94
|
-
if (isBottomPlot && axis.visible) {
|
|
92
|
+
if (isBottomPlot && axis.visible && axis.lineVisible !== false) {
|
|
95
93
|
domain = {
|
|
96
94
|
start: [0, axisTop + axisHeight],
|
|
97
95
|
end: [axisWidth, axisTop + axisHeight],
|
|
98
|
-
lineColor: (
|
|
96
|
+
lineColor: (_b = axis.lineColor) !== null && _b !== void 0 ? _b : '',
|
|
99
97
|
};
|
|
100
98
|
}
|
|
101
99
|
const ticks = [];
|
|
@@ -264,14 +262,14 @@ export async function prepareXAxisData({ axis, boundsOffsetLeft, boundsOffsetRig
|
|
|
264
262
|
axisScale,
|
|
265
263
|
axis: 'x',
|
|
266
264
|
});
|
|
267
|
-
const halfBandwidth = ((
|
|
265
|
+
const halfBandwidth = ((_d = (_c = axisScale.bandwidth) === null || _c === void 0 ? void 0 : _c.call(axisScale)) !== null && _d !== void 0 ? _d : 0) / 2;
|
|
268
266
|
const startPos = halfBandwidth + Math.min(from, to);
|
|
269
267
|
const endPos = Math.min(Math.abs(to - from), axisWidth - Math.min(from, to));
|
|
270
268
|
const plotBandWidth = Math.min(endPos, axisWidth);
|
|
271
269
|
if (plotBandWidth < 0) {
|
|
272
270
|
continue;
|
|
273
271
|
}
|
|
274
|
-
const perpExtent = (
|
|
272
|
+
const perpExtent = (_e = calculateNumericProperty({ value: plotBand.size, base: axisHeight })) !== null && _e !== void 0 ? _e : axisHeight;
|
|
275
273
|
// X axis is positioned at the bottom of the plot area, so 'start' = bottom edge.
|
|
276
274
|
const bandY = plotBand.align === 'end' ? axisTop : axisTop + axisHeight - perpExtent;
|
|
277
275
|
const getPlotLabelSize = getTextSizeFn({ style: plotBand.label.style });
|
|
@@ -290,8 +288,8 @@ export async function prepareXAxisData({ axis, boundsOffsetLeft, boundsOffsetRig
|
|
|
290
288
|
? {
|
|
291
289
|
text: plotBand.label.text,
|
|
292
290
|
style: plotBand.label.style,
|
|
293
|
-
x: plotBand.label.padding + ((
|
|
294
|
-
y: plotBand.label.padding + ((
|
|
291
|
+
x: plotBand.label.padding + ((_f = labelSize === null || labelSize === void 0 ? void 0 : labelSize.hangingOffset) !== null && _f !== void 0 ? _f : 0),
|
|
292
|
+
y: plotBand.label.padding + ((_g = labelSize === null || labelSize === void 0 ? void 0 : labelSize.width) !== null && _g !== void 0 ? _g : 0),
|
|
295
293
|
rotate: -90,
|
|
296
294
|
qa: plotBand.label.qa,
|
|
297
295
|
}
|
|
@@ -118,7 +118,7 @@ export async function prepareYAxisData({ axis, split, scale, top: topOffset, wid
|
|
|
118
118
|
const axisHeight = ((_b = split === null || split === void 0 ? void 0 : split.plots[axis.plotIndex]) === null || _b === void 0 ? void 0 : _b.height) || height;
|
|
119
119
|
const domainX = axis.position === 'left' ? 0 : width;
|
|
120
120
|
let domain = null;
|
|
121
|
-
if (axis.visible) {
|
|
121
|
+
if (axis.visible && axis.lineVisible !== false) {
|
|
122
122
|
domain = {
|
|
123
123
|
start: [domainX, axisPlotTopPosition],
|
|
124
124
|
end: [domainX, axisPlotTopPosition + axisHeight],
|
|
@@ -9,6 +9,7 @@ export declare function getNormalizedXAxis(props: {
|
|
|
9
9
|
type?: import("../../..").ChartAxisType;
|
|
10
10
|
labels?: import("../../..").ChartAxisLabels;
|
|
11
11
|
lineColor?: string;
|
|
12
|
+
lineVisible?: boolean;
|
|
12
13
|
title?: import("../../..").ChartAxisTitle;
|
|
13
14
|
min?: number;
|
|
14
15
|
max?: number;
|
|
@@ -6,7 +6,9 @@ function mapSeriesTypeToZoomType(seriesType) {
|
|
|
6
6
|
case SERIES_TYPE.Area: {
|
|
7
7
|
return [ZOOM_TYPE.X, ZOOM_TYPE.XY, ZOOM_TYPE.Y];
|
|
8
8
|
}
|
|
9
|
-
case SERIES_TYPE.BarX:
|
|
9
|
+
case SERIES_TYPE.BarX: {
|
|
10
|
+
return [ZOOM_TYPE.X, ZOOM_TYPE.XY];
|
|
11
|
+
}
|
|
10
12
|
case SERIES_TYPE.XRange: {
|
|
11
13
|
return [ZOOM_TYPE.X];
|
|
12
14
|
}
|
|
@@ -18,7 +18,7 @@ async function setLabelSettings({ axis, seriesData, width, axisLabels, }) {
|
|
|
18
18
|
const tickValues = getXAxisTickValues({ axis, scale, labelLineHeight, series: seriesData });
|
|
19
19
|
const tickStep = getMinSpaceBetween(tickValues, (d) => Number(d.value));
|
|
20
20
|
if (axis.type === 'datetime' && !(axisLabels === null || axisLabels === void 0 ? void 0 : axisLabels.dateFormat) && tickStep >= TIME_UNITS.day) {
|
|
21
|
-
axis.labels.dateFormat = getDefaultDateFormat(tickStep);
|
|
21
|
+
axis.labels.dateFormat = getDefaultDateFormat(tickStep, axisLabels === null || axisLabels === void 0 ? void 0 : axisLabels.dateTimeLabelFormats);
|
|
22
22
|
}
|
|
23
23
|
const labels = tickValues.map((tick) => formatAxisTickLabel({
|
|
24
24
|
axis,
|
|
@@ -104,6 +104,7 @@ export const getPreparedXAxis = async ({ xAxis, seriesData, width, boundsWidth,
|
|
|
104
104
|
margin: isLabelsEnabled ? get(xAxis, 'labels.margin', axisLabelsDefaults.margin) : 0,
|
|
105
105
|
padding: get(xAxis, 'labels.padding', axisLabelsDefaults.padding),
|
|
106
106
|
dateFormat: get(xAxis, 'labels.dateFormat'),
|
|
107
|
+
dateTimeLabelFormats: get(xAxis, 'labels.dateTimeLabelFormats'),
|
|
107
108
|
numberFormat: get(xAxis, 'labels.numberFormat'),
|
|
108
109
|
rotation: get(xAxis, 'labels.rotation', 0),
|
|
109
110
|
style: labelsStyle,
|
|
@@ -114,6 +115,7 @@ export const getPreparedXAxis = async ({ xAxis, seriesData, width, boundsWidth,
|
|
|
114
115
|
html: labelsHtml,
|
|
115
116
|
},
|
|
116
117
|
lineColor: get(xAxis, 'lineColor'),
|
|
118
|
+
lineVisible: get(xAxis, 'lineVisible', true),
|
|
117
119
|
categories: xAxis === null || xAxis === void 0 ? void 0 : xAxis.categories,
|
|
118
120
|
timestamps: get(xAxis, 'timestamps'),
|
|
119
121
|
title: {
|
|
@@ -128,6 +128,7 @@ export const getPreparedYAxis = ({ height, boundsHeight, width, seriesData, yAxi
|
|
|
128
128
|
html: labelsHtml,
|
|
129
129
|
},
|
|
130
130
|
lineColor: get(axisItem, 'lineColor'),
|
|
131
|
+
lineVisible: get(axisItem, 'lineVisible', true),
|
|
131
132
|
categories: axisItem.categories,
|
|
132
133
|
timestamps: get(axisItem, 'timestamps'),
|
|
133
134
|
title: {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export const seriesOptionsDefaults = {
|
|
2
2
|
'bar-x': {
|
|
3
3
|
barMaxWidth: 50,
|
|
4
|
-
barPadding: 0.
|
|
5
|
-
groupPadding: 0.
|
|
4
|
+
barPadding: 0.2,
|
|
5
|
+
groupPadding: 0.1,
|
|
6
6
|
stackGap: 1,
|
|
7
7
|
states: {
|
|
8
8
|
hover: {
|
|
@@ -17,8 +17,8 @@ export const seriesOptionsDefaults = {
|
|
|
17
17
|
},
|
|
18
18
|
'bar-y': {
|
|
19
19
|
barMaxWidth: 50,
|
|
20
|
-
barPadding: 0.
|
|
21
|
-
groupPadding: 0.
|
|
20
|
+
barPadding: 0.2,
|
|
21
|
+
groupPadding: 0.1,
|
|
22
22
|
stackGap: 1,
|
|
23
23
|
states: {
|
|
24
24
|
hover: {
|
|
@@ -105,7 +105,7 @@ export const seriesOptionsDefaults = {
|
|
|
105
105
|
},
|
|
106
106
|
waterfall: {
|
|
107
107
|
barMaxWidth: 50,
|
|
108
|
-
barPadding: 0.
|
|
108
|
+
barPadding: 0.2,
|
|
109
109
|
states: {
|
|
110
110
|
hover: {
|
|
111
111
|
enabled: true,
|
|
@@ -121,8 +121,9 @@ export const prepareBarXData = async (args) => {
|
|
|
121
121
|
});
|
|
122
122
|
const groupGap = Math.max(bandSize * groupPadding, MIN_BAR_GROUP_GAP);
|
|
123
123
|
const groupSize = bandSize - groupGap;
|
|
124
|
-
const
|
|
125
|
-
const
|
|
124
|
+
const barSlotSize = groupSize / maxGroupSize;
|
|
125
|
+
const rectGap = Math.max(barSlotSize * barPadding, MIN_BAR_GAP);
|
|
126
|
+
const rectWidth = Math.max(MIN_BAR_WIDTH, Math.min(barSlotSize - rectGap, barMaxWidth));
|
|
126
127
|
const plotIndexes = Array.from(dataByPlots.keys());
|
|
127
128
|
for (let plotDataIndex = 0; plotDataIndex < plotIndexes.length; plotDataIndex++) {
|
|
128
129
|
const data = (_b = dataByPlots.get(plotIndexes[plotDataIndex])) !== null && _b !== void 0 ? _b : {};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { DurationInput } from '@gravity-ui/date-utils';
|
|
2
2
|
import type { AXIS_TYPE, DashStyle } from '../../constants';
|
|
3
|
+
import type { DateTimeLabelFormats } from '../../utils/time';
|
|
3
4
|
import type { FormatNumberOptions } from '../formatter';
|
|
4
5
|
import type { MeaningfulAny } from '../misc';
|
|
5
6
|
import type { BaseTextStyle } from './base';
|
|
@@ -21,6 +22,38 @@ export interface ChartAxisLabels {
|
|
|
21
22
|
*/
|
|
22
23
|
padding?: number;
|
|
23
24
|
dateFormat?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Per-granularity display formats for the x-axis datetime labels.
|
|
27
|
+
* Ignored when `dateFormat` is set.
|
|
28
|
+
*
|
|
29
|
+
* Each value is a format string in the same form as the `format` argument to
|
|
30
|
+
* [`DateTime#format`](https://gravity-ui.github.io/date-utils/pages/api/DateTime/overview.html) in `@gravity-ui/date-utils`
|
|
31
|
+
* (see the **`FormatInput`** type there): Day.js–style tokens, e.g. `YYYY`, `MM`, `DD`, `HH`, `mm`, `ss`, `SSS`, `MMM`.
|
|
32
|
+
*
|
|
33
|
+
* Additional custom token: `B` expands to the half-year digit (`1` or `2`) — `B` stands for *bi*-annual since `H` and `h` are reserved by Day.js for clock hours.
|
|
34
|
+
* Use Day.js escape syntax for the label prefix, e.g. `'YYYY [H]B'` → `"2024 H1"`.
|
|
35
|
+
* Mirrors the quarter pattern: `'YYYY [Q]Q'` → `"2024 Q2"`.
|
|
36
|
+
*
|
|
37
|
+
* Partial objects are merged with the built-in `DATETIME_LABEL_FORMATS`; omitted keys keep defaults.
|
|
38
|
+
* @example ISO-like date and time
|
|
39
|
+
* ```ts
|
|
40
|
+
* dateTimeLabelFormats: { day: 'YYYY-MM-DD', hour: 'YYYY-MM-DD HH:mm', minute: 'YYYY-MM-DD HH:mm' }
|
|
41
|
+
* ```
|
|
42
|
+
* @example US-style calendar date
|
|
43
|
+
* ```ts
|
|
44
|
+
* dateTimeLabelFormats: { day: 'MM/DD/YYYY', week: 'MM/DD/YYYY' }
|
|
45
|
+
* ```
|
|
46
|
+
* @example Quarter and half-year labels
|
|
47
|
+
* ```ts
|
|
48
|
+
* dateTimeLabelFormats: { quarter: 'YYYY [Q]Q', halfYear: 'YYYY [H]B' }
|
|
49
|
+
* ```
|
|
50
|
+
* @example Coarse ranges: short month and full year
|
|
51
|
+
* ```ts
|
|
52
|
+
* dateTimeLabelFormats: { month: 'YYYY-MM', year: 'YYYY' }
|
|
53
|
+
* ```
|
|
54
|
+
* @see https://gravity-ui.github.io/date-utils/pages/api/DateTime/overview.html
|
|
55
|
+
*/
|
|
56
|
+
dateTimeLabelFormats?: DateTimeLabelFormats;
|
|
24
57
|
numberFormat?: FormatNumberOptions;
|
|
25
58
|
style?: Partial<BaseTextStyle>;
|
|
26
59
|
/**
|
|
@@ -125,6 +158,11 @@ export interface ChartAxis {
|
|
|
125
158
|
labels?: ChartAxisLabels;
|
|
126
159
|
/** The color of the line marking the axis itself. */
|
|
127
160
|
lineColor?: string;
|
|
161
|
+
/**
|
|
162
|
+
* Whether to display the line marking the axis itself.
|
|
163
|
+
* @default true
|
|
164
|
+
*/
|
|
165
|
+
lineVisible?: boolean;
|
|
128
166
|
title?: ChartAxisTitle;
|
|
129
167
|
/**
|
|
130
168
|
* The minimum value of the axis. If undefined the min value is automatically calculated.
|
|
@@ -65,13 +65,13 @@ export interface ChartSeriesOptions {
|
|
|
65
65
|
*/
|
|
66
66
|
barMaxWidth?: number;
|
|
67
67
|
/**
|
|
68
|
-
* Padding between each
|
|
69
|
-
* @default 0.
|
|
68
|
+
* Padding between each bar as a fraction of the space allocated per bar.
|
|
69
|
+
* @default 0.2
|
|
70
70
|
*/
|
|
71
71
|
barPadding?: number;
|
|
72
72
|
/**
|
|
73
73
|
* Padding between each value groups, in x axis units
|
|
74
|
-
* @default 0.
|
|
74
|
+
* @default 0.1
|
|
75
75
|
*/
|
|
76
76
|
groupPadding?: number;
|
|
77
77
|
/**
|
|
@@ -113,13 +113,13 @@ export interface ChartSeriesOptions {
|
|
|
113
113
|
*/
|
|
114
114
|
barMaxWidth?: number;
|
|
115
115
|
/**
|
|
116
|
-
* Padding between each
|
|
117
|
-
* @default 0.
|
|
116
|
+
* Padding between each bar as a fraction of the space allocated per bar.
|
|
117
|
+
* @default 0.2
|
|
118
118
|
*/
|
|
119
119
|
barPadding?: number;
|
|
120
120
|
/**
|
|
121
121
|
* Padding between each value groups, in x axis units
|
|
122
|
-
* @default 0.
|
|
122
|
+
* @default 0.1
|
|
123
123
|
*/
|
|
124
124
|
groupPadding?: number;
|
|
125
125
|
/**
|
|
@@ -254,8 +254,8 @@ export interface ChartSeriesOptions {
|
|
|
254
254
|
*/
|
|
255
255
|
barMaxWidth?: number;
|
|
256
256
|
/**
|
|
257
|
-
* Padding between each
|
|
258
|
-
* @default 0.
|
|
257
|
+
* Padding between each bar as a fraction of the space allocated per bar.
|
|
258
|
+
* @default 0.2
|
|
259
259
|
*/
|
|
260
260
|
barPadding?: number;
|
|
261
261
|
/** Options for the series states that provide additional styling information to the series. */
|
|
@@ -18,7 +18,8 @@ export interface ChartZoom {
|
|
|
18
18
|
*
|
|
19
19
|
* Supported zoom types by series type:
|
|
20
20
|
* - `Area`, `Line`, `Scatter`: `x`, `y`, `xy`
|
|
21
|
-
* - `BarX
|
|
21
|
+
* - `BarX`: `x`, `xy`
|
|
22
|
+
* - `XRange`: `x`
|
|
22
23
|
* - `BarY`: `y`
|
|
23
24
|
*
|
|
24
25
|
* Default zoom type by series type:
|
|
@@ -38,7 +38,12 @@ export function getXAxisTickValues({ axis, labelLineHeight, scale, series, }) {
|
|
|
38
38
|
return [];
|
|
39
39
|
}
|
|
40
40
|
const scaleTicksCount = getTicksCount({ axis, axisWidth, series });
|
|
41
|
-
const
|
|
41
|
+
const dateTimeLabelFormats = axis.type === 'datetime' ? axis.labels.dateTimeLabelFormats : undefined;
|
|
42
|
+
const scaleTicks = getScaleTicks({
|
|
43
|
+
scale,
|
|
44
|
+
ticksCount: scaleTicksCount,
|
|
45
|
+
dateTimeLabelFormats,
|
|
46
|
+
});
|
|
42
47
|
const originalTickValues = scaleTicks.map((t) => ({
|
|
43
48
|
x: scale(t),
|
|
44
49
|
value: t,
|
|
@@ -52,7 +57,7 @@ export function getXAxisTickValues({ axis, labelLineHeight, scale, series, }) {
|
|
|
52
57
|
let ticksCount = result.length - 1;
|
|
53
58
|
while (availableSpaceForLabel < labelLineHeight && result.length > 1) {
|
|
54
59
|
ticksCount = ticksCount ? ticksCount - 1 : result.length - 1;
|
|
55
|
-
const newScaleTicks = getScaleTicks({ scale, ticksCount });
|
|
60
|
+
const newScaleTicks = getScaleTicks({ scale, ticksCount, dateTimeLabelFormats });
|
|
56
61
|
result = newScaleTicks.map((t) => ({
|
|
57
62
|
x: scale(t),
|
|
58
63
|
value: t,
|
|
@@ -194,14 +194,16 @@ export async function axisBottom(args) {
|
|
|
194
194
|
.append('path')
|
|
195
195
|
.attr('d', tickPath.toString())
|
|
196
196
|
.attr('stroke', tickColor !== null && tickColor !== void 0 ? tickColor : 'currentColor');
|
|
197
|
-
// Remove tick that has the same x coordinate like domain
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
197
|
+
// Remove tick that has the same x coordinate like domain (only when domain line is visible)
|
|
198
|
+
if (domain.visible !== false) {
|
|
199
|
+
selection
|
|
200
|
+
.selectAll('.tick')
|
|
201
|
+
.filter((d) => {
|
|
202
|
+
return position(d) === 0;
|
|
203
|
+
})
|
|
204
|
+
.select('path')
|
|
205
|
+
.remove();
|
|
206
|
+
}
|
|
205
207
|
if (labelsHtml) {
|
|
206
208
|
appendHtmlLabels({
|
|
207
209
|
htmlSelection,
|
|
@@ -236,7 +238,9 @@ export async function axisBottom(args) {
|
|
|
236
238
|
x,
|
|
237
239
|
});
|
|
238
240
|
}
|
|
239
|
-
const { size: domainSize, color: domainColor } = domain;
|
|
240
|
-
|
|
241
|
+
const { size: domainSize, color: domainColor, visible: domainVisible } = domain;
|
|
242
|
+
if (domainVisible !== false) {
|
|
243
|
+
selection.call(addDomain, { size: domainSize, color: domainColor });
|
|
244
|
+
}
|
|
241
245
|
};
|
|
242
246
|
}
|
|
@@ -42,7 +42,8 @@ export function getBarYLayout(args) {
|
|
|
42
42
|
const groupGap = Math.max(bandSize * groupPadding, MIN_BAR_GROUP_GAP);
|
|
43
43
|
const maxGroupSize = max(Object.values(groupedData), (d) => Object.values(d).length) || 1;
|
|
44
44
|
const groupSize = bandSize - groupGap;
|
|
45
|
-
const
|
|
46
|
-
const
|
|
45
|
+
const barSlotSize = groupSize / maxGroupSize;
|
|
46
|
+
const barGap = Math.max(barSlotSize * barPadding, MIN_BAR_GAP);
|
|
47
|
+
const barSize = Math.max(MIN_BAR_WIDTH, Math.min(barSlotSize - barGap, barMaxWidth));
|
|
47
48
|
return { bandSize, barGap, barSize };
|
|
48
49
|
}
|
|
@@ -4,11 +4,48 @@ import { formatNumber, getDefaultUnit } from '../../libs';
|
|
|
4
4
|
import { DEFAULT_DATE_FORMAT } from '../constants';
|
|
5
5
|
import { DATETIME_LABEL_FORMATS, TIME_UNITS, getDefaultDateFormat, getDefaultTimeOnlyFormat, } from './time';
|
|
6
6
|
const LETTER_MOUNTH_AT_START_FORMAT_REGEXP = /^M{3,}/;
|
|
7
|
+
/**
|
|
8
|
+
* Expands the custom `B` token before passing the format string to `date.format()`.
|
|
9
|
+
*
|
|
10
|
+
* - `B` — half-year (*B*i-annual) digit: expands to `1` or `2`.
|
|
11
|
+
* `H` and `h` are already reserved by Day.js (24 h and 12 h clock hours respectively),
|
|
12
|
+
* so `B` (from Latin *bi*, "twice a year") is used as the token for half-year.
|
|
13
|
+
* No major date library defines a native half-year token, so this is a custom convention.
|
|
14
|
+
* Use Day.js escape syntax for the label prefix, e.g. `'YYYY [H]B'` → `"2024 H1"`.
|
|
15
|
+
* Mirrors the quarter pattern: `'YYYY [Q]Q'` → `"2024 Q2"`.
|
|
16
|
+
*
|
|
17
|
+
* Tokens inside `[…]` Day.js escape blocks are passed through unchanged.
|
|
18
|
+
*/
|
|
19
|
+
function processCustomDateTokens(format, date) {
|
|
20
|
+
let result = '';
|
|
21
|
+
let i = 0;
|
|
22
|
+
while (i < format.length) {
|
|
23
|
+
if (format[i] === '[') {
|
|
24
|
+
const end = format.indexOf(']', i + 1);
|
|
25
|
+
if (end === -1) {
|
|
26
|
+
result += format[i++];
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
result += format.slice(i, end + 1);
|
|
30
|
+
i = end + 1;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
else if (format[i] === 'B') {
|
|
34
|
+
result += date.month() < 6 ? '1' : '2';
|
|
35
|
+
i++;
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
result += format[i++];
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
7
43
|
function getFormattedDate(args) {
|
|
8
44
|
const { value, format = DEFAULT_DATE_FORMAT } = args;
|
|
9
45
|
const date = dateTimeUtc({ input: value });
|
|
10
46
|
if (date === null || date === void 0 ? void 0 : date.isValid()) {
|
|
11
|
-
const
|
|
47
|
+
const processedFormat = processCustomDateTokens(format, date);
|
|
48
|
+
const formattedDate = date.format(processedFormat);
|
|
12
49
|
if (LETTER_MOUNTH_AT_START_FORMAT_REGEXP.test(format)) {
|
|
13
50
|
return capitalize(formattedDate);
|
|
14
51
|
}
|
|
@@ -57,7 +94,7 @@ export function formatAxisTickLabel(args) {
|
|
|
57
94
|
format = isMidnight ? DATETIME_LABEL_FORMATS.day : getDefaultTimeOnlyFormat(step);
|
|
58
95
|
}
|
|
59
96
|
else {
|
|
60
|
-
format = getDefaultDateFormat(step);
|
|
97
|
+
format = getDefaultDateFormat(step, axis.labels.dateTimeLabelFormats);
|
|
61
98
|
}
|
|
62
99
|
return getFormattedDate({ value: date, format });
|
|
63
100
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { DateTimeLabelFormats } from '../time';
|
|
1
2
|
/**
|
|
2
3
|
* Generates time ticks for the given interval.
|
|
3
4
|
*
|
|
@@ -5,4 +6,4 @@
|
|
|
5
6
|
* weeks are considered to start on Monday (ISO 8601 standard)
|
|
6
7
|
* instead of Sunday (d3 default).
|
|
7
8
|
*/
|
|
8
|
-
export declare function getDateTimeTicks(start: Date, stop: Date, count?: number): Date[];
|
|
9
|
+
export declare function getDateTimeTicks(start: Date, stop: Date, count?: number, formats?: DateTimeLabelFormats): Date[];
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { bisector, tickStep } from 'd3-array';
|
|
2
2
|
import { utcDay, utcHour, utcMillisecond, utcMinute, utcMonth, utcSecond, utcMonday as utcWeek, utcYear, } from 'd3-time';
|
|
3
3
|
import { DAY, HOUR, MINUTE, MONTH, SECOND, WEEK, YEAR } from '../../constants';
|
|
4
|
-
|
|
4
|
+
// Base tick intervals matching the original D3 utcTicks algorithm
|
|
5
|
+
// (with Monday-start weeks). Extra granularities live in optionalTickIntervals
|
|
6
|
+
// and are injected only when the matching dateTimeLabelFormats key is provided.
|
|
7
|
+
const baseTickIntervals = [
|
|
5
8
|
[utcSecond, 1, SECOND],
|
|
6
9
|
[utcSecond, 5, 5 * SECOND],
|
|
7
10
|
[utcSecond, 15, 15 * SECOND],
|
|
@@ -21,20 +24,37 @@ const tickIntervals = [
|
|
|
21
24
|
[utcMonth, 3, 3 * MONTH],
|
|
22
25
|
[utcYear, 1, YEAR],
|
|
23
26
|
];
|
|
27
|
+
const optionalTickIntervals = {
|
|
28
|
+
halfYear: [utcMonth, 6, 6 * MONTH],
|
|
29
|
+
};
|
|
24
30
|
// utcDay.every(2) resets its day counter at the start of each month (field = getUTCDate() - 1),
|
|
25
31
|
// so in a 31-day month the last tick lands on day 31 and the next tick is day 1 of the following
|
|
26
32
|
// month — only 1 day apart. Filtering by absolute Unix day number avoids the monthly reset.
|
|
27
33
|
const utcEvery2Days = utcDay.filter((d) => Math.floor(d.getTime() / DAY) % 2 === 0);
|
|
28
|
-
function
|
|
34
|
+
function buildTickIntervals(formats) {
|
|
35
|
+
if (!formats) {
|
|
36
|
+
return baseTickIntervals;
|
|
37
|
+
}
|
|
38
|
+
const extras = Object.keys(formats).flatMap((key) => {
|
|
39
|
+
const entry = optionalTickIntervals[key];
|
|
40
|
+
return entry ? [entry] : [];
|
|
41
|
+
});
|
|
42
|
+
if (extras.length === 0) {
|
|
43
|
+
return baseTickIntervals;
|
|
44
|
+
}
|
|
45
|
+
return [...baseTickIntervals, ...extras].sort((a, b) => a[2] - b[2]);
|
|
46
|
+
}
|
|
47
|
+
function getDateTimeTickInterval(start, stop, count, formats) {
|
|
48
|
+
const intervals = buildTickIntervals(formats);
|
|
29
49
|
const target = Math.abs(stop - start) / count;
|
|
30
|
-
const i = bisector(([, , step]) => step).right(
|
|
31
|
-
if (i ===
|
|
50
|
+
const i = bisector(([, , step]) => step).right(intervals, target);
|
|
51
|
+
if (i === intervals.length) {
|
|
32
52
|
return utcYear.every(tickStep(start / YEAR, stop / YEAR, count));
|
|
33
53
|
}
|
|
34
54
|
if (i === 0) {
|
|
35
55
|
return utcMillisecond.every(Math.max(tickStep(start, stop, count), 1));
|
|
36
56
|
}
|
|
37
|
-
const [t, step] =
|
|
57
|
+
const [t, step] = intervals[target / intervals[i - 1][2] < intervals[i][2] / target ? i - 1 : i];
|
|
38
58
|
if (t === utcDay && step === 2) {
|
|
39
59
|
return utcEvery2Days;
|
|
40
60
|
}
|
|
@@ -47,12 +67,12 @@ function getDateTimeTickInterval(start, stop, count) {
|
|
|
47
67
|
* weeks are considered to start on Monday (ISO 8601 standard)
|
|
48
68
|
* instead of Sunday (d3 default).
|
|
49
69
|
*/
|
|
50
|
-
export function getDateTimeTicks(start, stop, count = 10) {
|
|
70
|
+
export function getDateTimeTicks(start, stop, count = 10, formats) {
|
|
51
71
|
const reverse = stop < start;
|
|
52
72
|
if (reverse) {
|
|
53
73
|
[start, stop] = [stop, start];
|
|
54
74
|
}
|
|
55
|
-
const interval = getDateTimeTickInterval(start.getTime(), stop.getTime(), count);
|
|
75
|
+
const interval = getDateTimeTickInterval(start.getTime(), stop.getTime(), count, formats);
|
|
56
76
|
const ticks = interval ? interval.range(start, new Date(Number(stop) + 1)) : [];
|
|
57
77
|
return reverse ? ticks.reverse() : ticks;
|
|
58
78
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { AxisDomain, AxisScale } from 'd3-axis';
|
|
2
2
|
import type { ChartScale } from '../../../hooks';
|
|
3
|
-
|
|
3
|
+
import { getDateTimeTicks } from './datetime';
|
|
4
|
+
export declare function getScaleTicks({ scale, ticksCount, dateTimeLabelFormats, }: {
|
|
4
5
|
scale: ChartScale | AxisScale<AxisDomain>;
|
|
5
6
|
ticksCount?: number;
|
|
7
|
+
dateTimeLabelFormats?: Parameters<typeof getDateTimeTicks>[3];
|
|
6
8
|
}): string[] | number[] | Date[];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getDateTimeTicks } from './datetime';
|
|
2
|
-
export function getScaleTicks({ scale, ticksCount, }) {
|
|
2
|
+
export function getScaleTicks({ scale, ticksCount, dateTimeLabelFormats, }) {
|
|
3
3
|
const scaleDomain = scale.domain();
|
|
4
4
|
switch (typeof scaleDomain[0]) {
|
|
5
5
|
case 'number': {
|
|
@@ -7,7 +7,7 @@ export function getScaleTicks({ scale, ticksCount, }) {
|
|
|
7
7
|
}
|
|
8
8
|
// datetime scale
|
|
9
9
|
case 'object': {
|
|
10
|
-
return getDateTimeTicks(scaleDomain[0], scaleDomain[scaleDomain.length - 1], ticksCount);
|
|
10
|
+
return getDateTimeTicks(scaleDomain[0], scaleDomain[scaleDomain.length - 1], ticksCount, dateTimeLabelFormats);
|
|
11
11
|
}
|
|
12
12
|
case 'string': {
|
|
13
13
|
return scaleDomain;
|
|
@@ -6,6 +6,8 @@ export const TIME_UNITS = {
|
|
|
6
6
|
day: 24 * 3600000,
|
|
7
7
|
week: 7 * 24 * 3600000,
|
|
8
8
|
month: 28 * 24 * 3600000,
|
|
9
|
+
quarter: 62 * 24 * 3600000,
|
|
10
|
+
halfYear: 160 * 24 * 3600000,
|
|
9
11
|
year: 364 * 24 * 3600000,
|
|
10
12
|
};
|
|
11
13
|
export const DATETIME_LABEL_FORMATS = {
|
|
@@ -16,6 +18,8 @@ export const DATETIME_LABEL_FORMATS = {
|
|
|
16
18
|
day: 'DD.MM.YY',
|
|
17
19
|
week: 'DD.MM.YY',
|
|
18
20
|
month: "MMM 'YY",
|
|
21
|
+
quarter: "MMM 'YY",
|
|
22
|
+
halfYear: "MMM 'YY",
|
|
19
23
|
year: 'YYYY',
|
|
20
24
|
};
|
|
21
25
|
function getTimeUnit(range) {
|