Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adjust the size of rectRounded/rectRot points to fit the circle with pointRadius #5858

Merged
merged 1 commit into from
Nov 28, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
166 changes: 95 additions & 71 deletions src/helpers/helpers.canvas.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

var helpers = require('./helpers.core');

var PI = Math.PI;
var RAD_PER_DEG = PI / 180;
var DOUBLE_PI = PI * 2;
var HALF_PI = PI / 2;
var QUARTER_PI = PI / 4;
var TWO_THIRDS_PI = PI * 2 / 3;

/**
* @namespace Chart.helpers.canvas
*/
Expand All @@ -27,20 +34,28 @@ var exports = module.exports = {
*/
roundedRect: function(ctx, x, y, width, height, radius) {
if (radius) {
// 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);
var r = Math.min(radius, height / 2, width / 2);
var left = x + r;
var top = y + r;
var right = x + width - r;
var bottom = y + height - r;

ctx.moveTo(x, top);
if (left < right && top < bottom) {
ctx.arc(left, top, r, -PI, -HALF_PI);
ctx.arc(right, top, r, -HALF_PI, 0);
ctx.arc(right, bottom, r, 0, HALF_PI);
ctx.arc(left, bottom, r, HALF_PI, PI);
} else if (left < right) {
ctx.moveTo(left, y);
ctx.arc(right, top, r, -HALF_PI, HALF_PI);
ctx.arc(left, top, r, HALF_PI, PI + HALF_PI);
} else if (top < bottom) {
ctx.arc(left, top, r, -PI, 0);
ctx.arc(left, bottom, r, 0, PI);
} else {
ctx.arc(left, top, r, -PI, PI);
}
ctx.closePath();
ctx.moveTo(x, y);
} else {
Expand All @@ -49,8 +64,8 @@ var exports = module.exports = {
},

drawPoint: function(ctx, style, radius, x, y, rotation) {
var type, edgeLength, xOffset, yOffset, height, size;
rotation = rotation || 0;
var type, xOffset, yOffset, size, cornerRadius;
var rad = (rotation || 0) * RAD_PER_DEG;

if (style && typeof style === 'object') {
type = style.toString();
Expand All @@ -64,88 +79,97 @@ var exports = module.exports = {
return;
}

ctx.save();
ctx.translate(x, y);
ctx.rotate(rotation * Math.PI / 180);
ctx.beginPath();

switch (style) {
// Default includes circle
default:
ctx.arc(0, 0, radius, 0, Math.PI * 2);
ctx.arc(x, y, radius, 0, DOUBLE_PI);
ctx.closePath();
break;
case 'triangle':
edgeLength = 3 * radius / Math.sqrt(3);
height = edgeLength * Math.sqrt(3) / 2;
ctx.moveTo(-edgeLength / 2, height / 3);
ctx.lineTo(edgeLength / 2, height / 3);
ctx.lineTo(0, -2 * height / 3);
ctx.moveTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius);
rad += TWO_THIRDS_PI;
ctx.lineTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius);
rad += TWO_THIRDS_PI;
ctx.lineTo(x + Math.sin(rad) * radius, y - Math.cos(rad) * radius);
ctx.closePath();
break;
case 'rect':
size = 1 / Math.SQRT2 * radius;
ctx.rect(-size, -size, 2 * size, 2 * size);
break;
case 'rectRounded':
var offset = radius / Math.SQRT2;
var leftX = -offset;
var topY = -offset;
var sideSize = Math.SQRT2 * radius;

// 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);
// NOTE: the rounded rect implementation changed to use `arc` instead of
// `quadraticCurveTo` since it generates better results when rect is
// almost a circle. 0.516 (instead of 0.5) produces results with visually
// closer proportion to the previous impl and it is inscribed in the
// circle with `radius`. For more details, see the following PRs:
// https://github.com/chartjs/Chart.js/issues/5597
// https://github.com/chartjs/Chart.js/issues/5858
cornerRadius = radius * 0.516;
size = radius - cornerRadius;
xOffset = Math.cos(rad + QUARTER_PI) * size;
yOffset = Math.sin(rad + QUARTER_PI) * size;
ctx.arc(x - xOffset, y - yOffset, cornerRadius, rad - PI, rad - HALF_PI);
ctx.arc(x + yOffset, y - xOffset, cornerRadius, rad - HALF_PI, rad);
ctx.arc(x + xOffset, y + yOffset, cornerRadius, rad, rad + HALF_PI);
ctx.arc(x - yOffset, y + xOffset, cornerRadius, rad + HALF_PI, rad + PI);
ctx.closePath();
break;
case 'rect':
if (!rotation) {
size = Math.SQRT1_2 * radius;
ctx.rect(x - size, y - size, 2 * size, 2 * size);
break;
}
rad += QUARTER_PI;
/* falls through */
case 'rectRot':
size = 1 / Math.SQRT2 * radius;
ctx.moveTo(-size, 0);
ctx.lineTo(0, size);
ctx.lineTo(size, 0);
ctx.lineTo(0, -size);
xOffset = Math.cos(rad) * radius;
yOffset = Math.sin(rad) * radius;
ctx.moveTo(x - xOffset, y - yOffset);
ctx.lineTo(x + yOffset, y - xOffset);
ctx.lineTo(x + xOffset, y + yOffset);
ctx.lineTo(x - yOffset, y + xOffset);
ctx.closePath();
break;
case 'cross':
ctx.moveTo(0, radius);
ctx.lineTo(0, -radius);
ctx.moveTo(-radius, 0);
ctx.lineTo(radius, 0);
break;
case 'crossRot':
xOffset = Math.cos(Math.PI / 4) * radius;
yOffset = Math.sin(Math.PI / 4) * radius;
ctx.moveTo(-xOffset, -yOffset);
ctx.lineTo(xOffset, yOffset);
ctx.moveTo(-xOffset, yOffset);
ctx.lineTo(xOffset, -yOffset);
rad += QUARTER_PI;
/* falls through */
case 'cross':
xOffset = Math.cos(rad) * radius;
yOffset = Math.sin(rad) * radius;
ctx.moveTo(x - xOffset, y - yOffset);
ctx.lineTo(x + xOffset, y + yOffset);
ctx.moveTo(x + yOffset, y - xOffset);
ctx.lineTo(x - yOffset, y + xOffset);
break;
case 'star':
ctx.moveTo(0, radius);
ctx.lineTo(0, -radius);
ctx.moveTo(-radius, 0);
ctx.lineTo(radius, 0);
xOffset = Math.cos(Math.PI / 4) * radius;
yOffset = Math.sin(Math.PI / 4) * radius;
ctx.moveTo(-xOffset, -yOffset);
ctx.lineTo(xOffset, yOffset);
ctx.moveTo(-xOffset, yOffset);
ctx.lineTo(xOffset, -yOffset);
xOffset = Math.cos(rad) * radius;
yOffset = Math.sin(rad) * radius;
ctx.moveTo(x - xOffset, y - yOffset);
ctx.lineTo(x + xOffset, y + yOffset);
ctx.moveTo(x + yOffset, y - xOffset);
ctx.lineTo(x - yOffset, y + xOffset);
rad += QUARTER_PI;
xOffset = Math.cos(rad) * radius;
yOffset = Math.sin(rad) * radius;
ctx.moveTo(x - xOffset, y - yOffset);
ctx.lineTo(x + xOffset, y + yOffset);
ctx.moveTo(x + yOffset, y - xOffset);
ctx.lineTo(x - yOffset, y + xOffset);
break;
case 'line':
ctx.moveTo(-radius, 0);
ctx.lineTo(radius, 0);
xOffset = Math.cos(rad) * radius;
yOffset = Math.sin(rad) * radius;
ctx.moveTo(x - xOffset, y - yOffset);
ctx.lineTo(x + xOffset, y + yOffset);
break;
case 'dash':
ctx.moveTo(0, 0);
ctx.lineTo(radius, 0);
ctx.moveTo(x, y);
ctx.lineTo(x + Math.cos(rad) * radius, y + Math.sin(rad) * radius);
break;
}

ctx.fill();
ctx.stroke();
ctx.restore();
},

clipArea: function(ctx, area) {
Expand Down
Binary file modified test/fixtures/controller.bubble/point-style.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/fixtures/controller.line/point-style.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/fixtures/controller.radar/point-style.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/fixtures/element.point/point-style-rect-rot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified test/fixtures/element.point/point-style-rect-rounded.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
56 changes: 56 additions & 0 deletions test/fixtures/element.point/rotation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
var gradient;

var datasets = ['circle', 'cross', 'crossRot', 'dash', 'line', 'rect', 'rectRounded', 'rectRot', 'star', 'triangle'].map(function(style, y) {
return {
pointStyle: style,
data: Array.apply(null, Array(17)).map(function(v, x) {
return {x: x, y: 10 - y};
})
};
});

var angles = Array.apply(null, Array(17)).map(function(v, i) {
return -180 + i * 22.5;
});

module.exports = {
config: {
type: 'bubble',
data: {
datasets: datasets
},
options: {
responsive: false,
legend: false,
title: false,
elements: {
point: {
rotation: angles,
radius: 10,
backgroundColor: function(context) {
if (!gradient) {
gradient = context.chart.ctx.createLinearGradient(0, 0, 512, 256);
gradient.addColorStop(0, '#ff0000');
gradient.addColorStop(1, '#0000ff');
}
return gradient;
},
borderColor: '#cccccc'
}
},
layout: {
padding: 20
},
scales: {
xAxes: [{display: false}],
yAxes: [{display: false}]
}
}
},
options: {
canvas: {
height: 256,
width: 512
}
}
};
Binary file added test/fixtures/element.point/rotation.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
39 changes: 39 additions & 0 deletions test/fixtures/helpers.canvas/rounded-rect.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
var roundedRect = Chart.helpers.canvas.roundedRect;

module.exports = {
config: {
type: 'line',
plugins: [{
afterDraw: function(chart) {
var ctx = chart.ctx;
ctx.strokeStyle = '#0000ff';
ctx.lineWidth = 4;
ctx.fillStyle = '#00ff00';
ctx.beginPath();
roundedRect(ctx, 10, 10, 50, 50, 25);
roundedRect(ctx, 70, 10, 100, 50, 25);
roundedRect(ctx, 10, 70, 50, 100, 25);
roundedRect(ctx, 70, 70, 100, 100, 25);
roundedRect(ctx, 180, 10, 50, 50, 100);
roundedRect(ctx, 240, 10, 100, 50, 100);
roundedRect(ctx, 180, 70, 50, 100, 100);
roundedRect(ctx, 240, 70, 100, 100, 100);
roundedRect(ctx, 350, 10, 50, 50, 0);
ctx.fill();
ctx.stroke();
}
}],
options: {
scales: {
xAxes: [{display: false}],
yAxes: [{display: false}]
}
}
},
options: {
canvas: {
height: 256,
width: 512
}
}
};
Binary file added test/fixtures/helpers.canvas/rounded-rect.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
14 changes: 1 addition & 13 deletions test/specs/element.point.tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,21 +124,12 @@ describe('Chart.elements.Point', function() {
}, {
name: 'setFillStyle',
args: ['rgba(0,0,0,0.1)']
}, {
name: 'save',
args: []
}, {
name: 'translate',
args: [10, 15]
}, {
name: 'rotate',
args: [0]
}, {
name: 'beginPath',
args: []
}, {
name: 'arc',
args: [0, 0, 2, 0, 2 * Math.PI]
args: [10, 15, 2, 0, 2 * Math.PI]
}, {
name: 'closePath',
args: [],
Expand All @@ -148,9 +139,6 @@ describe('Chart.elements.Point', function() {
}, {
name: 'stroke',
args: []
}, {
name: 'restore',
args: []
}]);
});

Expand Down
Loading