I want to draw an arrow using the canvas tag, javascript. I've made it using the quadratic function, but I'm having problems to calculate the angle of rotation of the arrow...
Anyone have a clue on this?
Thank you
I want to draw an arrow using the canvas tag, javascript. I've made it using the quadratic function, but I'm having problems to calculate the angle of rotation of the arrow...
Anyone have a clue on this?
Thank you
You can do:
ctx.save();
ctx.translate(xOrigin, yOrigin);
ctx.rotate(angle);
// draw your arrow, with its origin at [0, 0]
ctx.restore();
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
arrow({x: 10, y: 10}, {x: 100, y: 170}, 10);
arrow({x: 40, y: 250}, {x: 10, y: 70}, 5);
function arrow (p1, p2, size) {
var angle = Math.atan2((p2.y - p1.y) , (p2.x - p1.x));
var hyp = Math.sqrt((p2.x - p1.x) * (p2.x - p1.x) + (p2.y - p1.y) * (p2.y - p1.y));
ctx.save();
ctx.translate(p1.x, p1.y);
ctx.rotate(angle);
// line
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(hyp - size, 0);
ctx.stroke();
// triangle
ctx.fillStyle = 'blue';
ctx.beginPath();
ctx.lineTo(hyp - size, size);
ctx.lineTo(hyp, 0);
ctx.lineTo(hyp - size, -size);
ctx.fill();
ctx.restore();
}
<canvas id = "canvas" width = "300" height = "400"></canvas>
Typescript version, with the fixed arrow tip when line width >> 1
function canvas_arrow( context, fromx, fromy, tox, toy ) {
const dx = tox - fromx;
const dy = toy - fromy;
const headlen = Math.sqrt( dx * dx + dy * dy ) * 0.3; // length of head in pixels
const angle = Math.atan2( dy, dx );
context.beginPath();
context.moveTo( fromx, fromy );
context.lineTo( tox, toy );
context.stroke();
context.beginPath();
context.moveTo( tox - headlen * Math.cos( angle - Math.PI / 6 ), toy - headlen * Math.sin( angle - Math.PI / 6 ) );
context.lineTo( tox, toy );
context.lineTo( tox - headlen * Math.cos( angle + Math.PI / 6 ), toy - headlen * Math.sin( angle + Math.PI / 6 ) );
context.stroke();
}
function RTEShape()
{
this.x = 50;
this.y = 50;
this.w = 100; // default width and height?
this.h = 100;
this.fill = '#444444';
this.text = "Test String";
this.type;
this.color;
this.size = 6;
// The selection color and width. Right now we have a red selection with a small width
this.mySelColor = '#CC0000';
this.mySelWidth = 2;
this.mySelBoxColor = 'darkred';// New for selection boxes
this.mySelBoxSize = 6;
}
RTEShape.prototype.buildArrow = function(canvas)
{
this.type = "arrow";
// Make sure we don't execute when canvas isn't supported
if (canvas.getContext){
// use getContext to use the canvas for drawing
var ctx = canvas.getContext('2d');
var oneThirdX = this.x + (this.w/3);
var twoThirdX = this.x + ((this.w*2)/3);
var oneFifthY = this.y - (this.y/5);
var twoFifthY = this.y - ((this.y*3)/5);
/**/
//ctx.beginPath();
ctx.moveTo(oneThirdX,this.y); // 125,125
ctx.lineTo(oneThirdX,oneFifthY); // 125,105
ctx.lineTo(this.x*2,oneFifthY); // 225,105
ctx.lineTo(this.x*2,twoFifthY); // 225,65
ctx.lineTo(oneThirdX,twoFifthY); // 125,65
ctx.lineTo(oneThirdX,(this.y/5)); // 125,45
ctx.lineTo(this.x,(this.y+(this.y/5))/2); // 45,85
ctx.fillStyle = "green";
ctx.fill();
ctx.fillStyle = "yellow";
ctx.fillRect(this.x,this.y,this.w,this.h);
} else {
alert('Error on buildArrow!\n'+err.description);
}
}
Hello and thank you very much for your suggestions.
May I suggest you drop the cumbersome atan ? You may as well use linear algebra to add or subtract angles:
var cospix=0.866025404; //cosinus of pi/6
function canvas_arrow(context, fromx, fromy, tox, toy) {
ctx.strokeStyle = '#AA0000';
var headlen = 10; // length of head in pixels
var dx = tox - fromx;
var dy = toy - fromy;
var length = Math.sqrt(dy*dy + dx*dx); //length of arrow
var sina = dy/length, cosa = dx/length; //computing sin and cos of arrow angle
var cosp=cosa*cospix-0.5*sina, cosm=cosa*cospix+0.5*sina,
sinp=cosa*0.5+cospix*sina, sinm=cospix*sina-cosa*0.5;
//computing cos and sin of arrow angle plus pi/6, respectively minus pi/6
//(p for plus, m for minus at the end of variable's names)
context.moveTo(fromx, fromy);
context.lineTo(tox, toy);
context.lineTo(tox - headlen * cosm, toy - headlen * sinm); //computing coordinates using the cos and sin computed above
context.moveTo(tox, toy);
context.lineTo(tox - headlen * cosp, toy - headlen * sinp); //computing coordinates using the cos and sin computed above
}
I also stumbled across this problem and gotta say that none of these solutions works nicely if you want to fill your arrow and make it transparent.
I wrote some code to achieve this. (I usually code in C++ so dont judge my code style please) :)
function transform(xy,angle,xy0){
// put x and y relative to x0 and y0 so we can rotate around that
const rel_x = xy[0] - xy0[0];
const rel_y = xy[1] - xy0[1];
// compute rotated relative points
const new_rel_x = Math.cos(angle) * rel_x - Math.sin(angle) * rel_y;
const new_rel_y = Math.sin(angle) * rel_x + Math.cos(angle) * rel_y;
return [xy0[0] + new_rel_x, xy0[1] + new_rel_y];
}
function draw_arrow(context, x0, y0, x1, y1, width, head_width, head_length){
// compute length first
const length = Math.sqrt((x1-x0)*(x1-x0)+(y1-y0)*(y1-y0))
let angle = Math.atan2(y1-y0, x1-x0);
// adjust the angle by 90 degrees since the arrow we rotate is rotated by 90 degrees
angle -= Math.PI / 2;
let p0 = [x0,y0];
// order will be: p1 -> p3 -> p5 -> p7 -> p6 -> p4 -> p2
// formulate the two base points
let p1 = [x0 + width / 2, y0];
let p2 = [x0 - width / 2, y0];
// formulate the upper base points which connect the pointy end with the lengthy thing
let p3 = [x0 + width / 2, y0 + length - head_length];
let p4 = [x0 - width / 2, y0 + length - head_length];
// formulate the outter points of the triangle
let p5 = [x0 + head_width / 2, y0 + length - head_length];
let p6 = [x0 - head_width / 2, y0 + length - head_length];
// end point of the arrow
let p7 = [x0, y0 + length];
p1 = transform(p1,angle,p0);
p2 = transform(p2,angle,p0);
p3 = transform(p3,angle,p0);
p4 = transform(p4,angle,p0);
p5 = transform(p5,angle,p0);
p6 = transform(p6,angle,p0)
p7 = transform(p7,angle,p0);
// move to start first
context.moveTo(p1[0], p1[1]);
context.beginPath();
// start drawing the lines
context.lineTo(p3[0], p3[1]);
context.lineTo(p5[0], p5[1]);
context.lineTo(p7[0], p7[1]);
context.lineTo(p6[0], p6[1]);
context.lineTo(p4[0], p4[1]);
context.lineTo(p2[0], p2[1]);
context.lineTo(p1[0], p1[1]);
context.closePath();
context.arc(x0,y0,width/2,angle-Math.PI,angle)
context.fill();
}
This results in a nicely looking arrow which I used for a chess website:
I've been struggeling with this for quite some time now. I needed to to this in both javascript and c#. For javascript i found a nice library jCanvas.
My main problem was drawing nicely looking arrow heads, which jCanvas does perfectly. For my c# project i reverse engineered the jCanvas code.
Hopefully this helps somebody
Here is the working solution
function draw_arrow(ctx,fx,fy,tx,ty){ //ctx is the context
var angle=Math.atan2(ty-fy,tx-fx);
ctx.moveTo(fx,fy); ctx.lineTo(tx,ty);
var w=3.5; //width of arrow to one side. 7 pixels wide arrow is pretty
ctx.strokeStyle="#4d4d4d"; ctx.fillStyle="#4d4d4d";
angle=angle+Math.PI/2; tx=tx+w*Math.cos(angle); ty=ty+w*Math.sin(angle);
ctx.lineTo(tx,ty);
//Drawing an isosceles triangle of sides proportional to 2:7:2
angle=angle-1.849096; tx=tx+w*3.5*Math.cos(angle); ty=ty+w*3.5*Math.sin(angle);
ctx.lineTo(tx,ty);
angle=angle-2.584993; tx=tx+w*3.5*Math.cos(angle); ty=ty+w*3.5*Math.sin(angle);
ctx.lineTo(tx,ty);
angle=angle-1.849096; tx=tx+w*Math.cos(angle); ty=ty+w*Math.sin(angle);
ctx.lineTo(tx,ty);
ctx.stroke(); ctx.fill();
}
While this question is mostly answered, I find the answers lacking. The top answer produces ugly arrows, many go beyond the point when using a width other than 1, and others have unnecessary steps.
This is the simplest answer that draws a pretty arrow head (proper triangle filled with color), and retracts the point of the arrow to consider the width of lines.
ctx = document.getElementById('canvas').getContext('2d');
/* Draw barrier */
ctx.beginPath();
ctx.moveTo(50, 30);
ctx.lineTo(450, 30);
ctx.stroke();
draw_arrow(50, 180, 150, 30);
draw_arrow(250, 180, 250, 30);
draw_arrow(450, 180, 350, 30);
function draw_arrow(x0, y0, x1, y1) {
const width = 8;
const head_len = 16;
const head_angle = Math.PI / 6;
const angle = Math.atan2(y1 - y0, x1 - x0);
ctx.lineWidth = width;
/* Adjust the point */
x1 -= width * Math.cos(angle);
y1 -= width * Math.sin(angle);
ctx.beginPath();
ctx.moveTo(x0, y0);
ctx.lineTo(x1, y1);
ctx.stroke();
ctx.beginPath();
ctx.lineTo(x1, y1);
ctx.lineTo(x1 - head_len * Math.cos(angle - head_angle), y1 - head_len * Math.sin(angle - head_angle));
ctx.lineTo(x1 - head_len * Math.cos(angle + head_angle), y1 - head_len * Math.sin(angle + head_angle));
ctx.closePath();
ctx.stroke();
ctx.fill();
}
<canvas id="canvas" width="500" height="180"></canvas>