Skip to content

Commit a6b3b99

Browse files
authored
Fix tooltip positioning issues (#8646)
* Fix tooltip positioning issues * Update fixture, add npm run dev:ff * Refactor determineXAlign * Simplify more * remove unneeded change
1 parent 1f6d0a2 commit a6b3b99

File tree

9 files changed

+138
-70
lines changed

9 files changed

+138
-70
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
"autobuild": "rollup -c -w",
3939
"build": "rollup -c",
4040
"dev": "karma start --auto-watch --no-single-run --browsers chrome --grep",
41+
"dev:ff": "karma start --auto-watch --no-single-run --browsers firefox --grep",
4142
"docs": "cd docs && npm install && npm run build",
4243
"lint-js": "eslint \"samples/**/*.html\" \"samples/**/*.js\" \"src/**/*.js\" \"test/**/*.js\"",
4344
"lint-md": "markdownlint-cli2 \"**/*.md\" \"**/*.mdx\" \"#**/node_modules\"",

src/plugins/plugin.tooltip.js

Lines changed: 44 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import Element from '../core/core.element';
33
import {each, noop, isNullOrUndef, isArray, _elementsEqual} from '../helpers/helpers.core';
44
import {toFont, toPadding} from '../helpers/helpers.options';
55
import {getRtlAdapter, overrideTextDirection, restoreTextDirection} from '../helpers/helpers.rtl';
6-
import {distanceBetweenPoints} from '../helpers/helpers.math';
6+
import {distanceBetweenPoints, _limitValue} from '../helpers/helpers.math';
77
import {drawPoint} from '../helpers';
88

99
/**
@@ -211,76 +211,67 @@ function getTooltipSize(tooltip, options) {
211211
return {width, height};
212212
}
213213

214-
/**
215-
* Helper to get the alignment of a tooltip given the size
216-
*/
217-
function determineAlignment(chart, options, size) {
218-
const {x, y, width, height} = size;
219-
const chartArea = chart.chartArea;
220-
let xAlign = 'center';
221-
let yAlign = 'center';
214+
function determineYAlign(chart, size) {
215+
const {y, height} = size;
222216

223217
if (y < height / 2) {
224-
yAlign = 'top';
218+
return 'top';
225219
} else if (y > (chart.height - height / 2)) {
226-
yAlign = 'bottom';
220+
return 'bottom';
227221
}
222+
return 'center';
223+
}
228224

229-
let lf, rf; // functions to determine left, right alignment
230-
const midX = (chartArea.left + chartArea.right) / 2;
231-
const midY = (chartArea.top + chartArea.bottom) / 2;
225+
function doesNotFitWithAlign(xAlign, chart, options, size) {
226+
const {x, width} = size;
227+
const caret = options.caretSize + options.caretPadding;
228+
if (xAlign === 'left' && x + width + caret > chart.width) {
229+
return true;
230+
}
232231

233-
if (yAlign === 'center') {
234-
lf = (value) => value <= midX;
235-
rf = (value) => value > midX;
236-
} else {
237-
lf = (value) => value <= (width / 2);
238-
rf = (value) => value >= (chart.width - (width / 2));
232+
if (xAlign === 'right' && x - width - caret < 0) {
233+
return true;
239234
}
235+
}
240236

241-
// functions to determine if left/right alignment causes tooltip to go outside chart
242-
const olf = (value) => value + width + options.caretSize + options.caretPadding > chart.width;
243-
const orf = (value) => value - width - options.caretSize - options.caretPadding < 0;
244-
// function to get the y alignment if the tooltip goes outside of the left or right edges
245-
const yf = (value) => value <= midY ? 'top' : 'bottom';
237+
function determineXAlign(chart, options, size, yAlign) {
238+
const {x, width} = size;
239+
const {width: chartWidth, chartArea: {left, right}} = chart;
240+
let xAlign = 'center';
246241

247-
if (lf(x)) {
242+
if (yAlign === 'center') {
243+
xAlign = x <= (left + right) / 2 ? 'left' : 'right';
244+
} else if (x <= width / 2) {
248245
xAlign = 'left';
249-
250-
// Is tooltip too wide and goes over the right side of the chart.?
251-
if (olf(x)) {
252-
xAlign = 'center';
253-
yAlign = yf(y);
254-
}
255-
} else if (rf(x)) {
246+
} else if (x >= chartWidth - width / 2) {
256247
xAlign = 'right';
248+
}
257249

258-
// Is tooltip too wide and goes outside left edge of canvas?
259-
if (orf(x)) {
260-
xAlign = 'center';
261-
yAlign = yf(y);
262-
}
250+
if (doesNotFitWithAlign(xAlign, chart, options, size)) {
251+
xAlign = 'center';
263252
}
264253

254+
return xAlign;
255+
}
256+
257+
/**
258+
* Helper to get the alignment of a tooltip given the size
259+
*/
260+
function determineAlignment(chart, options, size) {
261+
const yAlign = options.yAlign || determineYAlign(chart, size);
262+
265263
return {
266-
xAlign: options.xAlign ? options.xAlign : xAlign,
267-
yAlign: options.yAlign ? options.yAlign : yAlign
264+
xAlign: options.xAlign || determineXAlign(chart, options, size, yAlign),
265+
yAlign
268266
};
269267
}
270268

271-
function alignX(size, xAlign, chartWidth) {
272-
// eslint-disable-next-line prefer-const
269+
function alignX(size, xAlign) {
273270
let {x, width} = size;
274271
if (xAlign === 'right') {
275272
x -= width;
276273
} else if (xAlign === 'center') {
277274
x -= (width / 2);
278-
if (x + width > chartWidth) {
279-
x = chartWidth - width;
280-
}
281-
if (x < 0) {
282-
x = 0;
283-
}
284275
}
285276
return x;
286277
}
@@ -307,7 +298,7 @@ function getBackgroundPoint(options, size, alignment, chart) {
307298
const paddingAndSize = caretSize + caretPadding;
308299
const radiusAndPadding = cornerRadius + caretPadding;
309300

310-
let x = alignX(size, xAlign, chart.width);
301+
let x = alignX(size, xAlign);
311302
const y = alignY(size, yAlign, paddingAndSize);
312303

313304
if (yAlign === 'center') {
@@ -322,7 +313,10 @@ function getBackgroundPoint(options, size, alignment, chart) {
322313
x += radiusAndPadding;
323314
}
324315

325-
return {x, y};
316+
return {
317+
x: _limitValue(x, 0, chart.width - size.width),
318+
y: _limitValue(y, 0, chart.height - size.height)
319+
};
326320
}
327321

328322
function getAlignedX(tooltip, align, options) {

test/fixtures/plugin.tooltip/opacity.js

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -47,17 +47,18 @@ module.exports = {
4747
plugins: {
4848
legend: false,
4949
title: false,
50-
filler: false
51-
},
52-
tooltips: {
53-
mode: 'nearest',
54-
intersect: false,
55-
callbacks: {
56-
label: function() {
57-
return '\u200b';
50+
filler: false,
51+
tooltip: {
52+
mode: 'nearest',
53+
intersect: false,
54+
callbacks: {
55+
label: function() {
56+
return '\u200b';
57+
},
5858
}
59-
}
59+
},
6060
},
61+
6162
layout: {
6263
padding: 15
6364
}
960 Bytes
Loading

test/fixtures/plugin.tooltip/point-style.js

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,18 @@ module.exports = {
3030
plugins: {
3131
legend: false,
3232
title: false,
33-
filler: false
34-
},
35-
tooltips: {
36-
mode: 'nearest',
37-
intersect: false,
38-
usePointStyle: true,
39-
callbacks: {
40-
label: function() {
41-
return '\u200b';
33+
filler: false,
34+
tooltip: {
35+
mode: 'nearest',
36+
intersect: false,
37+
padding: 5,
38+
usePointStyle: true,
39+
callbacks: {
40+
label: function() {
41+
return '\u200b';
42+
}
4243
}
43-
}
44+
},
4445
},
4546
layout: {
4647
padding: 15
-1 Bytes
Loading
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
const data = [];
2+
for (let x = 0; x < 3; x++) {
3+
for (let y = 0; y < 3; y++) {
4+
data.push({x, y});
5+
}
6+
}
7+
8+
module.exports = {
9+
config: {
10+
type: 'scatter',
11+
data: {
12+
datasets: [{
13+
data,
14+
backgroundColor: 'red',
15+
radius: 8
16+
}],
17+
},
18+
options: {
19+
scales: {
20+
x: {display: false},
21+
y: {display: false}
22+
},
23+
plugins: {
24+
legend: false,
25+
title: false,
26+
filler: false,
27+
tooltip: {
28+
mode: 'point',
29+
intersect: true,
30+
// spriteText: use white background to hide any gaps between fonts
31+
backgroundColor: 'white',
32+
borderColor: 'black',
33+
borderWidth: 1,
34+
callbacks: {
35+
beforeLabel: () => 'before label',
36+
label: () => 'label',
37+
afterLabel: () => 'after1\nafter2\nafter3\nafter4\nafter5'
38+
}
39+
}
40+
},
41+
},
42+
plugins: [{
43+
afterDraw: function(chart) {
44+
const canvas = chart.canvas;
45+
const rect = canvas.getBoundingClientRect();
46+
const meta = chart.getDatasetMeta(0);
47+
let point, event;
48+
49+
for (let i = 0; i < data.length; i++) {
50+
point = meta.data[i];
51+
event = {
52+
type: 'mousemove',
53+
target: canvas,
54+
clientX: rect.left + point.x,
55+
clientY: rect.top + point.y
56+
};
57+
chart._handleEvent(event);
58+
chart.tooltip.handleEvent(event);
59+
chart.tooltip.draw(chart.ctx);
60+
}
61+
}
62+
}]
63+
},
64+
options: {
65+
spriteText: true,
66+
canvas: {
67+
height: 400,
68+
width: 500
69+
}
70+
}
71+
};
37.8 KB
Loading

test/specs/plugin.tooltip.tests.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ const tooltipPlugin = Chart.registry.getPlugin('tooltip');
33
const Tooltip = tooltipPlugin._element;
44

55
describe('Plugin.Tooltip', function() {
6-
describe('auto', jasmine.fixture.specs('core.tooltip'));
6+
describe('auto', jasmine.fixture.specs('plugin.tooltip'));
77

88
describe('config', function() {
99
it('should not include the dataset label in the body string if not defined', function() {

0 commit comments

Comments
 (0)