Skip to content

Commit e576b26

Browse files
authored
fix(tooltip): placement with left/top legends and single bars (#771)
fixes #769 and #770
1 parent 4590a22 commit e576b26

11 files changed

Lines changed: 190 additions & 57 deletions
Loading
Loading
Loading
Loading

integration/tests/legend_stories.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,34 @@ describe('Legend stories', () => {
6262
delay: 200, // needed for icon to load
6363
});
6464
});
65+
66+
describe('Tooltip placement with legend', () => {
67+
it('should render tooltip with left legend', async () => {
68+
await common.expectChartWithMouseAtUrlToMatchScreenshot('http://localhost:9001/?path=/story/legend--left', {
69+
bottom: 190,
70+
left: 310,
71+
});
72+
});
73+
74+
it('should render tooltip with top legend', async () => {
75+
await common.expectChartWithMouseAtUrlToMatchScreenshot('http://localhost:9001/?path=/story/legend--top', {
76+
top: 150,
77+
left: 320,
78+
});
79+
});
80+
81+
it('should render tooltip with right legend', async () => {
82+
await common.expectChartWithMouseAtUrlToMatchScreenshot('http://localhost:9001/?path=/story/legend--right', {
83+
bottom: 180,
84+
left: 330,
85+
});
86+
});
87+
88+
it('should render tooltip with bottom legend', async () => {
89+
await common.expectChartWithMouseAtUrlToMatchScreenshot('http://localhost:9001/?path=/story/legend--bottom', {
90+
top: 150,
91+
left: 320,
92+
});
93+
});
94+
});
6595
});

src/chart_types/xy_chart/crosshair/crosshair_utils.ts

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { Rotation } from '../../../utils/commons';
2323
import { Dimensions } from '../../../utils/dimensions';
2424
import { Point } from '../../../utils/point';
2525
import { isHorizontalRotation, isVerticalRotation } from '../state/utils/common';
26+
import { ChartDimensions } from '../utils/dimensions';
2627

