1

So I looked at the answer here: Draw arrow head in canvas using Angle

But it didn't seem to do it the way I wanted. I definitely do not want to use rotate, what I would like, is based on an Angle in degrees (0 being up on the screen, 180 being down, etc) is draw an arrow pointing in that direction.

Now I slept through trig in highschool so the correct usage of Rads, Sin and Cos are... well, they elude me :(.

Anyways, I have the angle already computed, and based on that I want to draw like the following:

enter image description here The top one is at 0 degrees in my computation, the lower one 90 degrees.

I'm using a 2d canvas as my draw surface.

2 Answers2

1

Inspired by the trigonometry of this answer I made a line + two smaller lines for the arrow part.

const size = 200;

var canvas = document.querySelector("canvas")
canvas.width = size;
canvas.height = size;
var ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, size, size);
ctx.strokeStyle = "red"

function lineToAngle(ctx, x1, y1, length, angle) {
  angle = (angle - 90) * Math.PI / 180;
  var x2 = x1 + length * Math.cos(angle),
    y2 = y1 + length * Math.sin(angle);

  ctx.beginPath();
  ctx.moveTo(x1, y1);
  ctx.lineTo(x2, y2);
  ctx.stroke();
  ctx.fill();

  return {
    x: x2,
    y: y2
  };
}

function draw_arrow(ctx, x1, y1, length, angle) {
  var pos = lineToAngle(ctx, x1, y1, length, angle);
  lineToAngle(ctx, pos.x, pos.y, 10, angle - 135);
  lineToAngle(ctx, pos.x, pos.y, 10, angle + 135);
}

var pos = draw_arrow(ctx, 50, 50, 50, 30);

ctx.strokeStyle = "blue"
for (var angle = 0; angle <= 360; angle += 30) {
  draw_arrow(ctx, 100, 100, 60, angle);
}
<canvas></canvas>
IT goldman
  • 14,885
  • 2
  • 14
  • 28
  • I like this one a lot, and I see by the angle - 90 that on a plane, 0 is to the right instead of like a screen and towards up. The function and ability to do sizes is very desirable! – Robert Clouse Aug 23 '22 at 22:33
  • It might be useful for creating something recursive to create a fractal tree. But that's a story for [another question](https://stackoverflow.com/a/49787754/3807365) – IT goldman Aug 24 '22 at 10:18
  • @ITgoldman Is it possible to have this JS Started with button (or other input)? I could not find a way - it always draws arrows when I open page... – Oříšek Aug 03 '23 at 11:44
  • You can just use the function `draw_arrow(ctx, x1, y1, length, angle)` and use it inside an event handler. remove the code from `var pos = draw_arrow(ctx, 50, 50, 50, 30);` to prevent it automatically drawing – IT goldman Aug 03 '23 at 12:28
  • @ITgoldman Thank You for your reply, but in my case adding button: doesn't work - arrow is not drawn when the button is clicked. With removing variable of position, should I remove entire variable? Or just some parts of it? – Oříšek Aug 07 '23 at 06:19
  • @Oříšek this is really a minimalistic working example, you should be able to reconstruct it to your needs. `draw_arrow` expects `ctx` which you get from the `` element, and then the position, length, and angle. – IT goldman Aug 07 '23 at 07:21
0

Overkill maybe?

This may help or may not.

Rather than create the arrow (shape) with a set of draw calls you can create a Path2D to hold the shape with the helper function Path (in demo code below) that converts a set of arrays (each array a sub path) to a Path2D object. For example. const arrow = Path([[0, 0, 180, 0], [160, -10, 180, 0, 160, 10]]); a simple line arrow. To draw the path call ctx.stroke(arrow) or ctx.fill(arrow);

Define a style using Style eg const red = Style(...["#F00", , 2, "round"])

Position, rotate, and scale

