From 683e86e549d8cc4f4f8d23b57b05ea4f41184768 Mon Sep 17 00:00:00 2001 From: Kaido Hallik Date: Sun, 12 Nov 2017 01:02:05 +0200 Subject: [PATCH] Avoid tooltip truncation in x axis if there is enough space (#3998) * In tooltip x align calculation take into account caretSize Truncation up to caretSize pixels could happen if label text produced tooltip element with size width: * left side tooltip: width < x and width > x - caretSize * right side tooltip: width < chartWidth - x and width > chartWidth - x - caretSize Default caretSize = 5, so with default configuration truncation up to 5 pixels could happen. * avoid tooltip truncation if possible use whole chart area for displaying tooltip * in xAlign calculation take into account caretPadding * add tests for tooltip truncation avoid logic * use caretX instead of xCaret * fix lint errors --- src/core/core.tooltip.js | 16 +++++--- test/specs/core.tooltip.tests.js | 67 ++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 5 deletions(-) diff --git a/src/core/core.tooltip.js b/src/core/core.tooltip.js index 73460f8d1..0072580df 100644 --- a/src/core/core.tooltip.js +++ b/src/core/core.tooltip.js @@ -299,10 +299,10 @@ module.exports = function(Chart) { } olf = function(x) { - return x + size.width > chart.width; + return x + size.width + model.caretSize + model.caretPadding > chart.width; }; orf = function(x) { - return x - size.width < 0; + return x - size.width - model.caretSize - model.caretPadding < 0; }; yf = function(y) { return y <= midY ? 'top' : 'bottom'; @@ -336,7 +336,7 @@ module.exports = function(Chart) { /** * @Helper to get the location a tooltip needs to be placed at given the initial position (via the vm) and the size and alignment */ - function getBackgroundPoint(vm, size, alignment) { + function getBackgroundPoint(vm, size, alignment, chart) { // Background Position var x = vm.x; var y = vm.y; @@ -353,6 +353,12 @@ module.exports = function(Chart) { x -= size.width; } else if (xAlign === 'center') { x -= (size.width / 2); + if (x + size.width > chart.width) { + x = chart.width - size.width; + } + if (x < 0) { + x = 0; + } } if (yAlign === 'top') { @@ -545,7 +551,7 @@ module.exports = function(Chart) { tooltipSize = getTooltipSize(this, model); alignment = determineAlignment(this, tooltipSize); // Final Size and Position - backgroundPoint = getBackgroundPoint(model, tooltipSize, alignment); + backgroundPoint = getBackgroundPoint(model, tooltipSize, alignment, me._chart); } else { model.opacity = 0; } @@ -617,7 +623,7 @@ module.exports = function(Chart) { x1 = x2 - caretSize; x3 = x2 + caretSize; } else { - x2 = ptX + (width / 2); + x2 = vm.caretX; x1 = x2 - caretSize; x3 = x2 + caretSize; } diff --git a/test/specs/core.tooltip.tests.js b/test/specs/core.tooltip.tests.js index 632f8121d..c0a162968 100755 --- a/test/specs/core.tooltip.tests.js +++ b/test/specs/core.tooltip.tests.js @@ -882,4 +882,71 @@ describe('Core.Tooltip', function() { expect(fn.calls.first().object instanceof Chart.Tooltip).toBe(true); }); }); + + it('Should avoid tooltip truncation in x axis if there is enough space to show tooltip without truncation', function() { + var chart = window.acquireChart({ + type: 'pie', + data: { + datasets: [{ + data: [ + 50, + 50 + ], + backgroundColor: [ + 'rgb(255, 0, 0)', + 'rgb(0, 255, 0)' + ], + label: 'Dataset 1' + }], + labels: [ + 'Red long tooltip text to avoid unnecessary loop steps', + 'Green long tooltip text to avoid unnecessary loop steps' + ] + }, + options: { + responsive: true, + animation: { + // without this slice center point is calculated wrong + animateRotate: false + } + } + }); + + // Trigger an event over top of the slice + for (var slice = 0; slice < 2; slice++) { + var meta = chart.getDatasetMeta(0); + var point = meta.data[slice].getCenterPoint(); + var tooltipPosition = meta.data[slice].tooltipPosition(); + var node = chart.canvas; + var rect = node.getBoundingClientRect(); + + var mouseMoveEvent = new MouseEvent('mousemove', { + view: window, + bubbles: true, + cancelable: true, + clientX: rect.left + point.x, + clientY: rect.top + point.y + }); + var mouseOutEvent = new MouseEvent('mouseout'); + + // Lets cycle while tooltip is narrower than chart area + var infiniteCycleDefense = 70; + for (var i = 0; i < infiniteCycleDefense; i++) { + chart.config.data.labels[slice] = chart.config.data.labels[slice] + 'l'; + chart.update(); + node.dispatchEvent(mouseOutEvent); + node.dispatchEvent(mouseMoveEvent); + var model = chart.tooltip._model; + expect(model.x).toBeGreaterThanOrEqual(0); + if (model.width <= chart.width) { + expect(model.x + model.width).toBeLessThanOrEqual(chart.width); + } + expect(model.caretX).toBe(tooltipPosition.x); + // if tooltip is longer than chart area then all tests done + if (model.width > chart.width) { + break; + } + } + } + }); });