]> git.ipfire.org Git - thirdparty/Chart.js.git/commitdiff
Prevent bezier points from being capped when a data point is off the chart (#5937)
authorAkihiko Kusanagi <nagi@nagi-p.com>
Sat, 5 Jan 2019 11:28:15 +0000 (19:28 +0800)
committerSimon Brunel <simonbrunel@users.noreply.github.com>
Sat, 5 Jan 2019 11:28:15 +0000 (12:28 +0100)
src/controllers/controller.line.js
src/elements/element.point.js
src/helpers/helpers.canvas.js
test/specs/helpers.canvas.tests.js

index fffb1ce55d255854a1e08a9ac4ca8d75fbaa8e59..03f7faf96d29f59abf14a3c15a620a6f50f45695 100644 (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);
+                                       }
+                               }
                        }
                }
        },
index b7909116a7c03fdd5f12e05cfebacafae8bad75b..8ae37eb8a2853b6e5d195d09caa1e79e727ce337 100644 (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;
index b1212dff46d7e5d49f9f059bab58a43890f12805..1513386a8cbbe5c12c1fead1da22c22b27c8b8de 100644 (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();
index ee42a414e6f2c4ddda1bbfd133166edc4b46daa7..9654ef28e7b40b34f8dd553e5258a7639be14752 100644 (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);
+               });
+       });
 });