Then the function drawPath(ctx, path, style, centerX, centerY, deg, scale) where...

  • ctx context to draw on.
  • path path to draw
  • style style to use,
  • centerX, centerY point to rotate around
  • deg angle in degrees from 0 deg pointing up, and positive values moving clockwize
  • scale lets you set the scale and defaults to 1.

Example use

// draw red arrow pointing up
const arrow = Path([[0, 0, 180, 0], [160, -10, 180, 0, 160, 10]]);
const red = Style(...["#F00", , 2, "round"]);
drawPath(ctx, arrow, red, 200, 200, 0);

Demo

Working example draw 6 arrows using 3 styles rotating around center of canvas.

const Path = (paths) => {
  var  j = 0, xx, yy, path = new Path2D();;
  for (const subPath of paths) {
    j = 0;
    while (j < subPath.length) {
      const [x, y] = [subPath[j++], subPath[j++]];
      j === 2 ?  
          path.moveTo(...([xx, yy] = [x, y])) :
          xx === x && yy === y ? path.closePath() : path.lineTo(x, y);
    }
  }
  return path;
}
const Style = (strokeStyle, fillStyle, lineWidth, lineJoin = "bevel") => ({strokeStyle, fillStyle, lineWidth, lineJoin});

const styles = {
    redLine:     Style(...["#F00", , 6, "round"]),
    greenFill:   Style(...[ , "#0A0"]),
    fillAndLine: Style(...["#000", "#0AF", 2, "round"]),
};
const paths = {
    lineArrow:    Path([ [10, 0, 180, 0], [160, -10, 180, 0, 160, 10] ]),
    fatArrow:     Path([ [10, -5, 180, -5, 170, -15, 200, 0, 170, 15, 180, 5, 10, 5, 10, -5] ]),
    diamondArrow: Path([ [60, 0, 100, -15, 150, 0, 100, 15, 60, 0] ]),
};

requestAnimationFrame(mainLoop);
const [W, H, ctx] = [can.width, can.height, can.getContext("2d")];
const DEG2RAD = Math.PI / 180, DEG_0_OFFSET = -90;

function drawPath(ctx, path, style, centerX, centerY, deg, scale = 1) {
     const rad = (deg + DEG_0_OFFSET) * DEG2RAD;
     const [ax, ay] = [Math.cos(rad) * scale, Math.sin(rad) * scale];
     ctx.setTransform(ax, ay, -ay, ax, centerX, centerY);
     Object.assign(ctx, style);
     style.fillStyle && ctx.fill(path);
     style.strokeStyle && ctx.stroke(path);
     ctx.setTransform(1, 0, 0, 1, 0, 0);
}
function mainLoop(time) {     
    ctx.clearRect(0, 0, W, H);
    drawPath(ctx, paths.fatArrow,     styles.fillAndLine, W * 0.5, H * 0.5, (time / 15000) * 360, 0.5);
    drawPath(ctx, paths.fatArrow,     styles.greenFill,   W * 0.5, H * 0.5, (time / 20000) * 360);
    drawPath(ctx, paths.diamondArrow, styles.fillAndLine, W * 0.5, H * 0.5, (time / 30000) * 360, 0.5);
    drawPath(ctx, paths.diamondArrow, styles.greenFill,   W * 0.5, H * 0.5, (time / 60000) * 360);
    drawPath(ctx, paths.lineArrow,    styles.redLine,     W * 0.5, H * 0.5, (time / 5000)  * 360, 0.9);
    drawPath(ctx, paths.lineArrow,    styles.redLine,     W * 0.5, H * 0.5, (time / 10000) * 360);
    requestAnimationFrame(mainLoop);
}
<canvas id="can" width="400" height="400"></canvas>
Blindman67
  • 51,134
  • 11
  • 73
  • 136
  • This is a very interesting solution, I like how you basically program an arrow into an array. This gives me very interesting ideas for further items I am messing with in my project – Robert Clouse Aug 23 '22 at 22:34