I\'m using the wPaint plugin and I am attempting to add a few more features. What I need is a drawn line to end with an \"arrowhead\". I have tried just about everything I c
As addition to markE's answer combined with user1707810 comment:
Both blocks of (start/end radians):
- ((this.x2 > this.x1)?-90:90)*Math.PI/180;
should be changed to :
- ((this.x2 >= this.x1)?-90:90)*Math.PI/180;
This is how to create a line object that draws arrowheads on both ends
The interesting part is calculating the angle of the arrowheads like this:
var startRadians=Math.atan((this.y2-this.y1)/(this.x2-this.x1));
startRadians+=((this.x2>=this.x1)?-90:90)*Math.PI/180;
var endRadians=Math.atan((this.y2-this.y1)/(this.x2-this.x1));
endRadians+=((this.x2>=this.x1)?90:-90)*Math.PI/180;
The rest is just drawing the line and 2 triangles for arrowheads the calculated rotations
Line.prototype.drawArrowhead=function(ctx,x,y,radians){
ctx.save();
ctx.beginPath();
ctx.translate(x,y);
ctx.rotate(radians);
ctx.moveTo(0,0);
ctx.lineTo(5,20);
ctx.lineTo(-5,20);
ctx.closePath();
ctx.restore();
ctx.fill();
}
Here is code and a Fiddle: http://jsfiddle.net/m1erickson/Sg7EZ/
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
body{ background-color: ivory; }
canvas{border:1px solid red;}
</style>
<script>
$(function(){
var canvas=document.getElementById("canvas");
var context=canvas.getContext("2d");
function Line(x1,y1,x2,y2){
this.x1=x1;
this.y1=y1;
this.x2=x2;
this.y2=y2;
}
Line.prototype.drawWithArrowheads=function(ctx){
// arbitrary styling
ctx.strokeStyle="blue";
ctx.fillStyle="blue";
ctx.lineWidth=1;
// draw the line
ctx.beginPath();
ctx.moveTo(this.x1,this.y1);
ctx.lineTo(this.x2,this.y2);
ctx.stroke();
// draw the starting arrowhead
var startRadians=Math.atan((this.y2-this.y1)/(this.x2-this.x1));
startRadians+=((this.x2>this.x1)?-90:90)*Math.PI/180;
this.drawArrowhead(ctx,this.x1,this.y1,startRadians);
// draw the ending arrowhead
var endRadians=Math.atan((this.y2-this.y1)/(this.x2-this.x1));
endRadians+=((this.x2>this.x1)?90:-90)*Math.PI/180;
this.drawArrowhead(ctx,this.x2,this.y2,endRadians);
}
Line.prototype.drawArrowhead=function(ctx,x,y,radians){
ctx.save();
ctx.beginPath();
ctx.translate(x,y);
ctx.rotate(radians);
ctx.moveTo(0,0);
ctx.lineTo(5,20);
ctx.lineTo(-5,20);
ctx.closePath();
ctx.restore();
ctx.fill();
}
// create a new line object
var line=new Line(50,50,150,150);
// draw the line
line.drawWithArrowheads(context);
}); // end $(function(){});
</script>
</head>
<body>
<canvas id="canvas" width=300 height=300></canvas>
</body>
</html>
This goes wrong for vertical lines. Try
var line=new Line(50,50,50,275)
Simpler version
key difference. Using Math.atan2
removes the need for if
This one also puts the arrowheads at the ends of the line rather than past the end of the line
In other words
this
start end
|<------->|
vs this
<|---------|>
function arrow(ctx, x1, y1, x2, y2, start, end) {
var rot = -Math.atan2(x1 - x2, y1 - y2);
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
if (start) {
arrowHead(x1, y1, rot);
}
if (end) {
arrowHead(x2, y2, rot + Math.PI);
}
}
function arrowHead(x, y, rot) {
ctx.save();
ctx.translate(x, y);
ctx.rotate(rot);
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(-5, -12);
ctx.lineTo(5, -12);
ctx.closePath();
ctx.fill();
ctx.restore();
}
// test it -------
var ctx = document.createElement("canvas").getContext("2d");
document.body.appendChild(ctx.canvas);
// draw some arrows
ctx.save();
ctx.translate(ctx.canvas.width / 2, ctx.canvas.height / 2);
for (var ii = 0; ii <= 12; ++ii) {
var u = ii / 12;
var color = hsl(u * 360, 1, 0.5);
ctx.fillStyle = color;
ctx.strokeStyle = color;
var a = u * Math.PI;
var x = Math.cos(a) * 120;
var y = Math.sin(a) * 70;
arrow(ctx, -x, -y, x, y, true, true);
// draw the end points to see the arrowheads match
ctx.fillStyle = "#000";
ctx.fillRect(-x - 1, -y - 1, 3, 3);
ctx.fillRect( x - 1, y - 1, 3, 3);
}
ctx.restore();
function hsl(h, s, l) {
return `hsl(${h},${s * 100}%,${l * 100}%)`;
}
canvas { border: 1px solid black; }
One problem with the above solution is if you thicken the line stroke it would poke through the arrowhead. It's not hard to fix but you'd have to compute the length of the line in pixels, then subtract the size of the arrowhead from both sides.
something like this
function arrow(ctx, x1, y1, x2, y2, start, end) {
var dx = x2 - x1;
var dy = y2 - y1;
var rot = -Math.atan2(dx, dy);
var len = Math.sqrt(dx * dx + dy * dy);
var arrowHeadLen = 10;
ctx.save();
ctx.translate(x1, y1);
ctx.rotate(rot);
ctx.beginPath();
ctx.moveTo(0, start ? arrowHeadLen : 0);
ctx.lineTo(0, len - (end ? arrowHeadLen : 0));
ctx.stroke();
if (end) {
ctx.save();
ctx.translate(0, len);
arrowHead(ctx);
ctx.restore();
}
if (start) {
ctx.rotate(Math.PI);
arrowHead(ctx);
}
ctx.restore();
}
function arrowHead(ctx) {
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(-5, -12);
ctx.lineTo(5, -12);
ctx.closePath();
ctx.fill();
}
// test it -------
var ctx = document.createElement("canvas").getContext("2d");
document.body.appendChild(ctx.canvas);
// draw some arrows
ctx.save();
ctx.translate(ctx.canvas.width / 2, ctx.canvas.height / 2);
for (var ii = 0; ii < 12; ++ii) {
var u = ii / 12;
var color = hsl(u * 360, 1, 0.5);
ctx.fillStyle = color;
ctx.strokeStyle = color;
var a = u * Math.PI;
var x = Math.cos(a) * 120;
var y = Math.sin(a) * 70;
arrow(ctx, -x, -y, x, y, true, true);
ctx.fillStyle = "#000"; // mark the ends so we can see where they are
ctx.fillRect(-x - 1, -y - 1, 3, 3);
ctx.fillRect( x - 1, y - 1, 3, 3);
}
ctx.restore();
function hsl(h, s, l) {
return `hsl(${h},${s * 100}%,${l * 100}%)`;
}
canvas { border: 1px solid black; }
In other words the first solution draws arrows like this
Where as the second solution draws arrows like this
my simple solution
ctx.beginPath();
ctx.moveTo(ax,ay);
ctx.lineTo(bx,by);
ctx.stroke();
ctx.closePath();
angle=Math.PI+Math.atan2(by-ay,bx-ax);
angle1=angle+Math.PI/6;
angle2=angle-Math.PI/6;
ctx.beginPath();
ctx.moveTo(bx,by);
ctx.arc(bx,by,5,angle1,angle2,true);
ctx.lineTo(bx,by);
ctx.fill();
ctx.closePath();