Draw arrow on canvas tag - javascript

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

As simple as I can get it. You'll have to prepend context.beginPath() and append context.stroke() yourself:
ctx = document.getElementById("c").getContext("2d");
ctx.beginPath();
canvas_arrow(ctx, 10, 30, 200, 150);
canvas_arrow(ctx, 100, 200, 400, 50);
canvas_arrow(ctx, 200, 30, 10, 150);
canvas_arrow(ctx, 400, 200, 100, 50);
ctx.stroke();
function canvas_arrow(context, fromx, fromy, tox, toy) {
var headlen = 10; // length of head in pixels
var dx = tox - fromx;
var dy = toy - fromy;
var angle = Math.atan2(dy, dx);
context.moveTo(fromx, fromy);
context.lineTo(tox, toy);
context.lineTo(tox - headlen * Math.cos(angle - Math.PI / 6), toy - headlen * Math.sin(angle - Math.PI / 6));
context.moveTo(tox, toy);
context.lineTo(tox - headlen * Math.cos(angle + Math.PI / 6), toy - headlen * Math.sin(angle + Math.PI / 6));
}
<html>
<body>
<canvas id="c" width="500" height="500"></canvas>
</body>

Ok, so the first answer on this page helped me greatly when I was trying to figure this problem out myself, although as someone else already stated, if you have a line width greater than 1px you get funny shapes. The fix that someone else suggested almost worked, but I still had some issues when trying to go for a thicker width arrow. After several hours of playing around with it I was able to combine the above solution with some of my own tinkering to come up with the following code that will draw an arrow at whatever thickness you desire without distorting the arrow shape.
function drawArrow(fromx, fromy, tox, toy){
//variables to be used when creating the arrow
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
const width = 22;
var headlen = 10;
// This makes it so the end of the arrow head is located at tox, toy, don't ask where 1.15 comes from
tox -= Math.cos(angle) * ((width*1.15));
toy -= Math.sin(angle) * ((width*1.15));
var angle = Math.atan2(toy-fromy,tox-fromx);
//starting path of the arrow from the start square to the end square and drawing the stroke
ctx.beginPath();
ctx.moveTo(fromx, fromy);
ctx.lineTo(tox, toy);
ctx.strokeStyle = "#cc0000";
ctx.lineWidth = width;
ctx.stroke();
//starting a new path from the head of the arrow to one of the sides of the point
ctx.beginPath();
ctx.moveTo(tox, toy);
ctx.lineTo(tox-headlen*Math.cos(angle-Math.PI/7),toy-headlen*Math.sin(angle-Math.PI/7));
//path from the side point of the arrow, to the other side point
ctx.lineTo(tox-headlen*Math.cos(angle+Math.PI/7),toy-headlen*Math.sin(angle+Math.PI/7));
//path from the side point back to the tip of the arrow, and then again to the opposite side point
ctx.lineTo(tox, toy);
ctx.lineTo(tox-headlen*Math.cos(angle-Math.PI/7),toy-headlen*Math.sin(angle-Math.PI/7));
//draws the paths created above
ctx.strokeStyle = "#cc0000";
ctx.lineWidth = width;
ctx.stroke();
ctx.fillStyle = "#cc0000";
ctx.fill();
}
This is now the code that I am using in my program. What I found to be the key with eliminating the distortion issue was continuing the stroke from the tip of the arrow to one side point, to the other side point, back to the tip, and back over to the first side point, then doing a fill. This corrected the shape of the arrow.
Hope this helps!

