javascripthtmlhtml5-canvas

HTML Canvas Arc only drawing outer sliver of circle


I am working on a visualizer to show the area on a graph that is currently being checked for points from a starting points POV, and within a limited FOV. I am trying to show this with canvas arc, however, instead of filling in the arc area, it is filling in the outermost sliver of the arc.

I have tried drawing lines using the rad angle, and those are successful, it is only the arc that does not appear to work correctly. I would expect that it would complete the arc within the two angles, instead of seemingly subtracting the area of the triangle within the arc.

Relevant section:

let startingLocation = {x:0,y:0};
let angle = 18.43494882292201;
let fovAngle = 30;
let r = 100;
//Convert start and end angles to radians
let theta = angle * (Math.PI / 180);
let theta2 = (angle + fovAngle) * (Math.PI / 180);

//Draw start to end arc area
ctx.moveTo(startingLocation.x, startingLocation.y);
ctx.beginPath();
ctx.fillStyle = 'green';
ctx.arc(startingLocation.x, startingLocation.y, r, theta, theta2, false);
ctx.fill();
ctx.closePath();

Full snippet

const canvas = document.getElementById('canvas');
let ctx = canvas.getContext("2d");
const width = document.getElementById('canvas').width;
const height = document.getElementById('canvas').height;

//Starting variables
let fovAngle = 30;
let data = [{
  x: 60,
  y: 20
}];
let startingLocation = {
  x: 0,
  y: 0
};

//Get angle in radians
let angle = Math.atan2(data[0].y - startingLocation.y, data[0].x - startingLocation.x);
//Convert to degrees
angle = (angle * 180) / Math.PI;
if (angle < 0) {
  angle = angle + 360;
}
drawCanvas(angle);

function drawCanvas(angle) {
  //Clear the canvas
  ctx.clearRect(0, 0, width, height);
  ctx.textAlign = 'start';
  ctx.textBaseline = 'alphabetic';

  //Draw the points
  var counter = 1;
  for (const point of data) {
    //Draw the point on the canvas (with an offset of two so the point is in the center of the square)
    ctx.fillStyle = 'blue';
    ctx.fillRect(point.x - 2, point.y - 2, 5, 5);

    //Label the point on the canvas (offset to center number, and to have the number above the square instead of on top of it)
    ctx.fillStyle = 'black';
    ctx.fillText(counter, point.x - 2, point.y - 5);
    counter++;
  }

  //Draw Starting Location
  ctx.fillStyle = 'red';
  ctx.fillRect(startingLocation.x - 2, startingLocation.y - 2, 5, 5);
  ctx.fillStyle = 'black';
  ctx.fillText("Start", startingLocation.x - 10, startingLocation.y - 5);

  if (angle != null) {

    //let r=Math.max(width,height)*2;
    let r = 100;

    //Convert start and end angles to radians
    let theta = angle * (Math.PI / 180);
    let theta2 = (angle + fovAngle) * (Math.PI / 180);

    //Draw start to end arc area
    ctx.moveTo(startingLocation.x, startingLocation.y);
    ctx.globalAlpha = 0.3;
    ctx.beginPath();
    ctx.fillStyle = 'green';
    ctx.arc(startingLocation.x, startingLocation.y, r, theta, theta2, false);
    ctx.fill();
    ctx.globalAlpha = 1;
    ctx.closePath();

    //Draw start angle line
    ctx.strokeStyle = 'green';
    ctx.beginPath();
    ctx.moveTo(startingLocation.x, startingLocation.y);
    ctx.lineTo(startingLocation.x + r * Math.cos(theta), startingLocation.y + r * Math.sin(theta));
    ctx.stroke();
    ctx.closePath();

    //Draw end angle line
    ctx.strokeStyle = 'green';
    ctx.beginPath();
    ctx.moveTo(startingLocation.x, startingLocation.y);
    ctx.lineTo(startingLocation.x + r * Math.cos(theta2), startingLocation.y + r * Math.sin(theta2));
    ctx.stroke();
    ctx.closePath();

    console.log(theta, theta2);
  }
}
#canvas {
  width: 200px;
  height: 200px;
  border: 1px solid black;
  margin: 10px 20px;
}
<!DOCTYPE html>
<html>

  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>HTML Canvas Arc</title>
  </head>

  <body>
    <canvas id="canvas" width=200 height=200></canvas>
  </body>

