]> git.ipfire.org Git - thirdparty/Chart.js.git/commitdiff
Stop mutating arc state while drawing (#9153)
authorJukka Kurkela <jukka.kurkela@gmail.com>
Tue, 25 May 2021 12:13:37 +0000 (15:13 +0300)
committerGitHub <noreply@github.com>
Tue, 25 May 2021 12:13:37 +0000 (08:13 -0400)
* Stop mutating arc state while drawing

* No need for default values

* Nits

* Remove #9152

* Use correct endAngle for clipping

src/elements/element.arc.js
test/specs/element.arc.tests.js

index 93a4a88fd6bd7b271c98d2798b5bd0618ffaf7f0..c283b577a8e2b63cb0d8cdfdb21fd99b8c0561b6 100644 (file)
@@ -3,8 +3,8 @@ import {_angleBetween, getAngleFromPoint, TAU, HALF_PI} from '../helpers/index';
 import {PI, _limitValue} from '../helpers/helpers.math';
 import {_readValueToProps} from '../helpers/helpers.options';
 
-function clipArc(ctx, element) {
-  const {startAngle, endAngle, pixelMargin, x, y, outerRadius, innerRadius} = element;
+function clipArc(ctx, element, endAngle) {
+  const {startAngle, pixelMargin, x, y, outerRadius, innerRadius} = element;
   let angleMargin = pixelMargin / outerRadius;
 
   // 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 {ArcElement} element
  */
-function pathArc(ctx, element, offset) {
-  const {x, y, startAngle: start, endAngle: end, pixelMargin, innerRadius: innerR} = element;
+function pathArc(ctx, element, offset, end) {
+  const {x, y, startAngle: start, pixelMargin, innerRadius: innerR} = element;
 
   const outerRadius = Math.max(element.outerRadius + 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) {
-  if (element.fullCircles) {
-    element.endAngle = element.startAngle + TAU;
-
-    pathArc(ctx, element, offset);
+  const {fullCircles, startAngle, circumference} = element;
+  let endAngle = element.endAngle;
+  if (fullCircles) {
+    pathArc(ctx, element, offset, startAngle + TAU);
 
-    for (let i = 0; i < element.fullCircles; ++i) {
+    for (let i = 0; i < fullCircles; ++i) {
       ctx.fill();
     }
-  }
-  if (!isNaN(element.circumference)) {
-    element.endAngle = element.startAngle + element.circumference % TAU;
+
+    if (!isNaN(circumference)) {
+      endAngle = startAngle + circumference % TAU;
+      if (circumference % TAU === 0) {
+        endAngle += TAU;
+      }
+    }
   }
 
-  pathArc(ctx, element, offset);
+  pathArc(ctx, element, offset, endAngle);
   ctx.fill();
+  return endAngle;
 }
 
 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 innerRadius = element.innerRadius + pixelMargin;
 
   let i;
 
   if (inner) {
-    element.endAngle = element.startAngle + TAU;
-    clipArc(ctx, element);
-    element.endAngle = endAngle;
-    if (element.endAngle === element.startAngle) {
-      element.endAngle += TAU;
-      element.fullCircles--;
-    }
+    clipArc(ctx, element, startAngle + TAU);
   }
 
   ctx.beginPath();
   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.beginPath();
   ctx.arc(x, y, outerRadius, startAngle, startAngle + TAU);
-  for (i = 0; i < element.fullCircles; ++i) {
+  for (i = 0; i < fullCircles; ++i) {
     ctx.stroke();
   }
 }
 
-function drawBorder(ctx, element, offset) {
+function drawBorder(ctx, element, offset, endAngle) {
   const {options} = element;
   const inner = options.borderAlign === 'inner';
 
@@ -227,10 +226,10 @@ function drawBorder(ctx, element, offset) {
   }
 
   if (inner) {
-    clipArc(ctx, element);
+    clipArc(ctx, element, endAngle);
   }
 
-  pathArc(ctx, element, offset);
+  pathArc(ctx, element, offset, endAngle);
   ctx.stroke();
 }
 
@@ -278,7 +277,7 @@ export default class ArcElement extends Element {
         * @param {boolean} [useFinalPosition]
         */
   getCenterPoint(useFinalPosition) {
-    const {x, y, startAngle, endAngle, innerRadius, outerRadius, circumference} = this.getProps([
+    const {x, y, startAngle, endAngle, innerRadius, outerRadius} = this.getProps([
       'x',
       'y',
       'startAngle',
@@ -287,7 +286,7 @@ export default class ArcElement extends Element {
       'outerRadius',
       'circumference'
     ], useFinalPosition);
-    const halfAngle = isNaN(circumference) ? (startAngle + endAngle) / 2 : startAngle + circumference / 2;
+    const halfAngle = (startAngle + endAngle) / 2;
     const halfRadius = (innerRadius + outerRadius) / 2;
     return {
       x: x + Math.cos(halfAngle) * halfRadius,
@@ -304,12 +303,12 @@ export default class ArcElement extends Element {
 
   draw(ctx) {
     const me = this;
-    const options = me.options;
+    const {options, circumference} = me;
     const offset = (options.offset || 0) / 2;
     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;
     }
 
@@ -328,8 +327,8 @@ export default class ArcElement extends Element {
     ctx.fillStyle = options.backgroundColor;
     ctx.strokeStyle = options.borderColor;
 
-    drawArc(ctx, me, radiusOffset);
-    drawBorder(ctx, me, radiusOffset);
+    const endAngle = drawArc(ctx, me, radiusOffset);
+    drawBorder(ctx, me, radiusOffset, endAngle);
 
     ctx.restore();
   }
index dffb3f3fbaa8be5359e0c37ef251a81e90a64659..e857b97dc102983145f76e33383d819fef5063d3 100644 (file)
@@ -66,7 +66,7 @@ describe('Arc element tests', function() {
     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
     var arc = new Chart.elements.ArcElement({
       startAngle: 0,
@@ -74,27 +74,18 @@ describe('Arc element tests', function() {
       x: 2,
       y: 2,
       innerRadius: 0,
-      outerRadius: 2
+      outerRadius: 2,
+      options: {}
     });
 
     var center = arc.getCenterPoint();
     expect(center.x).toBeCloseTo(1, 6);
     expect(center.y).toBeCloseTo(2, 6);
-  });
 
-  it ('should get the center of full circle using circumference', function() {
-    // Mock out the arc as if the controller put it there
-    var arc = new Chart.elements.ArcElement({
-      startAngle: 0,
-      endAngle: 0,
-      x: 2,
-      y: 2,
-      innerRadius: 0,
-      outerRadius: 2,
-      circumference: Math.PI * 2
-    });
+    var ctx = window.createMockContext();
+    arc.draw(ctx);
 
-    var center = arc.getCenterPoint();
+    center = arc.getCenterPoint();
     expect(center.x).toBeCloseTo(1, 6);
     expect(center.y).toBeCloseTo(2, 6);
   });