Here is another method to draw arrows. It uses the triangle method from here: https://stackoverflow.com/a/8937325/1828637
A little helper function.
function canvas_arrow(context, fromx, fromy, tox, toy, r){
var x_center = tox;
var y_center = toy;
var angle;
var x;
var y;
context.beginPath();
angle = Math.atan2(toy-fromy,tox-fromx)
x = r*Math.cos(angle) + x_center;
y = r*Math.sin(angle) + y_center;
context.moveTo(x, y);
angle += (1/3)*(2*Math.PI)
x = r*Math.cos(angle) + x_center;
y = r*Math.sin(angle) + y_center;
context.lineTo(x, y);
angle += (1/3)*(2*Math.PI)
x = r*Math.cos(angle) + x_center;
y = r*Math.sin(angle) + y_center;
context.lineTo(x, y);
context.closePath();
context.fill();
}
And here is a demonstration of it to draw arrows at the start and at the end of a line.
var can = document.getElementById('c');
var ctx = can.getContext('2d');
ctx.lineWidth = 10;
ctx.strokeStyle = 'steelblue';
ctx.fillStyle = 'steelbllue'; // for the triangle fill
ctx.lineJoin = 'butt';
ctx.beginPath();
ctx.moveTo(50, 50);
ctx.lineTo(150, 150);
ctx.stroke();
canvas_arrow(ctx, 50, 50, 150, 150, 10);
canvas_arrow(ctx, 150, 150, 50, 50, 10);
function canvas_arrow(context, fromx, fromy, tox, toy, r){
var x_center = tox;
var y_center = toy;
var angle;
var x;
var y;
context.beginPath();
angle = Math.atan2(toy-fromy,tox-fromx)
x = r*Math.cos(angle) + x_center;
y = r*Math.sin(angle) + y_center;
context.moveTo(x, y);
angle += (1/3)*(2*Math.PI)
x = r*Math.cos(angle) + x_center;
y = r*Math.sin(angle) + y_center;
context.lineTo(x, y);
angle += (1/3)*(2*Math.PI)
x = r*Math.cos(angle) + x_center;
y = r*Math.sin(angle) + y_center;
context.lineTo(x, y);
context.closePath();
context.fill();
}
<canvas id="c" width=300 height=300></canvas>

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();
}

Given a size and the starting position, following code will draw the arrow for you.
function draw_arrow(context, startX, startY, size) {
var arrowX = startX + 0.75 * size;
var arrowTopY = startY - 0.707 * (0.25 * size);
var arrowBottomY = startY + 0.707 * (0.25 * size);
context.moveTo(startX, startY);
context.lineTo(startX + size, startX);
context.lineTo(arrowX, arrowTopY);
context.moveTo(startX + size, startX);
context.lineTo(arrowX, arrowBottomY);
context.stroke();
}
window.onload = function() {
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
var startX = 50;
var startY = 50;
var size = 100;
context.lineWidth = 2;
draw_arrow(context, startX, startY, size);
};
body {
margin: 0px;
padding: 0px;
}
#myCanvas {
border: 1px solid #9C9898;
}
<!DOCTYPE HTML>
<html>
<body onmousedown="return false;">
<canvas id="myCanvas" width="578" height="200"></canvas>
</body>
</html>

This code is similar to Titus Cieslewski's solution, maybe the arrow is a bit nicer:
function canvasDrawArrow(context, fromx, fromy, tox, toy) {
var headlen = 10.0;
var back = 4.0;
var angle1 = Math.PI / 13.0;
var angle2 = Math.atan2(toy - fromy, tox - fromx);
var diff1 = angle2 - angle1;
var diff2 = angle2 + angle1;
var xx = getBack(back, fromx, fromy, tox, toy);
var yy = getBack(back, fromy, fromx, toy, tox);
context.moveTo(fromx, fromy);
context.lineTo(tox, toy);
context.moveTo(xx, yy);
context.lineTo(xx - headlen * Math.cos(diff1), yy - headlen * Math.sin(diff1));
context.moveTo(xx, yy);
context.lineTo(xx - headlen * Math.cos(diff2), yy - headlen * Math.sin(diff2));
}
function getBack(len, x1, y1, x2, y2) {
return x2 - (len * (x2 - x1) / (Math.sqrt(Math.pow(y2 - y1, 2) + Math.pow(x2 - x1, 2))));
}
this works well with lineWidth > 1. It can come in handy when drawing x and y axis

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:

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
}

