@gravity-ui/charts 1.11.2 → 1.11.3
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/AxisY.d.ts +1 -0
- package/dist/cjs/components/Axis/AxisY.js +14 -13
- package/dist/cjs/components/ChartInner/index.js +2 -2
- package/dist/cjs/components/ChartInner/useChartInnerProps.d.ts +1 -0
- package/dist/cjs/components/ChartInner/useChartInnerProps.js +9 -2
- package/dist/cjs/components/Legend/index.js +6 -2
- package/dist/cjs/hooks/useChartOptions/index.d.ts +6 -2
- package/dist/cjs/hooks/useChartOptions/index.js +4 -4
- package/dist/cjs/hooks/useSeries/prepare-legend.js +5 -1
- package/dist/cjs/hooks/useSeries/types.d.ts +1 -0
- package/dist/cjs/utils/chart/axis-generators/bottom.js +21 -28
- package/dist/esm/components/Axis/AxisY.d.ts +1 -0
- package/dist/esm/components/Axis/AxisY.js +14 -13
- package/dist/esm/components/ChartInner/index.js +2 -2
- package/dist/esm/components/ChartInner/useChartInnerProps.d.ts +1 -0
- package/dist/esm/components/ChartInner/useChartInnerProps.js +9 -2
- package/dist/esm/components/Legend/index.js +6 -2
- package/dist/esm/hooks/useChartOptions/index.d.ts +6 -2
- package/dist/esm/hooks/useChartOptions/index.js +4 -4
- package/dist/esm/hooks/useSeries/prepare-legend.js +5 -1
- package/dist/esm/hooks/useSeries/types.d.ts +1 -0
- package/dist/esm/utils/chart/axis-generators/bottom.js +21 -28
- package/package.json +1 -1
|
@@ -10,6 +10,7 @@ type Props = {
|
|
|
10
10
|
plotBeforeRef?: React.MutableRefObject<SVGGElement | null>;
|
|
11
11
|
plotAfterRef?: React.MutableRefObject<SVGGElement | null>;
|
|
12
12
|
bottomLimit?: number;
|
|
13
|
+
topLimit?: number;
|
|
13
14
|
};
|
|
14
15
|
export declare const AxisY: (props: Props) => React.JSX.Element;
|
|
15
16
|
export {};
|
|
@@ -4,11 +4,8 @@ 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,
|
|
8
|
-
let topOffset = axis.labels.lineHeight / 2;
|
|
9
|
-
if (isTopOffsetOverload) {
|
|
10
|
-
topOffset = 0;
|
|
11
|
-
}
|
|
7
|
+
const { node, axis, startTopOffset } = args;
|
|
8
|
+
let topOffset = startTopOffset !== null && startTopOffset !== void 0 ? startTopOffset : axis.labels.lineHeight / 2;
|
|
12
9
|
let leftOffset = axis.labels.margin;
|
|
13
10
|
if (axis.position === 'left') {
|
|
14
11
|
leftOffset = leftOffset * -1;
|
|
@@ -85,7 +82,7 @@ function getTitlePosition(args) {
|
|
|
85
82
|
return { x, y };
|
|
86
83
|
}
|
|
87
84
|
export const AxisY = (props) => {
|
|
88
|
-
const { axes: allAxes, width, height: totalHeight, scale, split, plotBeforeRef, plotAfterRef, bottomLimit = 0, } = props;
|
|
85
|
+
const { axes: allAxes, width, height: totalHeight, scale, split, plotBeforeRef, plotAfterRef, bottomLimit = 0, topLimit = 0, } = props;
|
|
89
86
|
const height = getAxisHeight({ split, boundsHeight: totalHeight });
|
|
90
87
|
const ref = React.useRef(null);
|
|
91
88
|
const lineGenerator = line();
|
|
@@ -140,16 +137,20 @@ export const AxisY = (props) => {
|
|
|
140
137
|
.style('transform', function () {
|
|
141
138
|
return transformLabel({ node: this, axis: d });
|
|
142
139
|
});
|
|
143
|
-
labels.each(function (_d, i) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
const
|
|
148
|
-
|
|
140
|
+
labels.each(function (_d, i, nodes) {
|
|
141
|
+
const isFirstNode = i === 0;
|
|
142
|
+
const isLastNode = i === nodes.length - 1;
|
|
143
|
+
if (isFirstNode || isLastNode) {
|
|
144
|
+
const labelNode = this;
|
|
145
|
+
const labelNodeRect = labelNode.getBoundingClientRect();
|
|
146
|
+
const shouldBeTransformed = (isFirstNode && labelNodeRect.bottom > bottomLimit) ||
|
|
147
|
+
(isLastNode && labelNodeRect.top < topLimit);
|
|
148
|
+
if (shouldBeTransformed) {
|
|
149
|
+
const text = select(labelNode);
|
|
149
150
|
const transform = transformLabel({
|
|
150
151
|
node: this,
|
|
151
152
|
axis: d,
|
|
152
|
-
|
|
153
|
+
startTopOffset: isLastNode ? labelNodeRect.height : 0,
|
|
153
154
|
});
|
|
154
155
|
text.style('transform', transform);
|
|
155
156
|
if (d.labels.rotation) {
|
|
@@ -23,7 +23,7 @@ export const ChartInner = (props) => {
|
|
|
23
23
|
const plotAfterRef = React.useRef(null);
|
|
24
24
|
const dispatcher = React.useMemo(() => getDispatcher(), []);
|
|
25
25
|
const clipPathId = useUniqId();
|
|
26
|
-
const { boundsHeight, boundsOffsetLeft, boundsOffsetTop, boundsWidth, handleLegendItemClick, handleZoomReset, isOutsideBounds, legendConfig, legendItems, preparedSeries, preparedSplit, preparedLegend, prevHeight, prevWidth, shapes, shapesData, title, tooltip, xAxis, xScale, yAxis, yScale, svgXPos, svgBottomPos, } = useChartInnerProps(Object.assign(Object.assign({}, props), { dispatcher,
|
|
26
|
+
const { boundsHeight, boundsOffsetLeft, boundsOffsetTop, boundsWidth, handleLegendItemClick, handleZoomReset, isOutsideBounds, legendConfig, legendItems, preparedSeries, preparedSplit, preparedLegend, prevHeight, prevWidth, shapes, shapesData, title, tooltip, xAxis, xScale, yAxis, yScale, svgXPos, svgBottomPos, svgTopPos, } = useChartInnerProps(Object.assign(Object.assign({}, props), { dispatcher,
|
|
27
27
|
htmlLayout, svgContainer: svgRef.current, plotNode: plotRef.current, clipPathId }));
|
|
28
28
|
const { tooltipPinned, togglePinTooltip, unpinTooltip } = useChartInnerState({
|
|
29
29
|
dispatcher,
|
|
@@ -94,7 +94,7 @@ 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, { bottomLimit: svgBottomPos, axes: yAxis, width: boundsWidth, height: boundsHeight, scale: yScale, split: preparedSplit, plotBeforeRef: plotBeforeRef, plotAfterRef: plotAfterRef }),
|
|
97
|
+
React.createElement(AxisY, { bottomLimit: svgBottomPos, topLimit: svgTopPos, axes: yAxis, width: boundsWidth, height: boundsHeight, scale: yScale, split: preparedSplit, plotBeforeRef: plotBeforeRef, plotAfterRef: plotAfterRef }),
|
|
98
98
|
xAxis && (React.createElement("g", { transform: `translate(0, ${boundsHeight})` },
|
|
99
99
|
React.createElement(AxisX, { leftmostLimit: svgXPos, axis: xAxis, width: boundsWidth, height: boundsHeight, scale: xScale, split: preparedSplit, plotBeforeRef: plotBeforeRef, plotAfterRef: plotAfterRef }))))),
|
|
100
100
|
React.createElement("g", { ref: plotBeforeRef }),
|
|
@@ -13,8 +13,14 @@ export function useChartInnerProps(props) {
|
|
|
13
13
|
const { width, height, data, dispatcher, htmlLayout, svgContainer, plotNode, clipPathId } = props;
|
|
14
14
|
const prevWidth = usePrevious(width);
|
|
15
15
|
const prevHeight = usePrevious(height);
|
|
16
|
+
const { chart, title, tooltip, colors } = useChartOptions({
|
|
17
|
+
seriesData: data.series.data,
|
|
18
|
+
chart: data.chart,
|
|
19
|
+
colors: data.colors,
|
|
20
|
+
title: data.title,
|
|
21
|
+
tooltip: data.tooltip,
|
|
22
|
+
});
|
|
16
23
|
const [zoomState, setZoomState] = React.useState({});
|
|
17
|
-
const { chart, title, tooltip, colors } = useChartOptions({ data });
|
|
18
24
|
const sortedSeriesData = React.useMemo(() => {
|
|
19
25
|
return getSortedSeriesData(data.series.data);
|
|
20
26
|
}, [data.series.data]);
|
|
@@ -141,12 +147,13 @@ export function useChartInnerProps(props) {
|
|
|
141
147
|
}
|
|
142
148
|
return acc;
|
|
143
149
|
}, 0);
|
|
144
|
-
const {
|
|
150
|
+
const { bottom, top, x } = (_a = svgContainer === null || svgContainer === void 0 ? void 0 : svgContainer.getBoundingClientRect()) !== null && _a !== void 0 ? _a : {};
|
|
145
151
|
const handleZoomReset = React.useCallback(() => {
|
|
146
152
|
setZoomState({});
|
|
147
153
|
}, []);
|
|
148
154
|
return {
|
|
149
155
|
svgBottomPos: bottom,
|
|
156
|
+
svgTopPos: top,
|
|
150
157
|
svgXPos: x,
|
|
151
158
|
boundsHeight,
|
|
152
159
|
boundsOffsetLeft,
|
|
@@ -2,7 +2,7 @@ import React from 'react';
|
|
|
2
2
|
import { line as lineGenerator, scaleLinear, select, symbol } from 'd3';
|
|
3
3
|
import { CONTINUOUS_LEGEND_SIZE } from '../../constants';
|
|
4
4
|
import { formatNumber } from '../../libs';
|
|
5
|
-
import { block, createGradientRect, getContinuesColorFn, getLabelsSize, getLineDashArray, getSymbol, handleOverflowingText, } from '../../utils';
|
|
5
|
+
import { block, createGradientRect, getContinuesColorFn, getLabelsSize, getLineDashArray, getSymbol, getUniqId, handleOverflowingText, } from '../../utils';
|
|
6
6
|
import { axisBottom } from '../../utils/chart/axis-generators';
|
|
7
7
|
import './styles.css';
|
|
8
8
|
const b = block('legend');
|
|
@@ -305,6 +305,7 @@ export const Legend = (props) => {
|
|
|
305
305
|
maxTickCount: 4,
|
|
306
306
|
tickColor: '#fff',
|
|
307
307
|
labelFormat: (value) => formatNumber(value, { unit: 'auto' }),
|
|
308
|
+
labelsStyle: legend.ticks.style,
|
|
308
309
|
},
|
|
309
310
|
domain: {
|
|
310
311
|
size: legend.width,
|
|
@@ -381,5 +382,8 @@ export const Legend = (props) => {
|
|
|
381
382
|
pageIndex,
|
|
382
383
|
htmlLayout,
|
|
383
384
|
]);
|
|
384
|
-
|
|
385
|
+
// due to asynchronous processing, we only need to work with the actual element
|
|
386
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
387
|
+
const key = React.useMemo(() => getUniqId(), [legend, config]);
|
|
388
|
+
return React.createElement("g", { key: key, className: b(), ref: ref, width: boundsWidth, height: legend.height });
|
|
385
389
|
};
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ChartSeries, ChartTitle, ChartTooltip, ChartOptions as GeneralChartOptions } from '../../types';
|
|
2
2
|
import type { ChartOptions } from './types';
|
|
3
3
|
type Args = {
|
|
4
|
-
|
|
4
|
+
seriesData: ChartSeries[];
|
|
5
|
+
chart?: GeneralChartOptions;
|
|
6
|
+
colors?: string[];
|
|
7
|
+
title?: ChartTitle;
|
|
8
|
+
tooltip?: ChartTooltip;
|
|
5
9
|
};
|
|
6
10
|
export declare const useChartOptions: (args: Args) => ChartOptions;
|
|
7
11
|
export {};
|
|
@@ -4,21 +4,21 @@ import { getPreparedChart } from './chart';
|
|
|
4
4
|
import { getPreparedTitle } from './title';
|
|
5
5
|
import { getPreparedTooltip } from './tooltip';
|
|
6
6
|
export const useChartOptions = (args) => {
|
|
7
|
-
const {
|
|
7
|
+
const { chart, colors, seriesData, title, tooltip } = args;
|
|
8
8
|
const options = React.useMemo(() => {
|
|
9
9
|
const preparedTitle = getPreparedTitle({ title });
|
|
10
10
|
const preparedTooltip = getPreparedTooltip({ tooltip });
|
|
11
11
|
const preparedChart = getPreparedChart({
|
|
12
12
|
chart,
|
|
13
13
|
preparedTitle,
|
|
14
|
-
seriesData
|
|
14
|
+
seriesData,
|
|
15
15
|
});
|
|
16
16
|
return {
|
|
17
|
+
colors: colors !== null && colors !== void 0 ? colors : DEFAULT_PALETTE,
|
|
17
18
|
chart: preparedChart,
|
|
18
19
|
title: preparedTitle,
|
|
19
20
|
tooltip: preparedTooltip,
|
|
20
|
-
colors: colors !== null && colors !== void 0 ? colors : DEFAULT_PALETTE,
|
|
21
21
|
};
|
|
22
|
-
}, [chart, colors, title, tooltip
|
|
22
|
+
}, [chart, colors, seriesData, title, tooltip]);
|
|
23
23
|
return options;
|
|
24
24
|
};
|
|
@@ -21,9 +21,13 @@ export async function getPreparedLegend(args) {
|
|
|
21
21
|
const titleText = isTitleEnabled ? get(legend, 'title.text', '') : '';
|
|
22
22
|
const titleSize = await getLabelsSize({ labels: [titleText], style: titleStyle });
|
|
23
23
|
const titleHeight = isTitleEnabled ? titleSize.maxHeight : 0;
|
|
24
|
+
const tickStyle = {
|
|
25
|
+
fontSize: '12px',
|
|
26
|
+
};
|
|
24
27
|
const ticks = {
|
|
25
28
|
labelsMargin: 4,
|
|
26
|
-
labelsLineHeight:
|
|
29
|
+
labelsLineHeight: (await getLabelsSize({ labels: ['Tmp'], style: tickStyle })).maxHeight,
|
|
30
|
+
style: tickStyle,
|
|
27
31
|
};
|
|
28
32
|
const colorScale = {
|
|
29
33
|
colors: [],
|
|
@@ -90,27 +90,15 @@ export async function axisBottom(args) {
|
|
|
90
90
|
});
|
|
91
91
|
}
|
|
92
92
|
else {
|
|
93
|
-
// remove overlapping labels
|
|
94
93
|
let elementX = 0;
|
|
95
|
-
selection
|
|
96
|
-
.selectAll('.tick')
|
|
97
|
-
.filter(function () {
|
|
98
|
-
const node = this;
|
|
99
|
-
const r = node.getBoundingClientRect();
|
|
100
|
-
if (r.left < elementX) {
|
|
101
|
-
return true;
|
|
102
|
-
}
|
|
103
|
-
elementX = r.right + labelsPaddings;
|
|
104
|
-
return false;
|
|
105
|
-
})
|
|
106
|
-
.remove();
|
|
107
94
|
// add an ellipsis to the labels that go beyond the boundaries of the chart
|
|
95
|
+
// and remove overlapping labels
|
|
108
96
|
labels.each(function (_d, i, nodes) {
|
|
109
|
-
var _a;
|
|
97
|
+
var _a, _b;
|
|
98
|
+
const currentElement = this;
|
|
99
|
+
const currentElementPosition = currentElement.getBoundingClientRect();
|
|
110
100
|
if (i === 0) {
|
|
111
|
-
const currentElement = this;
|
|
112
101
|
const text = select(currentElement);
|
|
113
|
-
const currentElementPosition = currentElement.getBoundingClientRect();
|
|
114
102
|
const nextElement = nodes[i + 1];
|
|
115
103
|
const nextElementPosition = nextElement === null || nextElement === void 0 ? void 0 : nextElement.getBoundingClientRect();
|
|
116
104
|
if (currentElementPosition.left < leftmostLimit) {
|
|
@@ -123,18 +111,23 @@ export async function axisBottom(args) {
|
|
|
123
111
|
setEllipsisForOverflowText(text, remainSpace);
|
|
124
112
|
}
|
|
125
113
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
114
|
+
else {
|
|
115
|
+
if (currentElementPosition.left < elementX) {
|
|
116
|
+
(_b = currentElement.closest('.tick')) === null || _b === void 0 ? void 0 : _b.remove();
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
elementX = currentElementPosition.right + labelsPaddings;
|
|
120
|
+
if (i === nodes.length - 1) {
|
|
121
|
+
const prevElement = nodes[i - 1];
|
|
122
|
+
const text = select(currentElement);
|
|
123
|
+
const prevElementPosition = prevElement === null || prevElement === void 0 ? void 0 : prevElement.getBoundingClientRect();
|
|
124
|
+
const lackingSpace = Math.max(0, currentElementPosition.right - right);
|
|
125
|
+
if (lackingSpace) {
|
|
126
|
+
const remainSpace = right - ((prevElementPosition === null || prevElementPosition === void 0 ? void 0 : prevElementPosition.right) || 0) - labelsPaddings;
|
|
127
|
+
const translateX = -lackingSpace;
|
|
128
|
+
text.style('transform', `translate(${translateX}px,${translateY}px)`);
|
|
129
|
+
setEllipsisForOverflowText(text, remainSpace);
|
|
130
|
+
}
|
|
138
131
|
}
|
|
139
132
|
}
|
|
140
133
|
});
|
|
@@ -10,6 +10,7 @@ type Props = {
|
|
|
10
10
|
plotBeforeRef?: React.MutableRefObject<SVGGElement | null>;
|
|
11
11
|
plotAfterRef?: React.MutableRefObject<SVGGElement | null>;
|
|
12
12
|
bottomLimit?: number;
|
|
13
|
+
topLimit?: number;
|
|
13
14
|
};
|
|
14
15
|
export declare const AxisY: (props: Props) => React.JSX.Element;
|
|
15
16
|
export {};
|
|
@@ -4,11 +4,8 @@ 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,
|
|
8
|
-
let topOffset = axis.labels.lineHeight / 2;
|
|
9
|
-
if (isTopOffsetOverload) {
|
|
10
|
-
topOffset = 0;
|
|
11
|
-
}
|
|
7
|
+
const { node, axis, startTopOffset } = args;
|
|
8
|
+
let topOffset = startTopOffset !== null && startTopOffset !== void 0 ? startTopOffset : axis.labels.lineHeight / 2;
|
|
12
9
|
let leftOffset = axis.labels.margin;
|
|
13
10
|
if (axis.position === 'left') {
|
|
14
11
|
leftOffset = leftOffset * -1;
|
|
@@ -85,7 +82,7 @@ function getTitlePosition(args) {
|
|
|
85
82
|
return { x, y };
|
|
86
83
|
}
|
|
87
84
|
export const AxisY = (props) => {
|
|
88
|
-
const { axes: allAxes, width, height: totalHeight, scale, split, plotBeforeRef, plotAfterRef, bottomLimit = 0, } = props;
|
|
85
|
+
const { axes: allAxes, width, height: totalHeight, scale, split, plotBeforeRef, plotAfterRef, bottomLimit = 0, topLimit = 0, } = props;
|
|
89
86
|
const height = getAxisHeight({ split, boundsHeight: totalHeight });
|
|
90
87
|
const ref = React.useRef(null);
|
|
91
88
|
const lineGenerator = line();
|
|
@@ -140,16 +137,20 @@ export const AxisY = (props) => {
|
|
|
140
137
|
.style('transform', function () {
|
|
141
138
|
return transformLabel({ node: this, axis: d });
|
|
142
139
|
});
|
|
143
|
-
labels.each(function (_d, i) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
const
|
|
148
|
-
|
|
140
|
+
labels.each(function (_d, i, nodes) {
|
|
141
|
+
const isFirstNode = i === 0;
|
|
142
|
+
const isLastNode = i === nodes.length - 1;
|
|
143
|
+
if (isFirstNode || isLastNode) {
|
|
144
|
+
const labelNode = this;
|
|
145
|
+
const labelNodeRect = labelNode.getBoundingClientRect();
|
|
146
|
+
const shouldBeTransformed = (isFirstNode && labelNodeRect.bottom > bottomLimit) ||
|
|
147
|
+
(isLastNode && labelNodeRect.top < topLimit);
|
|
148
|
+
if (shouldBeTransformed) {
|
|
149
|
+
const text = select(labelNode);
|
|
149
150
|
const transform = transformLabel({
|
|
150
151
|
node: this,
|
|
151
152
|
axis: d,
|
|
152
|
-
|
|
153
|
+
startTopOffset: isLastNode ? labelNodeRect.height : 0,
|
|
153
154
|
});
|
|
154
155
|
text.style('transform', transform);
|
|
155
156
|
if (d.labels.rotation) {
|
|
@@ -23,7 +23,7 @@ export const ChartInner = (props) => {
|
|
|
23
23
|
const plotAfterRef = React.useRef(null);
|
|
24
24
|
const dispatcher = React.useMemo(() => getDispatcher(), []);
|
|
25
25
|
const clipPathId = useUniqId();
|
|
26
|
-
const { boundsHeight, boundsOffsetLeft, boundsOffsetTop, boundsWidth, handleLegendItemClick, handleZoomReset, isOutsideBounds, legendConfig, legendItems, preparedSeries, preparedSplit, preparedLegend, prevHeight, prevWidth, shapes, shapesData, title, tooltip, xAxis, xScale, yAxis, yScale, svgXPos, svgBottomPos, } = useChartInnerProps(Object.assign(Object.assign({}, props), { dispatcher,
|
|
26
|
+
const { boundsHeight, boundsOffsetLeft, boundsOffsetTop, boundsWidth, handleLegendItemClick, handleZoomReset, isOutsideBounds, legendConfig, legendItems, preparedSeries, preparedSplit, preparedLegend, prevHeight, prevWidth, shapes, shapesData, title, tooltip, xAxis, xScale, yAxis, yScale, svgXPos, svgBottomPos, svgTopPos, } = useChartInnerProps(Object.assign(Object.assign({}, props), { dispatcher,
|
|
27
27
|
htmlLayout, svgContainer: svgRef.current, plotNode: plotRef.current, clipPathId }));
|
|
28
28
|
const { tooltipPinned, togglePinTooltip, unpinTooltip } = useChartInnerState({
|
|
29
29
|
dispatcher,
|
|
@@ -94,7 +94,7 @@ 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, { bottomLimit: svgBottomPos, axes: yAxis, width: boundsWidth, height: boundsHeight, scale: yScale, split: preparedSplit, plotBeforeRef: plotBeforeRef, plotAfterRef: plotAfterRef }),
|
|
97
|
+
React.createElement(AxisY, { bottomLimit: svgBottomPos, topLimit: svgTopPos, axes: yAxis, width: boundsWidth, height: boundsHeight, scale: yScale, split: preparedSplit, plotBeforeRef: plotBeforeRef, plotAfterRef: plotAfterRef }),
|
|
98
98
|
xAxis && (React.createElement("g", { transform: `translate(0, ${boundsHeight})` },
|
|
99
99
|
React.createElement(AxisX, { leftmostLimit: svgXPos, axis: xAxis, width: boundsWidth, height: boundsHeight, scale: xScale, split: preparedSplit, plotBeforeRef: plotBeforeRef, plotAfterRef: plotAfterRef }))))),
|
|
100
100
|
React.createElement("g", { ref: plotBeforeRef }),
|
|
@@ -13,8 +13,14 @@ export function useChartInnerProps(props) {
|
|
|
13
13
|
const { width, height, data, dispatcher, htmlLayout, svgContainer, plotNode, clipPathId } = props;
|
|
14
14
|
const prevWidth = usePrevious(width);
|
|
15
15
|
const prevHeight = usePrevious(height);
|
|
16
|
+
const { chart, title, tooltip, colors } = useChartOptions({
|
|
17
|
+
seriesData: data.series.data,
|
|
18
|
+
chart: data.chart,
|
|
19
|
+
colors: data.colors,
|
|
20
|
+
title: data.title,
|
|
21
|
+
tooltip: data.tooltip,
|
|
22
|
+
});
|
|
16
23
|
const [zoomState, setZoomState] = React.useState({});
|
|
17
|
-
const { chart, title, tooltip, colors } = useChartOptions({ data });
|
|
18
24
|
const sortedSeriesData = React.useMemo(() => {
|
|
19
25
|
return getSortedSeriesData(data.series.data);
|
|
20
26
|
}, [data.series.data]);
|
|
@@ -141,12 +147,13 @@ export function useChartInnerProps(props) {
|
|
|
141
147
|
}
|
|
142
148
|
return acc;
|
|
143
149
|
}, 0);
|
|
144
|
-
const {
|
|
150
|
+
const { bottom, top, x } = (_a = svgContainer === null || svgContainer === void 0 ? void 0 : svgContainer.getBoundingClientRect()) !== null && _a !== void 0 ? _a : {};
|
|
145
151
|
const handleZoomReset = React.useCallback(() => {
|
|
146
152
|
setZoomState({});
|
|
147
153
|
}, []);
|
|
148
154
|
return {
|
|
149
155
|
svgBottomPos: bottom,
|
|
156
|
+
svgTopPos: top,
|
|
150
157
|
svgXPos: x,
|
|
151
158
|
boundsHeight,
|
|
152
159
|
boundsOffsetLeft,
|
|
@@ -2,7 +2,7 @@ import React from 'react';
|
|
|
2
2
|
import { line as lineGenerator, scaleLinear, select, symbol } from 'd3';
|
|
3
3
|
import { CONTINUOUS_LEGEND_SIZE } from '../../constants';
|
|
4
4
|
import { formatNumber } from '../../libs';
|
|
5
|
-
import { block, createGradientRect, getContinuesColorFn, getLabelsSize, getLineDashArray, getSymbol, handleOverflowingText, } from '../../utils';
|
|
5
|
+
import { block, createGradientRect, getContinuesColorFn, getLabelsSize, getLineDashArray, getSymbol, getUniqId, handleOverflowingText, } from '../../utils';
|
|
6
6
|
import { axisBottom } from '../../utils/chart/axis-generators';
|
|
7
7
|
import './styles.css';
|
|
8
8
|
const b = block('legend');
|
|
@@ -305,6 +305,7 @@ export const Legend = (props) => {
|
|
|
305
305
|
maxTickCount: 4,
|
|
306
306
|
tickColor: '#fff',
|
|
307
307
|
labelFormat: (value) => formatNumber(value, { unit: 'auto' }),
|
|
308
|
+
labelsStyle: legend.ticks.style,
|
|
308
309
|
},
|
|
309
310
|
domain: {
|
|
310
311
|
size: legend.width,
|
|
@@ -381,5 +382,8 @@ export const Legend = (props) => {
|
|
|
381
382
|
pageIndex,
|
|
382
383
|
htmlLayout,
|
|
383
384
|
]);
|
|
384
|
-
|
|
385
|
+
// due to asynchronous processing, we only need to work with the actual element
|
|
386
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
387
|
+
const key = React.useMemo(() => getUniqId(), [legend, config]);
|
|
388
|
+
return React.createElement("g", { key: key, className: b(), ref: ref, width: boundsWidth, height: legend.height });
|
|
385
389
|
};
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ChartSeries, ChartTitle, ChartTooltip, ChartOptions as GeneralChartOptions } from '../../types';
|
|
2
2
|
import type { ChartOptions } from './types';
|
|
3
3
|
type Args = {
|
|
4
|
-
|
|
4
|
+
seriesData: ChartSeries[];
|
|
5
|
+
chart?: GeneralChartOptions;
|
|
6
|
+
colors?: string[];
|
|
7
|
+
title?: ChartTitle;
|
|
8
|
+
tooltip?: ChartTooltip;
|
|
5
9
|
};
|
|
6
10
|
export declare const useChartOptions: (args: Args) => ChartOptions;
|
|
7
11
|
export {};
|
|
@@ -4,21 +4,21 @@ import { getPreparedChart } from './chart';
|
|
|
4
4
|
import { getPreparedTitle } from './title';
|
|
5
5
|
import { getPreparedTooltip } from './tooltip';
|
|
6
6
|
export const useChartOptions = (args) => {
|
|
7
|
-
const {
|
|
7
|
+
const { chart, colors, seriesData, title, tooltip } = args;
|
|
8
8
|
const options = React.useMemo(() => {
|
|
9
9
|
const preparedTitle = getPreparedTitle({ title });
|
|
10
10
|
const preparedTooltip = getPreparedTooltip({ tooltip });
|
|
11
11
|
const preparedChart = getPreparedChart({
|
|
12
12
|
chart,
|
|
13
13
|
preparedTitle,
|
|
14
|
-
seriesData
|
|
14
|
+
seriesData,
|
|
15
15
|
});
|
|
16
16
|
return {
|
|
17
|
+
colors: colors !== null && colors !== void 0 ? colors : DEFAULT_PALETTE,
|
|
17
18
|
chart: preparedChart,
|
|
18
19
|
title: preparedTitle,
|
|
19
20
|
tooltip: preparedTooltip,
|
|
20
|
-
colors: colors !== null && colors !== void 0 ? colors : DEFAULT_PALETTE,
|
|
21
21
|
};
|
|
22
|
-
}, [chart, colors, title, tooltip
|
|
22
|
+
}, [chart, colors, seriesData, title, tooltip]);
|
|
23
23
|
return options;
|
|
24
24
|
};
|
|
@@ -21,9 +21,13 @@ export async function getPreparedLegend(args) {
|
|
|
21
21
|
const titleText = isTitleEnabled ? get(legend, 'title.text', '') : '';
|
|
22
22
|
const titleSize = await getLabelsSize({ labels: [titleText], style: titleStyle });
|
|
23
23
|
const titleHeight = isTitleEnabled ? titleSize.maxHeight : 0;
|
|
24
|
+
const tickStyle = {
|
|
25
|
+
fontSize: '12px',
|
|
26
|
+
};
|
|
24
27
|
const ticks = {
|
|
25
28
|
labelsMargin: 4,
|
|
26
|
-
labelsLineHeight:
|
|
29
|
+
labelsLineHeight: (await getLabelsSize({ labels: ['Tmp'], style: tickStyle })).maxHeight,
|
|
30
|
+
style: tickStyle,
|
|
27
31
|
};
|
|
28
32
|
const colorScale = {
|
|
29
33
|
colors: [],
|
|
@@ -90,27 +90,15 @@ export async function axisBottom(args) {
|
|
|
90
90
|
});
|
|
91
91
|
}
|
|
92
92
|
else {
|
|
93
|
-
// remove overlapping labels
|
|
94
93
|
let elementX = 0;
|
|
95
|
-
selection
|
|
96
|
-
.selectAll('.tick')
|
|
97
|
-
.filter(function () {
|
|
98
|
-
const node = this;
|
|
99
|
-
const r = node.getBoundingClientRect();
|
|
100
|
-
if (r.left < elementX) {
|
|
101
|
-
return true;
|
|
102
|
-
}
|
|
103
|
-
elementX = r.right + labelsPaddings;
|
|
104
|
-
return false;
|
|
105
|
-
})
|
|
106
|
-
.remove();
|
|
107
94
|
// add an ellipsis to the labels that go beyond the boundaries of the chart
|
|
95
|
+
// and remove overlapping labels
|
|
108
96
|
labels.each(function (_d, i, nodes) {
|
|
109
|
-
var _a;
|
|
97
|
+
var _a, _b;
|
|
98
|
+
const currentElement = this;
|
|
99
|
+
const currentElementPosition = currentElement.getBoundingClientRect();
|
|
110
100
|
if (i === 0) {
|
|
111
|
-
const currentElement = this;
|
|
112
101
|
const text = select(currentElement);
|
|
113
|
-
const currentElementPosition = currentElement.getBoundingClientRect();
|
|
114
102
|
const nextElement = nodes[i + 1];
|
|
115
103
|
const nextElementPosition = nextElement === null || nextElement === void 0 ? void 0 : nextElement.getBoundingClientRect();
|
|
116
104
|
if (currentElementPosition.left < leftmostLimit) {
|
|
@@ -123,18 +111,23 @@ export async function axisBottom(args) {
|
|
|
123
111
|
setEllipsisForOverflowText(text, remainSpace);
|
|
124
112
|
}
|
|
125
113
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
114
|
+
else {
|
|
115
|
+
if (currentElementPosition.left < elementX) {
|
|
116
|
+
(_b = currentElement.closest('.tick')) === null || _b === void 0 ? void 0 : _b.remove();
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
elementX = currentElementPosition.right + labelsPaddings;
|
|
120
|
+
if (i === nodes.length - 1) {
|
|
121
|
+
const prevElement = nodes[i - 1];
|
|
122
|
+
const text = select(currentElement);
|
|
123
|
+
const prevElementPosition = prevElement === null || prevElement === void 0 ? void 0 : prevElement.getBoundingClientRect();
|
|
124
|
+
const lackingSpace = Math.max(0, currentElementPosition.right - right);
|
|
125
|
+
if (lackingSpace) {
|
|
126
|
+
const remainSpace = right - ((prevElementPosition === null || prevElementPosition === void 0 ? void 0 : prevElementPosition.right) || 0) - labelsPaddings;
|
|
127
|
+
const translateX = -lackingSpace;
|
|
128
|
+
text.style('transform', `translate(${translateX}px,${translateY}px)`);
|
|
129
|
+
setEllipsisForOverflowText(text, remainSpace);
|
|
130
|
+
}
|
|
138
131
|
}
|
|
139
132
|
}
|
|
140
133
|
});
|