2728
export interface SnappedPosition {
2829
position: number;
@@ -168,26 +169,25 @@ export function getCursorBandPosition(
168169

169170
/** @internal */
170171
export function getTooltipAnchorPosition(
171-
chartDimensions: Dimensions,
172+
{ chartDimensions, offset }: ChartDimensions,
172173
chartRotation: Rotation,
173174
cursorBandPosition: Dimensions,
174175
cursorPosition: { x: number; y: number },
175-
isSingleValueXScale: boolean,
176176
): TooltipAnchorPosition {
177177
const isRotated = isVerticalRotation(chartRotation);
178178
const hPosition = getHorizontalTooltipPosition(
179179
cursorPosition.x,
180180
cursorBandPosition,
181181
chartDimensions,
182+
offset.left,
182183
isRotated,
183-
isSingleValueXScale,
184184
);
185185
const vPosition = getVerticalTooltipPosition(
186186
cursorPosition.y,
187187
cursorBandPosition,
188188
chartDimensions,
189+
offset.top,
189190
isRotated,
190-
isSingleValueXScale,
191191
);
192192
return {
193193
isRotated,
@@ -200,40 +200,41 @@ function getHorizontalTooltipPosition(
200200
cursorXPosition: number,
201201
cursorBandPosition: Dimensions,
202202
chartDimensions: Dimensions,
203+
globalOffset: number,
203204
isRotated: boolean,
204-
isSingleValueXScale: boolean,
205205
): { x0?: number; x1: number } {
206206
if (!isRotated) {
207207
return {
208-
x0: cursorBandPosition.left,
209-
x1: cursorBandPosition.left + (isSingleValueXScale ? 0 : cursorBandPosition.width),
208+
x0: cursorBandPosition.left + globalOffset,
209+
x1: cursorBandPosition.left + cursorBandPosition.width + globalOffset,
210210
};
211211
}
212212
return {
213213
// NOTE: x0 set to zero blocks tooltip placement on left when rotated 90 deg
214214
// Delete this comment before merging and verifing this doesn't break anything.
215-
x1: chartDimensions.left + cursorXPosition,
215+
x1: chartDimensions.left + cursorXPosition + globalOffset,
216216
};
217217
}
218218

219219
function getVerticalTooltipPosition(
220220
cursorYPosition: number,
221221
cursorBandPosition: Dimensions,
222222
chartDimensions: Dimensions,
223+
globalOffset: number,
223224
isRotated: boolean,
224-
isSingleValueXScale: boolean,
225225
): {
226226
y0: number;
227227
y1: number;
228228
} {
229229
if (!isRotated) {
230+
const y = cursorYPosition + chartDimensions.top + globalOffset;
230231
return {
231-
y0: cursorYPosition + chartDimensions.top,
232-
y1: cursorYPosition + chartDimensions.top,
232+
y0: y,
233+
y1: y,
233234
};
234235
}
235236
return {
236-
y0: cursorBandPosition.top,
237-
y1: (isSingleValueXScale ? 0 : cursorBandPosition.height) + cursorBandPosition.top,
237+
y0: cursorBandPosition.top + globalOffset,
238+
y1: cursorBandPosition.height + cursorBandPosition.top + globalOffset,
238239
};
239240
}

src/chart_types/xy_chart/state/selectors/compute_chart_dimensions.ts

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@ import createCachedSelector from 're-reselect';
2222
import { getChartContainerDimensionsSelector } from '../../../../state/selectors/get_chart_container_dimensions';
2323
import { getChartIdSelector } from '../../../../state/selectors/get_chart_id';
2424
import { getChartThemeSelector } from '../../../../state/selectors/get_chart_theme';
25-
import { Dimensions } from '../../../../utils/dimensions';
26-
import { computeChartDimensions } from '../../utils/dimensions';
25+
import { getLegendSizeSelector, LegendSizing } from '../../../../state/selectors/get_legend_size';
26+
import { Position } from '../../../../utils/commons';
27+
import { computeChartDimensions, ChartDimensions } from '../../utils/dimensions';
2728
import { computeAxisTicksDimensionsSelector } from './compute_axis_ticks_dimensions';
2829
import { getAxesStylesSelector } from './get_axis_styles';
2930
import { getAxisSpecsSelector } from './get_specs';
@@ -36,15 +37,39 @@ export const computeChartDimensionsSelector = createCachedSelector(
3637
computeAxisTicksDimensionsSelector,
3738
getAxisSpecsSelector,
3839
getAxesStylesSelector,
40+
getLegendSizeSelector,
3941
],
40-
(
41-
chartContainerDimensions,
42-
chartTheme,
43-
axesTicksDimensions,
44-
axesSpecs,
45-
axesStyles,
46-
): {
47-
chartDimensions: Dimensions;
48-
leftMargin: number;
49-
} => computeChartDimensions(chartContainerDimensions, chartTheme, axesTicksDimensions, axesStyles, axesSpecs),
42+
(chartContainerDimensions, chartTheme, axesTicksDimensions, axesSpecs, axesStyles, legendSize): ChartDimensions =>
43+
computeChartDimensions(
44+
chartContainerDimensions,
45+
chartTheme,
46+
axesTicksDimensions,
47+
axesStyles,
48+
axesSpecs,
49+
getLegendDimension(legendSize),
50+
),
5051
)(getChartIdSelector);
52+
53+
function getLegendDimension({
54+
position,
55+
width,
56+
height,
57+
margin,
58+
}: LegendSizing): {
59+
top: number;
60+
left: number;
61+
} {
62+
let left = 0;
63+
let top = 0;
64+
65+
if (position === Position.Left) {
66+
left = width + margin * 2;
67+
} else if (position === Position.Top) {
68+
top = height + margin * 2;
69+
}
70+
71+
return {
72+
left,
73+
top,
74+
};
75+
}

src/chart_types/xy_chart/state/selectors/get_tooltip_position.ts

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ import createCachedSelector from 're-reselect';
2121

2222
import { TooltipAnchorPosition } from '../../../../components/tooltip/types';
2323
import { getChartIdSelector } from '../../../../state/selectors/get_chart_id';
24+
import { getLegendSizeSelector } from '../../../../state/selectors/get_legend_size';
2425
import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs';
2526
import { getTooltipAnchorPosition } from '../../crosshair/crosshair_utils';
2627
import { computeChartDimensionsSelector } from './compute_chart_dimensions';
27-
import { getComputedScalesSelector } from './get_computed_scales';
2828
import { getCursorBandPositionSelector } from './get_cursor_band';
2929
import { getProjectedPointerPositionSelector } from './get_projected_pointer_position';
3030

@@ -35,24 +35,13 @@ export const getTooltipAnchorPositionSelector = createCachedSelector(
3535
getSettingsSpecSelector,
3636
getCursorBandPositionSelector,
3737
getProjectedPointerPositionSelector,
38-
getComputedScalesSelector,
38+
getLegendSizeSelector,
3939
],
40-
(
41-
{ chartDimensions },
42-
settings,
43-
cursorBandPosition,
44-
projectedPointerPosition,
45-
scales,
46-
): TooltipAnchorPosition | null => {
40+
(chartDimensions, settings, cursorBandPosition, projectedPointerPosition): TooltipAnchorPosition | null => {
4741
if (!cursorBandPosition) {
4842
return null;
4943
}
50-
return getTooltipAnchorPosition(
51-
chartDimensions,
52-
settings.rotation,
53-
cursorBandPosition,
54-
projectedPointerPosition,
55-
scales.xScale.isSingleValue(),
56-
);
44+
45+
return getTooltipAnchorPosition(chartDimensions, settings.rotation, cursorBandPosition, projectedPointerPosition);
5746
},
5847
)(getChartIdSelector);

src/chart_types/xy_chart/utils/dimensions.test.ts

Lines changed: 57 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ describe('Computed chart dimensions', () => {
4747
top: 10,
4848
bottom: 10,
4949
};
50+
const legendSize = {
51+
top: 0,
52+
left: 0,
53+
};
5054

5155
const axis1Dims: AxisTicksDimensions = {
5256
tickValues: [0, 1],
@@ -89,7 +93,14 @@ describe('Computed chart dimensions', () => {
8993
const axisDims = new Map<AxisId, AxisTicksDimensions>();
9094
const axisStyles = new Map();
9195
const axisSpecs: AxisSpec[] = [];
92-
const { chartDimensions } = computeChartDimensions(parentDim, chartTheme, axisDims, axisStyles, axisSpecs);
96+
const { chartDimensions } = computeChartDimensions(
97+
parentDim,
98+
chartTheme,
99+
axisDims,
100+
axisStyles,
101+
axisSpecs,
102+
legendSize,
103+
);
93104
expect(chartDimensions.left + chartDimensions.width).toBeLessThanOrEqual(parentDim.width);
94105
expect(chartDimensions.top + chartDimensions.height).toBeLessThanOrEqual(parentDim.height);
95106
expect(chartDimensions).toMatchSnapshot();
@@ -101,7 +112,14 @@ describe('Computed chart dimensions', () => {
101112
const axisStyles = new Map();
102113
const axisSpecs = [axisLeftSpec];
103114
axisDims.set('axis_1', axis1Dims);
104-
const { chartDimensions } = computeChartDimensions(parentDim, chartTheme, axisDims, axisStyles, axisSpecs);
115+
const { chartDimensions } = computeChartDimensions(
116+
parentDim,
117+
chartTheme,
118+
axisDims,
119+
axisStyles,
120+
axisSpecs,
121+
legendSize,
122+
);
105123
expect(chartDimensions.left + chartDimensions.width).toBeLessThanOrEqual(parentDim.width);
106124
expect(chartDimensions.top + chartDimensions.height).toBeLessThanOrEqual(parentDim.height);
107125
expect(chartDimensions).toMatchSnapshot();
@@ -113,7 +131,14 @@ describe('Computed chart dimensions', () => {
113131
const axisStyles = new Map();
114132
const axisSpecs = [{ ...axisLeftSpec, position: Position.Right }];
115133
axisDims.set('axis_1', axis1Dims);
116-
const { chartDimensions } = computeChartDimensions(parentDim, chartTheme, axisDims, axisStyles, axisSpecs);
134+
const { chartDimensions } = computeChartDimensions(
135+
parentDim,
136+
chartTheme,
137+
axisDims,
138+
axisStyles,
139+
axisSpecs,
140+
legendSize,
141+
);
117142
expect(chartDimensions.left + chartDimensions.width).toBeLessThanOrEqual(parentDim.width);
118143
expect(chartDimensions.top + chartDimensions.height).toBeLessThanOrEqual(parentDim.height);
119144
expect(chartDimensions).toMatchSnapshot();
@@ -130,7 +155,14 @@ describe('Computed chart dimensions', () => {
130155
},
131156
];
132157
axisDims.set('axis_1', axis1Dims);
133-
const { chartDimensions } = computeChartDimensions(parentDim, chartTheme, axisDims, axisStyles, axisSpecs);
158+
const { chartDimensions } = computeChartDimensions(
159+
parentDim,
160+
chartTheme,
161+
axisDims,
162+
axisStyles,
163+
axisSpecs,
164+
legendSize,
165+
);
134166
expect(chartDimensions.left + chartDimensions.width).toBeLessThanOrEqual(parentDim.width);
135167
expect(chartDimensions.top + chartDimensions.height).toBeLessThanOrEqual(parentDim.height);
136168
expect(chartDimensions).toMatchSnapshot();
@@ -147,7 +179,14 @@ describe('Computed chart dimensions', () => {
147179
},
148180
];
149181
axisDims.set('axis_1', axis1Dims);
150-
const { chartDimensions } = computeChartDimensions(parentDim, chartTheme, axisDims, axisStyles, axisSpecs);
182+
const { chartDimensions } = computeChartDimensions(
183+
parentDim,
184+
chartTheme,
185+
axisDims,
186+
axisStyles,
187+
axisSpecs,
188+
legendSize,
189+
);
151190
expect(chartDimensions.left + chartDimensions.width).toBeLessThanOrEqual(parentDim.width);
152191
expect(chartDimensions.top + chartDimensions.height).toBeLessThanOrEqual(parentDim.height);
153192
expect(chartDimensions).toMatchSnapshot();
@@ -162,7 +201,7 @@ describe('Computed chart dimensions', () => {
162201
},
163202
];
164203
axisDims.set('foo', axis1Dims);
165-
const chartDimensions = computeChartDimensions(parentDim, chartTheme, axisDims, axisStyles, axisSpecs);
204+
const chartDimensions = computeChartDimensions(parentDim, chartTheme, axisDims, axisStyles, axisSpecs, legendSize);
166205

167206
const expectedDims = {
168207
chartDimensions: {
@@ -172,6 +211,10 @@ describe('Computed chart dimensions', () => {
172211
top: 20,
173212
},
174213
leftMargin: 10,
214+
offset: {
215+
top: 0,
216+
left: 0,
217+
},
175218
};
176219

177220
expect(chartDimensions).toEqual(expectedDims);
@@ -184,7 +227,14 @@ describe('Computed chart dimensions', () => {
184227
hide: true,
185228
position: Position.Bottom,
186229
});
187-
const hiddenAxisChartDimensions = computeChartDimensions(parentDim, chartTheme, axisDims, axisStyles, axisSpecs);
230+
const hiddenAxisChartDimensions = computeChartDimensions(
231+
parentDim,
232+
chartTheme,
233+
axisDims,
234+
axisStyles,
235+
axisSpecs,
236+
legendSize,
237+
);
188238

189239
expect(hiddenAxisChartDimensions).toEqual(expectedDims);
190240
});

0 commit comments

Comments
 (0)