Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What does startAngle mean in an HTML5 canvas ellipse?

Tags:

When drawing a canvas ellipse, I was surprised to discover that 'startAngle' doesn't actually seem to specify the angle from the ellipse's origin. As seen in the code snippet below, two ellipses with the same 'startAngle' but different radius values begin their arcs in very different places.

The tall ellipse seems to begin on an angle of 50 or 60 degrees, as measured from the origin, while the wide ellipse's angle looks like 15 or 20 degrees.

So what is 'startAngle' exactly?

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/ellipse

var c = document.getElementById("canvas");
var ctx = c.getContext("2d");

var startAngle = 0.5;
var endAngle = Math.PI * 2;

ctx.beginPath();
ctx.ellipse(70, 150, 50, 140, 0, startAngle, endAngle);
ctx.stroke();

ctx.beginPath();
ctx.ellipse(300, 150, 140, 50, 0, startAngle, endAngle);
ctx.stroke();
<html>
<body>

<canvas id="canvas" width="500" height="300">

</body>
</html>
like image 804
parsim Avatar asked Sep 03 '18 11:09

parsim


2 Answers

It's like you first start drawing a circular arc and than stretch and squish that to match the dimensions of the elliptical arc.

var c = document.getElementById("canvas");
var ctx = c.getContext("2d");

var startAngle = Math.PI * 2 / 360 * 45;
var endAngle = Math.PI * 2 / 360 * 315;

ctx.fillStyle = "rgba(255, 255, 255, 0.125)";

ctx.beginPath();
ctx.moveTo(70, 150);
ctx.ellipse(70, 150, 50, 50, 0, startAngle, endAngle);
ctx.lineTo(70, 150);
ctx.stroke();
ctx.fill();

ctx.beginPath();
ctx.moveTo(70, 150);
ctx.ellipse(70, 150, 50, 60, 0, startAngle, endAngle);
ctx.lineTo(70, 150);
ctx.stroke();
ctx.fill();

ctx.beginPath();
ctx.moveTo(70, 150);
ctx.ellipse(70, 150, 50, 70, 0, startAngle, endAngle);
ctx.lineTo(70, 150);
ctx.stroke();
ctx.fill();

ctx.beginPath();
ctx.moveTo(70, 150);
ctx.ellipse(70, 150, 50, 80, 0, startAngle, endAngle);
ctx.lineTo(70, 150);
ctx.stroke();
ctx.fill();

ctx.beginPath();
ctx.moveTo(70, 150);
ctx.ellipse(70, 150, 50, 90, 0, startAngle, endAngle);
ctx.lineTo(70, 150);
ctx.stroke();
ctx.fill();

ctx.beginPath();
ctx.moveTo(70, 150);
ctx.ellipse(70, 150, 50, 100, 0, startAngle, endAngle);
ctx.lineTo(70, 150);
ctx.stroke();
ctx.fill();

ctx.beginPath();
ctx.moveTo(70, 150);
ctx.ellipse(70, 150, 50, 110, 0, startAngle, endAngle);
ctx.lineTo(70, 150);
ctx.stroke();
ctx.fill();

ctx.beginPath();
ctx.moveTo(70, 150);
ctx.ellipse(70, 150, 50, 120, 0, startAngle, endAngle);
ctx.lineTo(70, 150);
ctx.stroke();
ctx.fill();

ctx.beginPath();
ctx.moveTo(70, 150);
ctx.ellipse(70, 150, 50, 130, 0, startAngle, endAngle);
ctx.lineTo(70, 150);
ctx.stroke();
ctx.fill();

ctx.beginPath();
ctx.moveTo(70, 150);
ctx.ellipse(70, 150, 50, 140, 0, startAngle, endAngle);
ctx.lineTo(70, 150);
ctx.stroke();
ctx.fill();

ctx.beginPath();
ctx.moveTo(300, 150);
ctx.ellipse(300, 150, 50, 50, 0, startAngle, endAngle);
ctx.lineTo(300, 150);
ctx.stroke();
ctx.fill();

ctx.beginPath();
ctx.moveTo(300, 150);
ctx.ellipse(300, 150, 60, 50, 0, startAngle, endAngle);
ctx.lineTo(300, 150);
ctx.stroke();
ctx.fill();

