getMaxOverflow() {
const me = this;
const meta = me._cachedMeta;
- const border = meta.dataset.options.borderWidth || 0;
+ const dataset = meta.dataset;
+ const border = dataset.options && dataset.options.borderWidth || 0;
const data = meta.data || [];
if (!data.length) {
return border;
}
- const firstPoint = data[0].size();
- const lastPoint = data[data.length - 1].size();
+ const firstPoint = data[0].size(me.resolveDataElementOptions(0));
+ const lastPoint = data[data.length - 1].size(me.resolveDataElementOptions(data.length - 1));
return Math.max(border, firstPoint, lastPoint) / 2;
}
me.notifyPlugins('beforeElementsUpdate');
// Make sure all dataset controllers have correct meta data counts
+ let minPadding = 0;
for (let i = 0, ilen = me.data.datasets.length; i < ilen; i++) {
const {controller} = me.getDatasetMeta(i);
const reset = !animsDisabled && newControllers.indexOf(controller) === -1;
// New controllers will be reset after the layout pass, so we only want to modify
// elements added to new datasets
controller.buildOrUpdateElements(reset);
+ minPadding = Math.max(+controller.getMaxOverflow(), minPadding);
}
-
- me._updateLayout();
+ me._minPadding = minPadding;
+ me._updateLayout(minPadding);
// Only reset the controllers if we have animations
if (!animsDisabled) {
* hook, in which case, plugins will not be called on `afterLayout`.
* @private
*/
- _updateLayout() {
+ _updateLayout(minPadding) {
const me = this;
if (me.notifyPlugins('beforeLayout', {cancelable: true}) === false) {
return;
}
- layouts.update(me, me.width, me.height);
+ layouts.update(me, me.width, me.height, minPadding);
const area = me.chartArea;
const noArea = area.width <= 0 || area.height <= 0;
callCallback(options.onHover || hoverOptions.onHover, [e, active, me], me);
if (e.type === 'mouseup' || e.type === 'click' || e.type === 'contextmenu') {
- if (_isPointInArea(e, me.chartArea)) {
+ if (_isPointInArea(e, me.chartArea, me._minPadding)) {
callCallback(options.onClick, [e, active, me], me);
}
}
function getIntersectItems(chart, position, axis, useFinalPosition) {
const items = [];
- if (!_isPointInArea(position, chart.chartArea)) {
+ if (!_isPointInArea(position, chart.chartArea, chart._minPadding)) {
return items;
}
let minDistance = Number.POSITIVE_INFINITY;
let items = [];
- if (!_isPointInArea(position, chart.chartArea)) {
+ if (!_isPointInArea(position, chart.chartArea, chart._minPadding)) {
return items;
}
* @param {Chart} chart - the chart
* @param {number} width - the width to fit into
* @param {number} height - the height to fit into
+ * @param {number} minPadding - minimum padding required for each side of chart area
*/
- update(chart, width, height) {
+ update(chart, width, height, minPadding) {
if (!chart) {
return;
}
vBoxMaxWidth: availableWidth / 2 / visibleVerticalBoxCount,
hBoxMaxHeight: availableHeight / 2
});
+ const maxPadding = Object.assign({}, padding);
+ updateMaxPadding(maxPadding, toPadding(minPadding));
const chartArea = Object.assign({
- maxPadding: Object.assign({}, padding),
+ maxPadding,
w: availableWidth,
h: availableHeight,
x: padding.left,
return {x, y};
}
- size() {
- const options = this.options || {};
- const radius = Math.max(options.radius, options.hoverRadius) || 0;
+ size(options) {
+ options = options || this.options || {};
+ let radius = options.radius || 0;
+ radius = Math.max(radius, radius && options.hoverRadius || 0);
const borderWidth = radius && options.borderWidth || 0;
return (radius + borderWidth) * 2;
}
* Returns true if the point is inside the rectangle
* @param {object} point - The point to test
* @param {object} area - The rectangle
+ * @param {number} [margin] - allowed margin
* @returns {boolean}
* @private
*/
-export function _isPointInArea(point, area) {
- const epsilon = 0.5; // margin - to match rounded decimals
+export function _isPointInArea(point, area, margin) {
+ margin = margin || 0.5; // margin - default is 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;
+ return point.x > area.left - margin && point.x < area.right + margin &&
+ point.y > area.top - margin && point.y < area.bottom + margin;
}
export function clipArea(ctx, area) {
datasets: [{
data: [0],
radius: 16,
+ borderWidth: 0,
backgroundColor: 'red'
}],
},
datasets: [{
data,
backgroundColor: 'red',
- radius: 8
+ radius: 1,
+ hoverRadius: 0
}],
},
options: {
var bar2 = meta.data[1];
expect(bar1.x).toBeCloseToPixel(179);
- expect(bar1.y).toBeCloseToPixel(114);
- expect(bar2.x).toBeCloseToPixel(435);
- expect(bar2.y).toBeCloseToPixel(0);
+ expect(bar1.y).toBeCloseToPixel(117);
+ expect(bar2.x).toBeCloseToPixel(431);
+ expect(bar2.y).toBeCloseToPixel(4);
});
it('should get the bar points for hidden dataset', function() {
expect(meta._parsed.length).toBe(2);
[
- {x: 0, y: 512},
- {x: 171, y: 0}
+ {x: 5, y: 507},
+ {x: 171, y: 5}
].forEach(function(expected, i) {
expect(meta.data[i].x).toBeCloseToPixel(expected.x);
expect(meta.data[i].y).toBeCloseToPixel(expected.y);
var meta = chart.getDatasetMeta(0);
// 1 point
var point = meta.data[0];
- expect(point.x).toBeCloseToPixel(0);
+ expect(point.x).toBeCloseToPixel(5);
// 2 points
chart.data.labels = ['One', 'Two'];
var points = meta.data;
- expect(points[0].x).toBeCloseToPixel(0);
- expect(points[1].x).toBeCloseToPixel(512);
+ expect(points[0].x).toBeCloseToPixel(5);
+ expect(points[1].x).toBeCloseToPixel(507);
// 3 points
chart.data.labels = ['One', 'Two', 'Three'];
points = meta.data;
- expect(points[0].x).toBeCloseToPixel(0);
+ expect(points[0].x).toBeCloseToPixel(5);
expect(points[1].x).toBeCloseToPixel(256);
- expect(points[2].x).toBeCloseToPixel(512);
+ expect(points[2].x).toBeCloseToPixel(507);
// 4 points
chart.data.labels = ['One', 'Two', 'Three', 'Four'];
points = meta.data;
- expect(points[0].x).toBeCloseToPixel(0);
+ expect(points[0].x).toBeCloseToPixel(5);
expect(points[1].x).toBeCloseToPixel(171);
expect(points[2].x).toBeCloseToPixel(340);
- expect(points[3].x).toBeCloseToPixel(512);
+ expect(points[3].x).toBeCloseToPixel(507);
});
it('should update elements when the y scale is stacked', function() {
var meta0 = chart.getDatasetMeta(0);
[
- {x: 0, y: 146},
- {x: 171, y: 439},
- {x: 341, y: 146},
- {x: 512, y: 439}
+ {x: 5, y: 148},
+ {x: 171, y: 435},
+ {x: 341, y: 148},
+ {x: 507, y: 435}
].forEach(function(values, i) {
expect(meta0.data[i].x).toBeCloseToPixel(values.x);
expect(meta0.data[i].y).toBeCloseToPixel(values.y);
var meta1 = chart.getDatasetMeta(1);
[
- {x: 0, y: 0},
- {x: 171, y: 73},
- {x: 341, y: 146},
- {x: 512, y: 497}
+ {x: 5, y: 5},
+ {x: 171, y: 76},
+ {x: 341, y: 148},
+ {x: 507, y: 492}
].forEach(function(values, i) {
expect(meta1.data[i].x).toBeCloseToPixel(values.x);
expect(meta1.data[i].y).toBeCloseToPixel(values.y);
var meta0 = chart.getDatasetMeta(0);
[
- {x: 0, y: 146},
- {x: 171, y: 439},
- {x: 341, y: 146},
- {x: 512, y: 439}
+ {x: 5, y: 148},
+ {x: 171, y: 435},
+ {x: 341, y: 148},
+ {x: 507, y: 435}
].forEach(function(values, i) {
expect(meta0.data[i].x).toBeCloseToPixel(values.x);
expect(meta0.data[i].y).toBeCloseToPixel(values.y);
var meta1 = chart.getDatasetMeta(1);
[
- {x: 0, y: 0},
- {x: 171, y: 73},
- {x: 341, y: 146},
- {x: 512, y: 497}
+ {x: 5, y: 5},
+ {x: 171, y: 76},
+ {x: 341, y: 148},
+ {x: 507, y: 492}
].forEach(function(values, i) {
expect(meta1.data[i].x).toBeCloseToPixel(values.x);
expect(meta1.data[i].y).toBeCloseToPixel(values.y);
var meta0 = chart.getDatasetMeta(0);
[
- {x: 0, y: 146},
- {x: 171, y: 439},
- {x: 341, y: 146},
- {x: 512, y: 439}
+ {x: 5, y: 148},
+ {x: 171, y: 435},
+ {x: 341, y: 148},
+ {x: 507, y: 435}
].forEach(function(values, i) {
expect(meta0.data[i].x).toBeCloseToPixel(values.x);
expect(meta0.data[i].y).toBeCloseToPixel(values.y);
var meta1 = chart.getDatasetMeta(1);
[
- {x: 0, y: 0},
- {x: 171, y: 73},
- {x: 341, y: 146},
- {x: 512, y: 497}
+ {x: 5, y: 5},
+ {x: 171, y: 76},
+ {x: 341, y: 148},
+ {x: 507, y: 492}
].forEach(function(values, i) {
expect(meta1.data[i].x).toBeCloseToPixel(values.x);
expect(meta1.data[i].y).toBeCloseToPixel(values.y);
var meta0 = chart.getDatasetMeta(0);
[
- {x: 0, y: 146},
- {x: 171, y: 439},
- {x: 341, y: 146},
- {x: 512, y: 439}
+ {x: 5, y: 148},
+ {x: 171, y: 435},
+ {x: 341, y: 148},
+ {x: 507, y: 435}
].forEach(function(values, i) {
expect(meta0.data[i].x).toBeCloseToPixel(values.x);
expect(meta0.data[i].y).toBeCloseToPixel(values.y);
var meta1 = chart.getDatasetMeta(1);
[
- {x: 0, y: 0},
- {x: 171, y: 73},
- {x: 341, y: 146},
- {x: 512, y: 497}
+ {x: 5, y: 5},
+ {x: 171, y: 76},
+ {x: 341, y: 148},
+ {x: 507, y: 492}
].forEach(function(values, i) {
expect(meta1.data[i].x).toBeCloseToPixel(values.x);
expect(meta1.data[i].y).toBeCloseToPixel(values.y);
expect(chart.getActiveElements()).toEqual([{datasetIndex: 0, index: 1, element: point}]);
});
+ it('should activate element on hover when minPadding pixels outside chart area', async function() {
+ var chart = acquireChart({
+ type: 'line',
+ data: {
+ labels: ['A', 'B', 'C', 'D'],
+ datasets: [{
+ data: [10, 20, 30, 100],
+ hoverRadius: 0
+ }],
+ },
+ options: {
+ scales: {
+ x: {display: false},
+ y: {display: false}
+ }
+ }
+ });
+
+ var point = chart.getDatasetMeta(0).data[0];
+
+ await jasmine.triggerMouseEvent(chart, 'mousemove', {x: 1, y: point.y});
+ expect(chart.getActiveElements()).toEqual([{datasetIndex: 0, index: 0, element: point}]);
+ });
+
it('should not activate elements when hover is disabled', async function() {
var chart = acquireChart({
type: 'line',