</html>

JSFiddle


Solution

  • That's because you do call beginPath() after you moveTo the start point, which will remove the moveTo subpath from the current path.
    In the absence of a subpath, calling arc() will automatically moveTo the start point of the arc.1

    Simply call beginPath before you call moveTo:

    const canvas = document.getElementById('canvas');
    let ctx = canvas.getContext("2d");
    const width = document.getElementById('canvas').width;
    const height = document.getElementById('canvas').height;
    
    //Starting variables
    let fovAngle = 30;
    let data = [{
      x: 60,
      y: 20
    }];
    let startingLocation = {
      x: 0,
      y: 0
    };
    
    //Get angle in radians
    let angle = Math.atan2(data[0].y - startingLocation.y, data[0].x - startingLocation.x);
    //Convert to degrees
    angle = (angle * 180) / Math.PI;
    if (angle < 0) {
      angle = angle + 360;
    }
    drawCanvas(angle);
    
    function drawCanvas(angle) {
      //Clear the canvas
      ctx.clearRect(0, 0, width, height);
      ctx.textAlign = 'start';
      ctx.textBaseline = 'alphabetic';
    
      //Draw the points
      var counter = 1;
      for (const point of data) {
        //Draw the point on the canvas (with an offset of two so the point is in the center of the square)
        ctx.fillStyle = 'blue';
        ctx.fillRect(point.x - 2, point.y - 2, 5, 5);
    
        //Label the point on the canvas (offset to center number, and to have the number above the square instead of on top of it)
        ctx.fillStyle = 'black';
        ctx.fillText(counter, point.x - 2, point.y - 5);
        counter++;
      }
    
      //Draw Starting Location
      ctx.fillStyle = 'red';
      ctx.fillRect(startingLocation.x - 2, startingLocation.y - 2, 5, 5);
      ctx.fillStyle = 'black';
      ctx.fillText("Start", startingLocation.x - 10, startingLocation.y - 5);
    
      if (angle != null) {
    
        //let r=Math.max(width,height)*2;
        let r = 100;
    
        //Convert start and end angles to radians
        let theta = angle * (Math.PI / 180);
        let theta2 = (angle + fovAngle) * (Math.PI / 180);
    
        //Draw start to end arc area
        ctx.beginPath(); // Call beginPath before moveTo
        ctx.moveTo(startingLocation.x, startingLocation.y);
        ctx.globalAlpha = 0.3;
        ctx.fillStyle = 'green';
        ctx.arc(startingLocation.x, startingLocation.y, r, theta, theta2, false);
        ctx.fill();
        ctx.globalAlpha = 1;
        ctx.closePath(); // useless
    
        //Draw start angle line
        ctx.strokeStyle = 'green';
        ctx.beginPath();
        ctx.moveTo(startingLocation.x, startingLocation.y);
        ctx.lineTo(startingLocation.x + r * Math.cos(theta), startingLocation.y + r * Math.sin(theta));
        ctx.stroke();
        ctx.closePath(); // useless
    
        //Draw end angle line
        ctx.strokeStyle = 'green';
        ctx.beginPath();
        ctx.moveTo(startingLocation.x, startingLocation.y);
        ctx.lineTo(startingLocation.x + r * Math.cos(theta2), startingLocation.y + r * Math.sin(theta2));
        ctx.stroke();
        ctx.closePath(); // useless
    
        console.log(theta, theta2);
      }
    }
    #canvas {
      width: 200px;
      height: 200px;
      border: 1px solid black;
      margin: 10px 20px;
    }
    <!DOCTYPE html>
    <html>
    
      <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>HTML Canvas Arc</title>
      </head>
    
      <body>
        <canvas id="canvas" width=200 height=200></canvas>
      </body>
    
    </html>

    Also note that all your closePath calls are useless here. This method is basically a lineTo(startX, startY) but is in no way akin to beginPath().

    1. Technically it's the inverse: if there is a subpath, a lineTo from the last point to the start point is drawn, but it boils down to the same.