ctx.beginPath();
ctx.moveTo(300, 150);
ctx.ellipse(300, 150, 70, 50, 0, startAngle, endAngle);
ctx.lineTo(300, 150);
ctx.stroke();
ctx.fill();

ctx.beginPath();
ctx.moveTo(300, 150);
ctx.ellipse(300, 150, 80, 50, 0, startAngle, endAngle);
ctx.lineTo(300, 150);
ctx.stroke();
ctx.fill();

ctx.beginPath();
ctx.moveTo(300, 150);
ctx.ellipse(300, 150, 90, 50, 0, startAngle, endAngle);
ctx.lineTo(300, 150);
ctx.stroke();
ctx.fill();

ctx.beginPath();
ctx.moveTo(300, 150);
ctx.ellipse(300, 150, 100, 50, 0, startAngle, endAngle);
ctx.lineTo(300, 150);
ctx.stroke();
ctx.fill();

ctx.beginPath();
ctx.moveTo(300, 150);
ctx.ellipse(300, 150, 110, 50, 0, startAngle, endAngle);
ctx.lineTo(300, 150);
ctx.stroke();
ctx.fill();

ctx.beginPath();
ctx.moveTo(300, 150);
ctx.ellipse(300, 150, 120, 50, 0, startAngle, endAngle);
ctx.lineTo(300, 150);
ctx.stroke();
ctx.fill();

ctx.beginPath();
ctx.moveTo(300, 150);
ctx.ellipse(300, 150, 130, 50, 0, startAngle, endAngle);
ctx.lineTo(300, 150);
ctx.stroke();
ctx.fill();

ctx.beginPath();
ctx.moveTo(300, 150);
ctx.ellipse(300, 150, 140, 50, 0, startAngle, endAngle);
ctx.lineTo(300, 150);
ctx.stroke();
<html>
<body>

<canvas id="canvas" width="500" height="300">

</body>
</html>

Edit

This is working as intended by the OP
The formula comes from this blog post: Finding the angle around an ellipse

var c = document.getElementById("canvas");
var ctx = c.getContext("2d");

var startAngle = Math.PI * 2 / 360 * 75;
var endAngle = Math.PI * 2 / 360 * 345;


ctx.fillStyle = "rgba(255, 255, 255, 0.125)";

function correctEllipse(ctx, cx, cy, w, h, r, sa, ea) {
  sa = Math.atan(w/h * Math.tan(sa))
  ea = Math.atan(w/h * Math.tan(ea))
  ctx.ellipse(cx, cy, w, h, r, sa, ea);
}


w = 50
h = 50

ctx.beginPath();
ctx.moveTo(70, 150);
correctEllipse(ctx, 70, 150, w, h, 0, startAngle, endAngle);
ctx.lineTo(70, 150);
ctx.stroke();
ctx.fill();

h = 60
ctx.beginPath();
ctx.moveTo(70, 150);
correctEllipse(ctx, 70, 150, w, h, 0, startAngle, endAngle);
ctx.lineTo(70, 150);
ctx.stroke();
ctx.fill();

h = 70
ctx.beginPath();
ctx.moveTo(70, 150);
correctEllipse(ctx, 70, 150, w, h, 0, startAngle, endAngle);
ctx.lineTo(70, 150);
ctx.stroke();
ctx.fill();

h = 80
ctx.beginPath();
ctx.moveTo(70, 150);
correctEllipse(ctx, 70, 150, w, h, 0, startAngle, endAngle);
ctx.lineTo(70, 150);
ctx.stroke();
ctx.fill();

h = 90
ctx.beginPath();
ctx.moveTo(70, 150);
correctEllipse(ctx, 70, 150, w, h, 0, startAngle, endAngle);
ctx.lineTo(70, 150);
ctx.stroke();
ctx.fill();

h = 100
ctx.beginPath();
ctx.moveTo(70, 150);
correctEllipse(ctx, 70, 150, w, h, 0, startAngle, endAngle);
ctx.lineTo(70, 150);
ctx.stroke();
ctx.fill();

h = 110
ctx.beginPath();
ctx.moveTo(70, 150);
correctEllipse(ctx, 70, 150, w, h, 0, startAngle, endAngle);
ctx.lineTo(70, 150);
ctx.stroke();
ctx.fill();

h = 120
ctx.beginPath();
ctx.moveTo(70, 150);
correctEllipse(ctx, 70, 150, w, h, 0, startAngle, endAngle);
ctx.lineTo(70, 150);
ctx.stroke();
ctx.fill();

