Fix tooltip positioning issues (#8646)

* Fix tooltip positioning issues

* Update fixture, add npm run dev:ff

* Refactor determineXAlign

* Simplify more

* remove unneeded change
This commit is contained in:
Jukka Kurkela
2021-03-16 14:06:05 +02:00
committed by GitHub
parent 1f6d0a244b
commit a6b3b99675
9 changed files with 147 additions and 79 deletions

View File

@@ -38,6 +38,7 @@
"autobuild": "rollup -c -w",
"build": "rollup -c",
"dev": "karma start --auto-watch --no-single-run --browsers chrome --grep",
"dev:ff": "karma start --auto-watch --no-single-run --browsers firefox --grep",
"docs": "cd docs && npm install && npm run build",
"lint-js": "eslint \"samples/**/*.html\" \"samples/**/*.js\" \"src/**/*.js\" \"test/**/*.js\"",
"lint-md": "markdownlint-cli2 \"**/*.md\" \"**/*.mdx\" \"#**/node_modules\"",

View File

@@ -3,7 +3,7 @@ import Element from '../core/core.element';
import {each, noop, isNullOrUndef, isArray, _elementsEqual} from '../helpers/helpers.core';
import {toFont, toPadding} from '../helpers/helpers.options';
import {getRtlAdapter, overrideTextDirection, restoreTextDirection} from '../helpers/helpers.rtl';
import {distanceBetweenPoints} from '../helpers/helpers.math';
import {distanceBetweenPoints, _limitValue} from '../helpers/helpers.math';
import {drawPoint} from '../helpers';
/**
@@ -211,76 +211,67 @@ function getTooltipSize(tooltip, options) {
return {width, height};
}
function determineYAlign(chart, size) {
const {y, height} = size;
if (y < height / 2) {
return 'top';
} else if (y > (chart.height - height / 2)) {
return 'bottom';
}
return 'center';
}
function doesNotFitWithAlign(xAlign, chart, options, size) {
const {x, width} = size;
const caret = options.caretSize + options.caretPadding;
if (xAlign === 'left' && x + width + caret > chart.width) {
return true;
}
if (xAlign === 'right' && x - width - caret < 0) {
return true;
}
}
function determineXAlign(chart, options, size, yAlign) {
const {x, width} = size;
const {width: chartWidth, chartArea: {left, right}} = chart;
let xAlign = 'center';
if (yAlign === 'center') {
xAlign = x <= (left + right) / 2 ? 'left' : 'right';
} else if (x <= width / 2) {
xAlign = 'left';
} else if (x >= chartWidth - width / 2) {
xAlign = 'right';
}
if (doesNotFitWithAlign(xAlign, chart, options, size)) {
xAlign = 'center';
}
return xAlign;
}
/**
* Helper to get the alignment of a tooltip given the size
*/
function determineAlignment(chart, options, size) {
const {x, y, width, height} = size;
const chartArea = chart.chartArea;
let xAlign = 'center';
let yAlign = 'center';
if (y < height / 2) {
yAlign = 'top';
} else if (y > (chart.height - height / 2)) {
yAlign = 'bottom';
}
let lf, rf; // functions to determine left, right alignment
const midX = (chartArea.left + chartArea.right) / 2;
const midY = (chartArea.top + chartArea.bottom) / 2;
if (yAlign === 'center') {
lf = (value) => value <= midX;
rf = (value) => value > midX;
} else {
lf = (value) => value <= (width / 2);
rf = (value) => value >= (chart.width - (width / 2));
}
// functions to determine if left/right alignment causes tooltip to go outside chart
const olf = (value) => value + width + options.caretSize + options.caretPadding > chart.width;
const orf = (value) => value - width - options.caretSize - options.caretPadding < 0;
// function to get the y alignment if the tooltip goes outside of the left or right edges
const yf = (value) => value <= midY ? 'top' : 'bottom';
if (lf(x)) {
xAlign = 'left';
// Is tooltip too wide and goes over the right side of the chart.?
if (olf(x)) {
xAlign = 'center';
yAlign = yf(y);
}
} else if (rf(x)) {
xAlign = 'right';
// Is tooltip too wide and goes outside left edge of canvas?
if (orf(x)) {
xAlign = 'center';
yAlign = yf(y);
}
}
const yAlign = options.yAlign || determineYAlign(chart, size);
return {
xAlign: options.xAlign ? options.xAlign : xAlign,
yAlign: options.yAlign ? options.yAlign : yAlign
xAlign: options.xAlign || determineXAlign(chart, options, size, yAlign),
yAlign
};
}
function alignX(size, xAlign, chartWidth) {
// eslint-disable-next-line prefer-const
function alignX(size, xAlign) {
let {x, width} = size;
if (xAlign === 'right') {
x -= width;
} else if (xAlign === 'center') {
x -= (width / 2);
if (x + width > chartWidth) {
x = chartWidth - width;
}
if (x < 0) {
x = 0;
}
}
return x;
}
@@ -307,7 +298,7 @@ function getBackgroundPoint(options, size, alignment, chart) {
const paddingAndSize = caretSize + caretPadding;
const radiusAndPadding = cornerRadius + caretPadding;
let x = alignX(size, xAlign, chart.width);
let x = alignX(size, xAlign);
const y = alignY(size, yAlign, paddingAndSize);
if (yAlign === 'center') {
@@ -322,7 +313,10 @@ function getBackgroundPoint(options, size, alignment, chart) {
x += radiusAndPadding;
}
return {x, y};
return {
x: _limitValue(x, 0, chart.width - size.width),
y: _limitValue(y, 0, chart.height - size.height)
};
}
function getAlignedX(tooltip, align, options) {

View File

@@ -47,17 +47,18 @@ module.exports = {
plugins: {
legend: false,
title: false,
filler: false
},
tooltips: {
mode: 'nearest',
intersect: false,
callbacks: {
label: function() {
return '\u200b';
filler: false,
tooltip: {
mode: 'nearest',
intersect: false,
callbacks: {
label: function() {
return '\u200b';
},
}
}
},
},
layout: {
padding: 15
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

@@ -30,17 +30,18 @@ module.exports = {
plugins: {
legend: false,
title: false,
filler: false
},
tooltips: {
mode: 'nearest',
intersect: false,
usePointStyle: true,
callbacks: {
label: function() {
return '\u200b';
filler: false,
tooltip: {
mode: 'nearest',
intersect: false,
padding: 5,
usePointStyle: true,
callbacks: {
label: function() {
return '\u200b';
}
}
}
},
},
layout: {
padding: 15

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -0,0 +1,71 @@
const data = [];
for (let x = 0; x < 3; x++) {
for (let y = 0; y < 3; y++) {
data.push({x, y});
}
}
module.exports = {
config: {
type: 'scatter',
data: {
datasets: [{
data,
backgroundColor: 'red',
radius: 8
}],
},
options: {
scales: {
x: {display: false},
y: {display: false}
},
plugins: {
legend: false,
title: false,
filler: false,
tooltip: {
mode: 'point',
intersect: true,
// spriteText: use white background to hide any gaps between fonts
backgroundColor: 'white',
borderColor: 'black',
borderWidth: 1,
callbacks: {
beforeLabel: () => 'before label',
label: () => 'label',
afterLabel: () => 'after1\nafter2\nafter3\nafter4\nafter5'
}
}
},
},
plugins: [{
afterDraw: function(chart) {
const canvas = chart.canvas;
const rect = canvas.getBoundingClientRect();
const meta = chart.getDatasetMeta(0);
let point, event;
for (let i = 0; i < data.length; i++) {
point = meta.data[i];
event = {
type: 'mousemove',
target: canvas,
clientX: rect.left + point.x,
clientY: rect.top + point.y
};
chart._handleEvent(event);
chart.tooltip.handleEvent(event);
chart.tooltip.draw(chart.ctx);
}
}
}]
},
options: {
spriteText: true,
canvas: {
height: 400,
width: 500
}
}
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View File

@@ -3,7 +3,7 @@ const tooltipPlugin = Chart.registry.getPlugin('tooltip');
const Tooltip = tooltipPlugin._element;
describe('Plugin.Tooltip', function() {
describe('auto', jasmine.fixture.specs('core.tooltip'));
describe('auto', jasmine.fixture.specs('plugin.tooltip'));
describe('config', function() {
it('should not include the dataset label in the body string if not defined', function() {