Stop mutating arc state while drawing (#9153)

* Stop mutating arc state while drawing

* No need for default values

* Nits

* Remove #9152

* Use correct endAngle for clipping
This commit is contained in:
Jukka Kurkela 2021-05-25 15:13:37 +03:00 committed by GitHub
parent 1d047355e7
commit 03eb826f8c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 38 additions and 48 deletions

View File

@ -3,8 +3,8 @@ import {_angleBetween, getAngleFromPoint, TAU, HALF_PI} from '../helpers/index';
import {PI, _limitValue} from '../helpers/helpers.math'; import {PI, _limitValue} from '../helpers/helpers.math';
import {_readValueToProps} from '../helpers/helpers.options'; import {_readValueToProps} from '../helpers/helpers.options';
function clipArc(ctx, element) { function clipArc(ctx, element, endAngle) {
const {startAngle, endAngle, pixelMargin, x, y, outerRadius, innerRadius} = element; const {startAngle, pixelMargin, x, y, outerRadius, innerRadius} = element;
let angleMargin = pixelMargin / outerRadius; let angleMargin = pixelMargin / outerRadius;
// Draw an inner border by clipping the arc and drawing a double-width border // Draw an inner border by clipping the arc and drawing a double-width border
@ -93,8 +93,8 @@ function rThetaToXY(r, theta, x, y) {
* @param {CanvasRenderingContext2D} ctx * @param {CanvasRenderingContext2D} ctx
* @param {ArcElement} element * @param {ArcElement} element
*/ */
function pathArc(ctx, element, offset) { function pathArc(ctx, element, offset, end) {
const {x, y, startAngle: start, endAngle: end, pixelMargin, innerRadius: innerR} = element; const {x, y, startAngle: start, pixelMargin, innerRadius: innerR} = element;
const outerRadius = Math.max(element.outerRadius + offset - pixelMargin, 0); const outerRadius = Math.max(element.outerRadius + offset - pixelMargin, 0);
const innerRadius = innerR > 0 ? innerR + offset + pixelMargin : 0; const innerRadius = innerR > 0 ? innerR + offset + pixelMargin : 0;
@ -159,54 +159,53 @@ function pathArc(ctx, element, offset) {
} }
function drawArc(ctx, element, offset) { function drawArc(ctx, element, offset) {
if (element.fullCircles) { const {fullCircles, startAngle, circumference} = element;
element.endAngle = element.startAngle + TAU; let endAngle = element.endAngle;
if (fullCircles) {
pathArc(ctx, element, offset, startAngle + TAU);
pathArc(ctx, element, offset); for (let i = 0; i < fullCircles; ++i) {
for (let i = 0; i < element.fullCircles; ++i) {
ctx.fill(); ctx.fill();
} }
if (!isNaN(circumference)) {
endAngle = startAngle + circumference % TAU;
if (circumference % TAU === 0) {
endAngle += TAU;
}
} }
if (!isNaN(element.circumference)) {
element.endAngle = element.startAngle + element.circumference % TAU;
} }
pathArc(ctx, element, offset); pathArc(ctx, element, offset, endAngle);
ctx.fill(); ctx.fill();
return endAngle;
} }
function drawFullCircleBorders(ctx, element, inner) { function drawFullCircleBorders(ctx, element, inner) {
const {x, y, startAngle, endAngle, pixelMargin} = element; const {x, y, startAngle, pixelMargin, fullCircles} = element;
const outerRadius = Math.max(element.outerRadius - pixelMargin, 0); const outerRadius = Math.max(element.outerRadius - pixelMargin, 0);
const innerRadius = element.innerRadius + pixelMargin; const innerRadius = element.innerRadius + pixelMargin;
let i; let i;
if (inner) { if (inner) {
element.endAngle = element.startAngle + TAU; clipArc(ctx, element, startAngle + TAU);
clipArc(ctx, element);
element.endAngle = endAngle;
if (element.endAngle === element.startAngle) {
element.endAngle += TAU;
element.fullCircles--;
}
} }
ctx.beginPath(); ctx.beginPath();
ctx.arc(x, y, innerRadius, startAngle + TAU, startAngle, true); ctx.arc(x, y, innerRadius, startAngle + TAU, startAngle, true);
for (i = 0; i < element.fullCircles; ++i) { for (i = 0; i < fullCircles; ++i) {
ctx.stroke(); ctx.stroke();
} }
ctx.beginPath(); ctx.beginPath();
ctx.arc(x, y, outerRadius, startAngle, startAngle + TAU); ctx.arc(x, y, outerRadius, startAngle, startAngle + TAU);
for (i = 0; i < element.fullCircles; ++i) { for (i = 0; i < fullCircles; ++i) {
ctx.stroke(); ctx.stroke();
} }
} }
function drawBorder(ctx, element, offset) { function drawBorder(ctx, element, offset, endAngle) {
const {options} = element; const {options} = element;
const inner = options.borderAlign === 'inner'; const inner = options.borderAlign === 'inner';
@ -227,10 +226,10 @@ function drawBorder(ctx, element, offset) {
} }
if (inner) { if (inner) {
clipArc(ctx, element); clipArc(ctx, element, endAngle);
} }
pathArc(ctx, element, offset); pathArc(ctx, element, offset, endAngle);
ctx.stroke(); ctx.stroke();
} }
@ -278,7 +277,7 @@ export default class ArcElement extends Element {
* @param {boolean} [useFinalPosition] * @param {boolean} [useFinalPosition]
*/ */
getCenterPoint(useFinalPosition) { getCenterPoint(useFinalPosition) {
const {x, y, startAngle, endAngle, innerRadius, outerRadius, circumference} = this.getProps([ const {x, y, startAngle, endAngle, innerRadius, outerRadius} = this.getProps([
'x', 'x',
'y', 'y',
'startAngle', 'startAngle',
@ -287,7 +286,7 @@ export default class ArcElement extends Element {
'outerRadius', 'outerRadius',
'circumference' 'circumference'
], useFinalPosition); ], useFinalPosition);
const halfAngle = isNaN(circumference) ? (startAngle + endAngle) / 2 : startAngle + circumference / 2; const halfAngle = (startAngle + endAngle) / 2;
const halfRadius = (innerRadius + outerRadius) / 2; const halfRadius = (innerRadius + outerRadius) / 2;
return { return {
x: x + Math.cos(halfAngle) * halfRadius, x: x + Math.cos(halfAngle) * halfRadius,
@ -304,12 +303,12 @@ export default class ArcElement extends Element {
draw(ctx) { draw(ctx) {
const me = this; const me = this;
const options = me.options; const {options, circumference} = me;
const offset = (options.offset || 0) / 2; const offset = (options.offset || 0) / 2;
me.pixelMargin = (options.borderAlign === 'inner') ? 0.33 : 0; me.pixelMargin = (options.borderAlign === 'inner') ? 0.33 : 0;
me.fullCircles = Math.floor(me.circumference / TAU); me.fullCircles = circumference > TAU ? Math.floor(circumference / TAU) : 0;
if (me.circumference === 0 || me.innerRadius < 0 || me.outerRadius < 0) { if (circumference === 0 || me.innerRadius < 0 || me.outerRadius < 0) {
return; return;
} }
@ -328,8 +327,8 @@ export default class ArcElement extends Element {
ctx.fillStyle = options.backgroundColor; ctx.fillStyle = options.backgroundColor;
ctx.strokeStyle = options.borderColor; ctx.strokeStyle = options.borderColor;
drawArc(ctx, me, radiusOffset); const endAngle = drawArc(ctx, me, radiusOffset);
drawBorder(ctx, me, radiusOffset); drawBorder(ctx, me, radiusOffset, endAngle);
ctx.restore(); ctx.restore();
} }

View File

@ -66,7 +66,7 @@ describe('Arc element tests', function() {
expect(center.y).toBeCloseTo(0.5, 6); expect(center.y).toBeCloseTo(0.5, 6);
}); });
it ('should get the center of full circle using endAngle', function() { it ('should get the center of full circle before and after draw', function() {
// Mock out the arc as if the controller put it there // Mock out the arc as if the controller put it there
var arc = new Chart.elements.ArcElement({ var arc = new Chart.elements.ArcElement({
startAngle: 0, startAngle: 0,
@ -74,27 +74,18 @@ describe('Arc element tests', function() {
x: 2, x: 2,
y: 2, y: 2,
innerRadius: 0, innerRadius: 0,
outerRadius: 2 outerRadius: 2,
options: {}
}); });
var center = arc.getCenterPoint(); var center = arc.getCenterPoint();
expect(center.x).toBeCloseTo(1, 6); expect(center.x).toBeCloseTo(1, 6);
expect(center.y).toBeCloseTo(2, 6); expect(center.y).toBeCloseTo(2, 6);
});
it ('should get the center of full circle using circumference', function() { var ctx = window.createMockContext();
// Mock out the arc as if the controller put it there arc.draw(ctx);
var arc = new Chart.elements.ArcElement({
startAngle: 0,
endAngle: 0,
x: 2,
y: 2,
innerRadius: 0,
outerRadius: 2,
circumference: Math.PI * 2
});
var center = arc.getCenterPoint(); center = arc.getCenterPoint();
expect(center.x).toBeCloseTo(1, 6); expect(center.x).toBeCloseTo(1, 6);
expect(center.y).toBeCloseTo(2, 6); expect(center.y).toBeCloseTo(2, 6);
}); });