mirror of
https://github.com/chartjs/Chart.js.git
synced 2026-03-12 11:16:51 +01:00
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:
@@ -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\"",
|
||||
|
||||
@@ -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) {
|
||||
|
||||
19
test/fixtures/plugin.tooltip/opacity.js
vendored
19
test/fixtures/plugin.tooltip/opacity.js
vendored
@@ -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
|
||||
}
|
||||
|
||||
BIN
test/fixtures/plugin.tooltip/opacity.png
vendored
BIN
test/fixtures/plugin.tooltip/opacity.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 20 KiB |
21
test/fixtures/plugin.tooltip/point-style.js
vendored
21
test/fixtures/plugin.tooltip/point-style.js
vendored
@@ -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
|
||||
|
||||
BIN
test/fixtures/plugin.tooltip/point-style.png
vendored
BIN
test/fixtures/plugin.tooltip/point-style.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
71
test/fixtures/plugin.tooltip/positioning.js
vendored
Normal file
71
test/fixtures/plugin.tooltip/positioning.js
vendored
Normal 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
|
||||
}
|
||||
}
|
||||
};
|
||||
BIN
test/fixtures/plugin.tooltip/positioning.png
vendored
Normal file
BIN
test/fixtures/plugin.tooltip/positioning.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user