diff --git a/package.json b/package.json index 4de42753413..19982c1427f 100644 --- a/package.json +++ b/package.json @@ -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\"", diff --git a/src/plugins/plugin.tooltip.js b/src/plugins/plugin.tooltip.js index fa6859b03ec..3d0643b40de 100644 --- a/src/plugins/plugin.tooltip.js +++ b/src/plugins/plugin.tooltip.js @@ -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}; } -/** - * 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'; +function determineYAlign(chart, size) { + const {y, height} = size; if (y < height / 2) { - yAlign = 'top'; + return 'top'; } else if (y > (chart.height - height / 2)) { - yAlign = 'bottom'; + return 'bottom'; } + return 'center'; +} - let lf, rf; // functions to determine left, right alignment - const midX = (chartArea.left + chartArea.right) / 2; - const midY = (chartArea.top + chartArea.bottom) / 2; +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 (yAlign === 'center') { - lf = (value) => value <= midX; - rf = (value) => value > midX; - } else { - lf = (value) => value <= (width / 2); - rf = (value) => value >= (chart.width - (width / 2)); + if (xAlign === 'right' && x - width - caret < 0) { + return true; } +} - // 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'; +function determineXAlign(chart, options, size, yAlign) { + const {x, width} = size; + const {width: chartWidth, chartArea: {left, right}} = chart; + let xAlign = 'center'; - if (lf(x)) { + if (yAlign === 'center') { + xAlign = x <= (left + right) / 2 ? 'left' : 'right'; + } else if (x <= width / 2) { 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)) { + } else if (x >= chartWidth - width / 2) { xAlign = 'right'; + } - // Is tooltip too wide and goes outside left edge of canvas? - if (orf(x)) { - xAlign = 'center'; - yAlign = yf(y); - } + 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 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) { diff --git a/test/fixtures/plugin.tooltip/opacity.js b/test/fixtures/plugin.tooltip/opacity.js index 4726fb832c7..c6c4424ecb2 100644 --- a/test/fixtures/plugin.tooltip/opacity.js +++ b/test/fixtures/plugin.tooltip/opacity.js @@ -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 } diff --git a/test/fixtures/plugin.tooltip/opacity.png b/test/fixtures/plugin.tooltip/opacity.png index 4ad9c4055fb..e44b000f938 100644 Binary files a/test/fixtures/plugin.tooltip/opacity.png and b/test/fixtures/plugin.tooltip/opacity.png differ diff --git a/test/fixtures/plugin.tooltip/point-style.js b/test/fixtures/plugin.tooltip/point-style.js index f14dc83b5f0..255e4f153b8 100644 --- a/test/fixtures/plugin.tooltip/point-style.js +++ b/test/fixtures/plugin.tooltip/point-style.js @@ -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 diff --git a/test/fixtures/plugin.tooltip/point-style.png b/test/fixtures/plugin.tooltip/point-style.png index defb03359fc..44662c6b007 100644 Binary files a/test/fixtures/plugin.tooltip/point-style.png and b/test/fixtures/plugin.tooltip/point-style.png differ diff --git a/test/fixtures/plugin.tooltip/positioning.js b/test/fixtures/plugin.tooltip/positioning.js new file mode 100644 index 00000000000..c30ff3e82c5 --- /dev/null +++ b/test/fixtures/plugin.tooltip/positioning.js @@ -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 + } + } +}; diff --git a/test/fixtures/plugin.tooltip/positioning.png b/test/fixtures/plugin.tooltip/positioning.png new file mode 100644 index 00000000000..da095f7e7fc Binary files /dev/null and b/test/fixtures/plugin.tooltip/positioning.png differ diff --git a/test/specs/plugin.tooltip.tests.js b/test/specs/plugin.tooltip.tests.js index 9dbd198e627..2a1fe22fea7 100644 --- a/test/specs/plugin.tooltip.tests.js +++ b/test/specs/plugin.tooltip.tests.js @@ -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() {