@gravity-ui/charts 1.0.1 → 1.1.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 +1 -0
- package/dist/cjs/components/Axis/AxisX.js +16 -8
- package/dist/cjs/components/Axis/AxisY.d.ts +1 -0
- package/dist/cjs/components/Axis/AxisY.js +38 -10
- package/dist/cjs/components/ChartInner/index.js +3 -3
- package/dist/cjs/components/ChartInner/useChartInnerProps.d.ts +3 -0
- package/dist/cjs/components/ChartInner/useChartInnerProps.js +14 -3
- package/dist/cjs/components/Tooltip/DefaultContent.js +41 -23
- package/dist/cjs/hooks/useChartDimensions/index.js +3 -0
- package/dist/cjs/hooks/useChartDimensions/utils.js +3 -0
- package/dist/cjs/hooks/useChartOptions/x-axis.js +1 -0
- package/dist/cjs/hooks/useChartOptions/y-axis.js +1 -0
- package/dist/cjs/hooks/useSeries/prepare-waterfall.js +40 -28
- package/dist/cjs/hooks/useSeries/types.d.ts +4 -3
- package/dist/cjs/hooks/useShapes/waterfall/prepare-data.js +4 -2
- package/dist/cjs/types/chart/axis.d.ts +2 -0
- package/dist/cjs/types/chart/waterfall.d.ts +9 -0
- package/dist/cjs/utils/chart/axis-generators/bottom.d.ts +1 -0
- package/dist/cjs/utils/chart/axis-generators/bottom.js +16 -1
- package/dist/cjs/utils/chart/get-closest-data.js +23 -13
- package/dist/cjs/utils/chart/index.js +5 -5
- package/dist/cjs/utils/chart/series/waterfall.d.ts +2 -2
- package/dist/cjs/utils/chart/series/waterfall.js +1 -7
- package/dist/cjs/utils/chart-ui/pie-center-text.d.ts +1 -0
- package/dist/cjs/utils/chart-ui/pie-center-text.js +3 -1
- package/dist/esm/components/Axis/AxisX.d.ts +1 -0
- package/dist/esm/components/Axis/AxisX.js +16 -8
- package/dist/esm/components/Axis/AxisY.d.ts +1 -0
- package/dist/esm/components/Axis/AxisY.js +38 -10
- package/dist/esm/components/ChartInner/index.js +3 -3
- package/dist/esm/components/ChartInner/useChartInnerProps.d.ts +3 -0
- package/dist/esm/components/ChartInner/useChartInnerProps.js +14 -3
- package/dist/esm/components/Tooltip/DefaultContent.js +41 -23
- package/dist/esm/hooks/useChartDimensions/index.js +3 -0
- package/dist/esm/hooks/useChartDimensions/utils.js +3 -0
- package/dist/esm/hooks/useChartOptions/x-axis.js +1 -0
- package/dist/esm/hooks/useChartOptions/y-axis.js +1 -0
- package/dist/esm/hooks/useSeries/prepare-waterfall.js +40 -28
- package/dist/esm/hooks/useSeries/types.d.ts +4 -3
- package/dist/esm/hooks/useShapes/waterfall/prepare-data.js +4 -2
- package/dist/esm/types/chart/axis.d.ts +2 -0
- package/dist/esm/types/chart/waterfall.d.ts +9 -0
- package/dist/esm/utils/chart/axis-generators/bottom.d.ts +1 -0
- package/dist/esm/utils/chart/axis-generators/bottom.js +16 -1
- package/dist/esm/utils/chart/get-closest-data.js +23 -13
- package/dist/esm/utils/chart/index.js +5 -5
- package/dist/esm/utils/chart/series/waterfall.d.ts +2 -2
- package/dist/esm/utils/chart/series/waterfall.js +1 -7
- package/dist/esm/utils/chart-ui/pie-center-text.d.ts +1 -0
- package/dist/esm/utils/chart-ui/pie-center-text.js +3 -1
- package/package.json +1 -1
|
@@ -42,12 +42,23 @@ export function getTitlePosition(args) {
|
|
|
42
42
|
return { x, y };
|
|
43
43
|
}
|
|
44
44
|
export const AxisX = React.memo(function AxisX(props) {
|
|
45
|
-
const { axis, width, height: totalHeight, scale, split, plotRef } = props;
|
|
45
|
+
const { axis, width, height: totalHeight, scale, split, plotRef, leftmostLimit } = props;
|
|
46
46
|
const ref = React.useRef(null);
|
|
47
47
|
React.useEffect(() => {
|
|
48
48
|
if (!ref.current) {
|
|
49
49
|
return;
|
|
50
50
|
}
|
|
51
|
+
const svgElement = select(ref.current);
|
|
52
|
+
svgElement.selectAll('*').remove();
|
|
53
|
+
const plotClassName = b('plot-x');
|
|
54
|
+
let plotContainer = null;
|
|
55
|
+
if (plotRef === null || plotRef === void 0 ? void 0 : plotRef.current) {
|
|
56
|
+
plotContainer = select(plotRef.current);
|
|
57
|
+
plotContainer.selectAll(`.${plotClassName}`).remove();
|
|
58
|
+
}
|
|
59
|
+
if (!axis.visible) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
51
62
|
let tickItems = [];
|
|
52
63
|
if (axis.grid.enabled) {
|
|
53
64
|
tickItems = new Array(split.plots.length || 1).fill(null).map((_, index) => {
|
|
@@ -59,6 +70,7 @@ export const AxisX = React.memo(function AxisX(props) {
|
|
|
59
70
|
}
|
|
60
71
|
const axisScale = scale;
|
|
61
72
|
const xAxisGenerator = axisBottom({
|
|
73
|
+
leftmostLimit,
|
|
62
74
|
scale: axisScale,
|
|
63
75
|
ticks: {
|
|
64
76
|
items: tickItems,
|
|
@@ -77,8 +89,6 @@ export const AxisX = React.memo(function AxisX(props) {
|
|
|
77
89
|
color: axis.lineColor,
|
|
78
90
|
},
|
|
79
91
|
});
|
|
80
|
-
const svgElement = select(ref.current);
|
|
81
|
-
svgElement.selectAll('*').remove();
|
|
82
92
|
svgElement.call(xAxisGenerator).attr('class', b());
|
|
83
93
|
// add an axis header if necessary
|
|
84
94
|
if (axis.title.text) {
|
|
@@ -105,15 +115,13 @@ export const AxisX = React.memo(function AxisX(props) {
|
|
|
105
115
|
});
|
|
106
116
|
}
|
|
107
117
|
// add plot lines
|
|
108
|
-
if (
|
|
118
|
+
if (plotContainer && axis.plotLines.length > 0) {
|
|
109
119
|
const plotLineClassName = b('plotLine');
|
|
110
|
-
const
|
|
111
|
-
plotLineContainer.selectAll(`.${plotLineClassName}-x`).remove();
|
|
112
|
-
const plotLinesSelection = plotLineContainer
|
|
120
|
+
const plotLinesSelection = plotContainer
|
|
113
121
|
.selectAll(`.${plotLineClassName}-x`)
|
|
114
122
|
.data(axis.plotLines)
|
|
115
123
|
.join('g')
|
|
116
|
-
.attr('class', `${plotLineClassName}-x`);
|
|
124
|
+
.attr('class', `${plotClassName} ${plotLineClassName}-x`);
|
|
117
125
|
const lineGenerator = line();
|
|
118
126
|
plotLinesSelection
|
|
119
127
|
.append('path')
|
|
@@ -4,8 +4,11 @@ import { block, calculateCos, calculateSin, formatAxisTickLabel, getAxisHeight,
|
|
|
4
4
|
import './styles.css';
|
|
5
5
|
const b = block('axis');
|
|
6
6
|
function transformLabel(args) {
|
|
7
|
-
const { node, axis } = args;
|
|
7
|
+
const { node, axis, isTopOffsetOverload = false } = args;
|
|
8
8
|
let topOffset = axis.labels.lineHeight / 2;
|
|
9
|
+
if (isTopOffsetOverload) {
|
|
10
|
+
topOffset = 0;
|
|
11
|
+
}
|
|
9
12
|
let leftOffset = axis.labels.margin;
|
|
10
13
|
if (axis.position === 'left') {
|
|
11
14
|
leftOffset = leftOffset * -1;
|
|
@@ -82,7 +85,7 @@ function getTitlePosition(args) {
|
|
|
82
85
|
return { x, y };
|
|
83
86
|
}
|
|
84
87
|
export const AxisY = (props) => {
|
|
85
|
-
const { axes, width, height: totalHeight, scale, split, plotRef } = props;
|
|
88
|
+
const { axes: allAxes, width, height: totalHeight, scale, split, plotRef, lowerLimit = 0, } = props;
|
|
86
89
|
const height = getAxisHeight({ split, boundsHeight: totalHeight });
|
|
87
90
|
const ref = React.useRef(null);
|
|
88
91
|
const lineGenerator = line();
|
|
@@ -90,8 +93,15 @@ export const AxisY = (props) => {
|
|
|
90
93
|
if (!ref.current) {
|
|
91
94
|
return;
|
|
92
95
|
}
|
|
96
|
+
const axes = allAxes.filter((a) => a.visible);
|
|
93
97
|
const svgElement = select(ref.current);
|
|
94
98
|
svgElement.selectAll('*').remove();
|
|
99
|
+
let plotContainer = null;
|
|
100
|
+
const plotClassName = b('plot-y');
|
|
101
|
+
if (plotRef === null || plotRef === void 0 ? void 0 : plotRef.current) {
|
|
102
|
+
plotContainer = select(plotRef.current);
|
|
103
|
+
plotContainer.selectAll(`.${plotClassName}`).remove();
|
|
104
|
+
}
|
|
95
105
|
const getAxisPosition = (axis) => {
|
|
96
106
|
var _a;
|
|
97
107
|
const top = ((_a = split.plots[axis.plotIndex]) === null || _a === void 0 ? void 0 : _a.top) || 0;
|
|
@@ -127,8 +137,8 @@ export const AxisY = (props) => {
|
|
|
127
137
|
});
|
|
128
138
|
yAxisGenerator(axisItem);
|
|
129
139
|
if (d.labels.enabled) {
|
|
130
|
-
const
|
|
131
|
-
|
|
140
|
+
const labels = axisItem.selectAll('.tick text');
|
|
141
|
+
const tickTexts = labels
|
|
132
142
|
// The offset must be applied before the labels are rotated.
|
|
133
143
|
// Therefore, we reset the values and make an offset in transform attribute.
|
|
134
144
|
// FIXME: give up axisLeft(d3) and switch to our own generation method
|
|
@@ -138,6 +148,26 @@ export const AxisY = (props) => {
|
|
|
138
148
|
.style('transform', function () {
|
|
139
149
|
return transformLabel({ node: this, axis: d });
|
|
140
150
|
});
|
|
151
|
+
labels.each(function (_d, i) {
|
|
152
|
+
if (i === 0) {
|
|
153
|
+
const currentElement = this;
|
|
154
|
+
const currentElementPosition = currentElement.getBoundingClientRect();
|
|
155
|
+
const text = select(currentElement);
|
|
156
|
+
if (currentElementPosition.bottom > lowerLimit) {
|
|
157
|
+
const transform = transformLabel({
|
|
158
|
+
node: this,
|
|
159
|
+
axis: d,
|
|
160
|
+
isTopOffsetOverload: true,
|
|
161
|
+
});
|
|
162
|
+
text.style('transform', transform);
|
|
163
|
+
if (d.labels.rotation) {
|
|
164
|
+
text.attr('text-anchor', () => {
|
|
165
|
+
return d.labels.rotation < 0 ? 'start' : 'end';
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
});
|
|
141
171
|
const textMaxWidth = !d.labels.rotation || Math.abs(d.labels.rotation) % 360 !== 90
|
|
142
172
|
? d.labels.maxWidth
|
|
143
173
|
: (height - d.labels.padding * (tickTexts.size() - 1)) / tickTexts.size();
|
|
@@ -160,15 +190,13 @@ export const AxisY = (props) => {
|
|
|
160
190
|
})
|
|
161
191
|
.remove();
|
|
162
192
|
}
|
|
163
|
-
if (
|
|
193
|
+
if (plotContainer && d.plotLines.length > 0) {
|
|
164
194
|
const plotLineClassName = b('plotLine');
|
|
165
|
-
const
|
|
166
|
-
plotLineContainer.selectAll(`.${plotLineClassName}`).remove();
|
|
167
|
-
const plotLinesSelection = plotLineContainer
|
|
195
|
+
const plotLinesSelection = plotContainer
|
|
168
196
|
.selectAll(`.${plotLineClassName}`)
|
|
169
197
|
.data(plotLines)
|
|
170
198
|
.join('g')
|
|
171
|
-
.attr('class', plotLineClassName)
|
|
199
|
+
.attr('class', `${plotClassName} ${plotLineClassName}`)
|
|
172
200
|
.style('transform', (plotLine) => plotLine.transform);
|
|
173
201
|
plotLinesSelection
|
|
174
202
|
.append('path')
|
|
@@ -240,6 +268,6 @@ export const AxisY = (props) => {
|
|
|
240
268
|
handleOverflowingText(nodes[index], height);
|
|
241
269
|
}
|
|
242
270
|
});
|
|
243
|
-
}, [
|
|
271
|
+
}, [allAxes, width, height, scale, split]);
|
|
244
272
|
return React.createElement("g", { ref: ref, className: b('container') });
|
|
245
273
|
};
|
|
@@ -17,7 +17,7 @@ export const ChartInner = (props) => {
|
|
|
17
17
|
const htmlLayerRef = React.useRef(null);
|
|
18
18
|
const plotRef = React.useRef(null);
|
|
19
19
|
const dispatcher = React.useMemo(() => getDispatcher(), []);
|
|
20
|
-
const { boundsHeight, boundsOffsetLeft, boundsOffsetTop, boundsWidth, handleLegendItemClick, legendConfig, legendItems, preparedSeries, preparedSplit, preparedLegend, prevHeight, prevWidth, shapes, shapesData, title, tooltip, xAxis, xScale, yAxis, yScale, } = useChartInnerProps(Object.assign(Object.assign({}, props), { dispatcher, htmlLayout: htmlLayerRef.current }));
|
|
20
|
+
const { boundsHeight, boundsOffsetLeft, boundsOffsetTop, boundsWidth, handleLegendItemClick, legendConfig, legendItems, preparedSeries, preparedSplit, preparedLegend, prevHeight, prevWidth, shapes, shapesData, title, tooltip, xAxis, xScale, yAxis, yScale, svgXPos, svgBottomPos, } = useChartInnerProps(Object.assign(Object.assign({}, props), { dispatcher, htmlLayout: htmlLayerRef.current, svgContainer: svgRef.current }));
|
|
21
21
|
const { tooltipPinned, togglePinTooltip, unpinTooltip } = useChartInnerState({
|
|
22
22
|
dispatcher,
|
|
23
23
|
tooltip,
|
|
@@ -67,9 +67,9 @@ 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, { axes: yAxis, width: boundsWidth, height: boundsHeight, scale: yScale, split: preparedSplit, plotRef: plotRef }),
|
|
70
|
+
React.createElement(AxisY, { lowerLimit: svgBottomPos, axes: yAxis, width: boundsWidth, height: boundsHeight, scale: yScale, split: preparedSplit, plotRef: plotRef }),
|
|
71
71
|
React.createElement("g", { transform: `translate(0, ${boundsHeight})` },
|
|
72
|
-
React.createElement(AxisX, { axis: xAxis, width: boundsWidth, height: boundsHeight, scale: xScale, split: preparedSplit, plotRef: plotRef })))),
|
|
72
|
+
React.createElement(AxisX, { leftmostLimit: svgXPos, axis: xAxis, width: boundsWidth, height: boundsHeight, scale: xScale, split: preparedSplit, plotRef: plotRef })))),
|
|
73
73
|
shapes),
|
|
74
74
|
preparedLegend.enabled && (React.createElement(Legend, { chartSeries: preparedSeries, boundsWidth: boundsWidth, legend: preparedLegend, items: legendItems, config: legendConfig, onItemClick: handleLegendItemClick, onUpdate: unpinTooltip }))),
|
|
75
75
|
React.createElement("div", { className: b('html-layer'), ref: htmlLayerRef, style: {
|
|
@@ -4,8 +4,11 @@ import type { ChartInnerProps } from './types';
|
|
|
4
4
|
type Props = ChartInnerProps & {
|
|
5
5
|
dispatcher: Dispatch<object>;
|
|
6
6
|
htmlLayout: HTMLElement | null;
|
|
7
|
+
svgContainer: SVGGElement | null;
|
|
7
8
|
};
|
|
8
9
|
export declare function useChartInnerProps(props: Props): {
|
|
10
|
+
svgBottomPos: number | undefined;
|
|
11
|
+
svgXPos: number | undefined;
|
|
9
12
|
boundsHeight: number;
|
|
10
13
|
boundsOffsetLeft: number;
|
|
11
14
|
boundsOffsetTop: number;
|
|
@@ -4,7 +4,8 @@ import { getYAxisWidth } from '../../hooks/useChartDimensions/utils';
|
|
|
4
4
|
import { getPreparedXAxis } from '../../hooks/useChartOptions/x-axis';
|
|
5
5
|
import { getPreparedYAxis } from '../../hooks/useChartOptions/y-axis';
|
|
6
6
|
export function useChartInnerProps(props) {
|
|
7
|
-
|
|
7
|
+
var _a;
|
|
8
|
+
const { width, height, data, dispatcher, htmlLayout, svgContainer } = props;
|
|
8
9
|
const prevWidth = usePrevious(width);
|
|
9
10
|
const prevHeight = usePrevious(height);
|
|
10
11
|
const { chart, title, tooltip } = useChartOptions({ data });
|
|
@@ -54,9 +55,19 @@ export function useChartInnerProps(props) {
|
|
|
54
55
|
htmlLayout,
|
|
55
56
|
});
|
|
56
57
|
const boundsOffsetTop = chart.margin.top;
|
|
57
|
-
// We
|
|
58
|
-
const boundsOffsetLeft = chart.margin.left +
|
|
58
|
+
// We need to calculate the width of each axis because the first axis can be hidden
|
|
59
|
+
const boundsOffsetLeft = chart.margin.left +
|
|
60
|
+
yAxis.reduce((acc, axis) => {
|
|
61
|
+
const width = getYAxisWidth(axis);
|
|
62
|
+
if (acc < width) {
|
|
63
|
+
acc = width;
|
|
64
|
+
}
|
|
65
|
+
return acc;
|
|
66
|
+
}, 0);
|
|
67
|
+
const { x, bottom } = (_a = svgContainer === null || svgContainer === void 0 ? void 0 : svgContainer.getBoundingClientRect()) !== null && _a !== void 0 ? _a : {};
|
|
59
68
|
return {
|
|
69
|
+
svgBottomPos: bottom,
|
|
70
|
+
svgXPos: x,
|
|
60
71
|
boundsHeight,
|
|
61
72
|
boundsOffsetLeft,
|
|
62
73
|
boundsOffsetTop,
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { dateTime } from '@gravity-ui/date-utils';
|
|
3
2
|
import get from 'lodash/get';
|
|
4
|
-
import { formatNumber } from '../../libs';
|
|
5
3
|
import { block, getDataCategoryValue, getWaterfallPointSubtotal } from '../../utils';
|
|
6
4
|
import { getFormattedValue } from '../../utils/chart/format';
|
|
7
5
|
const b = block('tooltip');
|
|
@@ -12,23 +10,14 @@ const getRowData = (fieldName, data, axis) => {
|
|
|
12
10
|
const categories = get(axis, 'categories', []);
|
|
13
11
|
return getDataCategoryValue({ axisDirection: fieldName, categories, data });
|
|
14
12
|
}
|
|
15
|
-
case 'datetime': {
|
|
16
|
-
const value = get(data, fieldName);
|
|
17
|
-
if (!value) {
|
|
18
|
-
return undefined;
|
|
19
|
-
}
|
|
20
|
-
return dateTime({ input: value }).format(DEFAULT_DATE_FORMAT);
|
|
21
|
-
}
|
|
22
|
-
case 'linear':
|
|
23
13
|
default: {
|
|
24
|
-
|
|
25
|
-
return formatNumber(value);
|
|
14
|
+
return get(data, fieldName);
|
|
26
15
|
}
|
|
27
16
|
}
|
|
28
17
|
};
|
|
29
18
|
const getXRowData = (data, xAxis) => getRowData('x', data, xAxis);
|
|
30
19
|
const getYRowData = (data, yAxis) => getRowData('y', data, yAxis);
|
|
31
|
-
const getMeasureValue = (data, xAxis, yAxis) => {
|
|
20
|
+
const getMeasureValue = ({ data, xAxis, yAxis, valueFormat, }) => {
|
|
32
21
|
var _a, _b, _c, _d;
|
|
33
22
|
if (data.every((item) => ['pie', 'treemap', 'waterfall', 'sankey'].includes(item.series.type))) {
|
|
34
23
|
return null;
|
|
@@ -37,12 +26,38 @@ const getMeasureValue = (data, xAxis, yAxis) => {
|
|
|
37
26
|
return (_b = (_a = data[0].category) === null || _a === void 0 ? void 0 : _a.key) !== null && _b !== void 0 ? _b : null;
|
|
38
27
|
}
|
|
39
28
|
if (data.some((item) => item.series.type === 'bar-y')) {
|
|
40
|
-
|
|
29
|
+
const format = valueFormat !== null && valueFormat !== void 0 ? valueFormat : getDefaultValueFormat({ axis: yAxis });
|
|
30
|
+
return getFormattedValue({
|
|
31
|
+
value: getYRowData((_c = data[0]) === null || _c === void 0 ? void 0 : _c.data, yAxis),
|
|
32
|
+
format,
|
|
33
|
+
});
|
|
41
34
|
}
|
|
42
|
-
|
|
35
|
+
const format = valueFormat !== null && valueFormat !== void 0 ? valueFormat : getDefaultValueFormat({ axis: xAxis });
|
|
36
|
+
return getFormattedValue({
|
|
37
|
+
value: getXRowData((_d = data[0]) === null || _d === void 0 ? void 0 : _d.data, xAxis),
|
|
38
|
+
format,
|
|
39
|
+
});
|
|
43
40
|
};
|
|
41
|
+
function getDefaultValueFormat({ axis }) {
|
|
42
|
+
switch (axis === null || axis === void 0 ? void 0 : axis.type) {
|
|
43
|
+
case 'linear':
|
|
44
|
+
case 'logarithmic': {
|
|
45
|
+
return {
|
|
46
|
+
type: 'number',
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
case 'datetime': {
|
|
50
|
+
return {
|
|
51
|
+
type: 'date',
|
|
52
|
+
format: DEFAULT_DATE_FORMAT,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
default:
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
44
59
|
export const DefaultContent = ({ hovered, xAxis, yAxis, valueFormat }) => {
|
|
45
|
-
const measureValue = getMeasureValue(hovered, xAxis, yAxis);
|
|
60
|
+
const measureValue = getMeasureValue({ data: hovered, xAxis, yAxis });
|
|
46
61
|
return (React.createElement(React.Fragment, null,
|
|
47
62
|
measureValue && React.createElement("div", null, measureValue),
|
|
48
63
|
hovered.map((seriesItem, i) => {
|
|
@@ -55,9 +70,10 @@ export const DefaultContent = ({ hovered, xAxis, yAxis, valueFormat }) => {
|
|
|
55
70
|
case 'line':
|
|
56
71
|
case 'area':
|
|
57
72
|
case 'bar-x': {
|
|
73
|
+
const format = valueFormat !== null && valueFormat !== void 0 ? valueFormat : getDefaultValueFormat({ axis: yAxis });
|
|
58
74
|
const formattedValue = getFormattedValue({
|
|
59
75
|
value: getYRowData(data, yAxis),
|
|
60
|
-
format
|
|
76
|
+
format,
|
|
61
77
|
});
|
|
62
78
|
const value = (React.createElement(React.Fragment, null,
|
|
63
79
|
series.name,
|
|
@@ -70,13 +86,14 @@ export const DefaultContent = ({ hovered, xAxis, yAxis, valueFormat }) => {
|
|
|
70
86
|
case 'waterfall': {
|
|
71
87
|
const isTotal = get(data, 'total', false);
|
|
72
88
|
const subTotalValue = getWaterfallPointSubtotal(data, series);
|
|
89
|
+
const format = valueFormat !== null && valueFormat !== void 0 ? valueFormat : getDefaultValueFormat({ axis: yAxis });
|
|
73
90
|
const subTotal = getFormattedValue({
|
|
74
91
|
value: subTotalValue,
|
|
75
|
-
format
|
|
92
|
+
format,
|
|
76
93
|
});
|
|
77
94
|
const formattedValue = getFormattedValue({
|
|
78
95
|
value: getYRowData(data, yAxis),
|
|
79
|
-
format
|
|
96
|
+
format,
|
|
80
97
|
});
|
|
81
98
|
return (React.createElement("div", { key: `${id}_${get(data, 'x')}` },
|
|
82
99
|
!isTotal && (React.createElement(React.Fragment, null,
|
|
@@ -93,9 +110,10 @@ export const DefaultContent = ({ hovered, xAxis, yAxis, valueFormat }) => {
|
|
|
93
110
|
subTotal)));
|
|
94
111
|
}
|
|
95
112
|
case 'bar-y': {
|
|
113
|
+
const format = valueFormat !== null && valueFormat !== void 0 ? valueFormat : getDefaultValueFormat({ axis: xAxis });
|
|
96
114
|
const formattedValue = getFormattedValue({
|
|
97
115
|
value: getXRowData(data, xAxis),
|
|
98
|
-
format
|
|
116
|
+
format,
|
|
99
117
|
});
|
|
100
118
|
const value = (React.createElement(React.Fragment, null,
|
|
101
119
|
series.name,
|
|
@@ -110,7 +128,7 @@ export const DefaultContent = ({ hovered, xAxis, yAxis, valueFormat }) => {
|
|
|
110
128
|
const seriesData = data;
|
|
111
129
|
const formattedValue = getFormattedValue({
|
|
112
130
|
value: seriesData.value,
|
|
113
|
-
format: valueFormat,
|
|
131
|
+
format: valueFormat !== null && valueFormat !== void 0 ? valueFormat : { type: 'number' },
|
|
114
132
|
});
|
|
115
133
|
return (React.createElement("div", { key: id, className: b('content-row') },
|
|
116
134
|
React.createElement("div", { className: b('color'), style: { backgroundColor: color } }),
|
|
@@ -124,7 +142,7 @@ export const DefaultContent = ({ hovered, xAxis, yAxis, valueFormat }) => {
|
|
|
124
142
|
const value = (_a = source.links.find((d) => d.name === (target === null || target === void 0 ? void 0 : target.name))) === null || _a === void 0 ? void 0 : _a.value;
|
|
125
143
|
const formattedValue = getFormattedValue({
|
|
126
144
|
value,
|
|
127
|
-
format: valueFormat,
|
|
145
|
+
format: valueFormat !== null && valueFormat !== void 0 ? valueFormat : { type: 'number' },
|
|
128
146
|
});
|
|
129
147
|
return (React.createElement("div", { key: id, className: b('content-row') },
|
|
130
148
|
React.createElement("div", { className: b('color'), style: { backgroundColor: source.color } }),
|
|
@@ -142,7 +160,7 @@ export const DefaultContent = ({ hovered, xAxis, yAxis, valueFormat }) => {
|
|
|
142
160
|
const seriesData = data;
|
|
143
161
|
const formattedValue = getFormattedValue({
|
|
144
162
|
value: seriesData.value,
|
|
145
|
-
format: valueFormat,
|
|
163
|
+
format: valueFormat !== null && valueFormat !== void 0 ? valueFormat : { type: 'number' },
|
|
146
164
|
});
|
|
147
165
|
const value = (React.createElement(React.Fragment, null,
|
|
148
166
|
React.createElement("span", null,
|
|
@@ -8,6 +8,9 @@ const getBottomOffset = (args) => {
|
|
|
8
8
|
if (preparedLegend.enabled) {
|
|
9
9
|
result += preparedLegend.height + preparedLegend.margin;
|
|
10
10
|
}
|
|
11
|
+
if (!preparedXAxis.visible) {
|
|
12
|
+
return result;
|
|
13
|
+
}
|
|
11
14
|
if (hasAxisRelatedSeries) {
|
|
12
15
|
if (preparedXAxis.title.text) {
|
|
13
16
|
result += preparedXAxis.title.height + preparedXAxis.title.margin;
|
|
@@ -6,6 +6,9 @@ export const getBoundsWidth = (args) => {
|
|
|
6
6
|
getWidthOccupiedByYAxis({ preparedAxis: preparedYAxis }));
|
|
7
7
|
};
|
|
8
8
|
export function getYAxisWidth(axis) {
|
|
9
|
+
if (!(axis === null || axis === void 0 ? void 0 : axis.visible)) {
|
|
10
|
+
return 0;
|
|
11
|
+
}
|
|
9
12
|
let result = 0;
|
|
10
13
|
if (axis === null || axis === void 0 ? void 0 : axis.title.text) {
|
|
11
14
|
result += axis.title.height + axis.title.margin;
|
|
@@ -110,6 +110,7 @@ export const getPreparedXAxis = ({ xAxis, series, width, }) => {
|
|
|
110
110
|
opacity: get(d, 'opacity', 1),
|
|
111
111
|
layerPlacement: get(d, 'layerPlacement', 'before'),
|
|
112
112
|
})),
|
|
113
|
+
visible: get(xAxis, 'visible', true),
|
|
113
114
|
};
|
|
114
115
|
const { height, rotation } = getLabelSettings({
|
|
115
116
|
axis: preparedXAxis,
|
|
@@ -116,6 +116,7 @@ export const getPreparedYAxis = ({ series, yAxis, height, }) => {
|
|
|
116
116
|
opacity: get(d, 'opacity', 1),
|
|
117
117
|
layerPlacement: get(d, 'layerPlacement', 'before'),
|
|
118
118
|
})),
|
|
119
|
+
visible: get(axisItem, 'visible', true),
|
|
119
120
|
};
|
|
120
121
|
if (labelsEnabled) {
|
|
121
122
|
preparedAxis.labels.width = getAxisLabelMaxWidth({ axis: preparedAxis, series });
|
|
@@ -4,35 +4,47 @@ import { getUniqId } from '../../utils';
|
|
|
4
4
|
import { DEFAULT_DATALABELS_PADDING, DEFAULT_DATALABELS_STYLE } from './constants';
|
|
5
5
|
import { prepareLegendSymbol } from './utils';
|
|
6
6
|
export function prepareWaterfallSeries(args) {
|
|
7
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
7
8
|
const { colorScale, series: seriesList, legend } = args;
|
|
8
9
|
const [, negativeColor, positiveColor] = DEFAULT_PALETTE;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
dataLabels:
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
10
|
+
const series = seriesList[0];
|
|
11
|
+
const common = {
|
|
12
|
+
id: '',
|
|
13
|
+
color: seriesList[0].color || colorScale(seriesList[0].name),
|
|
14
|
+
type: series.type,
|
|
15
|
+
name: series.name,
|
|
16
|
+
visible: get(series, 'visible', true),
|
|
17
|
+
legend: {
|
|
18
|
+
enabled: get(series, 'legend.enabled', legend.enabled),
|
|
19
|
+
symbol: prepareLegendSymbol(series),
|
|
20
|
+
},
|
|
21
|
+
dataLabels: {
|
|
22
|
+
enabled: ((_a = series.dataLabels) === null || _a === void 0 ? void 0 : _a.enabled) || true,
|
|
23
|
+
style: Object.assign({}, DEFAULT_DATALABELS_STYLE, (_b = series.dataLabels) === null || _b === void 0 ? void 0 : _b.style),
|
|
24
|
+
allowOverlap: ((_c = series.dataLabels) === null || _c === void 0 ? void 0 : _c.allowOverlap) || false,
|
|
25
|
+
padding: get(series, 'dataLabels.padding', DEFAULT_DATALABELS_PADDING),
|
|
26
|
+
html: get(series, 'dataLabels.html', false),
|
|
27
|
+
format: (_d = series.dataLabels) === null || _d === void 0 ? void 0 : _d.format,
|
|
28
|
+
},
|
|
29
|
+
cursor: get(series, 'cursor', null),
|
|
30
|
+
data: [],
|
|
31
|
+
};
|
|
32
|
+
const positive = Object.assign(Object.assign({}, common), { name: (_g = (_f = (_e = series.legend) === null || _e === void 0 ? void 0 : _e.itemText) === null || _f === void 0 ? void 0 : _f.positive) !== null && _g !== void 0 ? _g : `${series.name} ↑`, id: getUniqId(), color: series.positiveColor || positiveColor, data: [] });
|
|
33
|
+
const negative = Object.assign(Object.assign({}, common), { name: (_k = (_j = (_h = series.legend) === null || _h === void 0 ? void 0 : _h.itemText) === null || _j === void 0 ? void 0 : _j.negative) !== null && _k !== void 0 ? _k : `${series.name} ↓`, id: getUniqId(), color: series.negativeColor || negativeColor, data: [] });
|
|
34
|
+
const totals = Object.assign(Object.assign({}, common), { name: (_o = (_m = (_l = series.legend) === null || _l === void 0 ? void 0 : _l.itemText) === null || _m === void 0 ? void 0 : _m.totals) !== null && _o !== void 0 ? _o : series.name, id: getUniqId(), data: [] });
|
|
35
|
+
series.data.forEach((d, index) => {
|
|
36
|
+
var _a;
|
|
37
|
+
const value = (_a = d === null || d === void 0 ? void 0 : d.y) !== null && _a !== void 0 ? _a : 0;
|
|
38
|
+
const dataItem = Object.assign(Object.assign({}, d), { index });
|
|
39
|
+
if (d === null || d === void 0 ? void 0 : d.total) {
|
|
40
|
+
totals.data.push(dataItem);
|
|
41
|
+
}
|
|
42
|
+
else if (value > 0) {
|
|
43
|
+
positive.data.push(dataItem);
|
|
44
|
+
}
|
|
45
|
+
else if (value < 0) {
|
|
46
|
+
negative.data.push(dataItem);
|
|
47
|
+
}
|
|
37
48
|
}, []);
|
|
49
|
+
return [positive, negative, totals];
|
|
38
50
|
}
|
|
@@ -243,9 +243,12 @@ export type PreparedTreemapSeries = {
|
|
|
243
243
|
};
|
|
244
244
|
layoutAlgorithm: `${LayoutAlgorithm}`;
|
|
245
245
|
} & BasePreparedSeries & Omit<TreemapSeries, keyof BasePreparedSeries>;
|
|
246
|
+
export type PreparedWaterfallSeriesData = WaterfallSeriesData & {
|
|
247
|
+
index: number;
|
|
248
|
+
};
|
|
246
249
|
export type PreparedWaterfallSeries = {
|
|
247
250
|
type: WaterfallSeries['type'];
|
|
248
|
-
data:
|
|
251
|
+
data: PreparedWaterfallSeriesData[];
|
|
249
252
|
dataLabels: {
|
|
250
253
|
enabled: boolean;
|
|
251
254
|
style: BaseTextStyle;
|
|
@@ -254,8 +257,6 @@ export type PreparedWaterfallSeries = {
|
|
|
254
257
|
html: boolean;
|
|
255
258
|
format?: ValueFormat;
|
|
256
259
|
};
|
|
257
|
-
positiveColor: string;
|
|
258
|
-
negativeColor: string;
|
|
259
260
|
} & BasePreparedSeries;
|
|
260
261
|
export type PreparedSankeySeries = {
|
|
261
262
|
type: SankeySeries['type'];
|
|
@@ -5,10 +5,12 @@ import { getFormattedValue } from '../../../utils/chart/format';
|
|
|
5
5
|
import { MIN_BAR_GAP, MIN_BAR_WIDTH } from '../constants';
|
|
6
6
|
import { getXValue, getYValue } from '../utils';
|
|
7
7
|
function getLabelData(d, plotHeight) {
|
|
8
|
+
var _a, _b;
|
|
8
9
|
if (!d.series.dataLabels.enabled) {
|
|
9
10
|
return undefined;
|
|
10
11
|
}
|
|
11
|
-
const
|
|
12
|
+
const labelValue = (_b = (_a = d.data.label) !== null && _a !== void 0 ? _a : d.data.y) !== null && _b !== void 0 ? _b : d.subTotal;
|
|
13
|
+
const text = getFormattedValue(Object.assign({ value: labelValue }, d.series.dataLabels));
|
|
12
14
|
const style = d.series.dataLabels.style;
|
|
13
15
|
const { maxHeight: height, maxWidth: width } = getLabelsSize({ labels: [text], style });
|
|
14
16
|
let y;
|
|
@@ -60,7 +62,7 @@ export const prepareWaterfallData = (args) => {
|
|
|
60
62
|
acc.push(...s.data.map((d) => ({ data: d, series: s })));
|
|
61
63
|
return acc;
|
|
62
64
|
}, []);
|
|
63
|
-
const data = sortBy(flattenData, (d) => d.data.
|
|
65
|
+
const data = sortBy(flattenData, (d) => d.data.index);
|
|
64
66
|
const bandWidth = getBandWidth({
|
|
65
67
|
series,
|
|
66
68
|
xAxis,
|
|
@@ -75,6 +75,8 @@ 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
|
+
/** Whether axis, including axis title, line, ticks and labels, should be visible. */
|
|
79
|
+
visible?: boolean;
|
|
78
80
|
}
|
|
79
81
|
export interface ChartXAxis extends ChartAxis {
|
|
80
82
|
}
|
|
@@ -36,5 +36,14 @@ export interface WaterfallSeries<T = MeaningfulAny> extends BaseSeries {
|
|
|
36
36
|
/** Individual series legend options. Has higher priority than legend options in widget data. */
|
|
37
37
|
legend?: ChartLegend & {
|
|
38
38
|
symbol?: RectLegendSymbolOptions;
|
|
39
|
+
/** The legend item text for positive, negative values and totals. */
|
|
40
|
+
itemText?: {
|
|
41
|
+
/** The legend item text for positive values. */
|
|
42
|
+
positive?: string;
|
|
43
|
+
/** The legend item text for negative values. */
|
|
44
|
+
negative?: string;
|
|
45
|
+
/** The legend item text for totals. */
|
|
46
|
+
totals?: string;
|
|
47
|
+
};
|
|
39
48
|
};
|
|
40
49
|
}
|
|
@@ -16,7 +16,7 @@ function addDomain(selection, options) {
|
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
export function axisBottom(args) {
|
|
19
|
-
const { scale, ticks: { labelFormat = (value) => String(value), labelsPaddings = 0, labelsMargin = 0, labelsMaxWidth = Infinity, labelsStyle, labelsLineHeight, items: tickItems, count: ticksCount, maxTickCount, rotation = 0, tickColor, }, domain, } = args;
|
|
19
|
+
const { leftmostLimit = 0, scale, ticks: { labelFormat = (value) => String(value), labelsPaddings = 0, labelsMargin = 0, labelsMaxWidth = Infinity, labelsStyle, labelsLineHeight, items: tickItems, count: ticksCount, maxTickCount, rotation = 0, tickColor, }, domain, } = args;
|
|
20
20
|
const offset = getXAxisOffset();
|
|
21
21
|
const position = getXTickPosition({ scale, offset });
|
|
22
22
|
const values = getXAxisItems({ scale, count: ticksCount, maxCount: maxTickCount });
|
|
@@ -103,6 +103,21 @@ export function axisBottom(args) {
|
|
|
103
103
|
.remove();
|
|
104
104
|
// add an ellipsis to the labels that go beyond the boundaries of the chart
|
|
105
105
|
labels.each(function (_d, i, nodes) {
|
|
106
|
+
if (i === 0) {
|
|
107
|
+
const currentElement = this;
|
|
108
|
+
const text = select(currentElement);
|
|
109
|
+
const currentElementPosition = currentElement.getBoundingClientRect();
|
|
110
|
+
const nextElement = nodes[i + 1];
|
|
111
|
+
const nextElementPosition = nextElement === null || nextElement === void 0 ? void 0 : nextElement.getBoundingClientRect();
|
|
112
|
+
if (currentElementPosition.left < leftmostLimit) {
|
|
113
|
+
const remainSpace = nextElementPosition.left -
|
|
114
|
+
currentElementPosition.right +
|
|
115
|
+
x -
|
|
116
|
+
labelsMargin;
|
|
117
|
+
text.attr('text-anchor', 'start');
|
|
118
|
+
setEllipsisForOverflowText(text, remainSpace);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
106
121
|
if (i === nodes.length - 1) {
|
|
107
122
|
const currentElement = this;
|
|
108
123
|
const prevElement = nodes[i - 1];
|