@gravity-ui/charts 1.34.8 → 1.34.9
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/AxisX/AxisX.js +0 -1
- package/dist/cjs/components/AxisX/prepare-axis-data.js +4 -23
- package/dist/cjs/components/AxisX/types.d.ts +1 -9
- package/dist/cjs/components/AxisY/prepare-axis-title.js +3 -34
- package/dist/cjs/components/AxisY/types.d.ts +1 -9
- package/dist/cjs/components/ChartInner/index.js +6 -6
- package/dist/cjs/components/ChartInner/useChartInnerProps.d.ts +0 -1
- package/dist/cjs/components/ChartInner/useChartInnerProps.js +16 -6
- package/dist/cjs/components/ChartInner/useChartInnerState.d.ts +2 -3
- package/dist/cjs/components/ChartInner/useChartInnerState.js +14 -22
- package/dist/cjs/components/RangeSlider/index.d.ts +0 -1
- package/dist/cjs/components/RangeSlider/index.js +1 -8
- package/dist/cjs/components/types/index.d.ts +9 -0
- package/dist/cjs/components/types/index.js +1 -0
- package/dist/cjs/components/utils/axis-title.d.ts +6 -0
- package/dist/cjs/components/utils/axis-title.js +39 -0
- package/dist/cjs/components/{utils.d.ts → utils/index.d.ts} +3 -3
- package/dist/cjs/components/{utils.js → utils/index.js} +1 -1
- package/dist/cjs/hooks/useAxisScales/index.js +3 -1
- package/dist/cjs/hooks/useAxisScales/x-scale.d.ts +0 -6
- package/dist/cjs/hooks/useAxisScales/x-scale.js +12 -35
- package/dist/cjs/hooks/useRangeSlider/types.d.ts +1 -1
- package/dist/cjs/hooks/useSeries/prepare-legend.js +2 -1
- package/dist/cjs/utils/chart/zoom.d.ts +2 -1
- package/dist/cjs/utils/chart/zoom.js +9 -0
- package/dist/esm/components/AxisX/AxisX.js +0 -1
- package/dist/esm/components/AxisX/prepare-axis-data.js +4 -23
- package/dist/esm/components/AxisX/types.d.ts +1 -9
- package/dist/esm/components/AxisY/prepare-axis-title.js +3 -34
- package/dist/esm/components/AxisY/types.d.ts +1 -9
- package/dist/esm/components/ChartInner/index.js +6 -6
- package/dist/esm/components/ChartInner/useChartInnerProps.d.ts +0 -1
- package/dist/esm/components/ChartInner/useChartInnerProps.js +16 -6
- package/dist/esm/components/ChartInner/useChartInnerState.d.ts +2 -3
- package/dist/esm/components/ChartInner/useChartInnerState.js +14 -22
- package/dist/esm/components/RangeSlider/index.d.ts +0 -1
- package/dist/esm/components/RangeSlider/index.js +1 -8
- package/dist/esm/components/types/index.d.ts +9 -0
- package/dist/esm/components/types/index.js +1 -0
- package/dist/esm/components/utils/axis-title.d.ts +6 -0
- package/dist/esm/components/utils/axis-title.js +39 -0
- package/dist/esm/components/{utils.d.ts → utils/index.d.ts} +3 -3
- package/dist/esm/components/{utils.js → utils/index.js} +1 -1
- package/dist/esm/hooks/useAxisScales/index.js +3 -1
- package/dist/esm/hooks/useAxisScales/x-scale.d.ts +0 -6
- package/dist/esm/hooks/useAxisScales/x-scale.js +12 -35
- package/dist/esm/hooks/useRangeSlider/types.d.ts +1 -1
- package/dist/esm/hooks/useSeries/prepare-legend.js +2 -1
- package/dist/esm/utils/chart/zoom.d.ts +2 -1
- package/dist/esm/utils/chart/zoom.js +9 -0
- package/package.json +1 -1
|
@@ -32,7 +32,6 @@ export const AxisX = (props) => {
|
|
|
32
32
|
.attr('class', b('title'))
|
|
33
33
|
.append('text')
|
|
34
34
|
.attr('text-anchor', 'start')
|
|
35
|
-
.style('dominant-baseline', 'text-after-edge')
|
|
36
35
|
.style('transform', `translate(${preparedAxisData.title.x}px, ${preparedAxisData.title.y}px) rotate(${preparedAxisData.title.rotate}deg) translate(0px, ${preparedAxisData.title.offset}px)`)
|
|
37
36
|
.attr('font-size', preparedAxisData.title.style.fontSize)
|
|
38
37
|
.selectAll('tspan')
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { getUniqId } from '@gravity-ui/uikit';
|
|
2
|
-
import { calculateSin, formatAxisTickLabel, getBandsPosition, getLabelsSize, getMinSpaceBetween, getTextSizeFn, getTextWithElipsis,
|
|
2
|
+
import { calculateSin, formatAxisTickLabel, getBandsPosition, getLabelsSize, getMinSpaceBetween, getTextSizeFn, getTextWithElipsis, } from '../../utils';
|
|
3
3
|
import { getXAxisTickValues } from '../../utils/chart/axis/x-axis';
|
|
4
|
+
import { getMultilineTitleContentRows } from '../utils/axis-title';
|
|
4
5
|
async function getSvgAxisLabel({ getTextSize, text, axis, top, left, labelMaxWidth, axisWidth, boundsOffsetLeft, boundsOffsetRight, }) {
|
|
5
6
|
var _a;
|
|
6
7
|
const rotation = axis.labels.rotation;
|
|
@@ -189,23 +190,7 @@ export async function prepareXAxisData({ axis, boundsOffsetLeft, boundsOffsetRig
|
|
|
189
190
|
const titleContent = [];
|
|
190
191
|
const titleMaxWidth = axisWidth;
|
|
191
192
|
if (axis.title.maxRowCount > 1) {
|
|
192
|
-
|
|
193
|
-
text: axis.title.text,
|
|
194
|
-
style: axis.title.style,
|
|
195
|
-
width: titleMaxWidth,
|
|
196
|
-
getTextSize: getTitleTextSize,
|
|
197
|
-
});
|
|
198
|
-
for (let i = 0; i < axis.title.maxRowCount && i < titleTextRows.length; i++) {
|
|
199
|
-
const textRow = titleTextRows[i];
|
|
200
|
-
const textRowContent = textRow.text.trim();
|
|
201
|
-
const textRowSize = await getTitleTextSize(textRowContent);
|
|
202
|
-
titleContent.push({
|
|
203
|
-
text: textRowContent,
|
|
204
|
-
x: 0,
|
|
205
|
-
y: textRow.y,
|
|
206
|
-
size: textRowSize,
|
|
207
|
-
});
|
|
208
|
-
}
|
|
193
|
+
titleContent.push(...(await getMultilineTitleContentRows({ axis, titleMaxWidth })));
|
|
209
194
|
}
|
|
210
195
|
else {
|
|
211
196
|
const text = await getTextWithElipsis({
|
|
@@ -245,11 +230,7 @@ export async function prepareXAxisData({ axis, boundsOffsetLeft, boundsOffsetRig
|
|
|
245
230
|
style: axis.title.style,
|
|
246
231
|
size: titleTextSize,
|
|
247
232
|
x,
|
|
248
|
-
y: height +
|
|
249
|
-
axis.labels.margin +
|
|
250
|
-
axis.labels.height +
|
|
251
|
-
axis.title.margin +
|
|
252
|
-
titleTextSize.height,
|
|
233
|
+
y: height + axis.labels.margin + axis.labels.height + axis.title.margin,
|
|
253
234
|
rotate: 0,
|
|
254
235
|
offset: 0,
|
|
255
236
|
};
|
|
@@ -1,14 +1,6 @@
|
|
|
1
1
|
import type { DashStyle } from 'src/constants';
|
|
2
2
|
import type { BaseTextStyle, HtmlItem, PlotLayerPlacement, PointPosition } from '../../types';
|
|
3
|
-
|
|
4
|
-
text: string;
|
|
5
|
-
x: number;
|
|
6
|
-
y: number;
|
|
7
|
-
size: {
|
|
8
|
-
width: number;
|
|
9
|
-
height: number;
|
|
10
|
-
};
|
|
11
|
-
};
|
|
3
|
+
import type { TextRowData } from '../types';
|
|
12
4
|
export type AxisSvgLabelData = {
|
|
13
5
|
x: number;
|
|
14
6
|
y: number;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { calculateCos, calculateSin, getLabelsSize, getTextSizeFn, getTextWithElipsis,
|
|
1
|
+
import { calculateCos, calculateSin, getLabelsSize, getTextSizeFn, getTextWithElipsis, } from '../../utils';
|
|
2
|
+
import { getMultilineTitleContentRows } from '../utils/axis-title';
|
|
2
3
|
export async function prepareSvgYAxisTitle({ axis, axisTop, axisHeight, axisWidth, axisLabelsWidth, }) {
|
|
3
4
|
if (!axis.title.text || axis.title.html) {
|
|
4
5
|
return null;
|
|
@@ -10,39 +11,7 @@ export async function prepareSvgYAxisTitle({ axis, axisTop, axisHeight, axisWidt
|
|
|
10
11
|
const titleContent = [];
|
|
11
12
|
const titleMaxWidth = rotateAngle === 0 ? axis.title.maxWidth : sin * axisHeight;
|
|
12
13
|
if (axis.title.maxRowCount > 1) {
|
|
13
|
-
|
|
14
|
-
text: axis.title.text,
|
|
15
|
-
style: axis.title.style,
|
|
16
|
-
width: titleMaxWidth,
|
|
17
|
-
getTextSize: getTitleTextSize,
|
|
18
|
-
});
|
|
19
|
-
titleTextRows = titleTextRows.reduce((acc, row, index) => {
|
|
20
|
-
if (index < axis.title.maxRowCount) {
|
|
21
|
-
acc.push(row);
|
|
22
|
-
}
|
|
23
|
-
else {
|
|
24
|
-
acc[axis.title.maxRowCount - 1].text += row.text;
|
|
25
|
-
}
|
|
26
|
-
return acc;
|
|
27
|
-
}, []);
|
|
28
|
-
for (let i = 0; i < titleTextRows.length; i++) {
|
|
29
|
-
const textRow = titleTextRows[i];
|
|
30
|
-
let textRowContent = textRow.text.trim();
|
|
31
|
-
if (i === titleTextRows.length - 1) {
|
|
32
|
-
textRowContent = await getTextWithElipsis({
|
|
33
|
-
text: textRowContent,
|
|
34
|
-
maxWidth: titleMaxWidth,
|
|
35
|
-
getTextWidth: async (s) => (await getTitleTextSize(s)).width,
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
const textRowSize = await getTitleTextSize(textRowContent);
|
|
39
|
-
titleContent.push({
|
|
40
|
-
text: textRowContent,
|
|
41
|
-
x: 0,
|
|
42
|
-
y: textRow.y,
|
|
43
|
-
size: textRowSize,
|
|
44
|
-
});
|
|
45
|
-
}
|
|
14
|
+
titleContent.push(...(await getMultilineTitleContentRows({ axis, titleMaxWidth })));
|
|
46
15
|
}
|
|
47
16
|
else {
|
|
48
17
|
const text = await getTextWithElipsis({
|
|
@@ -1,14 +1,6 @@
|
|
|
1
1
|
import type { DashStyle } from 'src/constants';
|
|
2
2
|
import type { BaseTextStyle, HtmlItem, PlotLayerPlacement, PointPosition } from '../../types';
|
|
3
|
-
|
|
4
|
-
text: string;
|
|
5
|
-
x: number;
|
|
6
|
-
y: number;
|
|
7
|
-
size: {
|
|
8
|
-
width: number;
|
|
9
|
-
height: number;
|
|
10
|
-
};
|
|
11
|
-
};
|
|
3
|
+
import type { TextRowData } from '../types';
|
|
12
4
|
export type AxisSvgLabelData = {
|
|
13
5
|
x: number;
|
|
14
6
|
y: number;
|
|
@@ -26,7 +26,7 @@ import './styles.css';
|
|
|
26
26
|
const b = block('chart');
|
|
27
27
|
const DEBOUNCED_VALUE_DELAY = 10;
|
|
28
28
|
export const ChartInner = (props) => {
|
|
29
|
-
var _a, _b, _c, _d, _e
|
|
29
|
+
var _a, _b, _c, _d, _e;
|
|
30
30
|
const { width, height, data, onReady } = props;
|
|
31
31
|
const svgRef = React.useRef(null);
|
|
32
32
|
const resetZoomButtonRef = React.useRef(null);
|
|
@@ -60,13 +60,13 @@ export const ChartInner = (props) => {
|
|
|
60
60
|
}, [data.xAxis]);
|
|
61
61
|
const { initialized, setInitialized, tooltipPinned, togglePinTooltip, unpinTooltip, rangeSliderState, updateRangeSliderState, updateZoomState, zoomState, } = useChartInnerState({
|
|
62
62
|
dispatcher,
|
|
63
|
-
preparedChart,
|
|
64
63
|
preparedRangeSlider,
|
|
65
64
|
tooltip: preparedTooltip,
|
|
66
65
|
});
|
|
67
66
|
const { allPreparedSeries, boundsHeight, boundsOffsetLeft, boundsOffsetTop, boundsWidth, handleLegendItemClick, isOutsideBounds, legendConfig, legendItems, preparedLegend, preparedSeries, preparedSeriesOptions, preparedSplit, prevHeight, prevWidth, shapes, shapesData, shapesReady, xAxis, xScale, yAxis, yScale, } = useChartInnerProps(Object.assign(Object.assign({}, props), { clipPathId,
|
|
68
67
|
dispatcher,
|
|
69
|
-
htmlLayout, plotNode: plotRef.current, preparedChart,
|
|
68
|
+
htmlLayout, plotNode: plotRef.current, preparedChart,
|
|
69
|
+
rangeSliderState, svgContainer: svgRef.current, updateZoomState,
|
|
70
70
|
zoomState }));
|
|
71
71
|
const debouncedBoundsWidth = useDebouncedValue({
|
|
72
72
|
value: boundsWidth,
|
|
@@ -96,8 +96,8 @@ export const ChartInner = (props) => {
|
|
|
96
96
|
tooltipThrottle: preparedTooltip.throttle,
|
|
97
97
|
isOutsideBounds,
|
|
98
98
|
});
|
|
99
|
-
const clickHandler = (
|
|
100
|
-
const pointerMoveHandler = (
|
|
99
|
+
const clickHandler = (_b = (_a = data.chart) === null || _a === void 0 ? void 0 : _a.events) === null || _b === void 0 ? void 0 : _b.click;
|
|
100
|
+
const pointerMoveHandler = (_d = (_c = data.chart) === null || _c === void 0 ? void 0 : _c.events) === null || _d === void 0 ? void 0 : _d.pointermove;
|
|
101
101
|
const prevRangeSliderDefaultRange = usePrevious(preparedRangeSlider.defaultRange);
|
|
102
102
|
useCrosshair({
|
|
103
103
|
split: preparedSplit,
|
|
@@ -246,7 +246,7 @@ export const ChartInner = (props) => {
|
|
|
246
246
|
React.createElement("g", { ref: plotBeforeRef }),
|
|
247
247
|
shapes,
|
|
248
248
|
React.createElement("g", { ref: plotAfterRef })),
|
|
249
|
-
((
|
|
249
|
+
((_e = xAxis === null || xAxis === void 0 ? void 0 : xAxis.rangeSlider) === null || _e === void 0 ? void 0 : _e.enabled) && (React.createElement(RangeSlider, { boundsOffsetLeft: debouncedOffsetLeft, boundsWidth: debouncedBoundsWidth, height: height, htmlLayout: htmlLayout, onUpdate: updateRangeSliderState, preparedChart: preparedChart, preparedLegend: preparedLegend, preparedSeries: debouncedAllPreparedSeries, preparedSeriesOptions: preparedSeriesOptions, preparedRangeSlider: xAxis.rangeSlider, rangeSliderState: rangeSliderState, ref: rangeSliderRef, width: width, xAxis: data.xAxis, yAxis: data.yAxis })),
|
|
250
250
|
(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 }))));
|
|
251
251
|
return (React.createElement("div", { className: b() },
|
|
252
252
|
React.createElement("svg", { ref: svgRef, width: width, height: height,
|
|
@@ -11,7 +11,6 @@ type Props = ChartInnerProps & {
|
|
|
11
11
|
svgContainer: SVGGElement | null;
|
|
12
12
|
updateZoomState: (nextZoomState: Partial<ZoomState>) => void;
|
|
13
13
|
zoomState: Partial<ZoomState>;
|
|
14
|
-
rangeSliderDomain?: [number, number];
|
|
15
14
|
rangeSliderState?: RangeSliderState;
|
|
16
15
|
};
|
|
17
16
|
export declare function useChartInnerProps(props: Props): {
|
|
@@ -4,7 +4,7 @@ import { useAxis, useAxisScales, useChartDimensions, useNormalizedOriginalData,
|
|
|
4
4
|
import { getYAxisWidth } from '../../hooks/useChartDimensions/utils';
|
|
5
5
|
import { getLegendComponents } from '../../hooks/useSeries/prepare-legend';
|
|
6
6
|
import { getPreparedOptions } from '../../hooks/useSeries/prepare-options';
|
|
7
|
-
import { getZoomedSeriesData } from '../../utils';
|
|
7
|
+
import { getEffectiveXRange, getZoomedSeriesData } from '../../utils';
|
|
8
8
|
import { hasAtLeastOneSeriesDataPerPlot } from './utils';
|
|
9
9
|
const CLIP_PATH_BY_SERIES_TYPE = {
|
|
10
10
|
[SERIES_TYPE.Scatter]: false,
|
|
@@ -35,7 +35,7 @@ function getBoundsOffsetLeft(args) {
|
|
|
35
35
|
}
|
|
36
36
|
export function useChartInnerProps(props) {
|
|
37
37
|
var _a;
|
|
38
|
-
const { clipPathId, data, dispatcher, height, htmlLayout, plotNode, preparedChart,
|
|
38
|
+
const { clipPathId, data, dispatcher, height, htmlLayout, plotNode, preparedChart, rangeSliderState, svgContainer, width, updateZoomState, zoomState, } = props;
|
|
39
39
|
const prevWidth = usePrevious(width);
|
|
40
40
|
const prevHeight = usePrevious(height);
|
|
41
41
|
const colors = React.useMemo(() => {
|
|
@@ -57,14 +57,25 @@ export function useChartInnerProps(props) {
|
|
|
57
57
|
seriesData: normalizedSeriesData,
|
|
58
58
|
seriesOptions: data.series.options,
|
|
59
59
|
});
|
|
60
|
+
const effectiveZoomState = React.useMemo(() => {
|
|
61
|
+
const result = {};
|
|
62
|
+
const effectiveX = getEffectiveXRange(zoomState.x, rangeSliderState);
|
|
63
|
+
if (effectiveX !== undefined) {
|
|
64
|
+
result.x = effectiveX;
|
|
65
|
+
}
|
|
66
|
+
if (zoomState.y !== undefined) {
|
|
67
|
+
result.y = zoomState.y;
|
|
68
|
+
}
|
|
69
|
+
return result;
|
|
70
|
+
}, [zoomState, rangeSliderState]);
|
|
60
71
|
const { preparedSeries, preparedShapesSeries } = React.useMemo(() => {
|
|
61
72
|
return getZoomedSeriesData({
|
|
62
73
|
seriesData: allPreparedSeries,
|
|
63
74
|
xAxis: normalizedXAxis,
|
|
64
75
|
yAxis: normalizedYAxis,
|
|
65
|
-
zoomState,
|
|
76
|
+
zoomState: effectiveZoomState,
|
|
66
77
|
});
|
|
67
|
-
}, [allPreparedSeries, normalizedXAxis, normalizedYAxis,
|
|
78
|
+
}, [allPreparedSeries, normalizedXAxis, normalizedYAxis, effectiveZoomState]);
|
|
68
79
|
const { legendConfig, legendItems } = React.useMemo(() => {
|
|
69
80
|
if (!preparedLegend) {
|
|
70
81
|
return { legendConfig: undefined, legendItems: [] };
|
|
@@ -126,7 +137,7 @@ export function useChartInnerProps(props) {
|
|
|
126
137
|
htmlLayout,
|
|
127
138
|
clipPathId,
|
|
128
139
|
isOutsideBounds,
|
|
129
|
-
zoomState,
|
|
140
|
+
zoomState: effectiveZoomState,
|
|
130
141
|
});
|
|
131
142
|
const handleAttemptToSetZoomState = React.useCallback((nextZoomState) => {
|
|
132
143
|
const { preparedSeries: nextZoomedSeriesData } = getZoomedSeriesData({
|
|
@@ -147,7 +158,6 @@ export function useChartInnerProps(props) {
|
|
|
147
158
|
plotContainerWidth: boundsWidth,
|
|
148
159
|
preparedSplit,
|
|
149
160
|
preparedZoom: preparedChart.zoom,
|
|
150
|
-
rangeSliderDomain,
|
|
151
161
|
xAxis,
|
|
152
162
|
xScale,
|
|
153
163
|
yAxis,
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { Dispatch } from 'd3';
|
|
3
|
-
import type {
|
|
3
|
+
import type { PreparedRangeSlider, PreparedTooltip, RangeSliderState, ZoomState } from '../../hooks';
|
|
4
4
|
type Props = {
|
|
5
5
|
dispatcher: Dispatch<object>;
|
|
6
|
-
preparedChart: PreparedChart;
|
|
7
6
|
preparedRangeSlider: PreparedRangeSlider;
|
|
8
7
|
tooltip?: PreparedTooltip;
|
|
9
8
|
};
|
|
@@ -14,7 +13,7 @@ export declare function useChartInnerState(props: Props): {
|
|
|
14
13
|
tooltipPinned: boolean;
|
|
15
14
|
togglePinTooltip: ((value: boolean, event: React.MouseEvent) => void) | undefined;
|
|
16
15
|
unpinTooltip: (() => void) | undefined;
|
|
17
|
-
updateRangeSliderState: (nextRangeSliderState?: RangeSliderState
|
|
16
|
+
updateRangeSliderState: (nextRangeSliderState?: RangeSliderState) => void;
|
|
18
17
|
updateZoomState: (nextZoomState: Partial<ZoomState>) => void;
|
|
19
18
|
zoomState: Partial<ZoomState>;
|
|
20
19
|
};
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import isEqual from 'lodash/isEqual';
|
|
3
|
-
import { ZOOM_TYPE } from '../../constants';
|
|
4
3
|
import { EventType, isMacintosh } from '../../utils';
|
|
5
|
-
const RANGE_SLIDER_SYNC_ZOOM_TYPES = [ZOOM_TYPE.X, ZOOM_TYPE.XY];
|
|
6
4
|
export function useChartInnerState(props) {
|
|
7
5
|
var _a, _b;
|
|
8
|
-
const { dispatcher,
|
|
6
|
+
const { dispatcher, preparedRangeSlider, tooltip } = props;
|
|
9
7
|
const [tooltipPinned, setTooltipPinned] = React.useState(false);
|
|
10
8
|
const [zoomState, setZoomState] = React.useState({});
|
|
11
9
|
const [rangeSliderState, setRangeSliderState] = React.useState();
|
|
@@ -13,7 +11,6 @@ export function useChartInnerState(props) {
|
|
|
13
11
|
const tooltipEnabled = tooltip === null || tooltip === void 0 ? void 0 : tooltip.enabled;
|
|
14
12
|
const tooltipPinEnabled = (_a = tooltip === null || tooltip === void 0 ? void 0 : tooltip.pin) === null || _a === void 0 ? void 0 : _a.enabled;
|
|
15
13
|
const modifierKey = (_b = tooltip === null || tooltip === void 0 ? void 0 : tooltip.pin) === null || _b === void 0 ? void 0 : _b.modifierKey;
|
|
16
|
-
const rangeSliderEnabled = preparedRangeSlider.enabled;
|
|
17
14
|
const togglePinTooltip = React.useCallback((value, event) => {
|
|
18
15
|
let resultValue = value;
|
|
19
16
|
if (value && modifierKey) {
|
|
@@ -36,16 +33,17 @@ export function useChartInnerState(props) {
|
|
|
36
33
|
const updateZoomState = React.useCallback((nextZoomState) => {
|
|
37
34
|
if (!isEqual(zoomState, nextZoomState)) {
|
|
38
35
|
setZoomState(nextZoomState);
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
36
|
+
// One-way sync: zoom → range slider. On full reset clear the slider state;
|
|
37
|
+
// on x-zoom change sync slider to the zoomed x range.
|
|
38
|
+
if (Object.keys(nextZoomState).length === 0) {
|
|
39
|
+
setRangeSliderState(undefined);
|
|
40
|
+
}
|
|
41
|
+
else if (nextZoomState.x !== undefined) {
|
|
42
|
+
setRangeSliderState({ min: nextZoomState.x[0], max: nextZoomState.x[1] });
|
|
45
43
|
}
|
|
46
44
|
}
|
|
47
|
-
}, [
|
|
48
|
-
const updateRangeSliderState = React.useCallback((nextRangeSliderState
|
|
45
|
+
}, [zoomState]);
|
|
46
|
+
const updateRangeSliderState = React.useCallback((nextRangeSliderState) => {
|
|
49
47
|
if (!isEqual(rangeSliderState, nextRangeSliderState)) {
|
|
50
48
|
setRangeSliderState(nextRangeSliderState
|
|
51
49
|
? {
|
|
@@ -53,17 +51,11 @@ export function useChartInnerState(props) {
|
|
|
53
51
|
min: nextRangeSliderState.min,
|
|
54
52
|
}
|
|
55
53
|
: undefined);
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
Object.keys(zoomState || {}).length > 0 &&
|
|
60
|
-
RANGE_SLIDER_SYNC_ZOOM_TYPES.includes(preparedChart.zoom.type)) {
|
|
61
|
-
setZoomState({
|
|
62
|
-
x: [nextRangeSliderState.min, nextRangeSliderState.max],
|
|
63
|
-
});
|
|
64
|
-
}
|
|
54
|
+
// Moving the slider clears zoom so the slider is always the sole source of truth.
|
|
55
|
+
// Zoom stays active only until the user explicitly touches the slider.
|
|
56
|
+
setZoomState({});
|
|
65
57
|
}
|
|
66
|
-
}, [
|
|
58
|
+
}, [rangeSliderState]);
|
|
67
59
|
return {
|
|
68
60
|
initialized,
|
|
69
61
|
rangeSliderState,
|
|
@@ -2,7 +2,6 @@ import React from 'react';
|
|
|
2
2
|
import type { RangeSliderProps } from '../../hooks';
|
|
3
3
|
import './styles.css';
|
|
4
4
|
export interface RangeSliderHandle {
|
|
5
|
-
getDomain: () => [number, number] | undefined;
|
|
6
5
|
resetState: () => void;
|
|
7
6
|
}
|
|
8
7
|
export declare const RangeSlider: React.MemoExoticComponent<React.ForwardRefExoticComponent<RangeSliderProps & React.RefAttributes<RangeSliderHandle>>>;
|
|
@@ -20,24 +20,17 @@ function RangeSliderComponent(props, forwardedRef) {
|
|
|
20
20
|
* in that case, the extra computations simply don't happen.
|
|
21
21
|
*
|
|
22
22
|
* Methods:
|
|
23
|
-
* - getDomain: returns the current X-axis domain (for synchronization with zoom)
|
|
24
23
|
* - resetState: resets the slider state to its initial value (these calculations
|
|
25
24
|
* are only possible within the component since it has access to the prepared series data)
|
|
26
25
|
*/
|
|
27
26
|
React.useImperativeHandle(forwardedRef, () => ({
|
|
28
|
-
getDomain: () => {
|
|
29
|
-
if (!xScale || isBandScale(xScale)) {
|
|
30
|
-
return undefined;
|
|
31
|
-
}
|
|
32
|
-
return xScale.domain().map(Number);
|
|
33
|
-
},
|
|
34
27
|
resetState: () => {
|
|
35
28
|
if (xScale && !isBandScale(xScale)) {
|
|
36
29
|
const initialRangeSliderState = getInitialRangeSliderState({
|
|
37
30
|
xScale,
|
|
38
31
|
defaultRange,
|
|
39
32
|
});
|
|
40
|
-
onUpdate(initialRangeSliderState
|
|
33
|
+
onUpdate(initialRangeSliderState);
|
|
41
34
|
}
|
|
42
35
|
},
|
|
43
36
|
}), [defaultRange, onUpdate, xScale]);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { getTextSizeFn, getTextWithElipsis, wrapText } from '../../utils';
|
|
2
|
+
export async function getMultilineTitleContentRows({ axis, titleMaxWidth, }) {
|
|
3
|
+
const titleContent = [];
|
|
4
|
+
const getTitleTextSize = getTextSizeFn({ style: axis.title.style });
|
|
5
|
+
let titleTextRows = await wrapText({
|
|
6
|
+
text: axis.title.text,
|
|
7
|
+
style: axis.title.style,
|
|
8
|
+
width: titleMaxWidth,
|
|
9
|
+
getTextSize: getTitleTextSize,
|
|
10
|
+
});
|
|
11
|
+
titleTextRows = titleTextRows.reduce((acc, row, index) => {
|
|
12
|
+
if (index < axis.title.maxRowCount) {
|
|
13
|
+
acc.push(row);
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
acc[axis.title.maxRowCount - 1].text += row.text;
|
|
17
|
+
}
|
|
18
|
+
return acc;
|
|
19
|
+
}, []);
|
|
20
|
+
for (let i = 0; i < titleTextRows.length; i++) {
|
|
21
|
+
const textRow = titleTextRows[i];
|
|
22
|
+
let textRowContent = textRow.text.trim();
|
|
23
|
+
if (i === titleTextRows.length - 1) {
|
|
24
|
+
textRowContent = await getTextWithElipsis({
|
|
25
|
+
text: textRowContent,
|
|
26
|
+
maxWidth: titleMaxWidth,
|
|
27
|
+
getTextWidth: async (s) => (await getTitleTextSize(s)).width,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
const textRowSize = await getTitleTextSize(textRowContent);
|
|
31
|
+
titleContent.push({
|
|
32
|
+
text: textRowContent,
|
|
33
|
+
x: 0,
|
|
34
|
+
y: textRow.y,
|
|
35
|
+
size: textRowSize,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return titleContent;
|
|
39
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { DashStyle } from '
|
|
2
|
-
import type { ChartScaleLinear, ChartScaleTime } from '
|
|
3
|
-
import type { ChartAxisRangeSlider } from '
|
|
1
|
+
import type { DashStyle } from '../../constants';
|
|
2
|
+
import type { ChartScaleLinear, ChartScaleTime } from '../../hooks';
|
|
3
|
+
import type { ChartAxisRangeSlider } from '../../types';
|
|
4
4
|
export declare function getInitialRangeSliderState(args: {
|
|
5
5
|
xScale: ChartScaleLinear | ChartScaleTime;
|
|
6
6
|
defaultRange?: ChartAxisRangeSlider['defaultRange'];
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { duration } from '@gravity-ui/date-utils';
|
|
2
2
|
import { line as lineGenerator } from 'd3';
|
|
3
3
|
import { select } from 'd3-selection';
|
|
4
|
-
import { getLineDashArray, isTimeScale } from '
|
|
4
|
+
import { getLineDashArray, isTimeScale } from '../../utils';
|
|
5
5
|
export function getInitialRangeSliderState(args) {
|
|
6
6
|
const { defaultRange, xScale } = args;
|
|
7
7
|
let minRange;
|
|
@@ -9,7 +9,9 @@ export { createXScale } from './x-scale';
|
|
|
9
9
|
export { createYScale } from './y-scale';
|
|
10
10
|
const createScales = (args) => {
|
|
11
11
|
const { boundsWidth, boundsHeight, isRangeSlider, rangeSliderState, series, split, xAxis, yAxis, zoomState, } = args;
|
|
12
|
-
|
|
12
|
+
// For range slider: always use all series regardless of visibility so the slider domain
|
|
13
|
+
// stays stable when the user hides/shows series via the legend.
|
|
14
|
+
let visibleSeries = isRangeSlider ? series : getOnlyVisibleSeries(series);
|
|
13
15
|
// Reassign to all series in case of all series unselected,
|
|
14
16
|
// otherwise we will get an empty space without grid
|
|
15
17
|
visibleSeries = visibleSeries.length === 0 ? series : visibleSeries;
|
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
import type { PreparedAxis, PreparedSeries, RangeSliderState } from '../../hooks';
|
|
2
2
|
import type { ChartAxis, ChartSeries } from '../../types';
|
|
3
|
-
export declare function getXMaxDomainResult(args: {
|
|
4
|
-
xMaxDomain: number;
|
|
5
|
-
xMaxProps?: number;
|
|
6
|
-
xMaxRangeSlider?: number;
|
|
7
|
-
xMaxZoom?: number;
|
|
8
|
-
}): number;
|
|
9
3
|
export declare function createXScale(args: {
|
|
10
4
|
axis: PreparedAxis | ChartAxis;
|
|
11
5
|
boundsWidth: number;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { extent, scaleBand, scaleLinear, scaleLog, scaleUtc } from 'd3';
|
|
2
2
|
import get from 'lodash/get';
|
|
3
3
|
import { DEFAULT_AXIS_TYPE, SERIES_TYPE } from '../../constants';
|
|
4
|
-
import {
|
|
4
|
+
import { getDefaultMaxXAxisValue, getDefaultMinXAxisValue, getDomainDataXBySeries, getEffectiveXRange, } from '../../utils';
|
|
5
5
|
import { getBandSize } from '../utils/get-band-size';
|
|
6
6
|
import { checkIsPointDomain, filterCategoriesByVisibleSeries, getMinMaxPropsOrState, hasOnlyMarkerSeries, validateArrayData, } from './utils';
|
|
7
7
|
const X_AXIS_ZOOM_PADDING = 0.02;
|
|
@@ -27,23 +27,6 @@ function isSeriesWithXAxisOffset(series) {
|
|
|
27
27
|
const types = [SERIES_TYPE.Heatmap, SERIES_TYPE.BarX];
|
|
28
28
|
return series.some((s) => types.includes(s.type));
|
|
29
29
|
}
|
|
30
|
-
export function getXMaxDomainResult(args) {
|
|
31
|
-
const { xMaxDomain, xMaxProps, xMaxRangeSlider, xMaxZoom } = args;
|
|
32
|
-
let xMaxDomainResult = xMaxDomain;
|
|
33
|
-
// When xMaxRangeSlider is provided, we use it directly without considering xMaxDomain.
|
|
34
|
-
// This is intentional: the range slider needs to display the chart's maxPadding area,
|
|
35
|
-
// which would be clipped if we constrained it to xMaxDomain.
|
|
36
|
-
if (typeof xMaxRangeSlider === 'number') {
|
|
37
|
-
xMaxDomainResult = xMaxRangeSlider;
|
|
38
|
-
}
|
|
39
|
-
else if (typeof xMaxZoom === 'number' && xMaxZoom < xMaxDomain) {
|
|
40
|
-
xMaxDomainResult = xMaxZoom;
|
|
41
|
-
}
|
|
42
|
-
else if (typeof xMaxProps === 'number' && xMaxProps < xMaxDomain) {
|
|
43
|
-
xMaxDomainResult = xMaxProps;
|
|
44
|
-
}
|
|
45
|
-
return xMaxDomainResult;
|
|
46
|
-
}
|
|
47
30
|
function getXScaleRange({ boundsWidth, hasZoomX }) {
|
|
48
31
|
const xAxisZoomPadding = boundsWidth * X_AXIS_ZOOM_PADDING;
|
|
49
32
|
const xRange = [0, boundsWidth];
|
|
@@ -54,22 +37,17 @@ function getXScaleRange({ boundsWidth, hasZoomX }) {
|
|
|
54
37
|
// eslint-disable-next-line complexity
|
|
55
38
|
export function createXScale(args) {
|
|
56
39
|
const { axis, boundsWidth, series, rangeSliderState, zoomStateX } = args;
|
|
40
|
+
const effectiveX = getEffectiveXRange(zoomStateX, rangeSliderState);
|
|
41
|
+
const effectiveXMin = effectiveX === null || effectiveX === void 0 ? void 0 : effectiveX[0];
|
|
42
|
+
const effectiveXMax = effectiveX === null || effectiveX === void 0 ? void 0 : effectiveX[1];
|
|
57
43
|
const [xMinPropsOrState, xMaxPropsOrState] = getMinMaxPropsOrState({
|
|
58
44
|
axis,
|
|
59
|
-
maxValues: [
|
|
60
|
-
minValues: [
|
|
45
|
+
maxValues: [effectiveXMax],
|
|
46
|
+
minValues: [effectiveXMin],
|
|
61
47
|
});
|
|
62
48
|
const xType = get(axis, 'type', DEFAULT_AXIS_TYPE);
|
|
63
49
|
const hasZoomX = Boolean(zoomStateX);
|
|
64
|
-
|
|
65
|
-
if (rangeSliderState && xCategories) {
|
|
66
|
-
xCategories = getAxisCategories({
|
|
67
|
-
categories: xCategories,
|
|
68
|
-
min: rangeSliderState.min,
|
|
69
|
-
max: rangeSliderState.max,
|
|
70
|
-
order: axis.order,
|
|
71
|
-
});
|
|
72
|
-
}
|
|
50
|
+
const xCategories = get(axis, 'categories');
|
|
73
51
|
const maxPadding = get(axis, 'maxPadding', 0);
|
|
74
52
|
const xAxisMaxPadding = boundsWidth * maxPadding + calculateXAxisPadding(series);
|
|
75
53
|
const range = getXScaleRange({
|
|
@@ -193,12 +171,11 @@ export function createXScale(args) {
|
|
|
193
171
|
!isPointDomain
|
|
194
172
|
? xMinPropsOrState
|
|
195
173
|
: xMinTimestamp;
|
|
196
|
-
const xMax =
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
});
|
|
174
|
+
const xMax = typeof xMaxPropsOrState === 'number' &&
|
|
175
|
+
xMaxPropsOrState < xMaxTimestamp &&
|
|
176
|
+
!isPointDomain
|
|
177
|
+
? xMaxPropsOrState
|
|
178
|
+
: xMaxTimestamp;
|
|
202
179
|
domain = [xMin, xMax];
|
|
203
180
|
const scale = scaleUtc().domain(domain).range(range);
|
|
204
181
|
let offsetMin = 0;
|
|
@@ -13,7 +13,7 @@ export interface RangeSliderProps {
|
|
|
13
13
|
boundsWidth: number;
|
|
14
14
|
height: number;
|
|
15
15
|
htmlLayout: HTMLElement | null;
|
|
16
|
-
onUpdate: (nextRangeSliderState?: RangeSliderState
|
|
16
|
+
onUpdate: (nextRangeSliderState?: RangeSliderState) => void;
|
|
17
17
|
preparedChart: PreparedChart;
|
|
18
18
|
preparedLegend: PreparedLegend | null;
|
|
19
19
|
preparedRangeSlider: PreparedRangeSlider;
|
|
@@ -8,7 +8,8 @@ import { getDefaultColorStops, getDomainForContinuousColorScale, getLabelsSize }
|
|
|
8
8
|
export async function getPreparedLegend(args) {
|
|
9
9
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
10
10
|
const { legend, series } = args;
|
|
11
|
-
const
|
|
11
|
+
const seriesWithEnabledLegend = series.filter((s) => { var _a; return ((_a = s.legend) === null || _a === void 0 ? void 0 : _a.enabled) !== false; });
|
|
12
|
+
const enabled = Boolean(typeof (legend === null || legend === void 0 ? void 0 : legend.enabled) === 'boolean' ? legend === null || legend === void 0 ? void 0 : legend.enabled : seriesWithEnabledLegend.length > 1);
|
|
12
13
|
const defaultItemStyle = clone(legendDefaults.itemStyle);
|
|
13
14
|
const itemStyle = get(legend, 'itemStyle');
|
|
14
15
|
const computedItemStyle = merge(defaultItemStyle, itemStyle);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PreparedSeries, PreparedXAxis, PreparedYAxis } from '../../hooks';
|
|
1
|
+
import type { PreparedSeries, PreparedXAxis, PreparedYAxis, RangeSliderState } from '../../hooks';
|
|
2
2
|
import type { ZoomState } from '../../hooks/useZoom/types';
|
|
3
3
|
import type { ChartXAxis, ChartYAxis } from '../../types';
|
|
4
4
|
export declare function getZoomedSeriesData(args: {
|
|
@@ -10,3 +10,4 @@ export declare function getZoomedSeriesData(args: {
|
|
|
10
10
|
preparedSeries: PreparedSeries[];
|
|
11
11
|
preparedShapesSeries: PreparedSeries[];
|
|
12
12
|
};
|
|
13
|
+
export declare function getEffectiveXRange(zoomStateX: [number, number] | undefined, rangeSliderState: RangeSliderState | undefined): [number, number] | undefined;
|
|
@@ -123,3 +123,12 @@ export function getZoomedSeriesData(args) {
|
|
|
123
123
|
preparedShapesSeries: zoomedShapesSeriesData,
|
|
124
124
|
};
|
|
125
125
|
}
|
|
126
|
+
export function getEffectiveXRange(zoomStateX, rangeSliderState) {
|
|
127
|
+
if (zoomStateX && rangeSliderState) {
|
|
128
|
+
return [
|
|
129
|
+
Math.max(zoomStateX[0], rangeSliderState.min),
|
|
130
|
+
Math.min(zoomStateX[1], rangeSliderState.max),
|
|
131
|
+
];
|
|
132
|
+
}
|
|
133
|
+
return (zoomStateX !== null && zoomStateX !== void 0 ? zoomStateX : (rangeSliderState ? [rangeSliderState.min, rangeSliderState.max] : undefined));
|
|
134
|
+
}
|
|
@@ -32,7 +32,6 @@ export const AxisX = (props) => {
|
|
|
32
32
|
.attr('class', b('title'))
|
|
33
33
|
.append('text')
|
|
34
34
|
.attr('text-anchor', 'start')
|
|
35
|
-
.style('dominant-baseline', 'text-after-edge')
|
|
36
35
|
.style('transform', `translate(${preparedAxisData.title.x}px, ${preparedAxisData.title.y}px) rotate(${preparedAxisData.title.rotate}deg) translate(0px, ${preparedAxisData.title.offset}px)`)
|
|
37
36
|
.attr('font-size', preparedAxisData.title.style.fontSize)
|
|
38
37
|
.selectAll('tspan')
|