@gravity-ui/charts 1.35.2 → 1.37.1
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 +15 -8
- package/dist/cjs/components/AxisX/prepare-axis-data.js +12 -0
- package/dist/cjs/components/AxisX/styles.css +7 -2
- package/dist/cjs/components/AxisX/types.d.ts +5 -0
- package/dist/cjs/components/AxisY/AxisY.js +13 -7
- package/dist/cjs/components/AxisY/prepare-axis-data.js +14 -0
- package/dist/cjs/components/AxisY/styles.css +7 -2
- package/dist/cjs/components/AxisY/types.d.ts +5 -0
- package/dist/cjs/components/ChartInner/index.js +2 -0
- package/dist/cjs/components/ChartInner/styles.css +1 -0
- package/dist/cjs/components/ChartInner/useChartInnerHandlers.d.ts +3 -0
- package/dist/cjs/components/ChartInner/useChartInnerHandlers.js +26 -3
- package/dist/cjs/components/ChartInner/useChartInnerProps.d.ts +1 -0
- package/dist/cjs/components/Tooltip/ChartTooltipContent.d.ts +3 -1
- package/dist/cjs/components/Tooltip/ChartTooltipContent.js +9 -2
- package/dist/cjs/components/Tooltip/DefaultTooltipContent/Row.js +1 -1
- package/dist/cjs/components/Tooltip/index.js +5 -2
- package/dist/cjs/components/Tooltip/styles.css +8 -1
- package/dist/cjs/constants/defaults/axis.d.ts +4 -0
- package/dist/cjs/constants/defaults/axis.js +4 -0
- package/dist/cjs/hooks/useAxis/types.d.ts +8 -1
- package/dist/cjs/hooks/useAxis/x-axis.js +9 -3
- package/dist/cjs/hooks/useAxis/y-axis.js +7 -1
- package/dist/cjs/hooks/useNormalizedOriginalData/index.d.ts +1 -0
- package/dist/cjs/hooks/useTooltip/index.d.ts +3 -1
- package/dist/cjs/hooks/useTooltip/index.js +7 -3
- package/dist/cjs/types/chart/axis.d.ts +16 -1
- package/dist/cjs/types/chart/tooltip.d.ts +7 -1
- package/dist/cjs/utils/chart/format.js +17 -2
- package/dist/cjs/utils/chart/get-hovered-plots.d.ts +14 -0
- package/dist/cjs/utils/chart/get-hovered-plots.js +67 -0
- package/dist/cjs/utils/chart/time.d.ts +1 -0
- package/dist/cjs/utils/chart/time.js +9 -0
- package/dist/esm/components/AxisX/AxisX.js +15 -8
- package/dist/esm/components/AxisX/prepare-axis-data.js +12 -0
- package/dist/esm/components/AxisX/styles.css +7 -2
- package/dist/esm/components/AxisX/types.d.ts +5 -0
- package/dist/esm/components/AxisY/AxisY.js +13 -7
- package/dist/esm/components/AxisY/prepare-axis-data.js +14 -0
- package/dist/esm/components/AxisY/styles.css +7 -2
- package/dist/esm/components/AxisY/types.d.ts +5 -0
- package/dist/esm/components/ChartInner/index.js +2 -0
- package/dist/esm/components/ChartInner/styles.css +1 -0
- package/dist/esm/components/ChartInner/useChartInnerHandlers.d.ts +3 -0
- package/dist/esm/components/ChartInner/useChartInnerHandlers.js +26 -3
- package/dist/esm/components/ChartInner/useChartInnerProps.d.ts +1 -0
- package/dist/esm/components/Tooltip/ChartTooltipContent.d.ts +3 -1
- package/dist/esm/components/Tooltip/ChartTooltipContent.js +9 -2
- package/dist/esm/components/Tooltip/DefaultTooltipContent/Row.js +1 -1
- package/dist/esm/components/Tooltip/index.js +5 -2
- package/dist/esm/components/Tooltip/styles.css +8 -1
- package/dist/esm/constants/defaults/axis.d.ts +4 -0
- package/dist/esm/constants/defaults/axis.js +4 -0
- package/dist/esm/hooks/useAxis/types.d.ts +8 -1
- package/dist/esm/hooks/useAxis/x-axis.js +9 -3
- package/dist/esm/hooks/useAxis/y-axis.js +7 -1
- package/dist/esm/hooks/useNormalizedOriginalData/index.d.ts +1 -0
- package/dist/esm/hooks/useTooltip/index.d.ts +3 -1
- package/dist/esm/hooks/useTooltip/index.js +7 -3
- package/dist/esm/types/chart/axis.d.ts +16 -1
- package/dist/esm/types/chart/tooltip.d.ts +7 -1
- package/dist/esm/utils/chart/format.js +17 -2
- package/dist/esm/utils/chart/get-hovered-plots.d.ts +14 -0
- package/dist/esm/utils/chart/get-hovered-plots.js +67 -0
- package/dist/esm/utils/chart/time.d.ts +1 -0
- package/dist/esm/utils/chart/time.js +9 -0
- package/package.json +1 -1
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import isEqual from 'lodash/isEqual';
|
|
3
3
|
export const useTooltip = ({ dispatcher, tooltip }) => {
|
|
4
|
-
const [{ hovered, pointerPosition }, setTooltipState] = React.useState({});
|
|
4
|
+
const [{ hovered, hoveredPlotLines, hoveredPlotBands, pointerPosition }, setTooltipState] = React.useState({});
|
|
5
5
|
const prevHovered = React.useRef(hovered);
|
|
6
6
|
React.useEffect(() => {
|
|
7
7
|
if (tooltip === null || tooltip === void 0 ? void 0 : tooltip.enabled) {
|
|
8
|
-
dispatcher.on('hover-shape.tooltip', (nextHovered, nextPointerPosition) => {
|
|
8
|
+
dispatcher.on('hover-shape.tooltip', (nextHovered, nextPointerPosition, nextHoveredPlots) => {
|
|
9
9
|
const filteredNextHovered = nextHovered === null || nextHovered === void 0 ? void 0 : nextHovered.filter((item) => 'y' in item.data ? item.data.y !== null : true);
|
|
10
10
|
const isHoveredChanged = !isEqual(prevHovered.current, filteredNextHovered);
|
|
11
11
|
const newTooltipState = {
|
|
12
|
-
pointerPosition: nextPointerPosition,
|
|
13
12
|
hovered: isHoveredChanged ? filteredNextHovered : prevHovered.current,
|
|
13
|
+
hoveredPlotLines: nextHoveredPlots === null || nextHoveredPlots === void 0 ? void 0 : nextHoveredPlots.lines,
|
|
14
|
+
hoveredPlotBands: nextHoveredPlots === null || nextHoveredPlots === void 0 ? void 0 : nextHoveredPlots.bands,
|
|
15
|
+
pointerPosition: nextPointerPosition,
|
|
14
16
|
};
|
|
15
17
|
if (isHoveredChanged) {
|
|
16
18
|
prevHovered.current = filteredNextHovered;
|
|
@@ -26,6 +28,8 @@ export const useTooltip = ({ dispatcher, tooltip }) => {
|
|
|
26
28
|
}, [dispatcher, tooltip]);
|
|
27
29
|
return {
|
|
28
30
|
hovered,
|
|
31
|
+
hoveredPlotLines,
|
|
32
|
+
hoveredPlotBands,
|
|
29
33
|
pointerPosition,
|
|
30
34
|
};
|
|
31
35
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { DurationInput } from '@gravity-ui/date-utils';
|
|
2
2
|
import type { AXIS_TYPE, DashStyle } from '../../constants';
|
|
3
3
|
import type { FormatNumberOptions } from '../formatter';
|
|
4
|
+
import type { MeaningfulAny } from '../misc';
|
|
4
5
|
import type { BaseTextStyle } from './base';
|
|
5
6
|
import type { ChartBrush } from './brush';
|
|
6
7
|
export type ChartAxisType = (typeof AXIS_TYPE)[keyof typeof AXIS_TYPE];
|
|
@@ -94,6 +95,16 @@ export interface ChartAxisTitle {
|
|
|
94
95
|
*/
|
|
95
96
|
maxRowCount?: number;
|
|
96
97
|
}
|
|
98
|
+
export interface ChartAxisTickMarks {
|
|
99
|
+
/** Enable or disable the tick marks on the axis.
|
|
100
|
+
* @default false
|
|
101
|
+
*/
|
|
102
|
+
enabled?: boolean;
|
|
103
|
+
/** The length of the tick marks in pixels (perpendicular to the axis).
|
|
104
|
+
* @default 6
|
|
105
|
+
*/
|
|
106
|
+
length?: number;
|
|
107
|
+
}
|
|
97
108
|
export interface ChartAxis {
|
|
98
109
|
categories?: string[];
|
|
99
110
|
/** Configure a crosshair that follows either the mouse pointer or the hovered point. */
|
|
@@ -161,6 +172,8 @@ export interface ChartAxis {
|
|
|
161
172
|
plotLines?: AxisPlotLine[];
|
|
162
173
|
/** An array of colored bands stretching across the plot area marking an interval on the axis. */
|
|
163
174
|
plotBands?: AxisPlotBand[];
|
|
175
|
+
/** Small perpendicular marks on the axis line at each tick position. */
|
|
176
|
+
tickMarks?: ChartAxisTickMarks;
|
|
164
177
|
/** Whether axis, including axis title, line, ticks and labels, should be visible. */
|
|
165
178
|
visible?: boolean;
|
|
166
179
|
/** Setting the order of the axis values. It is not applied by default.
|
|
@@ -226,6 +239,8 @@ export interface AxisPlot {
|
|
|
226
239
|
* It is assigned as a data-qa attribute to an element. */
|
|
227
240
|
qa?: string;
|
|
228
241
|
};
|
|
242
|
+
/** Custom data associated with the plot line/band, accessible in tooltip renderer args. */
|
|
243
|
+
custom?: MeaningfulAny;
|
|
229
244
|
}
|
|
230
245
|
export interface AxisPlotLine extends AxisPlot {
|
|
231
246
|
/** The position of the line in axis units. */
|
|
@@ -259,7 +274,7 @@ export interface AxisPlotBand extends AxisPlot {
|
|
|
259
274
|
*/
|
|
260
275
|
to: number | string | null;
|
|
261
276
|
}
|
|
262
|
-
export interface AxisCrosshair extends Omit<AxisPlotLine, 'value' | 'label'> {
|
|
277
|
+
export interface AxisCrosshair extends Omit<AxisPlotLine, 'value' | 'label' | 'custom'> {
|
|
263
278
|
/** Whether the crosshair should snap to the point or follow the pointer independent of points.
|
|
264
279
|
* @default true
|
|
265
280
|
*/
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { TOOLTIP_TOTALS_BUILT_IN_AGGREGATION } from '../../constants';
|
|
2
2
|
import type { MeaningfulAny } from '../misc';
|
|
3
3
|
import type { AreaSeries, AreaSeriesData } from './area';
|
|
4
|
-
import type { ChartXAxis, ChartYAxis } from './axis';
|
|
4
|
+
import type { AxisPlotBand, AxisPlotLine, ChartXAxis, ChartYAxis } from './axis';
|
|
5
5
|
import type { BarXSeries, BarXSeriesData } from './bar-x';
|
|
6
6
|
import type { BarYSeries, BarYSeriesData } from './bar-y';
|
|
7
7
|
import type { CustomFormat, ValueFormat } from './base';
|
|
@@ -91,8 +91,14 @@ export type TooltipDataChunk<T = MeaningfulAny> = (TooltipDataChunkBarX<T> | Too
|
|
|
91
91
|
};
|
|
92
92
|
export interface ChartTooltipRendererArgs<T = MeaningfulAny> {
|
|
93
93
|
hovered: TooltipDataChunk<T>[];
|
|
94
|
+
/** Plot lines that intersect with the current pointer position. */
|
|
95
|
+
hoveredPlotLines?: AxisPlotLine[];
|
|
96
|
+
/** Plot bands that contain the current pointer position. */
|
|
97
|
+
hoveredPlotBands?: AxisPlotBand[];
|
|
94
98
|
xAxis?: ChartXAxis | null;
|
|
95
99
|
yAxis?: ChartYAxis;
|
|
100
|
+
/** Formatting settings for tooltip header row (includes computed default). */
|
|
101
|
+
headerFormat?: ValueFormat | CustomFormat;
|
|
96
102
|
}
|
|
97
103
|
export interface ChartTooltipTotalsAggregationArgs<T = MeaningfulAny> extends ChartTooltipRendererArgs<T> {
|
|
98
104
|
}
|
|
@@ -2,7 +2,7 @@ import { dateTimeUtc } from '@gravity-ui/date-utils';
|
|
|
2
2
|
import capitalize from 'lodash/capitalize';
|
|
3
3
|
import { DEFAULT_DATE_FORMAT } from '../../constants';
|
|
4
4
|
import { formatNumber, getDefaultUnit } from '../../libs';
|
|
5
|
-
import { getDefaultDateFormat } from './time';
|
|
5
|
+
import { DATETIME_LABEL_FORMATS, TIME_UNITS, getDefaultDateFormat, getDefaultTimeOnlyFormat, } from './time';
|
|
6
6
|
const LETTER_MOUNTH_AT_START_FORMAT_REGEXP = /^M{3,}/;
|
|
7
7
|
function getFormattedDate(args) {
|
|
8
8
|
const { value, format = DEFAULT_DATE_FORMAT } = args;
|
|
@@ -43,7 +43,22 @@ export function formatAxisTickLabel(args) {
|
|
|
43
43
|
}
|
|
44
44
|
case 'datetime': {
|
|
45
45
|
const date = value;
|
|
46
|
-
|
|
46
|
+
let format;
|
|
47
|
+
if (axis.labels.dateFormat) {
|
|
48
|
+
format = axis.labels.dateFormat;
|
|
49
|
+
}
|
|
50
|
+
else if (step !== undefined && step < TIME_UNITS.day) {
|
|
51
|
+
const d = dateTimeUtc({ input: date });
|
|
52
|
+
const isMidnight = d.isValid() &&
|
|
53
|
+
d.hour() === 0 &&
|
|
54
|
+
d.minute() === 0 &&
|
|
55
|
+
d.second() === 0 &&
|
|
56
|
+
d.millisecond() === 0;
|
|
57
|
+
format = isMidnight ? DATETIME_LABEL_FORMATS.day : getDefaultTimeOnlyFormat(step);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
format = getDefaultDateFormat(step);
|
|
61
|
+
}
|
|
47
62
|
return getFormattedDate({ value: date, format });
|
|
48
63
|
}
|
|
49
64
|
case 'linear':
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { PreparedXAxis, PreparedYAxis } from '../../hooks';
|
|
2
|
+
import type { ChartScale } from '../../hooks/useAxisScales/types';
|
|
3
|
+
import type { AxisPlotBand, AxisPlotLine } from '../../types';
|
|
4
|
+
export declare function getHoveredPlots(args: {
|
|
5
|
+
pointerX: number;
|
|
6
|
+
pointerY: number;
|
|
7
|
+
xAxis?: PreparedXAxis | null;
|
|
8
|
+
yAxis: PreparedYAxis[];
|
|
9
|
+
xScale?: ChartScale;
|
|
10
|
+
yScale?: (ChartScale | undefined)[];
|
|
11
|
+
}): {
|
|
12
|
+
plotLines: AxisPlotLine[];
|
|
13
|
+
plotBands: AxisPlotBand[];
|
|
14
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { getBandsPosition, isBandScale } from './axis/common';
|
|
2
|
+
const PLOT_LINE_HIT_THRESHOLD_PX = 4;
|
|
3
|
+
function getHoveredAxisPlotBands(args) {
|
|
4
|
+
const { pointerPx, plotBands, scale, axis } = args;
|
|
5
|
+
const axisScale = scale;
|
|
6
|
+
const result = [];
|
|
7
|
+
for (const band of plotBands) {
|
|
8
|
+
const { from, to } = getBandsPosition({ band, axisScale, axis });
|
|
9
|
+
const startPx = Math.min(from, to);
|
|
10
|
+
const endPx = Math.max(from, to);
|
|
11
|
+
if (pointerPx >= startPx && pointerPx <= endPx) {
|
|
12
|
+
result.push(band);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return result;
|
|
16
|
+
}
|
|
17
|
+
function getHoveredAxisPlotLines(args) {
|
|
18
|
+
const { pointerPx, plotLines, scale } = args;
|
|
19
|
+
const result = [];
|
|
20
|
+
if (isBandScale(scale)) {
|
|
21
|
+
return result;
|
|
22
|
+
}
|
|
23
|
+
for (const line of plotLines) {
|
|
24
|
+
const linePx = Number(scale(line.value));
|
|
25
|
+
if (Math.abs(pointerPx - linePx) <= PLOT_LINE_HIT_THRESHOLD_PX + line.width / 2) {
|
|
26
|
+
result.push(line);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
31
|
+
export function getHoveredPlots(args) {
|
|
32
|
+
const { pointerX, pointerY, xAxis, yAxis, xScale, yScale } = args;
|
|
33
|
+
const plotLines = [];
|
|
34
|
+
const plotBands = [];
|
|
35
|
+
if (xAxis && xScale) {
|
|
36
|
+
plotBands.push(...getHoveredAxisPlotBands({
|
|
37
|
+
pointerPx: pointerX,
|
|
38
|
+
plotBands: xAxis.plotBands,
|
|
39
|
+
scale: xScale,
|
|
40
|
+
axis: 'x',
|
|
41
|
+
}));
|
|
42
|
+
plotLines.push(...getHoveredAxisPlotLines({
|
|
43
|
+
pointerPx: pointerX,
|
|
44
|
+
plotLines: xAxis.plotLines,
|
|
45
|
+
scale: xScale,
|
|
46
|
+
}));
|
|
47
|
+
}
|
|
48
|
+
for (let i = 0; i < yAxis.length; i++) {
|
|
49
|
+
const yAxisItem = yAxis[i];
|
|
50
|
+
const yScaleItem = yScale === null || yScale === void 0 ? void 0 : yScale[i];
|
|
51
|
+
if (!yAxisItem || !yScaleItem) {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
plotBands.push(...getHoveredAxisPlotBands({
|
|
55
|
+
pointerPx: pointerY,
|
|
56
|
+
plotBands: yAxisItem.plotBands,
|
|
57
|
+
scale: yScaleItem,
|
|
58
|
+
axis: 'y',
|
|
59
|
+
}));
|
|
60
|
+
plotLines.push(...getHoveredAxisPlotLines({
|
|
61
|
+
pointerPx: pointerY,
|
|
62
|
+
plotLines: yAxisItem.plotLines,
|
|
63
|
+
scale: yScaleItem,
|
|
64
|
+
}));
|
|
65
|
+
}
|
|
66
|
+
return { plotLines, plotBands };
|
|
67
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export declare const TIME_UNITS: Record<string, number>;
|
|
2
2
|
export declare const DATETIME_LABEL_FORMATS: Record<keyof typeof TIME_UNITS, string>;
|
|
3
3
|
export declare function getDefaultDateFormat(range?: number): string;
|
|
4
|
+
export declare function getDefaultTimeOnlyFormat(step: number): string;
|
|
@@ -32,3 +32,12 @@ export function getDefaultDateFormat(range) {
|
|
|
32
32
|
}
|
|
33
33
|
return DATETIME_LABEL_FORMATS.day;
|
|
34
34
|
}
|
|
35
|
+
export function getDefaultTimeOnlyFormat(step) {
|
|
36
|
+
if (step < TIME_UNITS.second) {
|
|
37
|
+
return 'HH:mm:ss.SSS';
|
|
38
|
+
}
|
|
39
|
+
if (step < TIME_UNITS.minute) {
|
|
40
|
+
return 'HH:mm:ss';
|
|
41
|
+
}
|
|
42
|
+
return 'HH:mm';
|
|
43
|
+
}
|
|
@@ -33,7 +33,7 @@ export const AxisX = (props) => {
|
|
|
33
33
|
.attr('class', b('title'))
|
|
34
34
|
.append('text')
|
|
35
35
|
.attr('text-anchor', 'start')
|
|
36
|
-
.
|
|
36
|
+
.attr('transform', `translate(${preparedAxisData.title.x},${preparedAxisData.title.y}) rotate(${preparedAxisData.title.rotate}) translate(0,${preparedAxisData.title.offset})`)
|
|
37
37
|
.attr('font-size', preparedAxisData.title.style.fontSize)
|
|
38
38
|
.attr('font-weight', (_a = preparedAxisData.title.style.fontWeight) !== null && _a !== void 0 ? _a : null)
|
|
39
39
|
.attr('fill', (_b = preparedAxisData.title.style.fontColor) !== null && _b !== void 0 ? _b : null)
|
|
@@ -43,6 +43,7 @@ export const AxisX = (props) => {
|
|
|
43
43
|
.html((d) => d.text)
|
|
44
44
|
.attr('x', (d) => d.x)
|
|
45
45
|
.attr('y', (d) => d.y)
|
|
46
|
+
.attr('dominant-baseline', 'text-before-edge')
|
|
46
47
|
.attr('text-anchor', 'start');
|
|
47
48
|
}
|
|
48
49
|
if (preparedAxisData.domain) {
|
|
@@ -67,13 +68,19 @@ export const AxisX = (props) => {
|
|
|
67
68
|
if (tickData.line) {
|
|
68
69
|
tickSelection.append('path').attr('d', lineGenerator(tickData.line.points));
|
|
69
70
|
}
|
|
71
|
+
if (tickData.mark) {
|
|
72
|
+
tickSelection
|
|
73
|
+
.append('path')
|
|
74
|
+
.attr('class', b('mark', { grid: preparedAxisData.gridEnabled }))
|
|
75
|
+
.attr('d', lineGenerator(tickData.mark.points));
|
|
76
|
+
}
|
|
70
77
|
if (tickData.svgLabel) {
|
|
71
78
|
const label = tickData.svgLabel;
|
|
72
79
|
const textSelection = tickSelection
|
|
73
80
|
.append('text')
|
|
74
|
-
.
|
|
75
|
-
`translate(${label.x}
|
|
76
|
-
label.angle ? `rotate(${label.angle}
|
|
81
|
+
.attr('transform', [
|
|
82
|
+
`translate(${label.x}, ${label.y})`,
|
|
83
|
+
label.angle ? `rotate(${label.angle})` : '',
|
|
77
84
|
]
|
|
78
85
|
.filter(Boolean)
|
|
79
86
|
.join(' '));
|
|
@@ -106,7 +113,7 @@ export const AxisX = (props) => {
|
|
|
106
113
|
.join('g')
|
|
107
114
|
.attr(plotDataAttr, 1)
|
|
108
115
|
.attr(plotBandDataAttr, 1)
|
|
109
|
-
.
|
|
116
|
+
.attr('transform', (d) => `translate(${d.x}, ${d.y})`);
|
|
110
117
|
plotBandsSelection
|
|
111
118
|
.append('rect')
|
|
112
119
|
.attr('width', (d) => d.width)
|
|
@@ -127,7 +134,7 @@ export const AxisX = (props) => {
|
|
|
127
134
|
.style('font-weight', (_b = label.style.fontWeight) !== null && _b !== void 0 ? _b : '')
|
|
128
135
|
.style('dominant-baseline', 'text-before-edge')
|
|
129
136
|
.style('text-anchor', 'start')
|
|
130
|
-
.
|
|
137
|
+
.attr('transform', `translate(${label.x}, ${label.y}) rotate(${label.rotate})`)
|
|
131
138
|
.attr('data-qa', (_c = label.qa) !== null && _c !== void 0 ? _c : null);
|
|
132
139
|
}
|
|
133
140
|
});
|
|
@@ -147,7 +154,7 @@ export const AxisX = (props) => {
|
|
|
147
154
|
.join('g')
|
|
148
155
|
.attr(plotDataAttr, 1)
|
|
149
156
|
.attr(plotLineDataAttr, 1)
|
|
150
|
-
.
|
|
157
|
+
.attr('transform', (d) => `translate(${d.x}, ${d.y})`);
|
|
151
158
|
plotLinesSelection
|
|
152
159
|
.append('path')
|
|
153
160
|
.attr('d', (d) => lineGenerator(d.points))
|
|
@@ -169,7 +176,7 @@ export const AxisX = (props) => {
|
|
|
169
176
|
.style('font-weight', (_b = label.style.fontWeight) !== null && _b !== void 0 ? _b : '')
|
|
170
177
|
.style('dominant-baseline', 'text-before-edge')
|
|
171
178
|
.style('text-anchor', 'start')
|
|
172
|
-
.
|
|
179
|
+
.attr('transform', `translate(${label.x}, ${label.y}) rotate(${label.rotate})`)
|
|
173
180
|
.attr('data-qa', (_c = label.qa) !== null && _c !== void 0 ? _c : null);
|
|
174
181
|
}
|
|
175
182
|
});
|
|
@@ -178,8 +178,19 @@ export async function prepareXAxisData({ axis, boundsOffsetLeft, boundsOffsetRig
|
|
|
178
178
|
],
|
|
179
179
|
};
|
|
180
180
|
}
|
|
181
|
+
let mark = null;
|
|
182
|
+
if (isBottomPlot && axis.tickMarks.enabled) {
|
|
183
|
+
const axisBottom = axisTop + axisHeight;
|
|
184
|
+
mark = {
|
|
185
|
+
points: [
|
|
186
|
+
[tickValue.x, axisBottom],
|
|
187
|
+
[tickValue.x, axisBottom + axis.tickMarks.length],
|
|
188
|
+
],
|
|
189
|
+
};
|
|
190
|
+
}
|
|
181
191
|
ticks.push({
|
|
182
192
|
line: tickLine,
|
|
193
|
+
mark,
|
|
183
194
|
svgLabel,
|
|
184
195
|
htmlLabel,
|
|
185
196
|
});
|
|
@@ -315,6 +326,7 @@ export async function prepareXAxisData({ axis, boundsOffsetLeft, boundsOffsetRig
|
|
|
315
326
|
}
|
|
316
327
|
xAxisItems.push({
|
|
317
328
|
id: getUniqId(),
|
|
329
|
+
gridEnabled: axis.grid.enabled,
|
|
318
330
|
title,
|
|
319
331
|
ticks,
|
|
320
332
|
domain,
|
|
@@ -6,9 +6,14 @@
|
|
|
6
6
|
stroke: none;
|
|
7
7
|
}
|
|
8
8
|
.gcharts-x-axis__tick {
|
|
9
|
-
stroke: var(--
|
|
9
|
+
stroke: var(--gcharts-axis-tick-color);
|
|
10
|
+
}
|
|
11
|
+
.gcharts-x-axis__mark {
|
|
12
|
+
stroke: var(--gcharts-axis-tick-mark-color, var(--g-color-line-generic-active));
|
|
13
|
+
}
|
|
14
|
+
.gcharts-x-axis__mark_grid {
|
|
15
|
+
stroke: var(--gcharts-axis-tick-mark-color, var(--gcharts-axis-tick-color));
|
|
10
16
|
}
|
|
11
17
|
.gcharts-x-axis__title {
|
|
12
|
-
dominant-baseline: text-before-edge;
|
|
13
18
|
fill: var(--g-color-text-secondary);
|
|
14
19
|
}
|
|
@@ -16,8 +16,12 @@ export type AxisSvgLabelData = {
|
|
|
16
16
|
export type AxisTickLine = {
|
|
17
17
|
points: PointPosition[];
|
|
18
18
|
};
|
|
19
|
+
export type AxisTickMarkData = {
|
|
20
|
+
points: PointPosition[];
|
|
21
|
+
};
|
|
19
22
|
export type AxisTickData = {
|
|
20
23
|
line: AxisTickLine | null;
|
|
24
|
+
mark: AxisTickMarkData | null;
|
|
21
25
|
svgLabel: AxisSvgLabelData | null;
|
|
22
26
|
htmlLabel: HtmlItem | null;
|
|
23
27
|
};
|
|
@@ -70,6 +74,7 @@ export type AxisDomainData = {
|
|
|
70
74
|
};
|
|
71
75
|
export type AxisXData = {
|
|
72
76
|
id: string;
|
|
77
|
+
gridEnabled: boolean;
|
|
73
78
|
title: AxisTitleData | null;
|
|
74
79
|
domain: AxisDomainData | null;
|
|
75
80
|
ticks: AxisTickData[];
|
|
@@ -38,8 +38,7 @@ export const AxisY = (props) => {
|
|
|
38
38
|
.attr('class', b('title'))
|
|
39
39
|
.append('text')
|
|
40
40
|
.attr('text-anchor', 'start')
|
|
41
|
-
.
|
|
42
|
-
.style('transform', `translate(${preparedAxisData.title.x}px, ${preparedAxisData.title.y}px) rotate(${preparedAxisData.title.rotate}deg) translate(0px, ${preparedAxisData.title.offset}px)`)
|
|
41
|
+
.attr('transform', `translate(${preparedAxisData.title.x}, ${preparedAxisData.title.y}) rotate(${preparedAxisData.title.rotate}) translate(0, ${preparedAxisData.title.offset})`)
|
|
43
42
|
.attr('font-size', preparedAxisData.title.style.fontSize)
|
|
44
43
|
.attr('font-weight', (_b = preparedAxisData.title.style.fontWeight) !== null && _b !== void 0 ? _b : null)
|
|
45
44
|
.attr('fill', (_c = preparedAxisData.title.style.fontColor) !== null && _c !== void 0 ? _c : null)
|
|
@@ -49,6 +48,7 @@ export const AxisY = (props) => {
|
|
|
49
48
|
.html((d) => d.text)
|
|
50
49
|
.attr('x', (d) => d.x)
|
|
51
50
|
.attr('y', (d) => d.y)
|
|
51
|
+
.attr('dominant-baseline', 'text-after-edge')
|
|
52
52
|
.attr('text-anchor', 'start');
|
|
53
53
|
}
|
|
54
54
|
if (preparedAxisData.domain) {
|
|
@@ -73,13 +73,19 @@ export const AxisY = (props) => {
|
|
|
73
73
|
if (tickData.line) {
|
|
74
74
|
tickSelection.append('path').attr('d', lineGenerator(tickData.line.points));
|
|
75
75
|
}
|
|
76
|
+
if (tickData.mark) {
|
|
77
|
+
tickSelection
|
|
78
|
+
.append('path')
|
|
79
|
+
.attr('class', b('mark', { grid: preparedAxisData.gridEnabled }))
|
|
80
|
+
.attr('d', lineGenerator(tickData.mark.points));
|
|
81
|
+
}
|
|
76
82
|
if (tickData.svgLabel) {
|
|
77
83
|
const label = tickData.svgLabel;
|
|
78
84
|
const textSelection = tickSelection
|
|
79
85
|
.append('text')
|
|
80
|
-
.
|
|
81
|
-
`translate(${label.x}
|
|
82
|
-
label.angle ? `rotate(${label.angle}
|
|
86
|
+
.attr('transform', [
|
|
87
|
+
`translate(${label.x}, ${label.y})`,
|
|
88
|
+
label.angle ? `rotate(${label.angle})` : '',
|
|
83
89
|
]
|
|
84
90
|
.filter(Boolean)
|
|
85
91
|
.join(' '));
|
|
@@ -112,7 +118,7 @@ export const AxisY = (props) => {
|
|
|
112
118
|
.join('g')
|
|
113
119
|
.attr(plotDataAttr, 1)
|
|
114
120
|
.attr(plotBandDataAttr, 1)
|
|
115
|
-
.
|
|
121
|
+
.attr('transform', (d) => `translate(${d.x}, ${d.y})`);
|
|
116
122
|
plotBandsSelection
|
|
117
123
|
.append('rect')
|
|
118
124
|
.attr('width', (d) => d.width)
|
|
@@ -153,7 +159,7 @@ export const AxisY = (props) => {
|
|
|
153
159
|
.join('g')
|
|
154
160
|
.attr(plotDataAttr, 1)
|
|
155
161
|
.attr(plotLineDataAttr, 1)
|
|
156
|
-
.
|
|
162
|
+
.attr('transform', (d) => `translate(${d.x}, ${d.y})`);
|
|
157
163
|
plotLinesSelection
|
|
158
164
|
.append('path')
|
|
159
165
|
.attr('d', (d) => lineGenerator(d.points))
|
|
@@ -175,8 +175,21 @@ export async function prepareYAxisData({ axis, split, scale, top: topOffset, wid
|
|
|
175
175
|
],
|
|
176
176
|
}
|
|
177
177
|
: null;
|
|
178
|
+
let mark = null;
|
|
179
|
+
if (axis.tickMarks.enabled) {
|
|
180
|
+
const isLeft = axis.position === 'left';
|
|
181
|
+
const markX = isLeft ? 0 : width;
|
|
182
|
+
const dir = isLeft ? -1 : 1;
|
|
183
|
+
mark = {
|
|
184
|
+
points: [
|
|
185
|
+
[markX, y],
|
|
186
|
+
[markX + dir * axis.tickMarks.length, y],
|
|
187
|
+
],
|
|
188
|
+
};
|
|
189
|
+
}
|
|
178
190
|
ticks.push({
|
|
179
191
|
line: tickLine,
|
|
192
|
+
mark,
|
|
180
193
|
svgLabel,
|
|
181
194
|
htmlLabel,
|
|
182
195
|
});
|
|
@@ -273,6 +286,7 @@ export async function prepareYAxisData({ axis, split, scale, top: topOffset, wid
|
|
|
273
286
|
}
|
|
274
287
|
return {
|
|
275
288
|
id: getUniqId(),
|
|
289
|
+
gridEnabled: axis.grid.enabled,
|
|
276
290
|
title,
|
|
277
291
|
ticks,
|
|
278
292
|
domain,
|
|
@@ -7,9 +7,14 @@
|
|
|
7
7
|
dominant-baseline: text-after-edge;
|
|
8
8
|
}
|
|
9
9
|
.gcharts-y-axis__tick {
|
|
10
|
-
stroke: var(--
|
|
10
|
+
stroke: var(--gcharts-axis-tick-color);
|
|
11
|
+
}
|
|
12
|
+
.gcharts-y-axis__mark {
|
|
13
|
+
stroke: var(--gcharts-axis-tick-mark-color, var(--g-color-line-generic-active));
|
|
14
|
+
}
|
|
15
|
+
.gcharts-y-axis__mark_grid {
|
|
16
|
+
stroke: var(--gcharts-axis-tick-mark-color, var(--gcharts-axis-tick-color));
|
|
11
17
|
}
|
|
12
18
|
.gcharts-y-axis__title {
|
|
13
|
-
dominant-baseline: text-after-edge;
|
|
14
19
|
fill: var(--g-color-text-secondary);
|
|
15
20
|
}
|
|
@@ -16,8 +16,12 @@ export type AxisSvgLabelData = {
|
|
|
16
16
|
export type AxisTickLine = {
|
|
17
17
|
points: PointPosition[];
|
|
18
18
|
};
|
|
19
|
+
export type AxisTickMarkData = {
|
|
20
|
+
points: PointPosition[];
|
|
21
|
+
};
|
|
19
22
|
export type AxisTickData = {
|
|
20
23
|
line: AxisTickLine | null;
|
|
24
|
+
mark: AxisTickMarkData | null;
|
|
21
25
|
svgLabel: AxisSvgLabelData | null;
|
|
22
26
|
htmlLabel: HtmlItem | null;
|
|
23
27
|
};
|
|
@@ -81,6 +85,7 @@ export type AxisDomainData = {
|
|
|
81
85
|
};
|
|
82
86
|
export type AxisYData = {
|
|
83
87
|
id: string;
|
|
88
|
+
gridEnabled: boolean;
|
|
84
89
|
title: HtmlAxisTitleData | SvgAxisTitleData | null;
|
|
85
90
|
domain: AxisDomainData | null;
|
|
86
91
|
ticks: AxisTickData[];
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type React from 'react';
|
|
2
2
|
import type { Dispatch } from 'd3';
|
|
3
3
|
import type { PreparedXAxis, PreparedYAxis, ShapeData } from '../../hooks';
|
|
4
|
+
import type { ChartScale } from '../../hooks/useAxisScales/types';
|
|
4
5
|
import type { useChartInnerState } from './useChartInnerState';
|
|
5
6
|
type ChartInnerState = ReturnType<typeof useChartInnerState>;
|
|
6
7
|
type Props = {
|
|
@@ -16,6 +17,8 @@ type Props = {
|
|
|
16
17
|
unpinTooltip: ChartInnerState['unpinTooltip'];
|
|
17
18
|
xAxis: PreparedXAxis | null;
|
|
18
19
|
yAxis: PreparedYAxis[];
|
|
20
|
+
xScale?: ChartScale;
|
|
21
|
+
yScale?: (ChartScale | undefined)[];
|
|
19
22
|
tooltipThrottle: number;
|
|
20
23
|
isOutsideBounds: (x: number, y: number) => boolean;
|
|
21
24
|
};
|
|
@@ -4,8 +4,9 @@ import throttle from 'lodash/throttle';
|
|
|
4
4
|
import { IS_TOUCH_ENABLED } from '../../constants';
|
|
5
5
|
import { EventType } from '../../utils';
|
|
6
6
|
import { getClosestPoints } from '../../utils/chart/get-closest-data';
|
|
7
|
+
import { getHoveredPlots } from '../../utils/chart/get-hovered-plots';
|
|
7
8
|
export function useChartInnerHandlers(props) {
|
|
8
|
-
const { boundsHeight, boundsOffsetLeft, boundsOffsetTop, boundsWidth, dispatcher, shapesData, svgContainer, togglePinTooltip, tooltipPinned, unpinTooltip, xAxis, yAxis, tooltipThrottle, isOutsideBounds, } = props;
|
|
9
|
+
const { boundsHeight, boundsOffsetLeft, boundsOffsetTop, boundsWidth, dispatcher, shapesData, svgContainer, togglePinTooltip, tooltipPinned, unpinTooltip, xAxis, yAxis, xScale, yScale, tooltipThrottle, isOutsideBounds, } = props;
|
|
9
10
|
const handleMove = ([pointerX, pointerY], event) => {
|
|
10
11
|
if (tooltipPinned) {
|
|
11
12
|
return;
|
|
@@ -24,11 +25,22 @@ export function useChartInnerHandlers(props) {
|
|
|
24
25
|
boundsHeight,
|
|
25
26
|
boundsWidth,
|
|
26
27
|
});
|
|
27
|
-
|
|
28
|
+
const { plotLines, plotBands } = getHoveredPlots({
|
|
29
|
+
pointerX: x,
|
|
30
|
+
pointerY: y,
|
|
31
|
+
xAxis,
|
|
32
|
+
yAxis,
|
|
33
|
+
xScale,
|
|
34
|
+
yScale,
|
|
35
|
+
});
|
|
36
|
+
const hoveredPlotsArg = { lines: plotLines, bands: plotBands };
|
|
37
|
+
dispatcher.call(EventType.HOVER_SHAPE, event.target, closest, [pointerX, pointerY], hoveredPlotsArg);
|
|
28
38
|
dispatcher.call(EventType.POINTERMOVE_CHART, {}, {
|
|
29
39
|
hovered: closest,
|
|
30
40
|
xAxis,
|
|
31
41
|
yAxis: yAxis[0],
|
|
42
|
+
hoveredPlotLines: plotLines,
|
|
43
|
+
hoveredPlotBands: plotBands,
|
|
32
44
|
}, event);
|
|
33
45
|
};
|
|
34
46
|
const handleMouseMove = (event) => {
|
|
@@ -77,11 +89,22 @@ export function useChartInnerHandlers(props) {
|
|
|
77
89
|
dispatcher.call(EventType.CLICK_CHART, undefined, { point: selected.data, series: selected.series }, event);
|
|
78
90
|
const nextTooltipFixed = !tooltipPinned;
|
|
79
91
|
if (!nextTooltipFixed) {
|
|
80
|
-
|
|
92
|
+
const { plotLines, plotBands } = getHoveredPlots({
|
|
93
|
+
pointerX: x,
|
|
94
|
+
pointerY: y,
|
|
95
|
+
xAxis,
|
|
96
|
+
yAxis,
|
|
97
|
+
xScale,
|
|
98
|
+
yScale,
|
|
99
|
+
});
|
|
100
|
+
const hoveredPlotsArg = { lines: plotLines, bands: plotBands };
|
|
101
|
+
dispatcher.call(EventType.HOVER_SHAPE, event.target, items, [pointerX, pointerY], hoveredPlotsArg);
|
|
81
102
|
dispatcher.call(EventType.POINTERMOVE_CHART, {}, {
|
|
82
103
|
hovered: items,
|
|
83
104
|
xAxis,
|
|
84
105
|
yAxis: yAxis[0],
|
|
106
|
+
hoveredPlotLines: plotLines,
|
|
107
|
+
hoveredPlotBands: plotBands,
|
|
85
108
|
}, event);
|
|
86
109
|
}
|
|
87
110
|
togglePinTooltip === null || togglePinTooltip === void 0 ? void 0 : togglePinTooltip(nextTooltipFixed, event);
|
|
@@ -77,6 +77,7 @@ export declare function useChartInnerProps(props: Props): {
|
|
|
77
77
|
ticks: {
|
|
78
78
|
pixelInterval?: number;
|
|
79
79
|
};
|
|
80
|
+
tickMarks: import("../../hooks").PreparedAxisTickMarks;
|
|
80
81
|
position: "left" | "right" | "top" | "bottom";
|
|
81
82
|
plotIndex: number;
|
|
82
83
|
plotLines: import("../../hooks").PreparedAxisPlotLine[];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import type { ChartTooltip, ChartXAxis, ChartYAxis, TooltipDataChunk } from '../../types';
|
|
2
|
+
import type { ChartTooltip, ChartTooltipRendererArgs, ChartXAxis, ChartYAxis, TooltipDataChunk } from '../../types';
|
|
3
3
|
export interface ChartTooltipContentProps {
|
|
4
4
|
hovered?: TooltipDataChunk[];
|
|
5
5
|
pinned?: boolean;
|
|
@@ -7,6 +7,8 @@ export interface ChartTooltipContentProps {
|
|
|
7
7
|
rowRenderer?: ChartTooltip['rowRenderer'];
|
|
8
8
|
valueFormat?: ChartTooltip['valueFormat'];
|
|
9
9
|
headerFormat?: ChartTooltip['headerFormat'];
|
|
10
|
+
hoveredPlotLines?: ChartTooltipRendererArgs['hoveredPlotLines'];
|
|
11
|
+
hoveredPlotBands?: ChartTooltipRendererArgs['hoveredPlotBands'];
|
|
10
12
|
totals?: ChartTooltip['totals'];
|
|
11
13
|
xAxis?: ChartXAxis | null;
|
|
12
14
|
yAxis?: ChartYAxis;
|
|
@@ -2,11 +2,18 @@ import React from 'react';
|
|
|
2
2
|
import isNil from 'lodash/isNil';
|
|
3
3
|
import { DefaultTooltipContent } from './DefaultTooltipContent';
|
|
4
4
|
export const ChartTooltipContent = React.memo((props) => {
|
|
5
|
-
const { hovered, xAxis, yAxis, renderer, rowRenderer, valueFormat, headerFormat, totals, pinned, qa, } = props;
|
|
5
|
+
const { hovered, hoveredPlotLines, hoveredPlotBands, xAxis, yAxis, renderer, rowRenderer, valueFormat, headerFormat, totals, pinned, qa, } = props;
|
|
6
6
|
if (!hovered) {
|
|
7
7
|
return null;
|
|
8
8
|
}
|
|
9
|
-
const customTooltip = renderer === null || renderer === void 0 ? void 0 : renderer({
|
|
9
|
+
const customTooltip = renderer === null || renderer === void 0 ? void 0 : renderer({
|
|
10
|
+
headerFormat,
|
|
11
|
+
hovered,
|
|
12
|
+
hoveredPlotLines,
|
|
13
|
+
hoveredPlotBands,
|
|
14
|
+
xAxis,
|
|
15
|
+
yAxis,
|
|
16
|
+
});
|
|
10
17
|
return isNil(customTooltip) ? (React.createElement(DefaultTooltipContent, { hovered: hovered, pinned: pinned, rowRenderer: rowRenderer, totals: totals, valueFormat: valueFormat, headerFormat: headerFormat, xAxis: xAxis, yAxis: yAxis, qa: qa })) : (customTooltip);
|
|
11
18
|
});
|
|
12
19
|
ChartTooltipContent.displayName = 'ChartTooltipContent';
|