h = 130
ctx.beginPath();
ctx.moveTo(70, 150);
correctEllipse(ctx, 70, 150, w, h, 0, startAngle, endAngle);
ctx.lineTo(70, 150);
ctx.stroke();
ctx.fill();

h = 140
ctx.beginPath();
ctx.moveTo(70, 150);
correctEllipse(ctx, 70, 150, w, h, 0, startAngle, endAngle);
ctx.lineTo(70, 150);
ctx.stroke();
ctx.fill();

h = 50
w = 50
ctx.beginPath();
ctx.moveTo(300, 150);
correctEllipse(ctx, 300, 150, w, h, 0, startAngle, endAngle);
ctx.lineTo(300, 150);
ctx.stroke();
ctx.fill();


w = 60
ctx.beginPath();
ctx.moveTo(300, 150);
correctEllipse(ctx, 300, 150, w, h, 0, startAngle, endAngle);
ctx.lineTo(300, 150);
ctx.stroke();
ctx.fill();

w = 70
ctx.beginPath();
ctx.moveTo(300, 150);
correctEllipse(ctx, 300, 150, w, h, 0, startAngle, endAngle);
ctx.lineTo(300, 150);
ctx.stroke();
ctx.fill();

w = 80
ctx.beginPath();
ctx.moveTo(300, 150);
correctEllipse(ctx, 300, 150, w, h, 0, startAngle, endAngle);
ctx.lineTo(300, 150);
ctx.stroke();
ctx.fill();

w = 90
ctx.beginPath();
ctx.moveTo(300, 150);
correctEllipse(ctx, 300, 150, w, h, 0, startAngle, endAngle);
ctx.lineTo(300, 150);
ctx.stroke();
ctx.fill();

w = 100
ctx.beginPath();
ctx.moveTo(300, 150);
correctEllipse(ctx, 300, 150, w, h, 0, startAngle, endAngle);
ctx.lineTo(300, 150);
ctx.stroke();
ctx.fill();

w = 110
ctx.beginPath();
ctx.moveTo(300, 150);
correctEllipse(ctx, 300, 150, w, h, 0, startAngle, endAngle);
ctx.lineTo(300, 150);
ctx.stroke();
ctx.fill();

w = 120
ctx.beginPath();
ctx.moveTo(300, 150);
correctEllipse(ctx, 300, 150, w, h, 0, startAngle, endAngle);
ctx.lineTo(300, 150);
ctx.stroke();
ctx.fill();

w = 130
ctx.beginPath();
ctx.moveTo(300, 150);
correctEllipse(ctx, 300, 150, w, h, 0, startAngle, endAngle);
ctx.lineTo(300, 150);
ctx.stroke();
ctx.fill();

w = 140
ctx.beginPath();
ctx.moveTo(300, 150);
correctEllipse(ctx, 300, 150, w, h, 0, startAngle, endAngle);
ctx.lineTo(300, 150);
ctx.stroke();
<html>
<body>

<canvas id="canvas" width="500" height="300">

</body>
</html>

Edit 2

You need a correction phase, because atan of tan is only correct in the interval
[-Math.PI/2;Math.PI/2]. Beyond this interval the value is shifted by k * Math.PI periodically. See this QA on Mathematics

// "use strict";

window.requestAnimFrame = (function(callback) {
  return (
    window.requestAnimationFrame ||
    window.webkitRequestAnimationFrame ||
    window.mozRequestAnimationFrame ||
    window.oRequestAnimationFrame ||
    window.msRequestAnimationFrame ||
    function(callback) {
      window.setTimeout(callback, 1000 / 60);
    }
  );
})();

var step = 0;

