@gravity-ui/charts 1.11.2 → 1.11.4
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 +3 -3
- package/dist/cjs/components/ChartInner/useChartInnerProps.d.ts +2 -0
- package/dist/cjs/components/ChartInner/useChartInnerProps.js +16 -8
- package/dist/cjs/components/Legend/index.d.ts +0 -1
- package/dist/cjs/components/Legend/index.js +18 -24
- package/dist/cjs/hooks/useChartOptions/index.d.ts +6 -2
- package/dist/cjs/hooks/useChartOptions/index.js +4 -4
- package/dist/cjs/hooks/useSeries/index.d.ts +9 -0
- package/dist/cjs/hooks/useSeries/index.js +59 -29
- package/dist/cjs/hooks/useSeries/prepare-legend.d.ts +2 -2
- package/dist/cjs/hooks/useSeries/prepare-legend.js +9 -7
- package/dist/cjs/hooks/useSeries/types.d.ts +2 -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 +3 -3
- package/dist/esm/components/ChartInner/useChartInnerProps.d.ts +2 -0
- package/dist/esm/components/ChartInner/useChartInnerProps.js +16 -8
- package/dist/esm/components/Legend/index.d.ts +0 -1
- package/dist/esm/components/Legend/index.js +18 -24
- package/dist/esm/hooks/useChartOptions/index.d.ts +6 -2
- package/dist/esm/hooks/useChartOptions/index.js +4 -4
- package/dist/esm/hooks/useSeries/index.d.ts +9 -0
- package/dist/esm/hooks/useSeries/index.js +59 -29
- package/dist/esm/hooks/useSeries/prepare-legend.d.ts +2 -2
- package/dist/esm/hooks/useSeries/prepare-legend.js +9 -7
- package/dist/esm/hooks/useSeries/types.d.ts +2 -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,13 +94,13 @@ 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 }),
|
|
101
101
|
shapes,
|
|
102
102
|
React.createElement("g", { ref: plotAfterRef })),
|
|
103
|
-
(preparedLegend === null || preparedLegend === void 0 ? void 0 : preparedLegend.enabled) && legendConfig && (React.createElement(Legend, { chartSeries: preparedSeries,
|
|
103
|
+
(preparedLegend === null || preparedLegend === void 0 ? void 0 : preparedLegend.enabled) && legendConfig && (React.createElement(Legend, { chartSeries: preparedSeries, legend: preparedLegend, items: legendItems, config: legendConfig, onItemClick: handleLegendItemClick, onUpdate: unpinTooltip, htmlLayout: htmlLayout }))),
|
|
104
104
|
React.createElement("div", { className: b('html-layer'), ref: setHtmlLayout, style: {
|
|
105
105
|
'--g-html-layout-transform': `translate(${boundsOffsetLeft}px, ${boundsOffsetTop}px)`,
|
|
106
106
|
} }),
|
|
@@ -11,6 +11,7 @@ type Props = ChartInnerProps & {
|
|
|
11
11
|
};
|
|
12
12
|
export declare function useChartInnerProps(props: Props): {
|
|
13
13
|
svgBottomPos: number | undefined;
|
|
14
|
+
svgTopPos: number | undefined;
|
|
14
15
|
svgXPos: number | undefined;
|
|
15
16
|
boundsHeight: number;
|
|
16
17
|
boundsOffsetLeft: number;
|
|
@@ -30,6 +31,7 @@ export declare function useChartInnerProps(props: Props): {
|
|
|
30
31
|
end: number;
|
|
31
32
|
}[];
|
|
32
33
|
} | undefined;
|
|
34
|
+
maxWidth: number;
|
|
33
35
|
} | undefined;
|
|
34
36
|
legendItems: never[] | import("../../hooks").LegendItem[][];
|
|
35
37
|
preparedLegend: import("../../hooks").PreparedLegend | null;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { useAxisScales, useChartDimensions, useChartOptions, usePrevious, useSeries, useShapes, useSplit, } from '../../hooks';
|
|
2
|
+
import { useAxisScales, useChartDimensions, useChartOptions, usePrevious, useSeries, useShapeSeries, useShapes, useSplit, } from '../../hooks';
|
|
3
3
|
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
|
import { getLegendComponents } from '../../hooks/useSeries/prepare-legend';
|
|
7
7
|
import { getPreparedOptions } from '../../hooks/useSeries/prepare-options';
|
|
8
|
+
import { getActiveLegendItems } from '../../hooks/useSeries/utils';
|
|
8
9
|
import { useZoom } from '../../hooks/useZoom';
|
|
9
10
|
import { getSortedSeriesData, getZoomedSeriesData } from '../../utils';
|
|
10
11
|
import { hasAtLeastOneSeriesDataPerPlot } from './utils';
|
|
@@ -13,8 +14,14 @@ export function useChartInnerProps(props) {
|
|
|
13
14
|
const { width, height, data, dispatcher, htmlLayout, svgContainer, plotNode, clipPathId } = props;
|
|
14
15
|
const prevWidth = usePrevious(width);
|
|
15
16
|
const prevHeight = usePrevious(height);
|
|
17
|
+
const { chart, title, tooltip, colors } = useChartOptions({
|
|
18
|
+
seriesData: data.series.data,
|
|
19
|
+
chart: data.chart,
|
|
20
|
+
colors: data.colors,
|
|
21
|
+
title: data.title,
|
|
22
|
+
tooltip: data.tooltip,
|
|
23
|
+
});
|
|
16
24
|
const [zoomState, setZoomState] = React.useState({});
|
|
17
|
-
const { chart, title, tooltip, colors } = useChartOptions({ data });
|
|
18
25
|
const sortedSeriesData = React.useMemo(() => {
|
|
19
26
|
return getSortedSeriesData(data.series.data);
|
|
20
27
|
}, [data.series.data]);
|
|
@@ -46,12 +53,13 @@ export function useChartInnerProps(props) {
|
|
|
46
53
|
seriesData: zoomedSeriesData,
|
|
47
54
|
seriesOptions: data.series.options,
|
|
48
55
|
});
|
|
49
|
-
const
|
|
56
|
+
const activeLegendItems = React.useMemo(() => getActiveLegendItems(preparedSeries), [preparedSeries]);
|
|
57
|
+
const { preparedSeries: preparedShapesSeries } = useShapeSeries({
|
|
50
58
|
colors,
|
|
51
|
-
legend: data.legend,
|
|
52
|
-
originalSeriesData: data.series.data,
|
|
53
59
|
seriesData: zoomedShapesSeriesData,
|
|
54
60
|
seriesOptions: data.series.options,
|
|
61
|
+
activeLegendItems,
|
|
62
|
+
preparedLegend,
|
|
55
63
|
});
|
|
56
64
|
const { legendConfig, legendItems } = React.useMemo(() => {
|
|
57
65
|
if (!preparedLegend) {
|
|
@@ -63,9 +71,8 @@ export function useChartInnerProps(props) {
|
|
|
63
71
|
chartMargin: chart.margin,
|
|
64
72
|
series: preparedSeries,
|
|
65
73
|
preparedLegend,
|
|
66
|
-
preparedYAxis: yAxis,
|
|
67
74
|
});
|
|
68
|
-
}, [width, height, chart.margin, preparedSeries, preparedLegend
|
|
75
|
+
}, [width, height, chart.margin, preparedSeries, preparedLegend]);
|
|
69
76
|
const { boundsWidth, boundsHeight } = useChartDimensions({
|
|
70
77
|
width,
|
|
71
78
|
height,
|
|
@@ -141,12 +148,13 @@ export function useChartInnerProps(props) {
|
|
|
141
148
|
}
|
|
142
149
|
return acc;
|
|
143
150
|
}, 0);
|
|
144
|
-
const {
|
|
151
|
+
const { bottom, top, x } = (_a = svgContainer === null || svgContainer === void 0 ? void 0 : svgContainer.getBoundingClientRect()) !== null && _a !== void 0 ? _a : {};
|
|
145
152
|
const handleZoomReset = React.useCallback(() => {
|
|
146
153
|
setZoomState({});
|
|
147
154
|
}, []);
|
|
148
155
|
return {
|
|
149
156
|
svgBottomPos: bottom,
|
|
157
|
+
svgTopPos: top,
|
|
150
158
|
svgXPos: x,
|
|
151
159
|
boundsHeight,
|
|
152
160
|
boundsOffsetLeft,
|
|
@@ -2,7 +2,6 @@ import React from 'react';
|
|
|
2
2
|
import type { LegendConfig, LegendItem, OnLegendItemClick, PreparedLegend, PreparedSeries } from '../../hooks';
|
|
3
3
|
import './styles.css';
|
|
4
4
|
type Props = {
|
|
5
|
-
boundsWidth: number;
|
|
6
5
|
chartSeries: PreparedSeries[];
|
|
7
6
|
legend: PreparedLegend;
|
|
8
7
|
items: LegendItem[][];
|
|
@@ -2,20 +2,20 @@ 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');
|
|
9
9
|
const getLegendPosition = (args) => {
|
|
10
|
-
const { align,
|
|
10
|
+
const { align, offsetLeft = 0, width, contentWidth } = args;
|
|
11
11
|
const top = 0;
|
|
12
12
|
if (align === 'left') {
|
|
13
|
-
return { top, left:
|
|
13
|
+
return { top, left: offsetLeft };
|
|
14
14
|
}
|
|
15
15
|
if (align === 'right') {
|
|
16
|
-
return { top, left:
|
|
16
|
+
return { top, left: offsetLeft + width - contentWidth };
|
|
17
17
|
}
|
|
18
|
-
return { top, left:
|
|
18
|
+
return { top, left: offsetLeft + width / 2 - contentWidth / 2 };
|
|
19
19
|
};
|
|
20
20
|
const appendPaginator = (args) => {
|
|
21
21
|
const { container, pageIndex, legend, transform, pages, onArrowClick } = args;
|
|
@@ -135,12 +135,12 @@ function renderLegendSymbol(args) {
|
|
|
135
135
|
});
|
|
136
136
|
}
|
|
137
137
|
export const Legend = (props) => {
|
|
138
|
-
const {
|
|
138
|
+
const { chartSeries, legend, items, config, htmlLayout, onItemClick, onUpdate } = props;
|
|
139
139
|
const ref = React.useRef(null);
|
|
140
140
|
const [pageIndex, setPageIndex] = React.useState(0);
|
|
141
141
|
React.useEffect(() => {
|
|
142
142
|
setPageIndex(0);
|
|
143
|
-
}, [
|
|
143
|
+
}, [config.maxWidth]);
|
|
144
144
|
React.useEffect(() => {
|
|
145
145
|
async function prepareLegend() {
|
|
146
146
|
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
@@ -250,12 +250,12 @@ export const Legend = (props) => {
|
|
|
250
250
|
case 'center': {
|
|
251
251
|
const legendLinePostion = getLegendPosition({
|
|
252
252
|
align: legend.align,
|
|
253
|
-
width:
|
|
254
|
-
offsetWidth: 0,
|
|
253
|
+
width: config.maxWidth,
|
|
255
254
|
contentWidth,
|
|
255
|
+
offsetLeft: config.offset.left,
|
|
256
256
|
});
|
|
257
257
|
left = legendLinePostion.left;
|
|
258
|
-
legendWidth =
|
|
258
|
+
legendWidth = config.maxWidth;
|
|
259
259
|
break;
|
|
260
260
|
}
|
|
261
261
|
case 'start': {
|
|
@@ -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,
|
|
@@ -360,9 +361,9 @@ export const Legend = (props) => {
|
|
|
360
361
|
}
|
|
361
362
|
const { left } = getLegendPosition({
|
|
362
363
|
align: legend.align,
|
|
363
|
-
width:
|
|
364
|
-
offsetWidth: config.offset.left,
|
|
364
|
+
width: config.maxWidth,
|
|
365
365
|
contentWidth: legendWidth,
|
|
366
|
+
offsetLeft: config.offset.left,
|
|
366
367
|
});
|
|
367
368
|
svgElement
|
|
368
369
|
.attr('transform', `translate(${[left, config.offset.top].join(',')})`)
|
|
@@ -370,16 +371,9 @@ export const Legend = (props) => {
|
|
|
370
371
|
htmlContainer === null || htmlContainer === void 0 ? void 0 : htmlContainer.style('transform', `translate(${left}px, ${config.offset.top}px)`);
|
|
371
372
|
}
|
|
372
373
|
prepareLegend();
|
|
373
|
-
}, [
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
legend,
|
|
379
|
-
items,
|
|
380
|
-
config,
|
|
381
|
-
pageIndex,
|
|
382
|
-
htmlLayout,
|
|
383
|
-
]);
|
|
384
|
-
return React.createElement("g", { className: b(), ref: ref, width: boundsWidth, height: legend.height });
|
|
374
|
+
}, [chartSeries, onItemClick, onUpdate, legend, items, config, pageIndex, htmlLayout]);
|
|
375
|
+
// due to asynchronous processing, we only need to work with the actual element
|
|
376
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
377
|
+
const key = React.useMemo(() => getUniqId(), [legend, config]);
|
|
378
|
+
return React.createElement("g", { key: key, className: b(), ref: ref, width: config.maxWidth, height: legend.height });
|
|
385
379
|
};
|
|
@@ -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
|
};
|
|
@@ -13,4 +13,13 @@ export declare const useSeries: (args: Args) => {
|
|
|
13
13
|
preparedSeries: PreparedSeries[];
|
|
14
14
|
handleLegendItemClick: OnLegendItemClick;
|
|
15
15
|
};
|
|
16
|
+
export declare const useShapeSeries: ({ seriesData, seriesOptions, colors, preparedLegend, activeLegendItems, }: {
|
|
17
|
+
colors: string[];
|
|
18
|
+
seriesData: ChartData["series"]["data"];
|
|
19
|
+
seriesOptions: ChartData["series"]["options"];
|
|
20
|
+
activeLegendItems: string[];
|
|
21
|
+
preparedLegend?: PreparedLegend | null;
|
|
22
|
+
}) => {
|
|
23
|
+
preparedSeries: PreparedSeries[];
|
|
24
|
+
};
|
|
16
25
|
export {};
|
|
@@ -5,6 +5,38 @@ import { usePrevious } from '../usePrevious';
|
|
|
5
5
|
import { getPreparedLegend } from './prepare-legend';
|
|
6
6
|
import { prepareSeries } from './prepareSeries';
|
|
7
7
|
import { getActiveLegendItems, getAllLegendItems } from './utils';
|
|
8
|
+
const useVisibleSeries = ({ preparedSeries, activeLegendItems, }) => {
|
|
9
|
+
return React.useMemo(() => {
|
|
10
|
+
return preparedSeries.map((singleSeries) => {
|
|
11
|
+
if (singleSeries.legend.enabled) {
|
|
12
|
+
return Object.assign(Object.assign({}, singleSeries), { visible: activeLegendItems.includes(singleSeries.name) });
|
|
13
|
+
}
|
|
14
|
+
return singleSeries;
|
|
15
|
+
});
|
|
16
|
+
}, [preparedSeries, activeLegendItems]);
|
|
17
|
+
};
|
|
18
|
+
const getPreparedSeries = async ({ seriesData, seriesOptions, colors, preparedLegend, }) => {
|
|
19
|
+
const seriesNames = getSeriesNames(seriesData);
|
|
20
|
+
const colorScale = scaleOrdinal(seriesNames, colors);
|
|
21
|
+
const groupedSeries = group(seriesData, (item) => item.type);
|
|
22
|
+
const acc = [];
|
|
23
|
+
if (!preparedLegend) {
|
|
24
|
+
return acc;
|
|
25
|
+
}
|
|
26
|
+
const list = Array.from(groupedSeries);
|
|
27
|
+
for (let i = 0; i < list.length; i++) {
|
|
28
|
+
const [seriesType, seriesList] = list[i];
|
|
29
|
+
acc.push(...(await prepareSeries({
|
|
30
|
+
type: seriesType,
|
|
31
|
+
series: seriesList,
|
|
32
|
+
seriesOptions,
|
|
33
|
+
legend: preparedLegend,
|
|
34
|
+
colorScale,
|
|
35
|
+
colors,
|
|
36
|
+
})));
|
|
37
|
+
}
|
|
38
|
+
return acc;
|
|
39
|
+
};
|
|
8
40
|
export const useSeries = (args) => {
|
|
9
41
|
const { legend, originalSeriesData, seriesData, seriesOptions, colors, preparedLegend: preparedLegendProps = null, } = args;
|
|
10
42
|
const [preparedLegend, setPreparedLegend] = React.useState(preparedLegendProps);
|
|
@@ -17,38 +49,18 @@ export const useSeries = (args) => {
|
|
|
17
49
|
const [activeLegendItems, setActiveLegendItems] = React.useState(getActiveLegendItems(preparedSeries));
|
|
18
50
|
React.useEffect(() => {
|
|
19
51
|
(async () => {
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
for (let i = 0; i < list.length; i++) {
|
|
29
|
-
const [seriesType, seriesList] = list[i];
|
|
30
|
-
acc.push(...(await prepareSeries({
|
|
31
|
-
type: seriesType,
|
|
32
|
-
series: seriesList,
|
|
33
|
-
seriesOptions,
|
|
34
|
-
legend: preparedLegend,
|
|
35
|
-
colorScale,
|
|
36
|
-
colors,
|
|
37
|
-
})));
|
|
38
|
-
}
|
|
39
|
-
setPreparedSeries(acc);
|
|
40
|
-
setActiveLegendItems(getActiveLegendItems(acc));
|
|
52
|
+
const items = await getPreparedSeries({
|
|
53
|
+
seriesData,
|
|
54
|
+
seriesOptions,
|
|
55
|
+
preparedLegend,
|
|
56
|
+
colors,
|
|
57
|
+
});
|
|
58
|
+
setPreparedSeries(items);
|
|
59
|
+
setActiveLegendItems(getActiveLegendItems(items));
|
|
41
60
|
})();
|
|
42
61
|
}, [seriesData, seriesOptions, preparedLegend, colors]);
|
|
43
62
|
const prevOriginalSeriesData = usePrevious(originalSeriesData);
|
|
44
|
-
const chartSeries =
|
|
45
|
-
return preparedSeries.map((singleSeries) => {
|
|
46
|
-
if (singleSeries.legend.enabled) {
|
|
47
|
-
return Object.assign(Object.assign({}, singleSeries), { visible: activeLegendItems.includes(singleSeries.name) });
|
|
48
|
-
}
|
|
49
|
-
return singleSeries;
|
|
50
|
-
});
|
|
51
|
-
}, [preparedSeries, activeLegendItems]);
|
|
63
|
+
const chartSeries = useVisibleSeries({ preparedSeries, activeLegendItems });
|
|
52
64
|
const handleLegendItemClick = React.useCallback(({ name, metaKey }) => {
|
|
53
65
|
const allItems = getAllLegendItems(preparedSeries);
|
|
54
66
|
const onlyItemSelected = activeLegendItems.length === 1 && activeLegendItems.includes(name);
|
|
@@ -81,3 +93,21 @@ export const useSeries = (args) => {
|
|
|
81
93
|
handleLegendItemClick,
|
|
82
94
|
};
|
|
83
95
|
};
|
|
96
|
+
export const useShapeSeries = ({ seriesData, seriesOptions, colors, preparedLegend, activeLegendItems, }) => {
|
|
97
|
+
const [preparedSeries, setPreparedSeries] = React.useState([]);
|
|
98
|
+
React.useEffect(() => {
|
|
99
|
+
(async () => {
|
|
100
|
+
const items = await getPreparedSeries({
|
|
101
|
+
seriesData,
|
|
102
|
+
seriesOptions,
|
|
103
|
+
preparedLegend,
|
|
104
|
+
colors,
|
|
105
|
+
});
|
|
106
|
+
setPreparedSeries(items);
|
|
107
|
+
})();
|
|
108
|
+
}, [seriesData, seriesOptions, preparedLegend, colors]);
|
|
109
|
+
const chartSeries = useVisibleSeries({ preparedSeries, activeLegendItems });
|
|
110
|
+
return {
|
|
111
|
+
preparedSeries: chartSeries,
|
|
112
|
+
};
|
|
113
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ChartData } from '../../types';
|
|
2
|
-
import type {
|
|
2
|
+
import type { PreparedChart } from '../useChartOptions/types';
|
|
3
3
|
import type { LegendItem, PreparedLegend, PreparedSeries } from './types';
|
|
4
4
|
export declare function getPreparedLegend(args: {
|
|
5
5
|
legend: ChartData['legend'];
|
|
@@ -11,7 +11,6 @@ export declare function getLegendComponents(args: {
|
|
|
11
11
|
chartMargin: PreparedChart['margin'];
|
|
12
12
|
series: PreparedSeries[];
|
|
13
13
|
preparedLegend: PreparedLegend;
|
|
14
|
-
preparedYAxis: PreparedAxis[];
|
|
15
14
|
}): {
|
|
16
15
|
legendConfig: {
|
|
17
16
|
offset: {
|
|
@@ -24,6 +23,7 @@ export declare function getLegendComponents(args: {
|
|
|
24
23
|
end: number;
|
|
25
24
|
}[];
|
|
26
25
|
} | undefined;
|
|
26
|
+
maxWidth: number;
|
|
27
27
|
};
|
|
28
28
|
legendItems: LegendItem[][];
|
|
29
29
|
};
|
|
@@ -4,8 +4,6 @@ import get from 'lodash/get';
|
|
|
4
4
|
import merge from 'lodash/merge';
|
|
5
5
|
import { CONTINUOUS_LEGEND_SIZE, legendDefaults } from '../../constants';
|
|
6
6
|
import { getDefaultColorStops, getDomainForContinuousColorScale, getLabelsSize } from '../../utils';
|
|
7
|
-
import { getBoundsWidth } from '../useChartDimensions';
|
|
8
|
-
import { getYAxisWidth } from '../useChartDimensions/utils';
|
|
9
7
|
export async function getPreparedLegend(args) {
|
|
10
8
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
11
9
|
const { legend, series } = args;
|
|
@@ -21,9 +19,13 @@ export async function getPreparedLegend(args) {
|
|
|
21
19
|
const titleText = isTitleEnabled ? get(legend, 'title.text', '') : '';
|
|
22
20
|
const titleSize = await getLabelsSize({ labels: [titleText], style: titleStyle });
|
|
23
21
|
const titleHeight = isTitleEnabled ? titleSize.maxHeight : 0;
|
|
22
|
+
const tickStyle = {
|
|
23
|
+
fontSize: '12px',
|
|
24
|
+
};
|
|
24
25
|
const ticks = {
|
|
25
26
|
labelsMargin: 4,
|
|
26
|
-
labelsLineHeight:
|
|
27
|
+
labelsLineHeight: (await getLabelsSize({ labels: ['Tmp'], style: tickStyle })).maxHeight,
|
|
28
|
+
style: tickStyle,
|
|
27
29
|
};
|
|
28
30
|
const colorScale = {
|
|
29
31
|
colors: [],
|
|
@@ -164,8 +166,8 @@ function getPagination(args) {
|
|
|
164
166
|
return { pages };
|
|
165
167
|
}
|
|
166
168
|
export function getLegendComponents(args) {
|
|
167
|
-
const { chartWidth, chartHeight, chartMargin, series, preparedLegend
|
|
168
|
-
const maxLegendWidth =
|
|
169
|
+
const { chartWidth, chartHeight, chartMargin, series, preparedLegend } = args;
|
|
170
|
+
const maxLegendWidth = chartWidth - chartMargin.right - chartMargin.left;
|
|
169
171
|
const maxLegendHeight = (chartHeight - chartMargin.top - chartMargin.bottom - preparedLegend.margin) / 2;
|
|
170
172
|
const flattenLegendItems = getFlattenLegendItems(series, preparedLegend);
|
|
171
173
|
const items = getGroupedLegendItems({
|
|
@@ -195,8 +197,8 @@ export function getLegendComponents(args) {
|
|
|
195
197
|
}
|
|
196
198
|
const top = chartHeight - chartMargin.bottom - preparedLegend.height;
|
|
197
199
|
const offset = {
|
|
198
|
-
left: chartMargin.left
|
|
200
|
+
left: chartMargin.left,
|
|
199
201
|
top,
|
|
200
202
|
};
|
|
201
|
-
return { legendConfig: { offset, pagination }, legendItems: items };
|
|
203
|
+
return { legendConfig: { offset, pagination, maxWidth: maxLegendWidth }, legendItems: items };
|
|
202
204
|
}
|
|
@@ -26,6 +26,7 @@ export type PreparedLegend = Required<Omit<ChartLegend, 'title' | 'colorScale'>>
|
|
|
26
26
|
ticks: {
|
|
27
27
|
labelsMargin: number;
|
|
28
28
|
labelsLineHeight: number;
|
|
29
|
+
style: BaseTextStyle;
|
|
29
30
|
};
|
|
30
31
|
colorScale: {
|
|
31
32
|
colors: string[];
|
|
@@ -52,6 +53,7 @@ export type LegendConfig = {
|
|
|
52
53
|
left: number;
|
|
53
54
|
top: number;
|
|
54
55
|
};
|
|
56
|
+
maxWidth: number;
|
|
55
57
|
pagination?: {
|
|
56
58
|
pages: {
|
|
57
59
|
start: number;
|
|
@@ -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,13 +94,13 @@ 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 }),
|
|
101
101
|
shapes,
|
|
102
102
|
React.createElement("g", { ref: plotAfterRef })),
|
|
103
|
-
(preparedLegend === null || preparedLegend === void 0 ? void 0 : preparedLegend.enabled) && legendConfig && (React.createElement(Legend, { chartSeries: preparedSeries,
|
|
103
|
+
(preparedLegend === null || preparedLegend === void 0 ? void 0 : preparedLegend.enabled) && legendConfig && (React.createElement(Legend, { chartSeries: preparedSeries, legend: preparedLegend, items: legendItems, config: legendConfig, onItemClick: handleLegendItemClick, onUpdate: unpinTooltip, htmlLayout: htmlLayout }))),
|
|
104
104
|
React.createElement("div", { className: b('html-layer'), ref: setHtmlLayout, style: {
|
|
105
105
|
'--g-html-layout-transform': `translate(${boundsOffsetLeft}px, ${boundsOffsetTop}px)`,
|
|
106
106
|
} }),
|
|
@@ -11,6 +11,7 @@ type Props = ChartInnerProps & {
|
|
|
11
11
|
};
|
|
12
12
|
export declare function useChartInnerProps(props: Props): {
|
|
13
13
|
svgBottomPos: number | undefined;
|
|
14
|
+
svgTopPos: number | undefined;
|
|
14
15
|
svgXPos: number | undefined;
|
|
15
16
|
boundsHeight: number;
|
|
16
17
|
boundsOffsetLeft: number;
|
|
@@ -30,6 +31,7 @@ export declare function useChartInnerProps(props: Props): {
|
|
|
30
31
|
end: number;
|
|
31
32
|
}[];
|
|
32
33
|
} | undefined;
|
|
34
|
+
maxWidth: number;
|
|
33
35
|
} | undefined;
|
|
34
36
|
legendItems: never[] | import("../../hooks").LegendItem[][];
|
|
35
37
|
preparedLegend: import("../../hooks").PreparedLegend | null;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { useAxisScales, useChartDimensions, useChartOptions, usePrevious, useSeries, useShapes, useSplit, } from '../../hooks';
|
|
2
|
+
import { useAxisScales, useChartDimensions, useChartOptions, usePrevious, useSeries, useShapeSeries, useShapes, useSplit, } from '../../hooks';
|
|
3
3
|
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
|
import { getLegendComponents } from '../../hooks/useSeries/prepare-legend';
|
|
7
7
|
import { getPreparedOptions } from '../../hooks/useSeries/prepare-options';
|
|
8
|
+
import { getActiveLegendItems } from '../../hooks/useSeries/utils';
|
|
8
9
|
import { useZoom } from '../../hooks/useZoom';
|
|
9
10
|
import { getSortedSeriesData, getZoomedSeriesData } from '../../utils';
|
|
10
11
|
import { hasAtLeastOneSeriesDataPerPlot } from './utils';
|
|
@@ -13,8 +14,14 @@ export function useChartInnerProps(props) {
|
|
|
13
14
|
const { width, height, data, dispatcher, htmlLayout, svgContainer, plotNode, clipPathId } = props;
|
|
14
15
|
const prevWidth = usePrevious(width);
|
|
15
16
|
const prevHeight = usePrevious(height);
|
|
17
|
+
const { chart, title, tooltip, colors } = useChartOptions({
|
|
18
|
+
seriesData: data.series.data,
|
|
19
|
+
chart: data.chart,
|
|
20
|
+
colors: data.colors,
|
|
21
|
+
title: data.title,
|
|
22
|
+
tooltip: data.tooltip,
|
|
23
|
+
});
|
|
16
24
|
const [zoomState, setZoomState] = React.useState({});
|
|
17
|
-
const { chart, title, tooltip, colors } = useChartOptions({ data });
|
|
18
25
|
const sortedSeriesData = React.useMemo(() => {
|
|
19
26
|
return getSortedSeriesData(data.series.data);
|
|
20
27
|
}, [data.series.data]);
|
|
@@ -46,12 +53,13 @@ export function useChartInnerProps(props) {
|
|
|
46
53
|
seriesData: zoomedSeriesData,
|
|
47
54
|
seriesOptions: data.series.options,
|
|
48
55
|
});
|
|
49
|
-
const
|
|
56
|
+
const activeLegendItems = React.useMemo(() => getActiveLegendItems(preparedSeries), [preparedSeries]);
|
|
57
|
+
const { preparedSeries: preparedShapesSeries } = useShapeSeries({
|
|
50
58
|
colors,
|
|
51
|
-
legend: data.legend,
|
|
52
|
-
originalSeriesData: data.series.data,
|
|
53
59
|
seriesData: zoomedShapesSeriesData,
|
|
54
60
|
seriesOptions: data.series.options,
|
|
61
|
+
activeLegendItems,
|
|
62
|
+
preparedLegend,
|
|
55
63
|
});
|
|
56
64
|
const { legendConfig, legendItems } = React.useMemo(() => {
|
|
57
65
|
if (!preparedLegend) {
|
|
@@ -63,9 +71,8 @@ export function useChartInnerProps(props) {
|
|
|
63
71
|
chartMargin: chart.margin,
|
|
64
72
|
series: preparedSeries,
|
|
65
73
|
preparedLegend,
|
|
66
|
-
preparedYAxis: yAxis,
|
|
67
74
|
});
|
|
68
|
-
}, [width, height, chart.margin, preparedSeries, preparedLegend
|
|
75
|
+
}, [width, height, chart.margin, preparedSeries, preparedLegend]);
|
|
69
76
|
const { boundsWidth, boundsHeight } = useChartDimensions({
|
|
70
77
|
width,
|
|
71
78
|
height,
|
|
@@ -141,12 +148,13 @@ export function useChartInnerProps(props) {
|
|
|
141
148
|
}
|
|
142
149
|
return acc;
|
|
143
150
|
}, 0);
|
|
144
|
-
const {
|
|
151
|
+
const { bottom, top, x } = (_a = svgContainer === null || svgContainer === void 0 ? void 0 : svgContainer.getBoundingClientRect()) !== null && _a !== void 0 ? _a : {};
|
|
145
152
|
const handleZoomReset = React.useCallback(() => {
|
|
146
153
|
setZoomState({});
|
|
147
154
|
}, []);
|
|
148
155
|
return {
|
|
149
156
|
svgBottomPos: bottom,
|
|
157
|
+
svgTopPos: top,
|
|
150
158
|
svgXPos: x,
|
|
151
159
|
boundsHeight,
|
|
152
160
|
boundsOffsetLeft,
|
|
@@ -2,7 +2,6 @@ import React from 'react';
|
|
|
2
2
|
import type { LegendConfig, LegendItem, OnLegendItemClick, PreparedLegend, PreparedSeries } from '../../hooks';
|
|
3
3
|
import './styles.css';
|
|
4
4
|
type Props = {
|
|
5
|
-
boundsWidth: number;
|
|
6
5
|
chartSeries: PreparedSeries[];
|
|
7
6
|
legend: PreparedLegend;
|
|
8
7
|
items: LegendItem[][];
|
|
@@ -2,20 +2,20 @@ 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');
|
|
9
9
|
const getLegendPosition = (args) => {
|
|
10
|
-
const { align,
|
|
10
|
+
const { align, offsetLeft = 0, width, contentWidth } = args;
|
|
11
11
|
const top = 0;
|
|
12
12
|
if (align === 'left') {
|
|
13
|
-
return { top, left:
|
|
13
|
+
return { top, left: offsetLeft };
|
|
14
14
|
}
|
|
15
15
|
if (align === 'right') {
|
|
16
|
-
return { top, left:
|
|
16
|
+
return { top, left: offsetLeft + width - contentWidth };
|
|
17
17
|
}
|
|
18
|
-
return { top, left:
|
|
18
|
+
return { top, left: offsetLeft + width / 2 - contentWidth / 2 };
|
|
19
19
|
};
|
|
20
20
|
const appendPaginator = (args) => {
|
|
21
21
|
const { container, pageIndex, legend, transform, pages, onArrowClick } = args;
|
|
@@ -135,12 +135,12 @@ function renderLegendSymbol(args) {
|
|
|
135
135
|
});
|
|
136
136
|
}
|
|
137
137
|
export const Legend = (props) => {
|
|
138
|
-
const {
|
|
138
|
+
const { chartSeries, legend, items, config, htmlLayout, onItemClick, onUpdate } = props;
|
|
139
139
|
const ref = React.useRef(null);
|
|
140
140
|
const [pageIndex, setPageIndex] = React.useState(0);
|
|
141
141
|
React.useEffect(() => {
|
|
142
142
|
setPageIndex(0);
|
|
143
|
-
}, [
|
|
143
|
+
}, [config.maxWidth]);
|
|
144
144
|
React.useEffect(() => {
|
|
145
145
|
async function prepareLegend() {
|
|
146
146
|
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
@@ -250,12 +250,12 @@ export const Legend = (props) => {
|
|
|
250
250
|
case 'center': {
|
|
251
251
|
const legendLinePostion = getLegendPosition({
|
|
252
252
|
align: legend.align,
|
|
253
|
-
width:
|
|
254
|
-
offsetWidth: 0,
|
|
253
|
+
width: config.maxWidth,
|
|
255
254
|
contentWidth,
|
|
255
|
+
offsetLeft: config.offset.left,
|
|
256
256
|
});
|
|
257
257
|
left = legendLinePostion.left;
|
|
258
|
-
legendWidth =
|
|
258
|
+
legendWidth = config.maxWidth;
|
|
259
259
|
break;
|
|
260
260
|
}
|
|
261
261
|
case 'start': {
|
|
@@ -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,
|
|
@@ -360,9 +361,9 @@ export const Legend = (props) => {
|
|
|
360
361
|
}
|
|
361
362
|
const { left } = getLegendPosition({
|
|
362
363
|
align: legend.align,
|
|
363
|
-
width:
|
|
364
|
-
offsetWidth: config.offset.left,
|
|
364
|
+
width: config.maxWidth,
|
|
365
365
|
contentWidth: legendWidth,
|
|
366
|
+
offsetLeft: config.offset.left,
|
|
366
367
|
});
|
|
367
368
|
svgElement
|
|
368
369
|
.attr('transform', `translate(${[left, config.offset.top].join(',')})`)
|
|
@@ -370,16 +371,9 @@ export const Legend = (props) => {
|
|
|
370
371
|
htmlContainer === null || htmlContainer === void 0 ? void 0 : htmlContainer.style('transform', `translate(${left}px, ${config.offset.top}px)`);
|
|
371
372
|
}
|
|
372
373
|
prepareLegend();
|
|
373
|
-
}, [
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
legend,
|
|
379
|
-
items,
|
|
380
|
-
config,
|
|
381
|
-
pageIndex,
|
|
382
|
-
htmlLayout,
|
|
383
|
-
]);
|
|
384
|
-
return React.createElement("g", { className: b(), ref: ref, width: boundsWidth, height: legend.height });
|
|
374
|
+
}, [chartSeries, onItemClick, onUpdate, legend, items, config, pageIndex, htmlLayout]);
|
|
375
|
+
// due to asynchronous processing, we only need to work with the actual element
|
|
376
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
377
|
+
const key = React.useMemo(() => getUniqId(), [legend, config]);
|
|
378
|
+
return React.createElement("g", { key: key, className: b(), ref: ref, width: config.maxWidth, height: legend.height });
|
|
385
379
|
};
|
|
@@ -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
|
};
|
|
@@ -13,4 +13,13 @@ export declare const useSeries: (args: Args) => {
|
|
|
13
13
|
preparedSeries: PreparedSeries[];
|
|
14
14
|
handleLegendItemClick: OnLegendItemClick;
|
|
15
15
|
};
|
|
16
|
+
export declare const useShapeSeries: ({ seriesData, seriesOptions, colors, preparedLegend, activeLegendItems, }: {
|
|
17
|
+
colors: string[];
|
|
18
|
+
seriesData: ChartData["series"]["data"];
|
|
19
|
+
seriesOptions: ChartData["series"]["options"];
|
|
20
|
+
activeLegendItems: string[];
|
|
21
|
+
preparedLegend?: PreparedLegend | null;
|
|
22
|
+
}) => {
|
|
23
|
+
preparedSeries: PreparedSeries[];
|
|
24
|
+
};
|
|
16
25
|
export {};
|
|
@@ -5,6 +5,38 @@ import { usePrevious } from '../usePrevious';
|
|
|
5
5
|
import { getPreparedLegend } from './prepare-legend';
|
|
6
6
|
import { prepareSeries } from './prepareSeries';
|
|
7
7
|
import { getActiveLegendItems, getAllLegendItems } from './utils';
|
|
8
|
+
const useVisibleSeries = ({ preparedSeries, activeLegendItems, }) => {
|
|
9
|
+
return React.useMemo(() => {
|
|
10
|
+
return preparedSeries.map((singleSeries) => {
|
|
11
|
+
if (singleSeries.legend.enabled) {
|
|
12
|
+
return Object.assign(Object.assign({}, singleSeries), { visible: activeLegendItems.includes(singleSeries.name) });
|
|
13
|
+
}
|
|
14
|
+
return singleSeries;
|
|
15
|
+
});
|
|
16
|
+
}, [preparedSeries, activeLegendItems]);
|
|
17
|
+
};
|
|
18
|
+
const getPreparedSeries = async ({ seriesData, seriesOptions, colors, preparedLegend, }) => {
|
|
19
|
+
const seriesNames = getSeriesNames(seriesData);
|
|
20
|
+
const colorScale = scaleOrdinal(seriesNames, colors);
|
|
21
|
+
const groupedSeries = group(seriesData, (item) => item.type);
|
|
22
|
+
const acc = [];
|
|
23
|
+
if (!preparedLegend) {
|
|
24
|
+
return acc;
|
|
25
|
+
}
|
|
26
|
+
const list = Array.from(groupedSeries);
|
|
27
|
+
for (let i = 0; i < list.length; i++) {
|
|
28
|
+
const [seriesType, seriesList] = list[i];
|
|
29
|
+
acc.push(...(await prepareSeries({
|
|
30
|
+
type: seriesType,
|
|
31
|
+
series: seriesList,
|
|
32
|
+
seriesOptions,
|
|
33
|
+
legend: preparedLegend,
|
|
34
|
+
colorScale,
|
|
35
|
+
colors,
|
|
36
|
+
})));
|
|
37
|
+
}
|
|
38
|
+
return acc;
|
|
39
|
+
};
|
|
8
40
|
export const useSeries = (args) => {
|
|
9
41
|
const { legend, originalSeriesData, seriesData, seriesOptions, colors, preparedLegend: preparedLegendProps = null, } = args;
|
|
10
42
|
const [preparedLegend, setPreparedLegend] = React.useState(preparedLegendProps);
|
|
@@ -17,38 +49,18 @@ export const useSeries = (args) => {
|
|
|
17
49
|
const [activeLegendItems, setActiveLegendItems] = React.useState(getActiveLegendItems(preparedSeries));
|
|
18
50
|
React.useEffect(() => {
|
|
19
51
|
(async () => {
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
for (let i = 0; i < list.length; i++) {
|
|
29
|
-
const [seriesType, seriesList] = list[i];
|
|
30
|
-
acc.push(...(await prepareSeries({
|
|
31
|
-
type: seriesType,
|
|
32
|
-
series: seriesList,
|
|
33
|
-
seriesOptions,
|
|
34
|
-
legend: preparedLegend,
|
|
35
|
-
colorScale,
|
|
36
|
-
colors,
|
|
37
|
-
})));
|
|
38
|
-
}
|
|
39
|
-
setPreparedSeries(acc);
|
|
40
|
-
setActiveLegendItems(getActiveLegendItems(acc));
|
|
52
|
+
const items = await getPreparedSeries({
|
|
53
|
+
seriesData,
|
|
54
|
+
seriesOptions,
|
|
55
|
+
preparedLegend,
|
|
56
|
+
colors,
|
|
57
|
+
});
|
|
58
|
+
setPreparedSeries(items);
|
|
59
|
+
setActiveLegendItems(getActiveLegendItems(items));
|
|
41
60
|
})();
|
|
42
61
|
}, [seriesData, seriesOptions, preparedLegend, colors]);
|
|
43
62
|
const prevOriginalSeriesData = usePrevious(originalSeriesData);
|
|
44
|
-
const chartSeries =
|
|
45
|
-
return preparedSeries.map((singleSeries) => {
|
|
46
|
-
if (singleSeries.legend.enabled) {
|
|
47
|
-
return Object.assign(Object.assign({}, singleSeries), { visible: activeLegendItems.includes(singleSeries.name) });
|
|
48
|
-
}
|
|
49
|
-
return singleSeries;
|
|
50
|
-
});
|
|
51
|
-
}, [preparedSeries, activeLegendItems]);
|
|
63
|
+
const chartSeries = useVisibleSeries({ preparedSeries, activeLegendItems });
|
|
52
64
|
const handleLegendItemClick = React.useCallback(({ name, metaKey }) => {
|
|
53
65
|
const allItems = getAllLegendItems(preparedSeries);
|
|
54
66
|
const onlyItemSelected = activeLegendItems.length === 1 && activeLegendItems.includes(name);
|
|
@@ -81,3 +93,21 @@ export const useSeries = (args) => {
|
|
|
81
93
|
handleLegendItemClick,
|
|
82
94
|
};
|
|
83
95
|
};
|
|
96
|
+
export const useShapeSeries = ({ seriesData, seriesOptions, colors, preparedLegend, activeLegendItems, }) => {
|
|
97
|
+
const [preparedSeries, setPreparedSeries] = React.useState([]);
|
|
98
|
+
React.useEffect(() => {
|
|
99
|
+
(async () => {
|
|
100
|
+
const items = await getPreparedSeries({
|
|
101
|
+
seriesData,
|
|
102
|
+
seriesOptions,
|
|
103
|
+
preparedLegend,
|
|
104
|
+
colors,
|
|
105
|
+
});
|
|
106
|
+
setPreparedSeries(items);
|
|
107
|
+
})();
|
|
108
|
+
}, [seriesData, seriesOptions, preparedLegend, colors]);
|
|
109
|
+
const chartSeries = useVisibleSeries({ preparedSeries, activeLegendItems });
|
|
110
|
+
return {
|
|
111
|
+
preparedSeries: chartSeries,
|
|
112
|
+
};
|
|
113
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ChartData } from '../../types';
|
|
2
|
-
import type {
|
|
2
|
+
import type { PreparedChart } from '../useChartOptions/types';
|
|
3
3
|
import type { LegendItem, PreparedLegend, PreparedSeries } from './types';
|
|
4
4
|
export declare function getPreparedLegend(args: {
|
|
5
5
|
legend: ChartData['legend'];
|
|
@@ -11,7 +11,6 @@ export declare function getLegendComponents(args: {
|
|
|
11
11
|
chartMargin: PreparedChart['margin'];
|
|
12
12
|
series: PreparedSeries[];
|
|
13
13
|
preparedLegend: PreparedLegend;
|
|
14
|
-
preparedYAxis: PreparedAxis[];
|
|
15
14
|
}): {
|
|
16
15
|
legendConfig: {
|
|
17
16
|
offset: {
|
|
@@ -24,6 +23,7 @@ export declare function getLegendComponents(args: {
|
|
|
24
23
|
end: number;
|
|
25
24
|
}[];
|
|
26
25
|
} | undefined;
|
|
26
|
+
maxWidth: number;
|
|
27
27
|
};
|
|
28
28
|
legendItems: LegendItem[][];
|
|
29
29
|
};
|
|
@@ -4,8 +4,6 @@ import get from 'lodash/get';
|
|
|
4
4
|
import merge from 'lodash/merge';
|
|
5
5
|
import { CONTINUOUS_LEGEND_SIZE, legendDefaults } from '../../constants';
|
|
6
6
|
import { getDefaultColorStops, getDomainForContinuousColorScale, getLabelsSize } from '../../utils';
|
|
7
|
-
import { getBoundsWidth } from '../useChartDimensions';
|
|
8
|
-
import { getYAxisWidth } from '../useChartDimensions/utils';
|
|
9
7
|
export async function getPreparedLegend(args) {
|
|
10
8
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
11
9
|
const { legend, series } = args;
|
|
@@ -21,9 +19,13 @@ export async function getPreparedLegend(args) {
|
|
|
21
19
|
const titleText = isTitleEnabled ? get(legend, 'title.text', '') : '';
|
|
22
20
|
const titleSize = await getLabelsSize({ labels: [titleText], style: titleStyle });
|
|
23
21
|
const titleHeight = isTitleEnabled ? titleSize.maxHeight : 0;
|
|
22
|
+
const tickStyle = {
|
|
23
|
+
fontSize: '12px',
|
|
24
|
+
};
|
|
24
25
|
const ticks = {
|
|
25
26
|
labelsMargin: 4,
|
|
26
|
-
labelsLineHeight:
|
|
27
|
+
labelsLineHeight: (await getLabelsSize({ labels: ['Tmp'], style: tickStyle })).maxHeight,
|
|
28
|
+
style: tickStyle,
|
|
27
29
|
};
|
|
28
30
|
const colorScale = {
|
|
29
31
|
colors: [],
|
|
@@ -164,8 +166,8 @@ function getPagination(args) {
|
|
|
164
166
|
return { pages };
|
|
165
167
|
}
|
|
166
168
|
export function getLegendComponents(args) {
|
|
167
|
-
const { chartWidth, chartHeight, chartMargin, series, preparedLegend
|
|
168
|
-
const maxLegendWidth =
|
|
169
|
+
const { chartWidth, chartHeight, chartMargin, series, preparedLegend } = args;
|
|
170
|
+
const maxLegendWidth = chartWidth - chartMargin.right - chartMargin.left;
|
|
169
171
|
const maxLegendHeight = (chartHeight - chartMargin.top - chartMargin.bottom - preparedLegend.margin) / 2;
|
|
170
172
|
const flattenLegendItems = getFlattenLegendItems(series, preparedLegend);
|
|
171
173
|
const items = getGroupedLegendItems({
|
|
@@ -195,8 +197,8 @@ export function getLegendComponents(args) {
|
|
|
195
197
|
}
|
|
196
198
|
const top = chartHeight - chartMargin.bottom - preparedLegend.height;
|
|
197
199
|
const offset = {
|
|
198
|
-
left: chartMargin.left
|
|
200
|
+
left: chartMargin.left,
|
|
199
201
|
top,
|
|
200
202
|
};
|
|
201
|
-
return { legendConfig: { offset, pagination }, legendItems: items };
|
|
203
|
+
return { legendConfig: { offset, pagination, maxWidth: maxLegendWidth }, legendItems: items };
|
|
202
204
|
}
|
|
@@ -26,6 +26,7 @@ export type PreparedLegend = Required<Omit<ChartLegend, 'title' | 'colorScale'>>
|
|
|
26
26
|
ticks: {
|
|
27
27
|
labelsMargin: number;
|
|
28
28
|
labelsLineHeight: number;
|
|
29
|
+
style: BaseTextStyle;
|
|
29
30
|
};
|
|
30
31
|
colorScale: {
|
|
31
32
|
colors: string[];
|
|
@@ -52,6 +53,7 @@ export type LegendConfig = {
|
|
|
52
53
|
left: number;
|
|
53
54
|
top: number;
|
|
54
55
|
};
|
|
56
|
+
maxWidth: number;
|
|
55
57
|
pagination?: {
|
|
56
58
|
pages: {
|
|
57
59
|
start: number;
|
|
@@ -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
|
});
|