@gravity-ui/charts 1.1.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/components/Axis/AxisX.js +42 -5
- package/dist/cjs/components/Axis/AxisY.d.ts +1 -1
- package/dist/cjs/components/Axis/AxisY.js +48 -5
- package/dist/cjs/components/ChartInner/index.js +1 -1
- package/dist/cjs/components/ChartInner/styles.css +2 -0
- package/dist/cjs/components/ChartInner/useChartInnerProps.js +3 -3
- package/dist/cjs/components/Legend/index.js +22 -10
- package/dist/cjs/components/Title/index.js +1 -1
- package/dist/cjs/constants/defaults/legend.d.ts +1 -0
- package/dist/cjs/constants/defaults/legend.js +1 -0
- package/dist/cjs/hooks/useChartOptions/types.d.ts +5 -3
- package/dist/cjs/hooks/useChartOptions/x-axis.js +7 -0
- package/dist/cjs/hooks/useChartOptions/y-axis.js +12 -1
- package/dist/cjs/hooks/useSeries/prepare-legend.js +1 -0
- package/dist/cjs/hooks/useShapes/HtmlLayer.js +2 -1
- package/dist/cjs/hooks/useShapes/area/prepare-data.js +1 -0
- package/dist/cjs/hooks/useShapes/bar-x/prepare-data.js +1 -0
- package/dist/cjs/hooks/useShapes/bar-y/prepare-data.js +1 -0
- package/dist/cjs/hooks/useShapes/line/prepare-data.js +1 -0
- package/dist/cjs/hooks/useShapes/pie/index.js +1 -1
- package/dist/cjs/hooks/useShapes/pie/prepare-data.js +11 -6
- package/dist/cjs/hooks/useShapes/radar/prepare-data.js +1 -0
- package/dist/cjs/hooks/useShapes/treemap/index.js +1 -1
- package/dist/cjs/hooks/useShapes/treemap/prepare-data.js +14 -8
- package/dist/cjs/i18n/keysets/en.json +3 -1
- package/dist/cjs/i18n/keysets/ru.json +3 -1
- package/dist/cjs/libs/chart-error/index.d.ts +1 -0
- package/dist/cjs/libs/chart-error/index.js +1 -0
- package/dist/cjs/types/chart/axis.d.ts +30 -7
- package/dist/cjs/types/chart/legend.d.ts +6 -0
- package/dist/cjs/types/chart-ui.d.ts +1 -0
- package/dist/cjs/utils/chart/axis.d.ts +12 -1
- package/dist/cjs/utils/chart/axis.js +35 -0
- package/dist/cjs/utils/chart/index.d.ts +2 -1
- package/dist/cjs/utils/chart/types.d.ts +1 -0
- package/dist/cjs/utils/chart/types.js +1 -0
- package/dist/cjs/validation/index.js +144 -0
- package/dist/esm/components/Axis/AxisX.js +42 -5
- package/dist/esm/components/Axis/AxisY.d.ts +1 -1
- package/dist/esm/components/Axis/AxisY.js +48 -5
- package/dist/esm/components/ChartInner/index.js +1 -1
- package/dist/esm/components/ChartInner/styles.css +2 -0
- package/dist/esm/components/ChartInner/useChartInnerProps.js +3 -3
- package/dist/esm/components/Legend/index.js +22 -10
- package/dist/esm/components/Title/index.js +1 -1
- package/dist/esm/constants/defaults/legend.d.ts +1 -0
- package/dist/esm/constants/defaults/legend.js +1 -0
- package/dist/esm/hooks/useChartOptions/types.d.ts +5 -3
- package/dist/esm/hooks/useChartOptions/x-axis.js +7 -0
- package/dist/esm/hooks/useChartOptions/y-axis.js +12 -1
- package/dist/esm/hooks/useSeries/prepare-legend.js +1 -0
- package/dist/esm/hooks/useShapes/HtmlLayer.js +2 -1
- package/dist/esm/hooks/useShapes/area/prepare-data.js +1 -0
- package/dist/esm/hooks/useShapes/bar-x/prepare-data.js +1 -0
- package/dist/esm/hooks/useShapes/bar-y/prepare-data.js +1 -0
- package/dist/esm/hooks/useShapes/line/prepare-data.js +1 -0
- package/dist/esm/hooks/useShapes/pie/index.js +1 -1
- package/dist/esm/hooks/useShapes/pie/prepare-data.js +11 -6
- package/dist/esm/hooks/useShapes/radar/prepare-data.js +1 -0
- package/dist/esm/hooks/useShapes/treemap/index.js +1 -1
- package/dist/esm/hooks/useShapes/treemap/prepare-data.js +14 -8
- package/dist/esm/i18n/keysets/en.json +3 -1
- package/dist/esm/i18n/keysets/ru.json +3 -1
- package/dist/esm/libs/chart-error/index.d.ts +1 -0
- package/dist/esm/libs/chart-error/index.js +1 -0
- package/dist/esm/types/chart/axis.d.ts +30 -7
- package/dist/esm/types/chart/legend.d.ts +6 -0
- package/dist/esm/types/chart-ui.d.ts +1 -0
- package/dist/esm/utils/chart/axis.d.ts +12 -1
- package/dist/esm/utils/chart/axis.js +35 -0
- package/dist/esm/utils/chart/index.d.ts +2 -1
- package/dist/esm/utils/chart/types.d.ts +1 -0
- package/dist/esm/utils/chart/types.js +1 -0
- package/dist/esm/validation/index.js +144 -0
- package/package.json +1 -1
|
@@ -75,12 +75,27 @@ export interface ChartAxis {
|
|
|
75
75
|
maxPadding?: number;
|
|
76
76
|
/** An array of lines stretching across the plot area, marking a specific value */
|
|
77
77
|
plotLines?: AxisPlotLine[];
|
|
78
|
+
/** An array of colored bands stretching across the plot area marking an interval on the axis. */
|
|
79
|
+
plotBands?: AxisPlotBand[];
|
|
78
80
|
/** Whether axis, including axis title, line, ticks and labels, should be visible. */
|
|
79
81
|
visible?: boolean;
|
|
80
82
|
}
|
|
81
83
|
export interface ChartXAxis extends ChartAxis {
|
|
82
84
|
}
|
|
83
|
-
export
|
|
85
|
+
export type PlotLayerPlacement = 'before' | 'after';
|
|
86
|
+
export interface AxisPlot {
|
|
87
|
+
/** Place the line behind or above the chart. */
|
|
88
|
+
layerPlacement?: PlotLayerPlacement;
|
|
89
|
+
/** The color of the plot line (hex, rgba). */
|
|
90
|
+
color?: string;
|
|
91
|
+
/**
|
|
92
|
+
* Individual opacity for the line.
|
|
93
|
+
*
|
|
94
|
+
* @default 1
|
|
95
|
+
* */
|
|
96
|
+
opacity?: number;
|
|
97
|
+
}
|
|
98
|
+
export interface AxisPlotLine extends AxisPlot {
|
|
84
99
|
/** The position of the line in axis units. */
|
|
85
100
|
value?: number;
|
|
86
101
|
/** The color of the plot line (hex, rgba). */
|
|
@@ -92,14 +107,22 @@ export interface AxisPlotLine {
|
|
|
92
107
|
width?: number;
|
|
93
108
|
/** Option for line stroke style. */
|
|
94
109
|
dashStyle?: `${DashStyle}`;
|
|
110
|
+
}
|
|
111
|
+
export interface AxisPlotBand extends AxisPlot {
|
|
95
112
|
/**
|
|
96
|
-
*
|
|
113
|
+
* The start position of the plot band in axis units.
|
|
97
114
|
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
115
|
+
* Can be a number, a string (e.g., a category), or a timestamp if representing a date.
|
|
116
|
+
* When representing a date, the value **must be a timestamp** (number of milliseconds since Unix epoch).
|
|
117
|
+
*/
|
|
118
|
+
from: number | string;
|
|
119
|
+
/**
|
|
120
|
+
* The end position of the plot band in axis units.
|
|
121
|
+
*
|
|
122
|
+
* Can be a number, a string (e.g., a category), or a timestamp if representing a date.
|
|
123
|
+
* When representing a date, the value **must be a timestamp** (number of milliseconds since Unix epoch).
|
|
124
|
+
*/
|
|
125
|
+
to: number | string;
|
|
103
126
|
}
|
|
104
127
|
export interface ChartYAxis extends ChartAxis {
|
|
105
128
|
/** Axis location.
|
|
@@ -15,6 +15,12 @@ export interface ChartLegend {
|
|
|
15
15
|
* @default center
|
|
16
16
|
* */
|
|
17
17
|
align?: 'left' | 'center' | 'right';
|
|
18
|
+
/**
|
|
19
|
+
* Defines how items should be positioned in the legend when overflowing (moving to the next line).
|
|
20
|
+
*
|
|
21
|
+
* @default center
|
|
22
|
+
* */
|
|
23
|
+
justifyContent?: 'start' | 'center';
|
|
18
24
|
/**
|
|
19
25
|
* Defines the pixel distance between each legend item
|
|
20
26
|
*
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { AxisDomain, AxisScale, ScaleBand } from 'd3';
|
|
2
|
-
import type { PreparedAxis, PreparedSplit } from '../../hooks';
|
|
2
|
+
import type { PreparedAxis, PreparedAxisPlotBand, PreparedSplit } from '../../hooks';
|
|
3
3
|
import type { TextRow } from './text';
|
|
4
|
+
import type { AxisDirection } from './types';
|
|
4
5
|
export declare function getTicksCount({ axis, range }: {
|
|
5
6
|
axis: PreparedAxis;
|
|
6
7
|
range: number;
|
|
@@ -29,3 +30,13 @@ export declare function getAxisTitleRows(args: {
|
|
|
29
30
|
axis: PreparedAxis;
|
|
30
31
|
textMaxWidth: number;
|
|
31
32
|
}): TextRow[];
|
|
33
|
+
interface GetBandsPositionArgs {
|
|
34
|
+
band: PreparedAxisPlotBand;
|
|
35
|
+
axisScale: AxisScale<AxisDomain>;
|
|
36
|
+
axis: AxisDirection;
|
|
37
|
+
}
|
|
38
|
+
export declare function getBandsPosition(args: GetBandsPositionArgs): {
|
|
39
|
+
from: number;
|
|
40
|
+
to: number;
|
|
41
|
+
};
|
|
42
|
+
export {};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import clamp from 'lodash/clamp';
|
|
1
2
|
import { wrapText } from './text';
|
|
2
3
|
export function getTicksCount({ axis, range }) {
|
|
3
4
|
let ticksCount;
|
|
@@ -69,3 +70,37 @@ export function getAxisTitleRows(args) {
|
|
|
69
70
|
return acc;
|
|
70
71
|
}, []);
|
|
71
72
|
}
|
|
73
|
+
export function getBandsPosition(args) {
|
|
74
|
+
var _a, _b, _c;
|
|
75
|
+
const { band, axisScale } = args;
|
|
76
|
+
const scalePosTo = axisScale(band.to);
|
|
77
|
+
const scalePosFrom = axisScale(band.from);
|
|
78
|
+
const isX = args.axis === 'x';
|
|
79
|
+
if (scalePosTo !== undefined && scalePosFrom !== undefined) {
|
|
80
|
+
return {
|
|
81
|
+
from: Math.max(scalePosFrom, 0),
|
|
82
|
+
to: Math.max(scalePosTo, 0),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
if (typeof band.from !== 'number' || typeof band.to !== 'number') {
|
|
86
|
+
throw new Error('Filed to create plot band');
|
|
87
|
+
}
|
|
88
|
+
const category = axisScale.domain();
|
|
89
|
+
const bandwidth = (_b = (_a = axisScale.bandwidth) === null || _a === void 0 ? void 0 : _a.call(axisScale)) !== null && _b !== void 0 ? _b : 1;
|
|
90
|
+
const halfBandwidth = bandwidth / 2;
|
|
91
|
+
const calcPosition = (value) => {
|
|
92
|
+
var _a, _b;
|
|
93
|
+
if (value >= category.length) {
|
|
94
|
+
return ((_a = axisScale(category[category.length - 1])) !== null && _a !== void 0 ? _a : 0) + halfBandwidth * (isX ? 1 : -1);
|
|
95
|
+
}
|
|
96
|
+
return (((_b = axisScale(category[clamp(Math.floor(value), 0, category.length - 1)])) !== null && _b !== void 0 ? _b : 0) +
|
|
97
|
+
bandwidth * (value - Math.floor(Math.abs(value))) * (isX ? 1 : -1));
|
|
98
|
+
};
|
|
99
|
+
const to = calcPosition(band.to);
|
|
100
|
+
const from = calcPosition(band.from);
|
|
101
|
+
const maxPos = ((_c = axisScale(category[isX ? category.length - 1 : 0])) !== null && _c !== void 0 ? _c : 0) + halfBandwidth;
|
|
102
|
+
return {
|
|
103
|
+
from: clamp(from, -halfBandwidth, maxPos),
|
|
104
|
+
to: clamp(to, -halfBandwidth, maxPos),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { AxisDomain } from 'd3';
|
|
2
2
|
import type { PreparedAxis } from '../../hooks';
|
|
3
3
|
import type { BaseTextStyle, ChartSeries, ChartSeriesData } from '../../types';
|
|
4
|
+
import type { AxisDirection } from './types';
|
|
4
5
|
export * from './math';
|
|
5
6
|
export * from './text';
|
|
6
7
|
export * from './time';
|
|
@@ -12,7 +13,6 @@ export * from './series';
|
|
|
12
13
|
export * from './color';
|
|
13
14
|
export declare const CHART_SERIES_WITH_VOLUME_ON_Y_AXIS: ChartSeries['type'][];
|
|
14
15
|
export declare const CHART_SERIES_WITH_VOLUME_ON_X_AXIS: ChartSeries['type'][];
|
|
15
|
-
export type AxisDirection = 'x' | 'y';
|
|
16
16
|
type UnknownSeries = {
|
|
17
17
|
type: ChartSeries['type'];
|
|
18
18
|
data: unknown;
|
|
@@ -76,3 +76,4 @@ export declare const getDataCategoryValue: (args: {
|
|
|
76
76
|
data: ChartSeriesData;
|
|
77
77
|
}) => string;
|
|
78
78
|
export declare function getClosestPointsRange(axis: PreparedAxis, points: AxisDomain[]): number | undefined;
|
|
79
|
+
export { AxisDirection };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type AxisDirection = 'x' | 'y';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -95,6 +95,148 @@ const validateXYSeries = (args) => {
|
|
|
95
95
|
}
|
|
96
96
|
});
|
|
97
97
|
};
|
|
98
|
+
const validateAxisPlotValues = (args) => {
|
|
99
|
+
const { series, xAxis, yAxis = [] } = args;
|
|
100
|
+
const yAxisIndex = get(series, 'yAxis', 0);
|
|
101
|
+
const seriesYAxis = yAxis[yAxisIndex];
|
|
102
|
+
if (yAxisIndex !== 0 && typeof seriesYAxis === 'undefined') {
|
|
103
|
+
throw new ChartError({
|
|
104
|
+
code: CHART_ERROR_CODE.INVALID_DATA,
|
|
105
|
+
message: i18n('error', 'label_invalid-y-axis-index', {
|
|
106
|
+
index: yAxisIndex,
|
|
107
|
+
}),
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
const xPlotBands = get(xAxis, 'plotBands', []);
|
|
111
|
+
const yPlotBands = get(yAxis, 'plotBands', []);
|
|
112
|
+
if (!xPlotBands.length && !yPlotBands.length) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const xType = get(xAxis, 'type', DEFAULT_AXIS_TYPE);
|
|
116
|
+
const yType = get(seriesYAxis, 'type', DEFAULT_AXIS_TYPE);
|
|
117
|
+
xPlotBands.forEach(({ from = 0, to = 0 }) => {
|
|
118
|
+
const fromNotEqualTo = typeof to !== typeof from;
|
|
119
|
+
if (fromNotEqualTo) {
|
|
120
|
+
throw new ChartError({
|
|
121
|
+
code: CHART_ERROR_CODE.INVALID_OPTION_TYPE,
|
|
122
|
+
message: i18n('error', 'label_axis-plot-band-options-not-equal', {
|
|
123
|
+
axis: 'x',
|
|
124
|
+
option: 'from',
|
|
125
|
+
}),
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
switch (xType) {
|
|
129
|
+
case 'category': {
|
|
130
|
+
const invalidFrom = typeof from !== 'string' && typeof from !== 'number';
|
|
131
|
+
const invalidTo = typeof to !== 'string' && typeof to !== 'number';
|
|
132
|
+
if (invalidFrom) {
|
|
133
|
+
throw new ChartError({
|
|
134
|
+
code: CHART_ERROR_CODE.INVALID_OPTION_TYPE,
|
|
135
|
+
message: i18n('error', 'label_invalid-axis-plot-band-option', {
|
|
136
|
+
axis: 'x',
|
|
137
|
+
option: 'from',
|
|
138
|
+
}),
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
if (invalidTo) {
|
|
142
|
+
throw new ChartError({
|
|
143
|
+
code: CHART_ERROR_CODE.INVALID_OPTION_TYPE,
|
|
144
|
+
message: i18n('error', 'label_invalid-axis-plot-band-option', {
|
|
145
|
+
axis: 'x',
|
|
146
|
+
option: 'to',
|
|
147
|
+
}),
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
case 'linear':
|
|
153
|
+
case 'datetime': {
|
|
154
|
+
const invalidFrom = typeof from !== 'number';
|
|
155
|
+
const invalidTo = typeof to !== 'number';
|
|
156
|
+
if (invalidFrom) {
|
|
157
|
+
throw new ChartError({
|
|
158
|
+
code: CHART_ERROR_CODE.INVALID_OPTION_TYPE,
|
|
159
|
+
message: i18n('error', 'label_invalid-axis-plot-band-option', {
|
|
160
|
+
axis: 'x',
|
|
161
|
+
option: 'from',
|
|
162
|
+
}),
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
if (invalidTo) {
|
|
166
|
+
throw new ChartError({
|
|
167
|
+
code: CHART_ERROR_CODE.INVALID_OPTION_TYPE,
|
|
168
|
+
message: i18n('error', 'label_invalid-axis-plot-band-option', {
|
|
169
|
+
axis: 'x',
|
|
170
|
+
option: 'to',
|
|
171
|
+
}),
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
yPlotBands.forEach(({ from = 0, to = 0 }) => {
|
|
179
|
+
const fromNotEqualTo = typeof to !== typeof from;
|
|
180
|
+
if (fromNotEqualTo) {
|
|
181
|
+
throw new ChartError({
|
|
182
|
+
code: CHART_ERROR_CODE.INVALID_OPTION_TYPE,
|
|
183
|
+
message: i18n('error', 'label_axis-plot-band-options-not-equal', {
|
|
184
|
+
axis: 'x',
|
|
185
|
+
option: 'from',
|
|
186
|
+
}),
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
switch (yType) {
|
|
190
|
+
case 'category': {
|
|
191
|
+
const invalidFrom = typeof from !== 'string' && typeof from !== 'number';
|
|
192
|
+
const invalidTo = typeof to !== 'string' && typeof to !== 'number';
|
|
193
|
+
if (invalidFrom) {
|
|
194
|
+
throw new ChartError({
|
|
195
|
+
code: CHART_ERROR_CODE.INVALID_OPTION_TYPE,
|
|
196
|
+
message: i18n('error', 'label_invalid-axis-plot-band-option', {
|
|
197
|
+
axis: 'y',
|
|
198
|
+
option: 'from',
|
|
199
|
+
}),
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
if (invalidTo) {
|
|
203
|
+
throw new ChartError({
|
|
204
|
+
code: CHART_ERROR_CODE.INVALID_OPTION_TYPE,
|
|
205
|
+
message: i18n('error', 'label_invalid-axis-plot-band-option', {
|
|
206
|
+
axis: 'y',
|
|
207
|
+
option: 'to',
|
|
208
|
+
}),
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
case 'linear':
|
|
214
|
+
case 'datetime': {
|
|
215
|
+
const invalidFrom = typeof from !== 'number';
|
|
216
|
+
const invalidTo = typeof to !== 'number';
|
|
217
|
+
if (invalidFrom) {
|
|
218
|
+
throw new ChartError({
|
|
219
|
+
code: CHART_ERROR_CODE.INVALID_OPTION_TYPE,
|
|
220
|
+
message: i18n('error', 'label_invalid-axis-plot-band-option', {
|
|
221
|
+
axis: 'y',
|
|
222
|
+
option: 'from',
|
|
223
|
+
}),
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
if (invalidTo) {
|
|
227
|
+
throw new ChartError({
|
|
228
|
+
code: CHART_ERROR_CODE.INVALID_OPTION_TYPE,
|
|
229
|
+
message: i18n('error', 'label_invalid-axis-plot-band-option', {
|
|
230
|
+
axis: 'y',
|
|
231
|
+
option: 'to',
|
|
232
|
+
}),
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
};
|
|
98
240
|
const validatePieSeries = ({ series }) => {
|
|
99
241
|
series.data.forEach(({ value }) => {
|
|
100
242
|
if (typeof value !== 'number') {
|
|
@@ -163,12 +305,14 @@ const validateSeries = (args) => {
|
|
|
163
305
|
case 'area':
|
|
164
306
|
case 'bar-y':
|
|
165
307
|
case 'bar-x': {
|
|
308
|
+
validateAxisPlotValues({ series, xAxis, yAxis });
|
|
166
309
|
validateXYSeries({ series, xAxis, yAxis });
|
|
167
310
|
validateStacking({ series });
|
|
168
311
|
break;
|
|
169
312
|
}
|
|
170
313
|
case 'line':
|
|
171
314
|
case 'scatter': {
|
|
315
|
+
validateAxisPlotValues({ series, xAxis, yAxis });
|
|
172
316
|
validateXYSeries({ series, xAxis, yAxis });
|
|
173
317
|
break;
|
|
174
318
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { line, select } from 'd3';
|
|
3
|
-
import { block, formatAxisTickLabel, getAxisTitleRows, getClosestPointsRange, getLineDashArray, getMaxTickCount, getScaleTicks, getTicksCount, handleOverflowingText, } from '../../utils';
|
|
3
|
+
import { block, formatAxisTickLabel, getAxisTitleRows, getBandsPosition, getClosestPointsRange, getLineDashArray, getMaxTickCount, getScaleTicks, getTicksCount, handleOverflowingText, } from '../../utils';
|
|
4
4
|
import { axisBottom } from '../../utils/chart/axis-generators';
|
|
5
5
|
import './styles.css';
|
|
6
6
|
const b = block('axis');
|
|
@@ -114,14 +114,51 @@ export const AxisX = React.memo(function AxisX(props) {
|
|
|
114
114
|
}
|
|
115
115
|
});
|
|
116
116
|
}
|
|
117
|
+
// add plot bands
|
|
118
|
+
if (plotContainer && axis.plotBands.length > 0) {
|
|
119
|
+
const plotBandClassName = b('plot-x-band');
|
|
120
|
+
const plotBandsSelection = plotContainer
|
|
121
|
+
.selectAll(`.${plotBandClassName}-x`)
|
|
122
|
+
.data(axis.plotBands)
|
|
123
|
+
.join('g')
|
|
124
|
+
.attr('class', `${plotClassName} ${plotBandClassName}-x`);
|
|
125
|
+
plotBandsSelection
|
|
126
|
+
.append('rect')
|
|
127
|
+
.attr('x', (band) => {
|
|
128
|
+
var _a, _b;
|
|
129
|
+
const { from, to } = getBandsPosition({ band, axisScale, axis: 'x' });
|
|
130
|
+
const halfBandwidth = ((_b = (_a = axisScale.bandwidth) === null || _a === void 0 ? void 0 : _a.call(axisScale)) !== null && _b !== void 0 ? _b : 0) / 2;
|
|
131
|
+
const startPos = halfBandwidth + Math.min(from, to);
|
|
132
|
+
return Math.max(0, startPos);
|
|
133
|
+
})
|
|
134
|
+
.attr('width', (band) => {
|
|
135
|
+
const { from, to } = getBandsPosition({ band, axisScale, axis: 'x' });
|
|
136
|
+
const startPos = width - Math.min(from, to);
|
|
137
|
+
const endPos = Math.min(Math.abs(to - from), startPos);
|
|
138
|
+
return Math.min(endPos, width);
|
|
139
|
+
})
|
|
140
|
+
.attr('y', 0)
|
|
141
|
+
.attr('height', totalHeight)
|
|
142
|
+
.attr('fill', (band) => band.color)
|
|
143
|
+
.attr('opacity', (band) => band.opacity);
|
|
144
|
+
plotBandsSelection.each((plotBandData, i, nodes) => {
|
|
145
|
+
const plotLineSelection = select(nodes[i]);
|
|
146
|
+
if (plotBandData.layerPlacement === 'before') {
|
|
147
|
+
plotLineSelection.lower();
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
plotLineSelection.raise();
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
}
|
|
117
154
|
// add plot lines
|
|
118
155
|
if (plotContainer && axis.plotLines.length > 0) {
|
|
119
|
-
const plotLineClassName = b('
|
|
156
|
+
const plotLineClassName = b('plot-x-line');
|
|
120
157
|
const plotLinesSelection = plotContainer
|
|
121
|
-
.selectAll(`.${plotLineClassName}
|
|
158
|
+
.selectAll(`.${plotLineClassName}`)
|
|
122
159
|
.data(axis.plotLines)
|
|
123
160
|
.join('g')
|
|
124
|
-
.attr('class', `${plotClassName} ${plotLineClassName}
|
|
161
|
+
.attr('class', `${plotClassName} ${plotLineClassName}`);
|
|
125
162
|
const lineGenerator = line();
|
|
126
163
|
plotLinesSelection
|
|
127
164
|
.append('path')
|
|
@@ -148,6 +185,6 @@ export const AxisX = React.memo(function AxisX(props) {
|
|
|
148
185
|
}
|
|
149
186
|
});
|
|
150
187
|
}
|
|
151
|
-
}, [axis, width, totalHeight, scale, split, plotRef]);
|
|
188
|
+
}, [axis, width, totalHeight, scale, split, plotRef, leftmostLimit]);
|
|
152
189
|
return React.createElement("g", { ref: ref });
|
|
153
190
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { axisLeft, axisRight, line, select } from 'd3';
|
|
3
|
-
import { block, calculateCos, calculateSin, formatAxisTickLabel, getAxisHeight, getAxisTitleRows, getClosestPointsRange, getLineDashArray, getScaleTicks, getTicksCount, handleOverflowingText, parseTransformStyle, setEllipsisForOverflowTexts, wrapText, } from '../../utils';
|
|
3
|
+
import { block, calculateCos, calculateSin, formatAxisTickLabel, getAxisHeight, getAxisTitleRows, getBandsPosition, getClosestPointsRange, getLineDashArray, getScaleTicks, getTicksCount, handleOverflowingText, parseTransformStyle, setEllipsisForOverflowTexts, wrapText, } from '../../utils';
|
|
4
4
|
import './styles.css';
|
|
5
5
|
const b = block('axis');
|
|
6
6
|
function transformLabel(args) {
|
|
@@ -85,7 +85,7 @@ function getTitlePosition(args) {
|
|
|
85
85
|
return { x, y };
|
|
86
86
|
}
|
|
87
87
|
export const AxisY = (props) => {
|
|
88
|
-
const { axes: allAxes, width, height: totalHeight, scale, split, plotRef,
|
|
88
|
+
const { axes: allAxes, width, height: totalHeight, scale, split, plotRef, bottomLimit = 0, } = props;
|
|
89
89
|
const height = getAxisHeight({ split, boundsHeight: totalHeight });
|
|
90
90
|
const ref = React.useRef(null);
|
|
91
91
|
const lineGenerator = line();
|
|
@@ -118,6 +118,12 @@ export const AxisY = (props) => {
|
|
|
118
118
|
}
|
|
119
119
|
return acc;
|
|
120
120
|
}, []);
|
|
121
|
+
const plotBands = axes.reduce((acc, axis) => {
|
|
122
|
+
if (axis.plotBands.length) {
|
|
123
|
+
acc.push(...axis.plotBands.map((plotBand) => (Object.assign(Object.assign({}, plotBand), { transform: getAxisPosition(axis) }))));
|
|
124
|
+
}
|
|
125
|
+
return acc;
|
|
126
|
+
}, []);
|
|
121
127
|
const axisSelection = svgElement
|
|
122
128
|
.selectAll('axis')
|
|
123
129
|
.data(axes)
|
|
@@ -153,7 +159,7 @@ export const AxisY = (props) => {
|
|
|
153
159
|
const currentElement = this;
|
|
154
160
|
const currentElementPosition = currentElement.getBoundingClientRect();
|
|
155
161
|
const text = select(currentElement);
|
|
156
|
-
if (currentElementPosition.bottom >
|
|
162
|
+
if (currentElementPosition.bottom > bottomLimit) {
|
|
157
163
|
const transform = transformLabel({
|
|
158
164
|
node: this,
|
|
159
165
|
axis: d,
|
|
@@ -190,8 +196,45 @@ export const AxisY = (props) => {
|
|
|
190
196
|
})
|
|
191
197
|
.remove();
|
|
192
198
|
}
|
|
199
|
+
if (plotContainer && d.plotBands.length > 0) {
|
|
200
|
+
const plotBandClassName = b('plot-y-band');
|
|
201
|
+
const plotBandsSelection = plotContainer
|
|
202
|
+
.selectAll(`.${plotBandClassName}`)
|
|
203
|
+
.data(plotBands)
|
|
204
|
+
.join('g')
|
|
205
|
+
.attr('class', `${plotClassName} ${plotBandClassName}`)
|
|
206
|
+
.style('transform', (plotBand) => plotBand.transform);
|
|
207
|
+
plotBandsSelection
|
|
208
|
+
.append('rect')
|
|
209
|
+
.attr('x', 0)
|
|
210
|
+
.attr('width', width)
|
|
211
|
+
.attr('y', (band) => {
|
|
212
|
+
var _a, _b;
|
|
213
|
+
const { from, to } = getBandsPosition({ band, axisScale, axis: 'y' });
|
|
214
|
+
const halfBandwidth = ((_b = (_a = axisScale.bandwidth) === null || _a === void 0 ? void 0 : _a.call(axisScale)) !== null && _b !== void 0 ? _b : 0) / 2;
|
|
215
|
+
const startPos = halfBandwidth + Math.min(from, to);
|
|
216
|
+
return Math.max(0, startPos);
|
|
217
|
+
})
|
|
218
|
+
.attr('height', (band) => {
|
|
219
|
+
const { from, to } = getBandsPosition({ band, axisScale, axis: 'y' });
|
|
220
|
+
const startPos = height - Math.min(from, to);
|
|
221
|
+
const endPos = Math.min(Math.abs(to - from), startPos);
|
|
222
|
+
return Math.min(endPos, height);
|
|
223
|
+
})
|
|
224
|
+
.attr('fill', (band) => band.color)
|
|
225
|
+
.attr('opacity', (band) => band.opacity);
|
|
226
|
+
plotBandsSelection.each((plotBandData, i, nodes) => {
|
|
227
|
+
const plotLineSelection = select(nodes[i]);
|
|
228
|
+
if (plotBandData.layerPlacement === 'before') {
|
|
229
|
+
plotLineSelection.lower();
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
plotLineSelection.raise();
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
}
|
|
193
236
|
if (plotContainer && d.plotLines.length > 0) {
|
|
194
|
-
const plotLineClassName = b('
|
|
237
|
+
const plotLineClassName = b('plot-y-line');
|
|
195
238
|
const plotLinesSelection = plotContainer
|
|
196
239
|
.selectAll(`.${plotLineClassName}`)
|
|
197
240
|
.data(plotLines)
|
|
@@ -268,6 +311,6 @@ export const AxisY = (props) => {
|
|
|
268
311
|
handleOverflowingText(nodes[index], height);
|
|
269
312
|
}
|
|
270
313
|
});
|
|
271
|
-
}, [allAxes, width, height, scale, split]);
|
|
314
|
+
}, [allAxes, width, height, scale, split, bottomLimit]);
|
|
272
315
|
return React.createElement("g", { ref: ref, className: b('container') });
|
|
273
316
|
};
|
|
@@ -67,7 +67,7 @@ export const ChartInner = (props) => {
|
|
|
67
67
|
})),
|
|
68
68
|
React.createElement("g", { width: boundsWidth, height: boundsHeight, transform: `translate(${[boundsOffsetLeft, boundsOffsetTop].join(',')})`, ref: plotRef },
|
|
69
69
|
xScale && (yScale === null || yScale === void 0 ? void 0 : yScale.length) && (React.createElement(React.Fragment, null,
|
|
70
|
-
React.createElement(AxisY, {
|
|
70
|
+
React.createElement(AxisY, { bottomLimit: svgBottomPos, axes: yAxis, width: boundsWidth, height: boundsHeight, scale: yScale, split: preparedSplit, plotRef: plotRef }),
|
|
71
71
|
React.createElement("g", { transform: `translate(0, ${boundsHeight})` },
|
|
72
72
|
React.createElement(AxisX, { leftmostLimit: svgXPos, axis: xAxis, width: boundsWidth, height: boundsHeight, scale: xScale, split: preparedSplit, plotRef: plotRef })))),
|
|
73
73
|
shapes),
|
|
@@ -58,9 +58,9 @@ export function useChartInnerProps(props) {
|
|
|
58
58
|
// We need to calculate the width of each axis because the first axis can be hidden
|
|
59
59
|
const boundsOffsetLeft = chart.margin.left +
|
|
60
60
|
yAxis.reduce((acc, axis) => {
|
|
61
|
-
const
|
|
62
|
-
if (acc <
|
|
63
|
-
acc =
|
|
61
|
+
const axisWidth = getYAxisWidth(axis);
|
|
62
|
+
if (acc < axisWidth) {
|
|
63
|
+
acc = axisWidth;
|
|
64
64
|
}
|
|
65
65
|
return acc;
|
|
66
66
|
}, 0);
|
|
@@ -114,6 +114,7 @@ function renderLegendSymbol(args) {
|
|
|
114
114
|
}
|
|
115
115
|
case 'symbol': {
|
|
116
116
|
const y = legend.lineHeight / 2;
|
|
117
|
+
const translateX = x + d.symbol.width / 2;
|
|
117
118
|
element
|
|
118
119
|
.append('svg:path')
|
|
119
120
|
.attr('d', () => {
|
|
@@ -123,7 +124,7 @@ function renderLegendSymbol(args) {
|
|
|
123
124
|
return symbol(scatterSymbol, d.symbol.width * d.symbol.width)();
|
|
124
125
|
})
|
|
125
126
|
.attr('transform', () => {
|
|
126
|
-
return 'translate(' +
|
|
127
|
+
return 'translate(' + translateX + ',' + y + ')';
|
|
127
128
|
})
|
|
128
129
|
.attr('class', className)
|
|
129
130
|
.style('fill', color);
|
|
@@ -185,21 +186,32 @@ export const Legend = (props) => {
|
|
|
185
186
|
const mods = { selected: d.visible, unselected: !d.visible };
|
|
186
187
|
return b('item-text', mods);
|
|
187
188
|
})
|
|
188
|
-
.
|
|
189
|
+
.html(function (d) {
|
|
189
190
|
return ('name' in d && d.name);
|
|
190
191
|
})
|
|
191
192
|
.style('font-size', legend.itemStyle.fontSize);
|
|
192
193
|
const contentWidth = ((_a = legendLine.node()) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect().width) || 0;
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
194
|
+
let left = 0;
|
|
195
|
+
switch (legend.justifyContent) {
|
|
196
|
+
case 'center': {
|
|
197
|
+
const legendLinePostion = getLegendPosition({
|
|
198
|
+
align: legend.align,
|
|
199
|
+
width: boundsWidth,
|
|
200
|
+
offsetWidth: 0,
|
|
201
|
+
contentWidth,
|
|
202
|
+
});
|
|
203
|
+
left = legendLinePostion.left;
|
|
204
|
+
legendWidth = boundsWidth;
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
case 'start': {
|
|
208
|
+
legendWidth = Math.max(legendWidth, contentWidth);
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
199
212
|
const top = legend.lineHeight * lineIndex;
|
|
200
213
|
legendLine.attr('transform', `translate(${[left, top].join(',')})`);
|
|
201
214
|
});
|
|
202
|
-
legendWidth = boundsWidth;
|
|
203
215
|
if (config.pagination) {
|
|
204
216
|
const transform = `translate(${[
|
|
205
217
|
0,
|
|
@@ -283,7 +295,7 @@ export const Legend = (props) => {
|
|
|
283
295
|
.attr('font-size', (_d = legend.title.style.fontSize) !== null && _d !== void 0 ? _d : null)
|
|
284
296
|
.attr('fill', (_e = legend.title.style.fontColor) !== null && _e !== void 0 ? _e : null)
|
|
285
297
|
.style('alignment-baseline', 'before-edge')
|
|
286
|
-
.
|
|
298
|
+
.html(legend.title.text);
|
|
287
299
|
}
|
|
288
300
|
const { left } = getLegendPosition({
|
|
289
301
|
align: legend.align,
|
|
@@ -5,5 +5,5 @@ const b = block('title');
|
|
|
5
5
|
export const Title = (props) => {
|
|
6
6
|
const { chartWidth, text, height, style } = props;
|
|
7
7
|
return (React.createElement("text", { className: b(), dx: chartWidth / 2, dy: height / 2, dominantBaseline: "middle", textAnchor: "middle", style: Object.assign({ lineHeight: `${height}px` }, style) },
|
|
8
|
-
React.createElement("tspan",
|
|
8
|
+
React.createElement("tspan", { dangerouslySetInnerHTML: { __html: text } })));
|
|
9
9
|
};
|