diff --git a/src/helpers/helpers.canvas.js b/src/helpers/helpers.canvas.js index 13b110268b9..4bfae9c48fd 100644 --- a/src/helpers/helpers.canvas.js +++ b/src/helpers/helpers.canvas.js @@ -27,18 +27,20 @@ var exports = module.exports = { */ roundedRect: function(ctx, x, y, width, height, radius) { if (radius) { - var rx = Math.min(radius, width / 2); - var ry = Math.min(radius, height / 2); - - ctx.moveTo(x + rx, y); - ctx.lineTo(x + width - rx, y); - ctx.quadraticCurveTo(x + width, y, x + width, y + ry); - ctx.lineTo(x + width, y + height - ry); - ctx.quadraticCurveTo(x + width, y + height, x + width - rx, y + height); - ctx.lineTo(x + rx, y + height); - ctx.quadraticCurveTo(x, y + height, x, y + height - ry); - ctx.lineTo(x, y + ry); - ctx.quadraticCurveTo(x, y, x + rx, y); + // NOTE(SB) `epsilon` helps to prevent minor artifacts appearing + // on Chrome when `r` is exactly half the height or the width. + var epsilon = 0.0000001; + var r = Math.min(radius, (height / 2) - epsilon, (width / 2) - epsilon); + + ctx.moveTo(x + r, y); + ctx.lineTo(x + width - r, y); + ctx.arcTo(x + width, y, x + width, y + r, r); + ctx.lineTo(x + width, y + height - r); + ctx.arcTo(x + width, y + height, x + width - r, y + height, r); + ctx.lineTo(x + r, y + height); + ctx.arcTo(x, y + height, x, y + height - r, r); + ctx.lineTo(x, y + r); + ctx.arcTo(x, y, x + r, y, r); } else { ctx.rect(x, y, width, height); } @@ -89,7 +91,13 @@ var exports = module.exports = { var topY = y - offset; var sideSize = Math.SQRT2 * radius; ctx.beginPath(); - this.roundedRect(ctx, leftX, topY, sideSize, sideSize, radius / 2); + + // NOTE(SB) the rounded rect implementation changed to use `arcTo` + // instead of `quadraticCurveTo` since it generates better results + // when rect is almost a circle. 0.425 (instead of 0.5) produces + // results visually closer to the previous impl. + this.roundedRect(ctx, leftX, topY, sideSize, sideSize, radius * 0.425); + ctx.closePath(); ctx.fill(); break; diff --git a/test/jasmine.context.js b/test/jasmine.context.js index 8f4171fee4d..3497c721918 100644 --- a/test/jasmine.context.js +++ b/test/jasmine.context.js @@ -75,6 +75,7 @@ Context.prototype._initMethods = function() { var me = this; var methods = { arc: function() {}, + arcTo: function() {}, beginPath: function() {}, bezierCurveTo: function() {}, clearRect: function() {}, diff --git a/test/specs/element.point.tests.js b/test/specs/element.point.tests.js index f09b912d3c0..0321112fb12 100644 --- a/test/specs/element.point.tests.js +++ b/test/specs/element.point.tests.js @@ -222,7 +222,7 @@ describe('Point element tests', function() { 15 - offset, Math.SQRT2 * 2, Math.SQRT2 * 2, - 2 / 2 + 2 * 0.425 ); expect(mockContext.getCalls()).toContain( jasmine.objectContaining({ diff --git a/test/specs/helpers.canvas.tests.js b/test/specs/helpers.canvas.tests.js index 81326529b49..0c20628d456 100644 --- a/test/specs/helpers.canvas.tests.js +++ b/test/specs/helpers.canvas.tests.js @@ -30,13 +30,13 @@ describe('Chart.helpers.canvas', function() { expect(context.getCalls()).toEqual([ {name: 'moveTo', args: [15, 20]}, {name: 'lineTo', args: [35, 20]}, - {name: 'quadraticCurveTo', args: [40, 20, 40, 25]}, + {name: 'arcTo', args: [40, 20, 40, 25, 5]}, {name: 'lineTo', args: [40, 55]}, - {name: 'quadraticCurveTo', args: [40, 60, 35, 60]}, + {name: 'arcTo', args: [40, 60, 35, 60, 5]}, {name: 'lineTo', args: [15, 60]}, - {name: 'quadraticCurveTo', args: [10, 60, 10, 55]}, + {name: 'arcTo', args: [10, 60, 10, 55, 5]}, {name: 'lineTo', args: [10, 25]}, - {name: 'quadraticCurveTo', args: [10, 20, 15, 20]} + {name: 'arcTo', args: [10, 20, 15, 20, 5]} ]); }); it('should optimize path if radius is 0', function() {