function animate() {
  requestAnimationFrame(animate);
  step++;

  if (step % 1 === 0) {
    ctx.clearRect(0, 0, 500, 300);
    step = 0;
    startAngle += 1;
    endAngle += 1;

    //   startAngle = ((startAngle + 180) % 360) - 180;
    //   endAngle = ((endAngle + 180) % 360) - 180;

    // endAngle += 1;

    // startAngle = startAngle % 360;
    // endAngle = endAngle % 360

    // if (endAngle < startAngle) {
    //   temp = endAngle
    //   endAngle = startAngle
    //   startAngle = temp
    // }

    // console.log(startAngle, endAngle)

    //startAngle = ((startAngle + 180) % 360) - 180;
    //endAngle = ((endAngle + 180) % 360) - 180;

    h = 50;
    w = 50;
    cx = 70;
    cy = 150;

    deltaW = 1;
    deltaH = 5;
    deltaCx = 3;
    deltaCy = 1;

    for (var i = 0; i < 15; i++) {
      currentW = w + deltaW * i;
      currentH = h + deltaH * i;
      currentCx = cx + deltaCx * i;
      currentCy = cy + deltaCy * i;
      angleEllipse(
        ctx,
        currentCx,
        currentCy,
        currentW,
        currentH,
        0,
        startAngle * Math.PI / 180,
        endAngle * Math.PI / 180
      );
    }

    h = 50;
    w = 50;
    cx = 300;
    cy = 150;

    deltaW = 5;
    deltaH = 1;
    deltaCx = 1;
    deltaCy = 3;
    for (var i = 0; i < 15; i++) {
      currentW = w + deltaW * i;
      currentH = h + deltaH * i;
      currentCx = cx + deltaCx * i;
      currentCy = cy + deltaCy * i;
      angleEllipse(
        ctx,
        currentCx,
        currentCy,
        currentW,
        currentH,
        0,
        startAngle * Math.PI / 180,
        endAngle * Math.PI / 180
      );
    }
  }
}

document.addEventListener("DOMContentLoaded", function() {
  animate();
});

var c = document.getElementById("canvas");
var ctx = c.getContext("2d");

var startAngle = 75;
var endAngle = 320;

ctx.fillStyle = "rgba(255, 255, 255, 0.125)";

function atantan(angle, w, h) {
  angle = (angle + Math.PI) % (2 * Math.PI) - Math.PI;
  var tempAngle = angle;
  angle = Math.atan(w / h * Math.tan(angle));
  if (tempAngle < -Math.PI / 2) {
    angle -= Math.PI;
  } else if (tempAngle > Math.PI / 2) {
    angle += Math.PI;
  }
  return angle;
}

function correctEllipse(ctx, cx, cy, w, h, r, sa, ea) {
  // sa should stay between negative Math.PI and positive Math.PI
  sa = atantan(sa, w, h);
  ea = atantan(ea, w, h);
  ctx.ellipse(cx, cy, w, h, r, sa, ea);
}

function angleEllipse(ctx, cx, cy, w, h, r, sa, ea) {
  ctx.beginPath();
  ctx.moveTo(cx, cy);
  correctEllipse(ctx, cx, cy, w, h, r, sa, ea);
  // ctx.ellipse(cx, cy, w, h, r, sa, ea);
  ctx.lineTo(cx, cy);
  ctx.stroke();
  ctx.fill();
}
<canvas id="canvas" width="500" height="300" />
like image 183
yunzen Avatar answered Oct 11 '22 12:10

yunzen


I think it is the eccentric angle. You can observe it through the following code. try to change the value of the angle variable, and see what happens。

<canvas style="border: 1px solid black;" width="800" height="600"></canvas>
let canvas = document.querySelector("canvas");
let ctx = canvas.getContext("2d");
var angle = 33;
var degree = angle * Math.PI / 180;

ctx.translate(200, 200);
ctx.beginPath();
ctx.moveTo(200, 100);
ctx.ellipse(200, 100, 200, 100, 0, 0, 2 * Math.PI);
ctx.stroke();

ctx.beginPath();
ctx.ellipse(200, 100, 100, 100, 0, 0, 2 * Math.PI);
ctx.stroke();

ctx.beginPath();
ctx.ellipse(200, 100, 200, 200, 0, 0, 2 * Math.PI);
ctx.stroke();

ctx.beginPath();
ctx.moveTo(200, 100);
ctx.lineTo(200 + 200 * Math.cos(degree), 100 + 200 * Math.sin(degree));
ctx.stroke();

ctx.beginPath();
var x = 200 + 100 * Math.cos(degree);
var y = 100 + 100 * Math.sin(degree);
ctx.moveTo(x, y);
ctx.lineTo(x + 100, y);
ctx.stroke();

ctx.beginPath();
ctx.ellipse(200, 100, 200, 100, 0, 0, degree);
ctx.lineWidth = 4;
ctx.strokeStyle = "red";
ctx.stroke();
like image 23
OTime Avatar answered Oct 11 '22 13:10

OTime