To get straight to the point, what I hope to achieve is to be able to create a connecting line between two elements with this shape:
DBDIAGRAM.IO
When the elements move, the line resets but always maintains a 90 degree angle, instead of being a straight or diagonal line between [x,y] to [x,y].
Is there some kind of algorithm for this? Maybe a grid with some kind of A* implementation?
I don't know how to make rounded corners easy, but the easiest example will be this:
const canvas = document.querySelector('canvas')
const ctx = canvas.getContext('2d')
// define the points
const p1 = {
x: 30,
y: 50
}
const p2 = {
x: 150,
y: 130
}
ctx.strokeStyle = 'red'
// draw the points
ctx.beginPath()
ctx.arc(p1.x, p1.y, 5, 0, Math.PI * 2)
ctx.stroke()
ctx.beginPath()
ctx.arc(p2.x, p2.y, 5, 0, Math.PI * 2)
ctx.stroke()
// get distance between
const horizontalDistance = p2.x - p1.x
ctx.strokeStyle = 'black'
// draw left part
ctx.beginPath()
ctx.moveTo(p1.x, p1.y)
ctx.lineTo(p1.x + horizontalDistance / 2, p1.y)
ctx.stroke()
// draw vertical part
ctx.beginPath()
ctx.moveTo(p1.x + horizontalDistance / 2, p1.y)
ctx.lineTo(p1.x + horizontalDistance / 2, p2.y)
ctx.stroke()
// draw right part
ctx.beginPath()
ctx.moveTo(p1.x + horizontalDistance / 2, p2.y)
ctx.lineTo(p2.x, p2.y)
ctx.stroke()
canvas {
border: 1px solid black;
}
<canvas></canvas>
Real-time version:
const canvas = document.querySelector('canvas')
const ctx = canvas.getContext('2d')
const p1 = {
x: canvas.width / 2,
y: canvas.height / 2
}
const p2 = {
x: 150,
y: 130
}
canvas.addEventListener('mousemove', e => {
const mousePos = getMousePos(canvas, e)
p2.x = mousePos.x
p2.y = mousePos.y
})
loop()
function loop() {
draw()
requestAnimationFrame(loop)
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height)
ctx.strokeStyle = 'red'
ctx.beginPath()
ctx.arc(p1.x, p1.y, 5, 0, Math.PI * 2)
ctx.stroke()
ctx.beginPath()
ctx.arc(p2.x, p2.y, 5, 0, Math.PI * 2)
ctx.stroke()
const horizontalDistance = p2.x - p1.x
ctx.strokeStyle = 'black'
ctx.beginPath()
ctx.moveTo(p1.x, p1.y)
ctx.lineTo(p1.x + horizontalDistance / 2, p1.y)
ctx.stroke()
ctx.beginPath()
ctx.moveTo(p1.x + horizontalDistance / 2, p1.y)
ctx.lineTo(p1.x + horizontalDistance / 2, p2.y)
ctx.stroke()
ctx.beginPath()
ctx.moveTo(p1.x + horizontalDistance / 2, p2.y)
ctx.lineTo(p2.x, p2.y)
ctx.stroke()
}
function getMousePos(canvas, evt) {
const rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
canvas {
border: 1px solid black;
}
<canvas></canvas>
I have two arcs with strokes and i want to have a line animate between them. The line should animate perpendicular to the points of the inner circle.
Here's something that I hacked together that is almost what I want.
things that are wrong with it are:
the length of the line is not the length between the 2 circle as it revolves around the inner circle.
sometimes the line is not perpendicular to the points of the inner circle. for example when it goes to the corner of the circle it tilts at an angle a little.
I had problem understanding how to use the trig function for lineTo. maybe because that changes the length of the line and I didn't know how to get the x and y coordinates of the outer circle to get the end point of the line. I'm used to doing math.cos * length and this gives me a line at an angle. I don't what a line I just wanted the coords of the outer circle.
window.onload = function(){
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
function lineAtAngle(startX, startY, angleDeg, length, startX2, startY2, angleDeg2, length2){
var angle = angleDeg * (Math.PI / 180);
var angle2 = angleDeg2 * (Math.PI / 180);
// context.moveTo(startX, startY);
context.beginPath();
context.moveTo(
Math.cos(angle) * length + startX,
Math.sin(angle) * length + startY
)
context.lineTo(
Math.cos(angle2) *(length2 )+ startX2,
Math.sin(angle2) *(length2) + startY2
)
// context.lineTo(canvas.width / 2 + 60, canvas.height / 2, angle2, length2)
context.lineWidth = 10;
context.stroke();
context.closePath();
console.log("startX2: " + startX2 + " startY2: " + startY2 )
console.log(Math.sin(angle2) + startY2)
console.log(length)
}
function myLineTo(startX, startY, angleDeg, length){
}
var length1 = canvas.width / 2 + 60 - canvas.width / 2 -30
var length2 = canvas.width / 2 ;
// var length2 = 1;
console.log(length2)
var angle1 = 0;
var angle2 = 0;
(function animate(){
context.clearRect(0,0, canvas.width, canvas.height);
window.requestAnimationFrame(animate);
context.beginPath()
context.arc(canvas.width / 2, canvas.height / 2, 30, 0, 2 * Math.PI, true)
context.lineWidth = 1;
context.stroke()
context.beginPath();
context.arc(canvas.width / 2, canvas.height / 2, 60, 0, 2 * Math.PI, true);
context.stroke();
context.closePath()
context.beginPath();
context.arc(canvas.width / 2, canvas.height / 2, 3, 0, 2 * Math.PI, true)
context.fill()
context.closePath();
angle1++
angle2++
// lineAtAngle(canvas.width / 2 , canvas.height / 2 , angle1, length1, canvas.width / 2 + 60, canvas.height / 2, angle2, length2 )
lineAtAngle(canvas.width / 2 , canvas.height / 2 , angle1, length1, canvas.width / 2 + 60, canvas.height / 2, angle2, length2 )
}())
}
canvas{
background: #aaa;
}
<canvas id="canvas" width="400" height="400"></canvas>
I believe the below is what you're trying to achieve.
I've simplified the code somewhat by using the Canvas's built-in methods to translate the coordinate space to the center and by removing the extra function which had way too many parameters passed when all it needed was one angle and two radii.
window.onload = function(){
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
var radius1 = 30;
var radius2 = 60;
var angle = 0;
(function animate(){
context.clearRect(0, 0, canvas.width, canvas.height);
// save state and adjust coordinate space
context.save();
context.translate(canvas.width / 2, canvas.height / 2);
context.lineWidth = 1;
context.beginPath()
context.arc(0, 0, radius1, 0, 2 * Math.PI, true)
context.stroke()
context.beginPath();
context.arc(0, 0, radius2, 0, 2 * Math.PI, true);
context.stroke();
context.beginPath();
context.arc(0, 0, 3, 0, 2 * Math.PI, true);
context.fill()
++angle;
var rads = angle * Math.PI / 180;
var x = Math.cos(rads);
var y = Math.sin(rads);
context.lineWidth = 10;
context.beginPath();
context.moveTo(radius1 * x, radius1 * y);
context.lineTo(radius2 * x, radius2 * y);
context.stroke();
// restore transformations for next pass
context.restore();
window.requestAnimationFrame(animate)
}())
}
canvas { background: #aaa; }
<canvas id="canvas" width="400" height="200"></canvas>
The idea for drawing a line is that you need to supply a start point (moveTo), and an end point (lineTo). Your current code is complicating the whole thing with multiple angles and lengths. What you want to imagine you are doing is starting at the center of your circle, then adding an offset which places the start point at the edge of the inner circle. Using trig is perfectly fine here. The length of your line will simply be outer radius minus inner radius, in the same direction as your offset (only one angle was needed).
Without fundamentally changing your approach, the code below shows the changes you could use to try this. Have your line function take a starting point (x, y) and an offset, as well as the angle and length. The starting point is your circle center, and the offset is the radius of the inner circle. The length (again) is simply the outer radius minus the inner radius.
window.onload = function(){
var innerCircleRadius = 30;
var outerCircleRadius = 60;
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
var angle1 = 0;
function lineAtAngle(startX, startY, angleDeg, offset, length) {
var angle = angleDeg * (Math.PI / 180); // Convert to radians.
var cosAngle = Math.cos(angle); // Only need cos(angle) once.
var sinAngle = Math.sin(angle); // Only need sin(angle) once.
var startXPos = cosAngle * offset + startX;
var startYPos = sinAngle * offset + startY;
var endXPos = cosAngle * length + startXPos;
var endYPos = sinAngle * length + startYPos;
context.beginPath();
context.moveTo(startXPos, startYPos);
context.lineTo(endXPos, endYPos);
context.lineWidth = 10;
context.stroke();
context.closePath();
}
(function animate() {
context.clearRect(0,0, canvas.width, canvas.height);
window.requestAnimationFrame(animate);
context.beginPath()
context.arc(canvas.width / 2, canvas.height / 2, innerCircleRadius, 0, 2 * Math.PI, true)
context.lineWidth = 1;
context.stroke()
context.beginPath();
context.arc(canvas.width / 2, canvas.height / 2, outerCircleRadius, 0, 2 * Math.PI, true);
context.stroke();
context.closePath()
context.beginPath();
context.arc(canvas.width / 2, canvas.height / 2, 3, 0, 2 * Math.PI, true)
context.fill()
context.closePath();
angle1++
lineAtAngle(canvas.width / 2 , canvas.height / 2 , angle1, innerCircleRadius, outerCircleRadius - innerCircleRadius);
}())
}
canvas {
background: #aaa;
}
<canvas id="canvas" width="400" height="400"></canvas>
I'm trying to bisect a circle with JavaScript and a <canvas> element. I used the formula given in the accepted answer to this question to find points on the edge of the circle, but for some reason when I give two opposite points on the circle (0 and 180, or 90 and 270, for example) I'm not getting a line that goes through the center of the circle.
My code, which you can see on JSFiddle, makes a nice Spirograph pattern, which is cool except that that's not what I'm trying to do.
How do I fix this so the lines go through the center?
(Ultimately I'm trying to draw a circle of fifths, but all I'm asking how to do now is get the lines to go through the center. Once that works I'll get on with the other steps to do the circle of fifths, which will obviously include drawing fewer lines and losing the Spirograph torus.)
Degrees in Javascript are specified in radians. Instead of checking for greater than or less than 180, and adding or subtracting 180, do the same with Math.PI radians.
http://jsfiddle.net/7w29h/1/
Drawing function and trigonometry function in Math expects angle to be specified in radian, not degree.
Demo
Diff with your current code:
function bisect(context, degrees, radius, cx, cy) {
// calculate the point on the edge of the circle
var x1 = cx + radius * Math.cos(degrees / 180 * Math.PI);
var y1 = cy + radius * Math.sin(degrees / 180 * Math.PI);
/* Trimmed */
// and calculate the point on the opposite side
var x2 = cx + radius * Math.cos(degrees2 / 180 * Math.PI);
var y2 = cy + radius * Math.sin(degrees2 / 180 * Math.PI);
/* Trimmed */
}
function draw(theCanvas) {
/* Trimmed */
// 2 * PI, which is 360 degree
context.arc(250, 250, 220, 0, Math.PI * 2, false);
/* Trimmed */
context.arc(250, 250, 110, 0, Math.PI * 2, false);
/* Trimmed */
// No need to go up to 360 degree, unless the increment does
// not divides 180
for (j = 2; j < 180; j = j + 3) {
bisect(context, j, 220, 250, 250);
}
/* Trimmed */
}
Appendix
This is the full source code from JSFiddle, keep the full copy here just in case.
HTML
<canvas id="the_canvas" width="500" height="500"></canvas>
CSS
canvas {
border:1px solid black;
}
JavaScript
function bisect(context, degrees, radius, cx, cy) {
// calculate the point on the edge of the circle
var x1 = cx + radius * Math.cos(degrees / 180 * Math.PI);
var y1 = cy + radius * Math.sin(degrees / 180 * Math.PI);
// get the point on the opposite side of the circle
// e.g. if 90, get 270, and vice versa
// (super verbose but easily readable)
if (degrees > 180) {
var degrees2 = degrees - 180;
} else {
var degrees2 = degrees + 180;
}
// and calculate the point on the opposite side
var x2 = cx + radius * Math.cos(degrees2 / 180 * Math.PI);
var y2 = cy + radius * Math.sin(degrees2 / 180 * Math.PI);
// now actually draw the line
context.beginPath();
context.moveTo(x1, y1)
context.lineTo(x2, y2)
context.stroke();
}
function draw(theCanvas) {
var context = theCanvas.getContext('2d');
// draw the big outer circle
context.beginPath();
context.strokeStyle = "#222222";
context.lineWidth = 2;
context.arc(250, 250, 220, 0, Math.PI * 2, false);
context.stroke();
context.closePath();
// smaller inner circle
context.beginPath();
context.strokeStyle = "#222222";
context.lineWidth = 1;
context.arc(250, 250, 110, 0, Math.PI * 2, false);
context.stroke();
context.closePath();
for (j=2; j < 180; j = j + 3) {
bisect(context, j, 220, 250, 250);
}
}
$(function () {
var theCanvas = document.getElementById('the_canvas');
console.log(theCanvas);
draw(theCanvas, 50, 0, 270);
});
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>