I order to build a HTML 5 datacenter floor plan, I would like to create a polygon filled with a grid. This grid must not be a picture pattern as I would like to be able to zoom or rotate the floor plan without having pixelization.
I would like to be able to create this kind of output :
How can I do that ?
There are multiple ways, like
using a clipping region
var ctx = c.getContext('2d');
drawShape();
ctx.stroke();
ctx.save(); // so we can remove the clipping
ctx.clip();
drawGrid();
ctx.restore(); // remove the clipping
function drawShape() {
ctx.beginPath();
var pts = [
20, 20,
80, 20,
90, 50,
120, 90,
30, 80,
20,20
];
for(var i=0;i<pts.length;i+=2){
ctx.lineTo(pts[i], pts[i+1]);
}
}
function drawGrid() {
ctx.beginPath();
for(var x=-.5; x<c.width; x+=20) {
ctx.moveTo(x, 0);
ctx.lineTo(x, c.height);
}
for(var y=-.5; y<c.height; y+=20) {
ctx.moveTo(0, y);
ctx.lineTo(c.width, y);
}
ctx.stroke();
}
<canvas id="c"></canvas>
using compositing
var ctx = c.getContext('2d');
drawGrid();
ctx.globalCompositeOperation = 'destination-in';
drawShape();
ctx.fill();
ctx.globalCompositeOperation = 'source-over';
ctx.stroke();
function drawShape() {
ctx.beginPath();
var pts = [
20, 20,
80, 20,
90, 50,
120, 90,
30, 80,
20,20
];
for(var i=0;i<pts.length;i+=2){
ctx.lineTo(pts[i], pts[i+1]);
}
}
function drawGrid() {
ctx.beginPath();
for(var x=-.5; x<c.width; x+=20) {
ctx.moveTo(x, 0);
ctx.lineTo(x, c.height);
}
for(var y=-.5; y<c.height; y+=20) {
ctx.moveTo(0, y);
ctx.lineTo(c.width, y);
}
ctx.stroke();
}
<canvas id="c"></canvas>
But in your case, a regular grid, it might actually be better to use a pattern.
Indeed, you'd have to only draw one cell every time you change the scale of your grid, for translations, this can be done internally.
So I didn't do the performance tests myself, and thus encourage you to double check it's worth it, but theoretically, it might be faster and esaier to manage than redrawing the grid every time.
var ctx = c.getContext('2d');
var pat_ctx = document.createElement('canvas').getContext('2d');
var cell_size = 20;
// just a basic drawing example
// first we generate the grid as a pattern
ctx.fillStyle = generatePattern(cell_size, cell_size);
drawShape();
ctx.stroke();
// we move the pattern by half a cell because we actually drawn only a cross
ctx.translate(-cell_size / 2, -cell_size / 2);
ctx.fill();
// make the grid follow the mouse
// without having to redraw ourself the grid
onmousemove = function(e) {
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, c.width, c.height);
drawShape();
ctx.stroke();
// move the grid
ctx.translate(e.clientX - cell_size / 2, e.clientY - -cell_size / 2);
ctx.fill();
}
// click to zoom (+shift to zoom out)
onclick = function(e) {
if (e.shiftKey) cell_size--;
else cell_size++;
ctx.fillStyle = generatePattern(cell_size, cell_size);
onmousemove(e);
}
// dimply draws a cross
function generatePattern(w, h) {
var canvas = pat_ctx.canvas;
canvas.width = w;
canvas.height = h;
pat_ctx.moveTo(w / 2, 0);
pat_ctx.lineTo(w / 2, h);
pat_ctx.moveTo(0, h / 2);
pat_ctx.lineTo(w, h / 2);
pat_ctx.stroke();
return pat_ctx.createPattern(canvas, 'repeat');
}
function drawShape() {
ctx.beginPath();
var pts = [
20, 20,
80, 20,
90, 50,
120, 90,
30, 80,
20, 20
];
for (var i = 0; i < pts.length; i += 2) {
ctx.lineTo(pts[i], pts[i + 1]);
}
}
<canvas id="c"></canvas>
Related
I am trying to make various shapes have a pulse like effect in canvas and managed to do it with a circle,
function drawCircle() {
// color in the background
context.fillStyle = "#EEEEEE";
context.fillRect(0, 0, canvas.width, canvas.height);
// draw the circle
context.beginPath();
var radius = 25 + 20 * Math.abs(Math.cos(angle)); //radius of circle
context.arc(25, 25, radius, 0, Math.PI * 2, false); //position on canvas
context.closePath();
// color in the circle
context.fillStyle = "#006699";
context.fill();
//'pulse'
angle += Math.PI / 220;
requestAnimationFrame(drawCircle);
}
drawCircle();
but I'm not sure how to go about doing any other shape. What I have so far for my triangle is
function drawTriangle() {
// draw the triangle
context.beginPath();
context.moveTo(75, 50);
context.lineTo(100, 75);
context.lineTo(100, 25);
context.fill();
context.rect(215, 100, Math.PI * 2, false); //position on canvas
context.closePath();
// color in the triangle
context.fillStyle = "#3f007f";
context.fill();
//'pulse'
angle += Math.PI / 280;
requestAnimationFrame(drawTriangle);
}
drawTriangle();
Any insight would be appreciated.
This can be simply achieved by changing the scale of the context matrix.
All you need to find is the position of the scaling anchor of your shape so that you can translate the matrix to the correct position after the scale has been applied.
In following example, I'll use the center of the shape as scaling anchor, since it seems it is what you wanted.
The extended version of the matrix transformations would be
ctx.translate(anchorX, anchorY);
ctx.scale(scaleFactor, scaleFactor);
ctx.translate(-anchorX, -anchorY);
which in below example has been reduced to
ctx.setTransform(
scale, 0, 0,
scale, anchorX - (anchorX * scale), anchorY - (anchorY * scale)
);
var ctx = canvas.getContext('2d');
var angle = 0;
var scale = 1;
var img = new Image();
img.src = 'https://dl.dropboxusercontent.com/s/4e90e48s5vtmfbd/aaa.png';
anim();
function anim() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
updateScale();
drawCircle();
drawTriangle();
drawImage();
ctx.setTransform(1, 0, 0, 1, 0, 0);
requestAnimationFrame(anim);
}
function updateScale() {
angle += Math.PI / 220;
scale = 0.5 + Math.abs(Math.cos(angle));
}
function drawCircle() {
ctx.beginPath();
var cx = 75,
cy = 50,
radius = 25;
// for the circle, centerX and centerY are given
var anchorX = cx,
anchorY = cy;
// with these anchorX, anchorY and scale,
// we can determine where we need to translate our context once scaled
var scaledX = anchorX - (anchorX * scale),
scaledY = anchorY - (anchorY * scale);
// then we apply the matrix in one go
ctx.setTransform(scale, 0, 0, scale, scaledX, scaledY);
// and we draw normally
ctx.arc(cx, cy, radius, 0, Math.PI * 2);
ctx.fill();
}
function drawTriangle() {
ctx.beginPath();
// for the triangle, we need to find the position between minX and maxX,
// and between minY and maxY
var anchorX = 175 + (200 - 175) / 2,
anchorY = 25 + (75 - 25) / 2;
var scaledX = anchorX - (anchorX * scale),
scaledY = anchorY - (anchorY * scale);
ctx.setTransform(scale, 0, 0, scale, scaledX, scaledY);
ctx.moveTo(175, 50);
ctx.lineTo(200, 75);
ctx.lineTo(200, 25);
ctx.fill();
}
function drawImage() {
if (!img.naturalWidth) return;
// for rects, it's just pos + (length / 2)
var anchorX = 250 + img.naturalWidth / 2,
anchorY = 25 + img.naturalHeight / 2;
var scaledX = anchorX - (anchorX * scale),
scaledY = anchorY - (anchorY * scale);
ctx.setTransform(scale, 0, 0, scale, scaledX, scaledY);
ctx.drawImage(img, 250, 25);
}
<canvas id="canvas" width="500"></canvas>
Sorry I am new to Canvas and dont know how to google this out. Problem is that I cant draw on mask if previous layer (night sky) is present.
Here are the two snippets:
const canvas = document.querySelector('#board canvas');
const ctx = canvas.getContext('2d');
const { width: w, height: h } = canvas;
// first layer
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, w, h);
ctx.fillStyle = '#555';
let x, y, radius;
for (let i = 0; i < 550; i++) {
x = Math.random() * w;
y = Math.random() * h;
radius = Math.random() * 3;
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2, false);
ctx.fill();
}
// destination
ctx.font = 'bold 70pt monospace';
ctx.fillStyle = 'black';
ctx.fillText('FOO', 10, 60);
ctx.fillText('BAR', 10, 118);
ctx.fill();
// source
ctx.globalCompositeOperation = 'source-atop';
for (let i = 0; i < 6; i++) {
ctx.fillStyle = `hsl(${i * (250 / 6)}, 90%, 55%)`;
ctx.fillRect(0, i * 20, 200, 20);
}
<div id="board">
<canvas width="640" height="480"></canvas>
</div>
EXPECTED RESULT (but with the first layer - night sky):
const canvas = document.querySelector('#board canvas');
const ctx = canvas.getContext('2d');
const { width: w, height: h } = canvas;
// destination
ctx.font = 'bold 70pt monospace';
ctx.fillStyle = 'black';
ctx.fillText('FOO', 10, 60);
ctx.fillText('BAR', 10, 118);
ctx.fill();
// source
ctx.globalCompositeOperation = 'source-atop';
for (let i = 0; i < 6; i++) {
ctx.fillStyle = `hsl(${i * (250 / 6)}, 90%, 55%)`;
ctx.fillRect(0, i * 20, 200, 20);
}
<div id="board">
<canvas width="640" height="480"></canvas>
</div>
Compositing will affect the whole context.
source-atop mode will draw only where there were existing pixels (i.e only where alpha > 0).
When you draw your background, all the pixels of your context have alpha values set to 1.
This means that source-atop will not produce anything on your fully opaque image.
Once you understand these points, it's clear that you need to make your compositing alone.
It could be e.g on a different off-screen canvas that you would then draw back on the main canvas with ctx.drawImage(canvas, x, y).
const canvas = document.querySelector('#board canvas');
const ctx = canvas.getContext('2d');
const {
width: w,
height: h
} = canvas;
// background
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, w, h);
ctx.fillStyle = '#555';
let x, y, radius;
for (let i = 0; i < 550; i++) {
x = Math.random() * w;
y = Math.random() * h;
radius = Math.random() * 3;
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2, false);
ctx.fill();
}
// text compositing on an off-screen context
const ctx2 = Object.assign(document.createElement('canvas'), {
width: 200,
height: 120
}).getContext('2d');
// text
ctx2.font = 'bold 70pt monospace';
ctx2.fillStyle = 'black';
ctx2.fillText('FOO', 10, 60);
ctx2.fillText('BAR', 10, 118);
ctx2.globalCompositeOperation = 'source-atop';
// rainbow
for (let i = 0; i < 6; i++) {
ctx2.fillStyle = `hsl(${i * (250 / 6)}, 90%, 55%)`;
ctx2.fillRect(0, i * 20, 200, 20);
}
// now draw our off-screen canvas on the main one
ctx.drawImage(ctx2.canvas, 0, 0);
<div id="board">
<canvas width="640" height="480"></canvas>
</div>
Or, since this is the only compositing in your composition, you can also do it all on the same, but use an other compositing mode: destination-over.
This mode will draw behind the existing content, this means that you will have to actually draw your background after you made the compositing.
const canvas = document.querySelector('#board canvas');
const ctx = canvas.getContext('2d');
const {
width: w,
height: h
} = canvas;
//
// text compositing on a clear context
drawText();
// will draw only where the text has been drawn
ctx.globalCompositeOperation = 'source-atop';
drawRainbow();
// from here we will draw behind
ctx.globalCompositeOperation = 'destination-over';
// so we need to first draw the stars, otherwise they'll be behind
drawStars();
//And finally the sky black background
drawSky();
//... reset
ctx.globalCompositeOperation = 'source-over';
function drawSky() {
ctx.fillStyle = 'black';
ctx.fillRect(0, 0, w, h);
}
function drawStars() {
ctx.fillStyle = '#555';
let x, y, radius;
for (let i = 0; i < 550; i++) {
x = Math.random() * w;
y = Math.random() * h;
radius = Math.random() * 3;
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2, false);
ctx.fill();
}
}
function drawText() {
ctx.font = 'bold 70pt monospace';
ctx.fillStyle = 'black';
ctx.fillText('FOO', 10, 60);
ctx.fillText('BAR', 10, 118);
}
function drawRainbow() {
for (let i = 0; i < 6; i++) {
ctx.fillStyle = `hsl(${i * (250 / 6)}, 90%, 55%)`;
ctx.fillRect(0, i * 20, 200, 20);
}
}
<div id="board">
<canvas width="640" height="480"></canvas>
</div>
How can I draw a non-gradient circle with colorstop, something like this:
The closest I got was using radial gradient http://jsfiddle.net/8tdz0bo4/2/:
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var x = 100,
y = 75,
innerRadius = 1,
outerRadius = 50,
radius = 60;
var gradient = ctx.createRadialGradient(x, y, innerRadius, x, y, outerRadius);
gradient.addColorStop(0, 'red');
gradient.addColorStop(1, 'transparent');
ctx.arc(x, y, radius, 0, 2 * Math.PI);
ctx.fillStyle = gradient;
ctx.fill();
ctx.lineWidth = 2;
ctx.strokeStyle = 'black';
ctx.stroke();
Answer is quite simple : to avoid any gradient, just build several steps having same start and end color like in :
0.0 red // first red step
0.5 red // end of first red step
0.5 blue // second blue step
1.0 blue. // end of blue step
With this idea, your code becomes :
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var x = 100,
y = 75,
innerRadius = 1,
outerRadius = 50,
radius = 60;
var gradient = ctx.createRadialGradient(x, y, innerRadius, x, y, outerRadius);
gradient.addColorStop(0, 'red');
gradient.addColorStop(0.6, 'red');
gradient.addColorStop(0.6, 'transparent');
gradient.addColorStop(1, 'transparent');
ctx.arc(x, y, radius, 0, 2 * Math.PI);
ctx.fillStyle = gradient;
ctx.fill();
ctx.lineWidth = 2;
ctx.strokeStyle = 'black';
ctx.stroke();
<canvas id='canvas'></canvas>
Add these
gradient.addColorStop(0.2, 'red');
gradient.addColorStop(0.2, 'transparent');
http://jsfiddle.net/8tdz0bo4/3/
Is it possible to use the variable i of a for-loop (for(i=0; i<3; i++)) in wich the Kinetic.Shape is created to set the y-value in the custom drawFunc();
My code of the for-loop for the dynamic shape creation looks the following:
var layer = new Kinetic.Layer();
for (i = 0; i < 3; i++){
layer.add(new Kinetic.Shape({
x: 0,
y: 0,
width: 400,
height: 400,
drawFunc: function(canvas){
console.log(i); //THIS LOG ALWAYS OUTPUTS THE MAX i-VALUE (here 3)
var ctx = canvas.getContext();
ctx.beginPath();
ctx.fillStyle = 'black';
ctx.fillRect(10, i*30+2, 200, 30);
ctx.closePath();
ctx.fill();
}
}));
}
stage.add(layer);
If i log the i-value in the custom drawFunc(); the result is always 3instead of 0, 1, 2, and because of that the ctx.fillRect draws all three shapes at y = 92.
Here is a fiddle of my code with the explained behaviour.
Am I missing something obvious? Any help is greatly appreciated.
You can try something like this. By adding the layers using a function the value passed to the createLayer function is saved with each Kinetic.Shape object that's created through closures.
function createLayer(nbr){
layer.add(new Kinetic.Shape({
x: 0,
y: 0,
width: 400,
height: 400,
drawFunc: function(canvas){
console.log(nbr);
var ctx = canvas.getContext();
ctx.beginPath();
ctx.fillStyle = 'black';
ctx.fillRect(10, nbr*30+2, 200, 30);
ctx.closePath();
ctx.fill();
}
}));
}
for (var i = 0; i < 3; i++){
createLayer(i);
}
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 could think of, but I can only get half of the arrow ( imagine <-----, but the head only extends to the bottom or the top, but never both directions.)
Here is the function for drawing the line (with the half arrowhead):
drawArrowMove: function(e, _self)
{
var xo = _self.canvasTempLeftOriginal;
var yo = _self.canvasTempTopOriginal;
if(e.pageX < xo) { e.x = e.x + e.w; e.w = e.w * -1}
if(e.pageY < yo) { e.y = e.y + e.h; e.h = e.h * -1}
_self.ctxTemp.lineJoin = "round";
_self.ctxTemp.beginPath();
_self.ctxTemp.moveTo(e.x, e.y);
_self.ctxTemp.lineTo(e.x + e.w, e.y + e.h);
_self.ctxTemp.closePath();
_self.ctxTemp.moveTo(e.x, e.y);
_self.ctxTemp.lineTo(15,10);
_self.ctxTemp.stroke();
}
Any help/ideas/tips would be helpful.
Thanks.
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)
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;
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();