@gravity-ui/charts 1.13.2 → 1.15.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.d.ts +5 -2
- package/dist/cjs/components/Axis/AxisX.js +28 -11
- package/dist/cjs/components/Axis/AxisY.d.ts +5 -2
- package/dist/cjs/components/Axis/AxisY.js +67 -9
- package/dist/cjs/components/ChartInner/index.js +2 -2
- package/dist/cjs/components/ChartInner/useChartInnerProps.js +7 -2
- package/dist/cjs/components/Legend/index.js +5 -4
- package/dist/cjs/components/Tooltip/ChartTooltipContent.d.ts +1 -0
- package/dist/cjs/components/Tooltip/ChartTooltipContent.js +2 -2
- package/dist/cjs/components/Tooltip/DefaultTooltipContent/index.d.ts +2 -1
- package/dist/cjs/components/Tooltip/DefaultTooltipContent/index.js +58 -15
- package/dist/cjs/components/Tooltip/index.js +1 -1
- package/dist/cjs/hooks/useAxisScales/index.d.ts +1 -0
- package/dist/cjs/hooks/useAxisScales/index.js +53 -37
- package/dist/cjs/hooks/useChartOptions/title.js +2 -2
- package/dist/cjs/hooks/useChartOptions/types.d.ts +1 -1
- package/dist/cjs/hooks/useChartOptions/x-axis.d.ts +3 -1
- package/dist/cjs/hooks/useChartOptions/x-axis.js +22 -13
- package/dist/cjs/hooks/useChartOptions/y-axis.js +17 -6
- package/dist/cjs/hooks/useSplit/index.js +2 -2
- package/dist/cjs/hooks/utils/bar-x.d.ts +16 -0
- package/dist/cjs/hooks/utils/bar-x.js +41 -0
- package/dist/cjs/i18n/keysets/en.json +3 -1
- package/dist/cjs/i18n/keysets/ru.json +3 -1
- package/dist/cjs/types/chart/axis.d.ts +11 -1
- package/dist/cjs/types/chart/tooltip.d.ts +14 -0
- package/dist/cjs/utils/chart/axis-generators/bottom.d.ts +18 -13
- package/dist/cjs/utils/chart/axis-generators/bottom.js +173 -73
- package/dist/cjs/utils/chart/index.d.ts +6 -10
- package/dist/cjs/utils/chart/index.js +25 -11
- package/dist/cjs/validation/index.js +2 -22
- package/dist/cjs/validation/validate-axes.d.ts +5 -0
- package/dist/cjs/validation/validate-axes.js +46 -0
- package/dist/esm/components/Axis/AxisX.d.ts +5 -2
- package/dist/esm/components/Axis/AxisX.js +28 -11
- package/dist/esm/components/Axis/AxisY.d.ts +5 -2
- package/dist/esm/components/Axis/AxisY.js +67 -9
- package/dist/esm/components/ChartInner/index.js +2 -2
- package/dist/esm/components/ChartInner/useChartInnerProps.js +7 -2
- package/dist/esm/components/Legend/index.js +5 -4
- package/dist/esm/components/Tooltip/ChartTooltipContent.d.ts +1 -0
- package/dist/esm/components/Tooltip/ChartTooltipContent.js +2 -2
- package/dist/esm/components/Tooltip/DefaultTooltipContent/index.d.ts +2 -1
- package/dist/esm/components/Tooltip/DefaultTooltipContent/index.js +58 -15
- package/dist/esm/components/Tooltip/index.js +1 -1
- package/dist/esm/hooks/useAxisScales/index.d.ts +1 -0
- package/dist/esm/hooks/useAxisScales/index.js +53 -37
- package/dist/esm/hooks/useChartOptions/title.js +2 -2
- package/dist/esm/hooks/useChartOptions/types.d.ts +1 -1
- package/dist/esm/hooks/useChartOptions/x-axis.d.ts +3 -1
- package/dist/esm/hooks/useChartOptions/x-axis.js +22 -13
- package/dist/esm/hooks/useChartOptions/y-axis.js +17 -6
- package/dist/esm/hooks/useSplit/index.js +2 -2
- package/dist/esm/hooks/utils/bar-x.d.ts +16 -0
- package/dist/esm/hooks/utils/bar-x.js +41 -0
- package/dist/esm/i18n/keysets/en.json +3 -1
- package/dist/esm/i18n/keysets/ru.json +3 -1
- package/dist/esm/types/chart/axis.d.ts +11 -1
- package/dist/esm/types/chart/tooltip.d.ts +14 -0
- package/dist/esm/utils/chart/axis-generators/bottom.d.ts +18 -13
- package/dist/esm/utils/chart/axis-generators/bottom.js +173 -73
- package/dist/esm/utils/chart/index.d.ts +6 -10
- package/dist/esm/utils/chart/index.js +25 -11
- package/dist/esm/validation/index.js +2 -22
- package/dist/esm/validation/validate-axes.d.ts +5 -0
- package/dist/esm/validation/validate-axes.js +46 -0
- package/package.json +1 -1
|
@@ -3,13 +3,16 @@ import type { ChartScale, PreparedAxis, PreparedSplit } from '../../hooks';
|
|
|
3
3
|
import './styles.css';
|
|
4
4
|
type Props = {
|
|
5
5
|
axis: PreparedAxis;
|
|
6
|
-
|
|
6
|
+
boundsOffsetLeft: number;
|
|
7
|
+
boundsOffsetTop: number;
|
|
7
8
|
height: number;
|
|
9
|
+
htmlLayout: HTMLElement | null;
|
|
8
10
|
scale: ChartScale;
|
|
9
11
|
split: PreparedSplit;
|
|
12
|
+
width: number;
|
|
13
|
+
leftmostLimit?: number;
|
|
10
14
|
plotBeforeRef?: React.MutableRefObject<SVGGElement | null>;
|
|
11
15
|
plotAfterRef?: React.MutableRefObject<SVGGElement | null>;
|
|
12
|
-
leftmostLimit?: number;
|
|
13
16
|
};
|
|
14
17
|
export declare function getTitlePosition(args: {
|
|
15
18
|
axis: PreparedAxis;
|
|
@@ -42,11 +42,11 @@ export function getTitlePosition(args) {
|
|
|
42
42
|
return { x, y };
|
|
43
43
|
}
|
|
44
44
|
export const AxisX = React.memo(function AxisX(props) {
|
|
45
|
-
const { axis,
|
|
45
|
+
const { axis, boundsOffsetLeft, boundsOffsetTop, height: totalHeight, htmlLayout, leftmostLimit, plotAfterRef, plotBeforeRef, split, scale, width, } = props;
|
|
46
46
|
const ref = React.useRef(null);
|
|
47
47
|
React.useEffect(() => {
|
|
48
48
|
(async () => {
|
|
49
|
-
if (!ref.current) {
|
|
49
|
+
if (!ref.current || !htmlLayout) {
|
|
50
50
|
return;
|
|
51
51
|
}
|
|
52
52
|
const svgElement = select(ref.current);
|
|
@@ -65,24 +65,29 @@ export const AxisX = React.memo(function AxisX(props) {
|
|
|
65
65
|
}
|
|
66
66
|
const axisScale = scale;
|
|
67
67
|
const xAxisGenerator = await axisBottom({
|
|
68
|
+
boundsOffsetLeft,
|
|
69
|
+
boundsOffsetTop,
|
|
70
|
+
domain: {
|
|
71
|
+
size: width,
|
|
72
|
+
color: axis.lineColor,
|
|
73
|
+
},
|
|
74
|
+
htmlLayout,
|
|
68
75
|
leftmostLimit,
|
|
69
76
|
scale: axisScale,
|
|
70
77
|
ticks: {
|
|
78
|
+
count: getTicksCount({ axis, range: width }),
|
|
79
|
+
labelsHtml: axis.labels.html,
|
|
71
80
|
items: tickItems,
|
|
72
81
|
labelFormat: getLabelFormatter({ axis, scale }),
|
|
73
|
-
|
|
82
|
+
labelsHeight: axis.labels.height,
|
|
83
|
+
labelsLineHeight: axis.labels.lineHeight,
|
|
74
84
|
labelsMargin: axis.labels.margin,
|
|
75
|
-
labelsStyle: axis.labels.style,
|
|
76
85
|
labelsMaxWidth: axis.labels.maxWidth,
|
|
77
|
-
|
|
78
|
-
|
|
86
|
+
labelsPaddings: axis.labels.padding,
|
|
87
|
+
labelsStyle: axis.labels.style,
|
|
79
88
|
maxTickCount: getMaxTickCount({ axis, width }),
|
|
80
89
|
rotation: axis.labels.rotation,
|
|
81
90
|
},
|
|
82
|
-
domain: {
|
|
83
|
-
size: width,
|
|
84
|
-
color: axis.lineColor,
|
|
85
|
-
},
|
|
86
91
|
});
|
|
87
92
|
svgElement.call(xAxisGenerator).attr('class', b());
|
|
88
93
|
// add an axis header if necessary
|
|
@@ -219,6 +224,18 @@ export const AxisX = React.memo(function AxisX(props) {
|
|
|
219
224
|
setPlotLines(plotAfterContainer, axis.plotLines.filter((d) => d.layerPlacement === 'after'));
|
|
220
225
|
}
|
|
221
226
|
})();
|
|
222
|
-
}, [
|
|
227
|
+
}, [
|
|
228
|
+
axis,
|
|
229
|
+
boundsOffsetLeft,
|
|
230
|
+
boundsOffsetTop,
|
|
231
|
+
htmlLayout,
|
|
232
|
+
leftmostLimit,
|
|
233
|
+
plotAfterRef,
|
|
234
|
+
plotBeforeRef,
|
|
235
|
+
scale,
|
|
236
|
+
split,
|
|
237
|
+
totalHeight,
|
|
238
|
+
width,
|
|
239
|
+
]);
|
|
223
240
|
return React.createElement("g", { ref: ref });
|
|
224
241
|
});
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { ChartScale, PreparedAxis, PreparedSplit } from '../../hooks';
|
|
3
3
|
import './styles.css';
|
|
4
|
-
|
|
4
|
+
interface Props {
|
|
5
5
|
axes: PreparedAxis[];
|
|
6
|
+
boundsOffsetTop: number;
|
|
7
|
+
boundsOffsetLeft: number;
|
|
6
8
|
scale: ChartScale[];
|
|
7
9
|
width: number;
|
|
8
10
|
height: number;
|
|
11
|
+
htmlLayout: HTMLElement | null;
|
|
9
12
|
split: PreparedSplit;
|
|
10
13
|
plotBeforeRef?: React.MutableRefObject<SVGGElement | null>;
|
|
11
14
|
plotAfterRef?: React.MutableRefObject<SVGGElement | null>;
|
|
12
15
|
bottomLimit?: number;
|
|
13
16
|
topLimit?: number;
|
|
14
|
-
}
|
|
17
|
+
}
|
|
15
18
|
export declare const AxisY: (props: Props) => React.JSX.Element;
|
|
16
19
|
export {};
|
|
@@ -3,6 +3,8 @@ import { axisLeft, axisRight, line, select } from 'd3';
|
|
|
3
3
|
import { block, calculateCos, calculateSin, formatAxisTickLabel, getAxisHeight, getAxisPlotsPosition, getAxisTitleRows, getBandsPosition, getClosestPointsRange, getLineDashArray, getScaleTicks, getTicksCount, handleOverflowingText, parseTransformStyle, setEllipsisForOverflowTexts, wrapText, } from '../../utils';
|
|
4
4
|
import './styles.css';
|
|
5
5
|
const b = block('axis');
|
|
6
|
+
const AXIS_LEFT_HTML_LABELS_DATA_ATTR = 'data-axis-left-html-labels';
|
|
7
|
+
const AXIS_RIGHT_HTML_LABELS_DATA_ATTR = 'data-axis-right-html-labels';
|
|
6
8
|
function transformLabel(args) {
|
|
7
9
|
const { node, axis, startTopOffset } = args;
|
|
8
10
|
let topOffset = startTopOffset !== null && startTopOffset !== void 0 ? startTopOffset : axis.labels.lineHeight / 2;
|
|
@@ -36,7 +38,7 @@ function getAxisGenerator(args) {
|
|
|
36
38
|
.tickSize(tickSize)
|
|
37
39
|
.tickPadding(preparedAxis.labels.margin)
|
|
38
40
|
.tickFormat((value) => {
|
|
39
|
-
if (!preparedAxis.labels.enabled) {
|
|
41
|
+
if (!preparedAxis.labels.enabled || preparedAxis.labels.html) {
|
|
40
42
|
return '';
|
|
41
43
|
}
|
|
42
44
|
return formatAxisTickLabel({
|
|
@@ -82,17 +84,20 @@ function getTitlePosition(args) {
|
|
|
82
84
|
return { x, y };
|
|
83
85
|
}
|
|
84
86
|
export const AxisY = (props) => {
|
|
85
|
-
const { axes: allAxes,
|
|
87
|
+
const { axes: allAxes, bottomLimit = 0, boundsOffsetLeft, boundsOffsetTop, height: totalHeight, htmlLayout, plotAfterRef, plotBeforeRef, scale, split, topLimit = 0, width, } = props;
|
|
86
88
|
const height = getAxisHeight({ split, boundsHeight: totalHeight });
|
|
87
89
|
const ref = React.useRef(null);
|
|
88
90
|
const lineGenerator = line();
|
|
89
91
|
React.useEffect(() => {
|
|
90
|
-
if (!ref.current) {
|
|
92
|
+
if (!ref.current || !htmlLayout) {
|
|
91
93
|
return;
|
|
92
94
|
}
|
|
93
95
|
const axes = allAxes.filter((a) => a.visible);
|
|
94
96
|
const svgElement = select(ref.current);
|
|
95
97
|
svgElement.selectAll('*').remove();
|
|
98
|
+
const htmlSelection = select(htmlLayout);
|
|
99
|
+
htmlSelection.selectAll(`[${AXIS_LEFT_HTML_LABELS_DATA_ATTR}]`).remove();
|
|
100
|
+
htmlSelection.selectAll(`[${AXIS_RIGHT_HTML_LABELS_DATA_ATTR}]`).remove();
|
|
96
101
|
let plotBeforeContainer = null;
|
|
97
102
|
let plotAfterContainer = null;
|
|
98
103
|
const plotDataAttr = 'data-plot-y';
|
|
@@ -111,6 +116,7 @@ export const AxisY = (props) => {
|
|
|
111
116
|
.attr('class', b())
|
|
112
117
|
.style('transform', (d) => getAxisPlotsPosition(d, split, width));
|
|
113
118
|
axisSelection.each((d, index, node) => {
|
|
119
|
+
var _a, _b;
|
|
114
120
|
const seriesScale = scale[index];
|
|
115
121
|
const axisItem = select(node[index]);
|
|
116
122
|
const axisScale = seriesScale;
|
|
@@ -125,7 +131,56 @@ export const AxisY = (props) => {
|
|
|
125
131
|
// because the standard generator interrupts the desired font
|
|
126
132
|
// https://github.com/d3/d3-axis/blob/main/src/axis.js#L110
|
|
127
133
|
axisItem.attr('font-family', null);
|
|
128
|
-
if (d.labels.enabled) {
|
|
134
|
+
if (d.labels.enabled && d.labels.html) {
|
|
135
|
+
const offsetTop = ((_a = svgElement.node()) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect().top) || 0;
|
|
136
|
+
const offsetLeft = ((_b = svgElement.node()) === null || _b === void 0 ? void 0 : _b.getBoundingClientRect().left) || 0;
|
|
137
|
+
const htmlLabelsData = [];
|
|
138
|
+
axisItem
|
|
139
|
+
.selectAll('.tick')
|
|
140
|
+
.data(axisScale.domain())
|
|
141
|
+
.each(function (tickContent) {
|
|
142
|
+
const rect = this.getBoundingClientRect();
|
|
143
|
+
htmlLabelsData.push({
|
|
144
|
+
content: tickContent,
|
|
145
|
+
right: rect.right,
|
|
146
|
+
top: rect.top,
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
const dataAttr = d.position === 'left'
|
|
150
|
+
? AXIS_LEFT_HTML_LABELS_DATA_ATTR
|
|
151
|
+
: AXIS_RIGHT_HTML_LABELS_DATA_ATTR;
|
|
152
|
+
htmlSelection.append('div').attr(dataAttr, 1).style('position', 'absolute');
|
|
153
|
+
htmlLabelsData.forEach((labelData) => {
|
|
154
|
+
htmlSelection
|
|
155
|
+
.selectAll(`[${dataAttr}]`)
|
|
156
|
+
.data([labelData])
|
|
157
|
+
.append('div')
|
|
158
|
+
.html(function (l) {
|
|
159
|
+
return l.content;
|
|
160
|
+
})
|
|
161
|
+
.style('font-size', d.labels.style.fontSize || '')
|
|
162
|
+
.style('position', 'absolute')
|
|
163
|
+
.style('white-space', 'nowrap')
|
|
164
|
+
.style('color', 'var(--g-color-text-secondary)')
|
|
165
|
+
.style('overflow', 'hidden')
|
|
166
|
+
.style('text-overflow', 'ellipsis')
|
|
167
|
+
.style('height', `${d.labels.height}px`)
|
|
168
|
+
.style('display', 'inline-flex')
|
|
169
|
+
.style('align-items', 'center')
|
|
170
|
+
.style('left', function (l) {
|
|
171
|
+
if (d.position === 'right') {
|
|
172
|
+
return `${l.right - offsetLeft + d.labels.margin + boundsOffsetLeft}px`;
|
|
173
|
+
}
|
|
174
|
+
const rect = this.getBoundingClientRect();
|
|
175
|
+
return `${boundsOffsetLeft - rect.width - d.labels.margin}px`;
|
|
176
|
+
})
|
|
177
|
+
.style('top', function (l) {
|
|
178
|
+
const rect = this.getBoundingClientRect();
|
|
179
|
+
return `${l.top + boundsOffsetTop - offsetTop - rect.height / 2}px`;
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
else if (d.labels.enabled) {
|
|
129
184
|
const labels = axisItem.selectAll('.tick text');
|
|
130
185
|
const tickTexts = labels
|
|
131
186
|
// The offset must be applied before the labels are rotated.
|
|
@@ -344,15 +399,18 @@ export const AxisY = (props) => {
|
|
|
344
399
|
});
|
|
345
400
|
}, [
|
|
346
401
|
allAxes,
|
|
347
|
-
width,
|
|
348
|
-
height,
|
|
349
|
-
scale,
|
|
350
|
-
split,
|
|
351
402
|
bottomLimit,
|
|
403
|
+
boundsOffsetLeft,
|
|
404
|
+
boundsOffsetTop,
|
|
405
|
+
height,
|
|
406
|
+
htmlLayout,
|
|
352
407
|
lineGenerator,
|
|
353
|
-
plotBeforeRef,
|
|
354
408
|
plotAfterRef,
|
|
409
|
+
plotBeforeRef,
|
|
410
|
+
scale,
|
|
411
|
+
split,
|
|
355
412
|
topLimit,
|
|
413
|
+
width,
|
|
356
414
|
]);
|
|
357
415
|
return React.createElement("g", { ref: ref, className: b('container') });
|
|
358
416
|
};
|
|
@@ -94,9 +94,9 @@ export const ChartInner = (props) => {
|
|
|
94
94
|
})),
|
|
95
95
|
React.createElement("g", { width: boundsWidth, height: boundsHeight, transform: `translate(${[boundsOffsetLeft, boundsOffsetTop].join(',')})`, ref: plotRef },
|
|
96
96
|
xScale && (yScale === null || yScale === void 0 ? void 0 : yScale.length) && (React.createElement(React.Fragment, null,
|
|
97
|
-
React.createElement(AxisY, {
|
|
97
|
+
React.createElement(AxisY, { axes: yAxis, bottomLimit: svgBottomPos, boundsOffsetLeft: boundsOffsetLeft, boundsOffsetTop: boundsOffsetTop, height: boundsHeight, htmlLayout: htmlLayout, plotAfterRef: plotAfterRef, plotBeforeRef: plotBeforeRef, scale: yScale, split: preparedSplit, topLimit: svgTopPos, width: boundsWidth }),
|
|
98
98
|
xAxis && (React.createElement("g", { transform: `translate(0, ${boundsHeight})` },
|
|
99
|
-
React.createElement(AxisX, {
|
|
99
|
+
React.createElement(AxisX, { axis: xAxis, boundsOffsetLeft: boundsOffsetLeft, boundsOffsetTop: boundsOffsetTop, height: boundsHeight, htmlLayout: htmlLayout, leftmostLimit: svgXPos, plotAfterRef: plotAfterRef, plotBeforeRef: plotBeforeRef, scale: xScale, split: preparedSplit, width: boundsWidth }))))),
|
|
100
100
|
React.createElement("g", { ref: plotBeforeRef }),
|
|
101
101
|
shapes,
|
|
102
102
|
React.createElement("g", { ref: plotAfterRef })),
|
|
@@ -39,8 +39,13 @@ export function useChartInnerProps(props) {
|
|
|
39
39
|
const [xAxis, setXAxis] = React.useState(null);
|
|
40
40
|
React.useEffect(() => {
|
|
41
41
|
setXAxis(null);
|
|
42
|
-
getPreparedXAxis({
|
|
43
|
-
|
|
42
|
+
getPreparedXAxis({
|
|
43
|
+
xAxis: data.xAxis,
|
|
44
|
+
width,
|
|
45
|
+
seriesData: zoomedSeriesData,
|
|
46
|
+
seriesOptions: preparedSeriesOptions,
|
|
47
|
+
}).then((val) => setXAxis(val));
|
|
48
|
+
}, [data.xAxis, preparedSeriesOptions, width, zoomedSeriesData]);
|
|
44
49
|
const [yAxis, setYAxis] = React.useState([]);
|
|
45
50
|
React.useEffect(() => {
|
|
46
51
|
setYAxis([]);
|
|
@@ -297,6 +297,11 @@ export const Legend = (props) => {
|
|
|
297
297
|
// ticks
|
|
298
298
|
const scale = scaleLinear(domain, [0, legend.width]);
|
|
299
299
|
const xAxisGenerator = await axisBottom({
|
|
300
|
+
domain: {
|
|
301
|
+
size: legend.width,
|
|
302
|
+
color: 'transparent',
|
|
303
|
+
},
|
|
304
|
+
htmlLayout,
|
|
300
305
|
scale,
|
|
301
306
|
ticks: {
|
|
302
307
|
items: [[0, -rectHeight]],
|
|
@@ -307,10 +312,6 @@ export const Legend = (props) => {
|
|
|
307
312
|
labelFormat: (value) => formatNumber(value, { unit: 'auto' }),
|
|
308
313
|
labelsStyle: legend.ticks.style,
|
|
309
314
|
},
|
|
310
|
-
domain: {
|
|
311
|
-
size: legend.width,
|
|
312
|
-
color: 'transparent',
|
|
313
|
-
},
|
|
314
315
|
});
|
|
315
316
|
const tickTop = legend.title.height + legend.title.margin + rectHeight;
|
|
316
317
|
const legendAxisClassname = b('axis');
|
|
@@ -2,10 +2,10 @@ import React from 'react';
|
|
|
2
2
|
import isNil from 'lodash/isNil';
|
|
3
3
|
import { DefaultTooltipContent } from './DefaultTooltipContent';
|
|
4
4
|
export const ChartTooltipContent = (props) => {
|
|
5
|
-
const { hovered, xAxis, yAxis, renderer, valueFormat, totals } = props;
|
|
5
|
+
const { hovered, xAxis, yAxis, renderer, rowRenderer, valueFormat, totals } = props;
|
|
6
6
|
if (!hovered) {
|
|
7
7
|
return null;
|
|
8
8
|
}
|
|
9
9
|
const customTooltip = renderer === null || renderer === void 0 ? void 0 : renderer({ hovered, xAxis, yAxis });
|
|
10
|
-
return isNil(customTooltip) ? (React.createElement(DefaultTooltipContent, { hovered: hovered, xAxis: xAxis, yAxis: yAxis, valueFormat: valueFormat, totals: totals })) : (customTooltip);
|
|
10
|
+
return isNil(customTooltip) ? (React.createElement(DefaultTooltipContent, { hovered: hovered, xAxis: xAxis, yAxis: yAxis, valueFormat: valueFormat, totals: totals, rowRenderer: rowRenderer })) : (customTooltip);
|
|
11
11
|
};
|
|
@@ -6,6 +6,7 @@ type Props = {
|
|
|
6
6
|
valueFormat?: ValueFormat;
|
|
7
7
|
xAxis?: ChartXAxis | null;
|
|
8
8
|
yAxis?: ChartYAxis;
|
|
9
|
+
rowRenderer?: ChartTooltip['rowRenderer'];
|
|
9
10
|
};
|
|
10
|
-
export declare const DefaultTooltipContent: ({ hovered, xAxis, yAxis, valueFormat, totals }: Props) => React.JSX.Element;
|
|
11
|
+
export declare const DefaultTooltipContent: ({ hovered, xAxis, yAxis, valueFormat, totals, rowRenderer, }: Props) => React.JSX.Element;
|
|
11
12
|
export {};
|
|
@@ -7,11 +7,27 @@ import { Row } from './Row';
|
|
|
7
7
|
import { RowTotals } from './RowTotals';
|
|
8
8
|
import { getDefaultValueFormat, getHoveredValues, getMeasureValue, getPreparedAggregation, getXRowData, } from './utils';
|
|
9
9
|
const b = block('tooltip');
|
|
10
|
-
export const DefaultTooltipContent = ({ hovered, xAxis, yAxis, valueFormat, totals }) => {
|
|
10
|
+
export const DefaultTooltipContent = ({ hovered, xAxis, yAxis, valueFormat, totals, rowRenderer, }) => {
|
|
11
11
|
const measureValue = getMeasureValue({ data: hovered, xAxis, yAxis });
|
|
12
12
|
const hoveredValues = getHoveredValues({ hovered, xAxis, yAxis });
|
|
13
|
+
const renderRow = ({ id, name, color, active, striped, value, formattedValue, }) => {
|
|
14
|
+
if (typeof rowRenderer === 'function') {
|
|
15
|
+
return rowRenderer({
|
|
16
|
+
id,
|
|
17
|
+
name,
|
|
18
|
+
color,
|
|
19
|
+
value,
|
|
20
|
+
formattedValue,
|
|
21
|
+
striped,
|
|
22
|
+
active,
|
|
23
|
+
className: b('content-row', { active, striped }),
|
|
24
|
+
hovered,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
return (React.createElement(Row, { key: id, active: active, color: color, label: React.createElement("span", { dangerouslySetInnerHTML: { __html: name } }), striped: striped, value: formattedValue }));
|
|
28
|
+
};
|
|
13
29
|
return (React.createElement("div", { className: b('content') },
|
|
14
|
-
measureValue && React.createElement("div", { className: b('series-name') }
|
|
30
|
+
measureValue && (React.createElement("div", { className: b('series-name'), dangerouslySetInnerHTML: { __html: measureValue } })),
|
|
15
31
|
// eslint-disable-next-line complexity
|
|
16
32
|
hovered.map((seriesItem, i) => {
|
|
17
33
|
var _a;
|
|
@@ -30,7 +46,15 @@ export const DefaultTooltipContent = ({ hovered, xAxis, yAxis, valueFormat, tota
|
|
|
30
46
|
value: hoveredValues[i],
|
|
31
47
|
format,
|
|
32
48
|
});
|
|
33
|
-
return (
|
|
49
|
+
return renderRow({
|
|
50
|
+
id,
|
|
51
|
+
active,
|
|
52
|
+
color,
|
|
53
|
+
name: series.name,
|
|
54
|
+
striped,
|
|
55
|
+
value: hoveredValues[i],
|
|
56
|
+
formattedValue,
|
|
57
|
+
});
|
|
34
58
|
}
|
|
35
59
|
case 'waterfall': {
|
|
36
60
|
const isTotal = get(data, 'total', false);
|
|
@@ -56,7 +80,15 @@ export const DefaultTooltipContent = ({ hovered, xAxis, yAxis, valueFormat, tota
|
|
|
56
80
|
value: hoveredValues[i],
|
|
57
81
|
format,
|
|
58
82
|
});
|
|
59
|
-
return (
|
|
83
|
+
return renderRow({
|
|
84
|
+
id,
|
|
85
|
+
active,
|
|
86
|
+
color,
|
|
87
|
+
name: series.name,
|
|
88
|
+
striped,
|
|
89
|
+
value: hoveredValues[i],
|
|
90
|
+
formattedValue,
|
|
91
|
+
});
|
|
60
92
|
}
|
|
61
93
|
case 'pie':
|
|
62
94
|
case 'treemap': {
|
|
@@ -65,11 +97,13 @@ export const DefaultTooltipContent = ({ hovered, xAxis, yAxis, valueFormat, tota
|
|
|
65
97
|
value: hoveredValues[i],
|
|
66
98
|
format: valueFormat || { type: 'number' },
|
|
67
99
|
});
|
|
68
|
-
return (
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
100
|
+
return renderRow({
|
|
101
|
+
id,
|
|
102
|
+
color,
|
|
103
|
+
name: [seriesData.name || seriesData.id].flat().join('\n'),
|
|
104
|
+
value: hoveredValues[i],
|
|
105
|
+
formattedValue,
|
|
106
|
+
});
|
|
73
107
|
}
|
|
74
108
|
case 'sankey': {
|
|
75
109
|
const { target, data: source } = seriesItem;
|
|
@@ -77,11 +111,13 @@ export const DefaultTooltipContent = ({ hovered, xAxis, yAxis, valueFormat, tota
|
|
|
77
111
|
value: hoveredValues[i],
|
|
78
112
|
format: valueFormat || { type: 'number' },
|
|
79
113
|
});
|
|
80
|
-
return (
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
114
|
+
return renderRow({
|
|
115
|
+
id,
|
|
116
|
+
color,
|
|
117
|
+
name: `${source.name} → ${target === null || target === void 0 ? void 0 : target.name}`,
|
|
118
|
+
value: hoveredValues[i],
|
|
119
|
+
formattedValue,
|
|
120
|
+
});
|
|
85
121
|
}
|
|
86
122
|
case 'radar': {
|
|
87
123
|
const radarSeries = series;
|
|
@@ -89,7 +125,14 @@ export const DefaultTooltipContent = ({ hovered, xAxis, yAxis, valueFormat, tota
|
|
|
89
125
|
value: hoveredValues[i],
|
|
90
126
|
format: valueFormat || { type: 'number' },
|
|
91
127
|
});
|
|
92
|
-
return (
|
|
128
|
+
return renderRow({
|
|
129
|
+
id,
|
|
130
|
+
color,
|
|
131
|
+
active,
|
|
132
|
+
name: radarSeries.name || radarSeries.id,
|
|
133
|
+
value: hoveredValues[i],
|
|
134
|
+
formattedValue,
|
|
135
|
+
});
|
|
93
136
|
}
|
|
94
137
|
default: {
|
|
95
138
|
return null;
|
|
@@ -23,5 +23,5 @@ export const Tooltip = (props) => {
|
|
|
23
23
|
}, [left, top]);
|
|
24
24
|
return (hovered === null || hovered === void 0 ? void 0 : hovered.length) ? (React.createElement(Popup, { anchorElement: anchor, className: b({ pinned: tooltipPinned }), disableTransition: true, floatingStyles: tooltipPinned ? undefined : { pointerEvents: 'none' }, offset: { mainAxis: 20 }, onOpenChange: tooltipPinned ? handleOnOpenChange : undefined, open: true, placement: ['right', 'left', 'top', 'bottom'] },
|
|
25
25
|
React.createElement("div", { className: b('popup-content') },
|
|
26
|
-
React.createElement(ChartTooltipContent, { hovered: hovered, xAxis: xAxis, yAxis: yAxis, renderer: tooltip.renderer, valueFormat: tooltip.valueFormat, totals: tooltip.totals })))) : null;
|
|
26
|
+
React.createElement(ChartTooltipContent, { hovered: hovered, xAxis: xAxis, yAxis: yAxis, renderer: tooltip.renderer, rowRenderer: tooltip.rowRenderer, valueFormat: tooltip.valueFormat, totals: tooltip.totals })))) : null;
|
|
27
27
|
};
|
|
@@ -29,6 +29,7 @@ export declare function createXScale(args: {
|
|
|
29
29
|
axis: PreparedAxis | ChartAxis;
|
|
30
30
|
boundsWidth: number;
|
|
31
31
|
series: (PreparedSeries | ChartSeries)[];
|
|
32
|
+
seriesOptions: PreparedSeriesOptions;
|
|
32
33
|
hasZoomX?: boolean;
|
|
33
34
|
}): ScaleBand<string> | ScaleLinear<number, number, never> | ScaleTime<number, number, never>;
|
|
34
35
|
/**
|
|
@@ -4,6 +4,7 @@ import get from 'lodash/get';
|
|
|
4
4
|
import { DEFAULT_AXIS_TYPE, SeriesType } from '../../constants';
|
|
5
5
|
import { CHART_SERIES_WITH_VOLUME_ON_Y_AXIS, getAxisHeight, getDataCategoryValue, getDefaultMaxXAxisValue, getDefaultMinXAxisValue, getDomainDataXBySeries, getDomainDataYBySeries, getOnlyVisibleSeries, isAxisRelatedSeries, isSeriesWithCategoryValues, } from '../../utils';
|
|
6
6
|
import { getBarYLayoutForNumericScale, groupBarYDataByYValue } from '../utils';
|
|
7
|
+
import { getBarXLayoutForNumericScale, groupBarXDataByXValue } from '../utils/bar-x';
|
|
7
8
|
const X_AXIS_ZOOM_PADDING = 0.02;
|
|
8
9
|
function isNumericalArrayData(data) {
|
|
9
10
|
return data.every((d) => typeof d === 'number' || d === null);
|
|
@@ -130,32 +131,55 @@ function calculateXAxisPadding(series) {
|
|
|
130
131
|
});
|
|
131
132
|
return result;
|
|
132
133
|
}
|
|
134
|
+
function getXScaleRange({ boundsWidth, series, seriesOptions, hasZoomX, axis, maxPadding, }) {
|
|
135
|
+
const xAxisZoomPadding = boundsWidth * X_AXIS_ZOOM_PADDING;
|
|
136
|
+
const xRange = [0, boundsWidth - maxPadding];
|
|
137
|
+
const xRangeZoom = [0 + xAxisZoomPadding, boundsWidth - xAxisZoomPadding];
|
|
138
|
+
const range = hasZoomX ? xRangeZoom : xRange;
|
|
139
|
+
const barXSeries = series.filter((s) => s.type === SeriesType.BarX);
|
|
140
|
+
if (barXSeries.length) {
|
|
141
|
+
const groupedData = groupBarXDataByXValue(barXSeries, axis);
|
|
142
|
+
if (Object.keys(groupedData).length > 1) {
|
|
143
|
+
const { bandSize } = getBarXLayoutForNumericScale({
|
|
144
|
+
plotWidth: boundsWidth - maxPadding,
|
|
145
|
+
groupedData,
|
|
146
|
+
seriesOptions,
|
|
147
|
+
});
|
|
148
|
+
const offset = bandSize / 2;
|
|
149
|
+
return [range[0] + offset, range[1] - offset];
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return range;
|
|
153
|
+
}
|
|
133
154
|
// eslint-disable-next-line complexity
|
|
134
155
|
export function createXScale(args) {
|
|
135
|
-
const { axis, boundsWidth, series, hasZoomX } = args;
|
|
156
|
+
const { axis, boundsWidth, series, seriesOptions, hasZoomX } = args;
|
|
136
157
|
const xMinProps = get(axis, 'min');
|
|
137
158
|
const xMaxProps = get(axis, 'max');
|
|
138
159
|
const xType = get(axis, 'type', DEFAULT_AXIS_TYPE);
|
|
139
160
|
const xCategories = get(axis, 'categories');
|
|
140
|
-
const xTimestamps = get(axis, 'timestamps');
|
|
141
161
|
const maxPadding = get(axis, 'maxPadding', 0);
|
|
142
162
|
const xAxisMaxPadding = boundsWidth * maxPadding + calculateXAxisPadding(series);
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
163
|
+
const range = getXScaleRange({
|
|
164
|
+
boundsWidth,
|
|
165
|
+
series,
|
|
166
|
+
seriesOptions,
|
|
167
|
+
hasZoomX,
|
|
168
|
+
axis,
|
|
169
|
+
maxPadding: xAxisMaxPadding,
|
|
170
|
+
});
|
|
146
171
|
switch (axis.order) {
|
|
147
172
|
case 'sortDesc':
|
|
148
173
|
case 'reverse': {
|
|
149
|
-
|
|
150
|
-
xRangeZoom.reverse();
|
|
174
|
+
range.reverse();
|
|
151
175
|
}
|
|
152
176
|
}
|
|
153
177
|
switch (xType) {
|
|
154
178
|
case 'linear':
|
|
155
179
|
case 'logarithmic': {
|
|
156
|
-
const
|
|
157
|
-
if (isNumericalArrayData(
|
|
158
|
-
const [xMinDomain, xMaxDomain] = extent(
|
|
180
|
+
const domainData = getDomainDataXBySeries(series);
|
|
181
|
+
if (isNumericalArrayData(domainData)) {
|
|
182
|
+
const [xMinDomain, xMaxDomain] = extent(domainData);
|
|
159
183
|
let xMin;
|
|
160
184
|
let xMax;
|
|
161
185
|
if (typeof xMinProps === 'number') {
|
|
@@ -176,11 +200,10 @@ export function createXScale(args) {
|
|
|
176
200
|
: xMaxDomain;
|
|
177
201
|
}
|
|
178
202
|
const scaleFn = xType === 'logarithmic' ? scaleLog : scaleLinear;
|
|
179
|
-
const scale = scaleFn()
|
|
180
|
-
.domain([xMin, xMax])
|
|
181
|
-
.range(hasZoomX ? xRangeZoom : xRange);
|
|
203
|
+
const scale = scaleFn().domain([xMin, xMax]).range(range);
|
|
182
204
|
if (!hasZoomX) {
|
|
183
|
-
|
|
205
|
+
// 10 is the default value for the number of ticks. Here, to preserve the appearance of a series with a small number of points
|
|
206
|
+
scale.nice(Math.max(10, domainData.length));
|
|
184
207
|
}
|
|
185
208
|
return scale;
|
|
186
209
|
}
|
|
@@ -195,40 +218,27 @@ export function createXScale(args) {
|
|
|
195
218
|
});
|
|
196
219
|
const xScale = scaleBand().domain(filteredCategories).range([0, boundsWidth]);
|
|
197
220
|
if (xScale.step() / 2 < xAxisMaxPadding) {
|
|
198
|
-
xScale.range(
|
|
221
|
+
xScale.range(range);
|
|
199
222
|
}
|
|
200
223
|
return xScale;
|
|
201
224
|
}
|
|
202
225
|
break;
|
|
203
226
|
}
|
|
204
227
|
case 'datetime': {
|
|
205
|
-
|
|
206
|
-
|
|
228
|
+
let domain = null;
|
|
229
|
+
const domainData = get(axis, 'timestamps') || getDomainDataXBySeries(series);
|
|
230
|
+
if (isNumericalArrayData(domainData)) {
|
|
231
|
+
const [xMinTimestamp, xMaxTimestamp] = extent(domainData);
|
|
207
232
|
const xMin = typeof xMinProps === 'number' ? xMinProps : xMinTimestamp;
|
|
208
233
|
const xMax = typeof xMaxProps === 'number' ? xMaxProps : xMaxTimestamp;
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
.range(hasZoomX ? xRangeZoom : xRange);
|
|
234
|
+
domain = [xMin, xMax];
|
|
235
|
+
const scale = scaleUtc().domain(domain).range(range);
|
|
212
236
|
if (!hasZoomX) {
|
|
213
|
-
|
|
237
|
+
// 10 is the default value for the number of ticks. Here, to preserve the appearance of a series with a small number of points
|
|
238
|
+
scale.nice(Math.max(10, domainData.length));
|
|
214
239
|
}
|
|
215
240
|
return scale;
|
|
216
241
|
}
|
|
217
|
-
else {
|
|
218
|
-
const domain = getDomainDataXBySeries(series);
|
|
219
|
-
if (isNumericalArrayData(domain)) {
|
|
220
|
-
const [xMinTimestamp, xMaxTimestamp] = extent(domain);
|
|
221
|
-
const xMin = typeof xMinProps === 'number' ? xMinProps : xMinTimestamp;
|
|
222
|
-
const xMax = typeof xMaxProps === 'number' ? xMaxProps : xMaxTimestamp;
|
|
223
|
-
const scale = scaleUtc()
|
|
224
|
-
.domain([xMin, xMax])
|
|
225
|
-
.range(hasZoomX ? xRangeZoom : xRange);
|
|
226
|
-
if (!hasZoomX) {
|
|
227
|
-
scale.nice();
|
|
228
|
-
}
|
|
229
|
-
return scale;
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
242
|
break;
|
|
233
243
|
}
|
|
234
244
|
}
|
|
@@ -242,7 +252,13 @@ const createScales = (args) => {
|
|
|
242
252
|
visibleSeries = visibleSeries.length === 0 ? series : visibleSeries;
|
|
243
253
|
return {
|
|
244
254
|
xScale: xAxis
|
|
245
|
-
? createXScale({
|
|
255
|
+
? createXScale({
|
|
256
|
+
axis: xAxis,
|
|
257
|
+
boundsWidth,
|
|
258
|
+
series: visibleSeries,
|
|
259
|
+
seriesOptions,
|
|
260
|
+
hasZoomX,
|
|
261
|
+
})
|
|
246
262
|
: undefined,
|
|
247
263
|
yScale: yAxis.map((axis, index) => {
|
|
248
264
|
const axisSeries = series.filter((s) => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import get from 'lodash/get';
|
|
2
|
-
import {
|
|
2
|
+
import { getHorizontalSvgTextHeight } from '../../utils';
|
|
3
3
|
const DEFAULT_TITLE_FONT_SIZE = '15px';
|
|
4
4
|
const TITLE_PADDINGS = 8 * 2;
|
|
5
5
|
export const getPreparedTitle = ({ title, }) => {
|
|
@@ -9,7 +9,7 @@ export const getPreparedTitle = ({ title, }) => {
|
|
|
9
9
|
fontWeight: get(title, 'style.fontWeight'),
|
|
10
10
|
};
|
|
11
11
|
const titleHeight = titleText
|
|
12
|
-
?
|
|
12
|
+
? getHorizontalSvgTextHeight({ text: titleText, style: titleStyle }) + TITLE_PADDINGS
|
|
13
13
|
: 0;
|
|
14
14
|
const preparedTitle = titleText
|
|
15
15
|
? { text: titleText, style: titleStyle, height: titleHeight }
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { DashStyle } from '../../constants';
|
|
2
2
|
import type { AxisCrosshair, AxisPlotBand, BaseTextStyle, ChartAxis, ChartAxisLabels, ChartAxisTitleAlignment, ChartAxisType, ChartData, ChartMargin, ChartZoom, DeepRequired, PlotLayerPlacement } from '../../types';
|
|
3
|
-
type PreparedAxisLabels = Omit<ChartAxisLabels, 'enabled' | 'padding' | 'style' | 'autoRotation'> & Required<Pick<ChartAxisLabels, 'enabled' | 'padding' | 'margin' | 'rotation'>> & {
|
|
3
|
+
type PreparedAxisLabels = Omit<ChartAxisLabels, 'enabled' | 'padding' | 'style' | 'autoRotation'> & Required<Pick<ChartAxisLabels, 'enabled' | 'padding' | 'margin' | 'rotation' | 'html'>> & {
|
|
4
4
|
style: BaseTextStyle;
|
|
5
5
|
rotation: number;
|
|
6
6
|
height: number;
|