You can push your matrix, rotate it, draw your arrow and then pop the matrix.

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>

Related

HTML5 Canvas rotate gradient around centre with best fit

I want to make a gradient that covers the whole canvas whatever the angle of it.
So I used a method found on a Stack Overflow post which is finally incorrect. The solution is almost right but, in fact, the canvas is not totally covered by the gradient.
It is this answer: https://stackoverflow.com/a/45628098/5594331
(You have to look at the last point named "Example of best fit.")
In my code example below, the yellow part should not be visible because it should be covered by the black and white gradient. This is mostly the code written in Blindman67's answer with some adjustments to highlight the problem.
I have drawn in green the control points of the gradient. With the right calculations, these should be stretched to the edges of the canvas at any angle.
var ctx = canvas.getContext("2d");
var w = canvas.width;
var h = canvas.height;
function bestFitGradient(angle){
var dist = Math.sqrt(w * w + h * h) / 2; // get the diagonal length
var diagAngle = Math.asin((h / 2) / dist); // get the diagonal angle
// Do the symmetry on the angle (move to first quad
var a1 = ((angle % (Math.PI *2))+ Math.PI*4) % (Math.PI * 2);
if(a1 > Math.PI){ a1 -= Math.PI }
if(a1 > Math.PI / 2 && a1 <= Math.PI){ a1 = (Math.PI / 2) - (a1 - (Math.PI / 2)) }
// get angles from center to edges for along and right of gradient
var ang1 = Math.PI/2 - diagAngle - Math.abs(a1);
var ang2 = Math.abs(diagAngle - Math.abs(a1));
// get distance from center to horizontal and vertical edges
var dist1 = Math.cos(ang1) * h;
var dist2 = Math.cos(ang2) * w;
// get the max distance
var scale = Math.max(dist2, dist1) / 2;
// get the vector to the start and end of gradient
var dx = Math.cos(angle) * scale;
var dy = Math.sin(angle) * scale;
var x0 = w / 2 + dx;
var y0 = h / 2 + dy;
var x1 = w / 2 - dx;
var y1 = h / 2 - dy;
// create the gradient
const g = ctx.createLinearGradient(x0, y0, x1, y1);
// add colours
g.addColorStop(0, "yellow");
g.addColorStop(0, "white");
g.addColorStop(.5, "black");
g.addColorStop(1, "white");
g.addColorStop(1, "yellow");
return {
g: g,
x0: x0,
y0: y0,
x1: x1,
y1: y1
};
}
function update(timer){
var r = bestFitGradient(timer / 1000);
// draw gradient
ctx.fillStyle = r.g;
ctx.fillRect(0,0,w,h);
// draw points
ctx.lineWidth = 3;
ctx.fillStyle = '#00FF00';
ctx.strokeStyle = '#FF0000';
ctx.beginPath();
ctx.arc(r.x0, r.y0, 5, 0, 2 * Math.PI, false);
ctx.stroke();
ctx.fill();
ctx.beginPath();
ctx.arc(r.x1, r.y1, 5, 0, 2 * Math.PI, false);
ctx.stroke();
ctx.fill();
requestAnimationFrame(update);
}
requestAnimationFrame(update);
canvas {
border : 2px solid red;
}
<canvas id="canvas" width="300" height="200"></canvas>
In this fiddle there is a function that calculates the distance between a rotated line and a point:
function distanceToPoint(px, py, angle) {
const cx = width / 2;
const cy = height / 2;
return Math.abs((Math.cos(angle) * (px - cx)) - (Math.sin(angle) * (py - cy)));
}
Which is then used to find the maximum distance between the line and the corner points (only two points are considered, because the distances to the other two points are mirrored):
const dist = Math.max(
distanceToPoint(0, 0, angle),
distanceToPoint(0, height, angle)
);
Which can be used to calculate offset points for the end of the gradient:
const ox = Math.cos(angle) * dist;
const oy = Math.sin(angle) * dist;
const gradient = context.createLinearGradient(
width / 2 + ox,
height / 2 + oy,
width / 2 - ox,
height / 2 - oy
)

