Skip to content

Commit

Permalink
Prevent bezier points from being capped when a data point is off the …
Browse files Browse the repository at this point in the history
…chart (chartjs#5937)
  • Loading branch information
nagix authored and simonbrunel committed Jan 5, 2019
1 parent ea55455 commit aef2749
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 13 deletions.
30 changes: 20 additions & 10 deletions src/controllers/controller.line.js
Expand Up @@ -5,6 +5,8 @@ var defaults = require('../core/core.defaults');
var elements = require('../elements/index');
var helpers = require('../helpers/index');

var _isPointInArea = helpers.canvas._isPointInArea;

defaults._set('line', {
showLines: true,
spanGaps: false,
Expand Down Expand Up @@ -243,13 +245,15 @@ module.exports = DatasetController.extend({

updateBezierControlPoints: function() {
var me = this;
var chart = me.chart;
var meta = me.getMeta();
var area = me.chart.chartArea;
var points = (meta.data || []);
var lineModel = meta.dataset._model;
var area = chart.chartArea;
var points = meta.data || [];
var i, ilen, point, model, controlPoints;

// Only consider points that are drawn in case the spanGaps option is used
if (meta.dataset._model.spanGaps) {
if (lineModel.spanGaps) {
points = points.filter(function(pt) {
return !pt._model.skip;
});
Expand All @@ -259,7 +263,7 @@ module.exports = DatasetController.extend({
return Math.max(Math.min(pt, max), min);
}

if (meta.dataset._model.cubicInterpolationMode === 'monotone') {
if (lineModel.cubicInterpolationMode === 'monotone') {
helpers.splineCurveMonotone(points);
} else {
for (i = 0, ilen = points.length; i < ilen; ++i) {
Expand All @@ -269,7 +273,7 @@ module.exports = DatasetController.extend({
helpers.previousItem(points, i)._model,
model,
helpers.nextItem(points, i)._model,
meta.dataset._model.tension
lineModel.tension
);
model.controlPointPreviousX = controlPoints.previous.x;
model.controlPointPreviousY = controlPoints.previous.y;
Expand All @@ -278,13 +282,19 @@ module.exports = DatasetController.extend({
}
}

if (me.chart.options.elements.line.capBezierPoints) {
if (chart.options.elements.line.capBezierPoints) {
for (i = 0, ilen = points.length; i < ilen; ++i) {
model = points[i]._model;
model.controlPointPreviousX = capControlPoint(model.controlPointPreviousX, area.left, area.right);
model.controlPointPreviousY = capControlPoint(model.controlPointPreviousY, area.top, area.bottom);
model.controlPointNextX = capControlPoint(model.controlPointNextX, area.left, area.right);
model.controlPointNextY = capControlPoint(model.controlPointNextY, area.top, area.bottom);
if (_isPointInArea(model, area)) {
if (i > 0 && _isPointInArea(points[i - 1]._model, area)) {
model.controlPointPreviousX = capControlPoint(model.controlPointPreviousX, area.left, area.right);
model.controlPointPreviousY = capControlPoint(model.controlPointPreviousY, area.top, area.bottom);
}
if (i < points.length - 1 && _isPointInArea(points[i + 1]._model, area)) {
model.controlPointNextX = capControlPoint(model.controlPointNextX, area.left, area.right);
model.controlPointNextY = capControlPoint(model.controlPointNextY, area.top, area.bottom);
}
}
}
}
},
Expand Down
4 changes: 1 addition & 3 deletions src/elements/element.point.js
Expand Up @@ -65,14 +65,12 @@ module.exports = Element.extend({

draw: function(chartArea) {
var vm = this._view;
var model = this._model;
var ctx = this._chart.ctx;
var pointStyle = vm.pointStyle;
var rotation = vm.rotation;
var radius = vm.radius;
var x = vm.x;
var y = vm.y;
var epsilon = 0.0000001; // 0.0000001 is margin in pixels for Accumulated error.
var globalDefaults = defaults.global;
var defaultColor = globalDefaults.defaultColor; // eslint-disable-line no-shadow

Expand All @@ -81,7 +79,7 @@ module.exports = Element.extend({
}

// Clipping for Points.
if (chartArea === undefined || (model.x > chartArea.left - epsilon && chartArea.right + epsilon > model.x && model.y > chartArea.top - epsilon && chartArea.bottom + epsilon > model.y)) {
if (chartArea === undefined || helpers.canvas._isPointInArea(vm, chartArea)) {
ctx.strokeStyle = vm.borderColor || defaultColor;
ctx.lineWidth = helpers.valueOrDefault(vm.borderWidth, globalDefaults.elements.point.borderWidth);
ctx.fillStyle = vm.backgroundColor || defaultColor;
Expand Down
14 changes: 14 additions & 0 deletions src/helpers/helpers.canvas.js
Expand Up @@ -172,6 +172,20 @@ var exports = {
ctx.stroke();
},

/**
* Returns true if the point is inside the rectangle
* @param {Object} point - The point to test
* @param {Object} area - The rectangle
* @returns {Boolean}
* @private
*/
_isPointInArea: function(point, area) {
var epsilon = 1e-6; // 1e-6 is margin in pixels for accumulated error.

return point.x > area.left - epsilon && point.x < area.right + epsilon &&
point.y > area.top - epsilon && point.y < area.bottom + epsilon;
},

clipArea: function(ctx, area) {
ctx.save();
ctx.beginPath();
Expand Down
14 changes: 14 additions & 0 deletions test/specs/helpers.canvas.tests.js
Expand Up @@ -86,4 +86,18 @@ describe('Chart.helpers.canvas', function() {
expect(context.getCalls()).toEqual([{name: 'rect', args: [10, 20, 30, 40]}]);
});
});

describe('isPointInArea', function() {
it('should determine if a point is in the area', function() {
var isPointInArea = helpers.canvas._isPointInArea;
var area = {left: 0, top: 0, right: 512, bottom: 256};

expect(isPointInArea({x: 0, y: 0}, area)).toBe(true);
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);
});
});
});

0 comments on commit aef2749

Please sign in to comment.