208 lines
5.2 KiB
JavaScript
208 lines
5.2 KiB
JavaScript
'use strict';
|
|
|
|
var defaults = require('../core/core.defaults');
|
|
var Element = require('../core/core.element');
|
|
var helpers = require('../helpers/index');
|
|
var TAU = Math.PI * 2;
|
|
|
|
defaults._set('global', {
|
|
elements: {
|
|
arc: {
|
|
backgroundColor: defaults.global.defaultColor,
|
|
borderColor: '#fff',
|
|
borderWidth: 2,
|
|
borderAlign: 'center'
|
|
}
|
|
}
|
|
});
|
|
|
|
function clipArc(ctx, arc) {
|
|
var startAngle = arc.startAngle;
|
|
var endAngle = arc.endAngle;
|
|
var pixelMargin = arc.pixelMargin;
|
|
var angleMargin = pixelMargin / arc.outerRadius;
|
|
var x = arc.x;
|
|
var y = arc.y;
|
|
|
|
// Draw an inner border by cliping the arc and drawing a double-width border
|
|
// Enlarge the clipping arc by 0.33 pixels to eliminate glitches between borders
|
|
ctx.beginPath();
|
|
ctx.arc(x, y, arc.outerRadius, startAngle - angleMargin, endAngle + angleMargin);
|
|
if (arc.innerRadius > pixelMargin) {
|
|
angleMargin = pixelMargin / arc.innerRadius;
|
|
ctx.arc(x, y, arc.innerRadius - pixelMargin, endAngle + angleMargin, startAngle - angleMargin, true);
|
|
} else {
|
|
ctx.arc(x, y, pixelMargin, endAngle + Math.PI / 2, startAngle - Math.PI / 2);
|
|
}
|
|
ctx.closePath();
|
|
ctx.clip();
|
|
}
|
|
|
|
function drawFullCircleBorders(ctx, vm, arc, inner) {
|
|
var endAngle = arc.endAngle;
|
|
var i;
|
|
|
|
if (inner) {
|
|
arc.endAngle = arc.startAngle + TAU;
|
|
clipArc(ctx, arc);
|
|
arc.endAngle = endAngle;
|
|
if (arc.endAngle === arc.startAngle && arc.fullCircles) {
|
|
arc.endAngle += TAU;
|
|
arc.fullCircles--;
|
|
}
|
|
}
|
|
|
|
ctx.beginPath();
|
|
ctx.arc(arc.x, arc.y, arc.innerRadius, arc.startAngle + TAU, arc.startAngle, true);
|
|
for (i = 0; i < arc.fullCircles; ++i) {
|
|
ctx.stroke();
|
|
}
|
|
|
|
ctx.beginPath();
|
|
ctx.arc(arc.x, arc.y, vm.outerRadius, arc.startAngle, arc.startAngle + TAU);
|
|
for (i = 0; i < arc.fullCircles; ++i) {
|
|
ctx.stroke();
|
|
}
|
|
}
|
|
|
|
function drawBorder(ctx, vm, arc) {
|
|
var inner = vm.borderAlign === 'inner';
|
|
|
|
if (inner) {
|
|
ctx.lineWidth = vm.borderWidth * 2;
|
|
ctx.lineJoin = 'round';
|
|
} else {
|
|
ctx.lineWidth = vm.borderWidth;
|
|
ctx.lineJoin = 'bevel';
|
|
}
|
|
|
|
if (arc.fullCircles) {
|
|
drawFullCircleBorders(ctx, vm, arc, inner);
|
|
}
|
|
|
|
if (inner) {
|
|
clipArc(ctx, arc);
|
|
}
|
|
|
|
ctx.beginPath();
|
|
ctx.arc(arc.x, arc.y, vm.outerRadius, arc.startAngle, arc.endAngle);
|
|
ctx.arc(arc.x, arc.y, arc.innerRadius, arc.endAngle, arc.startAngle, true);
|
|
ctx.closePath();
|
|
ctx.stroke();
|
|
}
|
|
|
|
module.exports = Element.extend({
|
|
_type: 'arc',
|
|
|
|
inLabelRange: function(mouseX) {
|
|
var vm = this._view;
|
|
|
|
if (vm) {
|
|
return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hoverRadius, 2));
|
|
}
|
|
return false;
|
|
},
|
|
|
|
inRange: function(chartX, chartY) {
|
|
var vm = this._view;
|
|
|
|
if (vm) {
|
|
var pointRelativePosition = helpers.getAngleFromPoint(vm, {x: chartX, y: chartY});
|
|
var angle = pointRelativePosition.angle;
|
|
var distance = pointRelativePosition.distance;
|
|
|
|
// Sanitise angle range
|
|
var startAngle = vm.startAngle;
|
|
var endAngle = vm.endAngle;
|
|
while (endAngle < startAngle) {
|
|
endAngle += TAU;
|
|
}
|
|
while (angle > endAngle) {
|
|
angle -= TAU;
|
|
}
|
|
while (angle < startAngle) {
|
|
angle += TAU;
|
|
}
|
|
|
|
// Check if within the range of the open/close angle
|
|
var betweenAngles = (angle >= startAngle && angle <= endAngle);
|
|
var withinRadius = (distance >= vm.innerRadius && distance <= vm.outerRadius);
|
|
|
|
return (betweenAngles && withinRadius);
|
|
}
|
|
return false;
|
|
},
|
|
|
|
getCenterPoint: function() {
|
|
var vm = this._view;
|
|
var halfAngle = (vm.startAngle + vm.endAngle) / 2;
|
|
var halfRadius = (vm.innerRadius + vm.outerRadius) / 2;
|
|
return {
|
|
x: vm.x + Math.cos(halfAngle) * halfRadius,
|
|
y: vm.y + Math.sin(halfAngle) * halfRadius
|
|
};
|
|
},
|
|
|
|
getArea: function() {
|
|
var vm = this._view;
|
|
return Math.PI * ((vm.endAngle - vm.startAngle) / (2 * Math.PI)) * (Math.pow(vm.outerRadius, 2) - Math.pow(vm.innerRadius, 2));
|
|
},
|
|
|
|
tooltipPosition: function() {
|
|
var vm = this._view;
|
|
var centreAngle = vm.startAngle + ((vm.endAngle - vm.startAngle) / 2);
|
|
var rangeFromCentre = (vm.outerRadius - vm.innerRadius) / 2 + vm.innerRadius;
|
|
|
|
return {
|
|
x: vm.x + (Math.cos(centreAngle) * rangeFromCentre),
|
|
y: vm.y + (Math.sin(centreAngle) * rangeFromCentre)
|
|
};
|
|
},
|
|
|
|
draw: function() {
|
|
var ctx = this._chart.ctx;
|
|
var vm = this._view;
|
|
var pixelMargin = (vm.borderAlign === 'inner') ? 0.33 : 0;
|
|
var arc = {
|
|
x: vm.x,
|
|
y: vm.y,
|
|
innerRadius: vm.innerRadius,
|
|
outerRadius: Math.max(vm.outerRadius - pixelMargin, 0),
|
|
pixelMargin: pixelMargin,
|
|
startAngle: vm.startAngle,
|
|
endAngle: vm.endAngle,
|
|
fullCircles: Math.floor(vm.circumference / TAU)
|
|
};
|
|
var i;
|
|
|
|
ctx.save();
|
|
|
|
ctx.fillStyle = vm.backgroundColor;
|
|
ctx.strokeStyle = vm.borderColor;
|
|
|
|
if (arc.fullCircles) {
|
|
arc.endAngle = arc.startAngle + TAU;
|
|
ctx.beginPath();
|
|
ctx.arc(arc.x, arc.y, arc.outerRadius, arc.startAngle, arc.endAngle);
|
|
ctx.arc(arc.x, arc.y, arc.innerRadius, arc.endAngle, arc.startAngle, true);
|
|
ctx.closePath();
|
|
for (i = 0; i < arc.fullCircles; ++i) {
|
|
ctx.fill();
|
|
}
|
|
arc.endAngle = arc.startAngle + vm.circumference % TAU;
|
|
}
|
|
|
|
ctx.beginPath();
|
|
ctx.arc(arc.x, arc.y, arc.outerRadius, arc.startAngle, arc.endAngle);
|
|
ctx.arc(arc.x, arc.y, arc.innerRadius, arc.endAngle, arc.startAngle, true);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
|
|
if (vm.borderWidth) {
|
|
drawBorder(ctx, vm, arc);
|
|
}
|
|
|
|
ctx.restore();
|
|
}
|
|
});
|