Rotate a line in 3rd dimension on HTML5 canvas

I can easily draw/rotate a line of given length around z-axis, y-axis or x-axis.
const ctx = document.getElementById("drawing").getContext("2d");
ctx.scale(1, -1); ctx.translate(0, -ctx.canvas.height); // flip canvas
const length = 50;
let t = 0;
//x = r sin(q) cos(f)
//y = r sin(q) sin(f)
//z = r cos(q)
function rotate_around_zaxis() {
const x1=50; const y1=50;
const line_angle = 20 * Math.PI/180;
const angle = 0;
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x1 + length * Math.sin(line_angle) * Math.cos(angle + t),
y1 + length * Math.sin(line_angle) * Math.sin(angle + t));
ctx.stroke();
}
function rotate_around_yaxis() {
const x1=150; const y1=50;
const line_angle = 20 * Math.PI/180;
const angle = 0;
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x1 + length * Math.sin(line_angle) * Math.cos(angle + t),
y1 + length /*Math.sin(angle + t)*/ * Math.cos(line_angle) );
ctx.stroke();
}
function rotate_around_xaxis() {
const x1=250; const y1=50;
const line_angle = 20 * Math.PI/180;
const angle = 0;
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x1 + length /**Math.sin(angle + t)*/ * Math.cos(line_angle),
y1 + length * Math.sin(line_angle) * Math.sin(angle + t));
ctx.stroke();
}
function line(x1, y1, x2, y2) {
ctx.beginPath(); ctx.moveTo(x1, y1); ctx.lineTo(x2, y2); ctx.stroke();
}
function animate() {
ctx.clearRect(0,0,300,100);
line(0, 50, 100, 50);line(50, 0, 50, 100);rotate_around_zaxis();
line(105, 50, 200, 50);line(150, 0, 150, 100);rotate_around_yaxis();
line(205, 50, 300, 50);line(250, 0, 250, 100);rotate_around_xaxis();
t+=Math.PI/180;
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
<canvas id="drawing" width=300 height=100></canvas>
However I can only do this around straight up/down y-axis degree or straight x-axis. I can not figure out rotation around an arbitrary line in space. In other words I don't know how to move it to any point in 3d space between x,y and z/.
I couldn't grasp rotation matrices. Rotation calculation on many places is given like this.
x' = x * cos(angle) - y * sin(angle);
y' = x * sin(angle) + y * cos(angle);
I don't understand where this equation fits in what I am trying to do.
I want to be able to rotate the line in cone like shape around any axis. How do I achieve this?
Well, theoretically, if the first example works, then to get the second one, you can apply the first one, and then just translate everything -60°
let newX2 = Math.cos(-60 / Math.PI/180) * x2
let newY2 = Math.sin(-60 / Math.PI/180) * y2
same for x1 and y1

How to draw triangle pointers inside of circle

I realize this is a simple Trigonometry question, but my high school is failing me right now.
Given an angle, that I have converted into radians to get the first point. How do I figure the next two points of the triangle to draw on the canvas, so as to make a small triangle always point outwards to the circle. So lets say Ive drawn a circle of a given radius already. Now I want a function to plot a triangle that sits on the edge of the circle inside of it, that points outwards no matter the angle. (follows the edge, so to speak)
function drawPointerTriangle(ctx, angle){
var radians = angle * (Math.PI/180)
var startX = this.radius + this.radius/1.34 * Math.cos(radians)
var startY = this.radius - this.radius/1.34 * Math.sin(radians)
// This gives me my starting point on the outer edge of the circle, plotted at the angle I need
ctx.moveTo(startX, startY);
// HOW DO I THEN CALCULATE x1,y1 and x2, y2. So that no matter what angle I enter into this function, the arrow/triangle always points outwards to the circle.
ctx.lineTo(x1, y1);
ctx.lineTo(x2, y2);
}
Example
You don't say what type of triangle you want to draw so I suppose that it is an equilateral triangle.
Take a look at this image (credit here)
I will call 3 points p1, p2, p3 from top right to bottom right, counterclockwise.
You can easily calculate the coordinate of three points of the triangle in the coordinate system with the origin is coincident with the triangle's centroid.
Given a point belongs to the edge of the circle and the point p1 that we just calculated, we can calculate parameters of the translation from our main coordinate system to the triangle's coordinate system. Then, we just have to translate the coordinate of two other points back to our main coordinate system. That is (x1,y1) and (x2,y2).
You can take a look at the demo below that is based on your code.
const w = 300;
const h = 300;
function calculateTrianglePoints(angle, width) {
let r = width / Math.sqrt(3);
let firstPoint = [
r * Math.cos(angle),
r * Math.sin(angle),
]
let secondPoint = [
r * Math.cos(angle + 2 * Math.PI / 3),
r * Math.sin(angle + 2 * Math.PI / 3),
]
let thirdPoint = [
r * Math.cos(angle + 4 * Math.PI / 3),
r * Math.sin(angle + 4 * Math.PI / 3),
]
return [firstPoint, secondPoint, thirdPoint]
}
const radius = 100
const triangleWidth = 20;
function drawPointerTriangle(ctx, angle) {
var radians = angle * (Math.PI / 180)
var startX = radius * Math.cos(radians)
var startY = radius * Math.sin(radians)
var [pt0, pt1, pt2] = calculateTrianglePoints(radians, triangleWidth);
var delta = [
startX - pt0[0],
startY - pt0[1],
]
pt1[0] = pt1[0] + delta[0]
pt1[1] = pt1[1] + delta[1]
pt2[0] = pt2[0] + delta[0]
pt2[1] = pt2[1] + delta[1]
ctx.beginPath();
// This gives me my starting point on the outer edge of the circle, plotted at the angle I need
ctx.moveTo(startX, startY);
[x1, y1] = pt1;
[x2, y2] = pt2;
// HOW DO I THEN CALCULATE x1,y1 and x2, y2. So that no matter what angle I enter into this function, the arrow/triangle always points outwards to the circle.
ctx.lineTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.closePath();
ctx.fillStyle = '#FF0000';
ctx.fill();
}
function drawCircle(ctx, radius) {
ctx.beginPath();
ctx.arc(0, 0, radius, 0, 2 * Math.PI);
ctx.closePath();
ctx.fillStyle = '#000';
ctx.fill();
}
function clear(ctx) {
ctx.fillStyle = '#fff';
ctx.fillRect(-w / 2, -h / 2, w, h);
}
function normalizeAngle(pointCoordinate, angle) {
const [x, y] = pointCoordinate;
if (x > 0 && y > 0) return angle;
else if (x > 0 && y < 0) return 360 + angle;
else if (x < 0 && y < 0) return 180 - angle;
else if (x < 0 && y > 0) return 180 - angle;
}
function getAngleFromPoint(point) {
const [x, y] = point;
if (x == 0 && y == 0) return 0;
else if (x == 0) return 90 * (y > 0 ? 1 : -1);
else if (y == 0) return 180 * (x >= 0 ? 0: 1);
const radians = Math.asin(y / Math.sqrt(
x ** 2 + y ** 2
))
return normalizeAngle(point, radians / (Math.PI / 180))
}
document.addEventListener('DOMContentLoaded', function() {
const canvas = document.querySelector('canvas');
const angleText = document.querySelector('.angle');
const ctx = canvas.getContext('2d');
ctx.translate(w / 2, h / 2);
drawCircle(ctx, radius);
drawPointerTriangle(ctx, 0);
canvas.addEventListener('mousemove', _.throttle(function(ev) {
let mouseCoordinate = [
ev.clientX - w / 2,
ev.clientY - h / 2
]
let degAngle = getAngleFromPoint(mouseCoordinate)
clear(ctx);
drawCircle(ctx, radius);
drawPointerTriangle(ctx, degAngle)
angleText.innerText = Math.floor((360 - degAngle)*100)/100;
}, 15))
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.9.1/underscore-min.js"></script>
<canvas width=300 height=300></canvas>
<div class="angle">0</div>
reduce the radius, change the angle and call again cos/sin:
function drawPointerTriangle(ctx, angle)
{
var radians = angle * (Math.PI/180);
var radius = this.radius/1.34;
var startX = this.center.x + radius * Math.cos(radians);
var startY = this.center.y + radius * Math.sin(radians);
ctx.moveTo(startX, startY);
radius *= 0.9;
radians += 0.1;
var x1 = this.center.x + radius * Math.cos(radians);
var y1 = this.center.y + radius * Math.sin(radians);
radians -= 0.2;
var x1 = this.center.x + radius * Math.cos(radians);
var y1 = this.center.y + radius * Math.sin(radians);
ctx.lineTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.lineTo(startX, startY);
}
the resulting triangle's size is proportional to the size of the circle.
in case you need an equilateral, fixed size triangle, use this:
//get h by pythagoras
h = sqrt( a^2 - (a/2)^2 );)
//get phi using arcustangens:
phi = atan( a/2, radius-h );
//reduced radius h by pythagoras:
radius = sqrt( (radius-h)^2 + (a/2)^2 );
radians += phi;
...
radians -= 2*phi;
...

How to draw an arrowhead with proper coordinates in the canvas

I have drawn the curved line using below lines of code, I need to draw an arrowhead.For this I need to draw 2 lines wth some angle and rotate it some some angle. It is very confusing to draw. I am following the post present in the link provided for arrowhead.
.html
<canvas id = "canvas" width = "100px" height = "120px"></canvas>
.ts
arrow({ x: 10, y: 10 }, { x: 100, y: 140 }, 15); //function called on reload.
function arrow(p1, p2, size) {
var angle = Math.atan2((p2.y - p1.y), (p2.x - p1.x));
//curve line
ctx.strokeStyle = 'white';
ctx.beginPath();
ctx.lineWidth=3;
ctx.moveTo(40,0);
ctx.bezierCurveTo(30, 0, -70, 75, 100, 150);
ctx.lineTo(100,120)
ctx.stroke();
//to draw a triangle ??
}
I tried look a like
arrow({ x: 10, y: 10 }, { x: 100, y: 140 }, 15); //function called on reload.
function arrow(p1, p2, size) {
var angle = Math.atan2((p2.y - p1.y), (p2.x - p1.x));
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
//curve line
ctx.fillStyle = "";
ctx.fillRect(0,0,200,200);
ctx.strokeStyle = 'white';
ctx.beginPath();
ctx.lineWidth=3;
ctx.moveTo(40,20);
ctx.bezierCurveTo(30,40, -0,110, 100, 149.5);
ctx.moveTo(100,150.6);
ctx.lineTo(82,133);
ctx.stroke();
ctx.moveTo(100,149.7);
ctx.lineTo(76,146);
ctx.stroke();
//to draw a triangle ??
}
<canvas id = "canvas" width = "150px" height = "300px"></canvas>
You need some mathematics for this. This javascript method will draw a simple arrow head at the end of a line using canvas. It doesn't matter from which angle you come into the "arrow head".
//..come to the end of the line at some point
ctx.lineTo(tox, toy);
//draw the arrow head
ctx.lineTo(tox - headlen * Math.cos(angle + Math.PI / 5), toy - headlen * Math.sin(angle + Math.PI / 5));
ctx.moveTo(tox, toy);
ctx.lineTo(tox - headlen * Math.cos(angle - Math.PI / 5), toy - headlen * Math.sin(angle - Math.PI / 5));

smoother lineWidth changes in canvas lineTo

so i'm trying to create a drawing tool in HTML5 canvas where the weight of the stroke increases the faster you move the mouse and decreases the slower you move. I'm using ctx.lineTo() but on my first attempt noticed that if i move too quickly the change in thickness is registered as obvious square increments ( rather than a smooth increase in weight )
so i changed the ctx.lineJoin and ctx.lineCap to "round" and it got a little better
but this is still not as smooth as i'd like. i'm shooting for something like this
any advice on how to make the change in weight a bit smoother would be great! here's a working demo: http://jsfiddle.net/0fhag522/1/
and here' a preview of my "dot" object ( the pen ) and my draw function:
var dot = {
start: false,
weight: 1,
open: function(x,y){
ctx.lineJoin = "round";
ctx.lineCap = "round";
ctx.beginPath();
ctx.moveTo(x,y);
},
connect: function(x,y){
ctx.lineWidth = this.weight;
ctx.lineTo(x,y);
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.moveTo(x,y);
},
close: function(){
ctx.closePath();
}
}
function draw(){
if(down){
if(!dot.start){
dot.close();
prevx = mx; prevy = my;
dot.open(mx,my);
dot.start=true;
}
else {
var dx = (prevx>mx) ? prevx-mx : mx-prevx;
var dy = (prevy>my) ? prevy-my : my-prevy;
dot.weight = Math.abs(dx-dy)/2;
dot.connect( mx,my );
prevx = mx; prevy = my;
}
}
}
Here is a simple function to create growing lines with a round line cap:
/*
* this function returns a Path2D object
* the path represents a growing line between two given points
*/
function createGrowingLine (x1, y1, x2, y2, startWidth, endWidth) {
// calculate direction vector of point 1 and 2
const directionVectorX = x2 - x1,
directionVectorY = y2 - y1;
// calculate angle of perpendicular vector
const perpendicularVectorAngle = Math.atan2(directionVectorY, directionVectorX) + Math.PI/2;
// construct shape
const path = new Path2D();
path.arc(x1, y1, startWidth/2, perpendicularVectorAngle, perpendicularVectorAngle + Math.PI);
path.arc(x2, y2, endWidth/2, perpendicularVectorAngle + Math.PI, perpendicularVectorAngle);
path.closePath();
return path;
}
const ctx = myCanvas.getContext('2d');
// create a growing line between P1(10, 10) and P2(250, 100)
// with a start line width of 10 and an end line width of 50
let line1 = createGrowingLine(10, 10, 250, 100, 10, 50);
ctx.fillStyle = 'green';
// draw growing line
ctx.fill(line1);
<canvas width="300" height="150" id="myCanvas"></canvas>
Explanation:
The function createGrowingLine constructs a shape between two given points by:
calculating the direction vector of the two points
calculating the angle in radians of the perpendicular vector
creating a semi circle path from the calculated angle to the calculated angle + 180 degree with the center and radius of the start point
creating another semi circle path from the calculated angle + 180 degree to the calculated angle with the center and radius of the end point
closing the path by connecting the start point of the first circle with the end point of the second circle
In case you do not want to have the rounded line cap use the following function:
/*
* this function returns a Path2D object
* the path represents a growing line between two given points
*/
function createGrowingLine (x1, y1, x2, y2, startWidth, endWidth) {
const startRadius = startWidth/2;
const endRadius = endWidth/2;
// calculate direction vector of point 1 and 2
let directionVectorX = x2 - x1,
directionVectorY = y2 - y1;
// calculate vector length
const directionVectorLength = Math.hypot(directionVectorX, directionVectorY);
// normalize direction vector (and therefore also the perpendicular vector)
directionVectorX = 1/directionVectorLength * directionVectorX;
directionVectorY = 1/directionVectorLength * directionVectorY;
// construct perpendicular vector
const perpendicularVectorX = -directionVectorY,
perpendicularVectorY = directionVectorX;
// construct shape
const path = new Path2D();
path.moveTo(x1 + perpendicularVectorX * startRadius, y1 + perpendicularVectorY * startRadius);
path.lineTo(x1 - perpendicularVectorX * startRadius, y1 - perpendicularVectorY * startRadius);
path.lineTo(x2 - perpendicularVectorX * endRadius, y2 - perpendicularVectorY * endRadius);
path.lineTo(x2 + perpendicularVectorX * endRadius, y2 + perpendicularVectorY * endRadius);
path.closePath();
return path;
}
const ctx = myCanvas.getContext('2d');
// create a growing line between P1(10, 10) and P2(250, 100)
// with a start line width of 10 and an end line width of 50
let line1 = createGrowingLine(10, 10, 250, 100, 10, 50);
ctx.fillStyle = 'green';
// draw growing line
ctx.fill(line1);
<canvas width="300" height="150" id="myCanvas"></canvas>
Since canvas does not have a variable width line you must draw closed paths between your line points.
However, this leaves a visible butt-joint.
To smooth the butt-joint, you can draw a circle at each joint.
Here is example code and a Demo:
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var cw = canvas.width;
var ch = canvas.height;
var $canvas = $("#canvas");
var canvasOffset = $canvas.offset();
var offsetX = canvasOffset.left;
var offsetY = canvasOffset.top;
var scrollX = $canvas.scrollLeft();
var scrollY = $canvas.scrollTop();
var isDown = false;
var startX;
var startY;
var PI = Math.PI;
var halfPI = PI / 2;
var points = [];
$("#canvas").mousedown(function(e) {
handleMouseDown(e);
});
function handleMouseDown(e) {
e.preventDefault();
e.stopPropagation();
mx = parseInt(e.clientX - offsetX);
my = parseInt(e.clientY - offsetY);
var pointsLength = points.length;
if (pointsLength == 0) {
points.push({
x: mx,
y: my,
width: Math.random() * 5 + 2
});
} else {
var p0 = points[pointsLength - 1];
var p1 = {
x: mx,
y: my,
width: Math.random() * 5 + 2
};
addAngle(p0, p1);
p0.angle = p1.angle;
addEndcap(p0);
addEndcap(p1);
points.push(p1);
extendLine(p0, p1);
}
}
function addAngle(p0, p1) {
var dx = p1.x - p0.x;
var dy = p1.y - p0.y;
p1.angle = Math.atan2(dy, dx);
}
function addEndcap(p) {
p.x0 = p.x + p.width * Math.cos(p.angle - halfPI);
p.y0 = p.y + p.width * Math.sin(p.angle - halfPI);
p.x1 = p.x + p.width * Math.cos(p.angle + halfPI);
p.y1 = p.y + p.width * Math.sin(p.angle + halfPI);
}
function extendLine(p0, p1) {
ctx.beginPath();
ctx.moveTo(p0.x0, p0.y0);
ctx.lineTo(p0.x1, p0.y1);
ctx.lineTo(p1.x1, p1.y1);
ctx.lineTo(p1.x0, p1.y0);
ctx.closePath();
ctx.fillStyle = 'blue';
ctx.fill();
// draw a circle to cover the butt-joint
ctx.beginPath();
ctx.moveTo(p1.x, p1.y);
ctx.arc(p1.x, p1.y, p1.width, 0, Math.PI * 2);
ctx.closePath();
ctx.fill();
}
body{ background-color: ivory; }
#canvas{border:1px solid red;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<h4>Click to add line segments.</h4>
<canvas id="canvas" width=300 height=300></canvas>

Categories