Limit interactions to chartArea (+/-0.5px) (#6943)

Limit interactions to chartArea (+/-0.5px)
This commit is contained in:
Jukka Kurkela
2020-01-12 01:10:32 +02:00
committed by Evert Timberg
parent 5054ecfd7e
commit f1677b6652
10 changed files with 54 additions and 38 deletions

View File

@@ -19,6 +19,7 @@ Chart.js 3.0 introduces a number of breaking changes. Chart.js 2.0 was released
### Interactions
* `interactions` are now limited to the chart area
* `{mode: 'label'}` was replaced with `{mode: 'index'}`
* `{mode: 'single'}` was replaced with `{mode: 'nearest', intersect: true}`
* `modes['X-axis']` was replaced with `{mode: 'index', intersect: false}`
@@ -117,6 +118,7 @@ Animation system was completely rewritten in Chart.js v3. Each property can now
* `Element._view`
* `TimeScale._getPixelForOffset`
* `TimeScale.getLabelWidth`
* `Tooltip._lastActive`
### Renamed

View File

@@ -12,7 +12,8 @@ defaults._set('horizontalBar', {
scales: {
x: {
type: 'linear',
position: 'bottom'
position: 'bottom',
beginAtZero: true
},
y: {
type: 'category',

View File

@@ -2,6 +2,7 @@
import helpers from '../helpers/index';
import {isNumber} from '../helpers/helpers.math';
import {_isPointInArea} from '../helpers/helpers.canvas';
/**
* Helper function to get relative position for an event
@@ -43,6 +44,8 @@ function evaluateAllVisibleItems(chart, handler) {
/**
* Helper function to check the items at the hovered index on the index scale
* @param {Chart} chart - the chart
* @param {string} axis - the axis mode. x|y|xy
* @param {object} position - the point to be nearest to
* @param {function} handler - the callback to execute for each visible item
* @return whether all scales were of a suitable type
*/
@@ -91,13 +94,18 @@ function getDistanceMetricForAxis(axis) {
/**
* Helper function to get the items that intersect the event position
* @param {ChartElement[]} items - elements to filter
* @param {Chart} chart - the chart
* @param {object} position - the point to be nearest to
* @param {string} axis - the axis mode. x|y|xy
* @return {ChartElement[]} the nearest items
*/
function getIntersectItems(chart, position, axis) {
const items = [];
if (!_isPointInArea(position, chart.chartArea)) {
return items;
}
const evaluationFunc = function(element, datasetIndex, index) {
if (element.inRange(position.x, position.y)) {
items.push({element, datasetIndex, index});
@@ -126,6 +134,10 @@ function getNearestItems(chart, position, axis, intersect) {
let minDistance = Number.POSITIVE_INFINITY;
let items = [];
if (!_isPointInArea(position, chart.chartArea)) {
return items;
}
const evaluationFunc = function(element, datasetIndex, index) {
if (intersect && !element.inRange(position.x, position.y)) {
return;

View File

@@ -219,10 +219,10 @@ function createTooltipItem(chart, item) {
const {label, value} = chart.getDatasetMeta(datasetIndex).controller._getLabelAndValue(index);
return {
label: label,
value: value,
index: index,
datasetIndex: datasetIndex
label,
value,
index,
datasetIndex
};
}
@@ -452,7 +452,6 @@ class Tooltip extends Element {
const me = this;
me.opacity = 0;
me._active = [];
me._lastActive = [];
me.initialize();
}
@@ -962,28 +961,26 @@ class Tooltip extends Element {
* @returns {boolean} true if the tooltip changed
*/
handleEvent(e) {
var me = this;
var options = me.options;
var changed = false;
me._lastActive = me._lastActive || [];
const me = this;
const options = me.options;
const lastActive = me._active || [];
let changed = false;
let active = [];
// Find Active Elements for tooltips
if (e.type === 'mouseout') {
me._active = [];
} else {
me._active = me._chart.getElementsAtEventForMode(e, options.mode, options);
if (e.type !== 'mouseout') {
active = me._chart.getElementsAtEventForMode(e, options.mode, options);
if (options.reverse) {
me._active.reverse();
active.reverse();
}
}
// Remember Last Actives
changed = !helpers._elementsEqual(me._active, me._lastActive);
changed = !helpers._elementsEqual(active, lastActive);
// Only handle target event on tooltip change
if (changed) {
me._lastActive = me._active;
me._active = active;
if (options.enabled || options.custom) {
me._eventPosition = {
@@ -992,7 +989,6 @@ class Tooltip extends Element {
};
me.update(true);
// me.pivot();
}
}

View File

@@ -153,7 +153,7 @@ export function drawPoint(ctx, style, radius, x, y, rotation) {
* @private
*/
export function _isPointInArea(point, area) {
var epsilon = 1e-6; // 1e-6 is margin in pixels for accumulated error.
const epsilon = 0.5; // margin - to match rounded decimals
return point.x > area.left - epsilon && point.x < area.right + epsilon &&
point.y > area.top - epsilon && point.y < area.bottom + epsilon;

View File

@@ -773,6 +773,11 @@ describe('Chart.controllers.line', function() {
}]
},
options: {
scales: {
x: {
offset: true
}
},
elements: {
point: {
backgroundColor: 'rgb(100, 150, 200)',

View File

@@ -1130,12 +1130,12 @@ describe('Chart', function() {
var tooltip = chart.tooltip;
expect(chart.lastActive[0].element).toEqual(point);
expect(tooltip._lastActive[0].element).toEqual(point);
expect(tooltip._active[0].element).toEqual(point);
// Update and confirm tooltip is updated
chart.update();
expect(chart.lastActive[0].element).toEqual(point);
expect(tooltip._lastActive[0].element).toEqual(point);
expect(tooltip._active[0].element).toEqual(point);
});
it ('should update the metadata', function() {

View File

@@ -143,8 +143,8 @@ describe('Core.Interaction', function() {
type: 'click',
chart: chart,
native: true, // needed otherwise things its a DOM event
x: 0,
y: 0
x: chart.chartArea.left,
y: chart.chartArea.top
};
var elements = Chart.Interaction.modes.index(chart, evt, {intersect: false}).map(item => item.element);
@@ -182,8 +182,8 @@ describe('Core.Interaction', function() {
type: 'click',
chart: chart,
native: true, // needed otherwise things its a DOM event
x: 0,
y: 0
x: chart.chartArea.left,
y: chart.chartArea.top
};
var elements = Chart.Interaction.modes.index(chart, evt, {axis: 'xy', intersect: false}).map(item => item.element);
@@ -279,8 +279,8 @@ describe('Core.Interaction', function() {
type: 'click',
chart: chart,
native: true, // needed otherwise things its a DOM event
x: 0,
y: 0
x: chart.chartArea.left,
y: chart.chartArea.top
};
var elements = Chart.Interaction.modes.dataset(chart, evt, {axis: 'x', intersect: false});
@@ -293,8 +293,8 @@ describe('Core.Interaction', function() {
type: 'click',
chart: chart,
native: true, // needed otherwise things its a DOM event
x: 0,
y: 0
x: chart.chartArea.left,
y: chart.chartArea.top
};
var elements = Chart.Interaction.modes.dataset(chart, evt, {axis: 'y', intersect: false});
@@ -307,8 +307,8 @@ describe('Core.Interaction', function() {
type: 'click',
chart: chart,
native: true, // needed otherwise things its a DOM event
x: 0,
y: 0
x: chart.chartArea.left,
y: chart.chartArea.top
};
var elements = Chart.Interaction.modes.dataset(chart, evt, {intersect: false});
@@ -348,8 +348,8 @@ describe('Core.Interaction', function() {
type: 'click',
chart: chart,
native: true, // needed otherwise things its a DOM event
x: 0,
y: 0
x: chart.chartArea.left,
y: chart.chartArea.top
};
// Nearest to 0,0 (top left) will be first point of dataset 2

View File

@@ -70,7 +70,7 @@ describe('Core.Tooltip', function() {
bubbles: true,
cancelable: true,
clientX: rect.left + point.x,
clientY: 0
clientY: rect.top + chart.chartArea.top + 5 // +5 to make tests work consistently
});
// Manually trigger rather than having an async test

View File

@@ -32,8 +32,8 @@ describe('Chart.helpers.canvas', function() {
expect(isPointInArea({x: -1e-12, y: -1e-12}, area)).toBe(true);
expect(isPointInArea({x: 512, y: 256}, area)).toBe(true);
expect(isPointInArea({x: 512 + 1e-12, y: 256 + 1e-12}, area)).toBe(true);
expect(isPointInArea({x: -1e-3, y: 0}, area)).toBe(false);
expect(isPointInArea({x: 0, y: 256 + 1e-3}, area)).toBe(false);
expect(isPointInArea({x: -0.5, y: 0}, area)).toBe(false);
expect(isPointInArea({x: 0, y: 256.5}, area)).toBe(false);
});
});
});