Prevent bezier points from being capped when a data point is off the chart (#5937)

This commit is contained in:
Akihiko Kusanagi 2019-01-05 19:28:15 +08:00 committed by Simon Brunel
parent c51ac8a64a
commit 26b7375329
4 changed files with 49 additions and 13 deletions

View File

@ -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,
@ -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;
});
@ -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) {
@ -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;
@ -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);
}
}
}
}
},

View File

@ -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
@ -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;

View File

@ -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();

View File

@ -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);
});
});
});