QML Canvas: different behaviour in rendering - javascript

I am trying to draw an annulus sector in QML using the Canvas object.
First, I have written the javascript code, and I have verified that it is correct by executing it in a browser.
Here it is:
var can = document.getElementById('myCanvas');
var ctx=can.getContext("2d");
var center = {
x: can.width / 2,
y: can.height / 2
};
var minRad = 100;
var maxRad = 250;
var startAngle = toRad(290);
var endAngle = toRad(310);
drawAxis();
drawSector();
function drawSector() {
var p1 = {
x: maxRad * Math.cos(startAngle),
y: maxRad * Math.sin(startAngle)
}
p1 = toCanvasSpace(p1);
var p2 = {
x: minRad * Math.cos(startAngle),
y: minRad * Math.sin(startAngle)
}
p2 = toCanvasSpace(p2);
var p3 = {
x: minRad * Math.cos(endAngle),
y: minRad * Math.sin(endAngle)
}
p3 = toCanvasSpace(p3);
var p4 = {
x: maxRad * Math.cos(endAngle),
y: maxRad * Math.sin(endAngle)
}
p4 = toCanvasSpace(p4);
ctx.beginPath();
ctx.moveTo(p1.x, p1.y);
ctx.arc(center.x, center.y, maxRad, startAngle, endAngle);
ctx.lineTo(p3.x, p3.y);
ctx.arc(center.x, center.y, minRad, endAngle, startAngle, true);
ctx.closePath();
ctx.strokeStyle = "blue";
ctx.lineWidth = 2;
ctx.stroke();
}
function drawAxis() {
ctx.beginPath();
ctx.moveTo(can.width / 2, 0);
ctx.lineTo(can.width / 2, can.height);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(0, can.height / 2);
ctx.lineTo(can.width, can.height / 2);
ctx.stroke();
}
function toRad(degrees) {
return degrees * Math.PI / 180;
}
function toCanvasSpace(p) {
var ret = {};
ret.x = p.x + can.width / 2;
ret.y = p.y + can.height / 2;
return ret;
}
Here you can run the code above.
The output is this:
Next, I moved the same code into a Canvas object in Qml.
See here the main.qml containing the Canvas:
import QtQuick 2.5
import QtQuick.Window 2.2
Window {
visible: true
width: 500
height: 500
x:500
Canvas
{
id: can
anchors.fill: parent
antialiasing: true
onPaint: {
var ctx=can.getContext("2d");
var center = {
x: can.width / 2,
y: can.height / 2
};
var minRad = 100;
var maxRad = 250;
var startAngle = toRad(290);
var endAngle = toRad(310);
drawAxis();
drawSector();
function drawSector() {
var p1 = {
x: maxRad * Math.cos(startAngle),
y: maxRad * Math.sin(startAngle)
}
p1=toCanvasSpace(p1);
var p2 = {
x: minRad * Math.cos(startAngle),
y: minRad * Math.sin(startAngle)
}
p2=toCanvasSpace(p2);
var p3 = {
x: minRad * Math.cos(endAngle),
y: minRad * Math.sin(endAngle)
}
p3=toCanvasSpace(p3);
var p4 = {
x: maxRad * Math.cos(endAngle),
y: maxRad * Math.sin(endAngle)
}
p4=toCanvasSpace(p4);
ctx.beginPath();
ctx.moveTo(p1.x, p1.y);
ctx.arc(center.x, center.y, maxRad, startAngle, endAngle);
ctx.lineTo(p3.x, p3.y);
ctx.arc(center.x, center.y, minRad, endAngle, startAngle, true);
ctx.closePath();
ctx.strokeStyle="blue";
ctx.lineWidth=2;
ctx.stroke();
}
function drawAxis() {
ctx.beginPath();
ctx.moveTo(can.width / 2, 0);
ctx.lineTo(can.width / 2, can.height);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(0, can.height / 2);
ctx.lineTo(can.width, can.height / 2);
ctx.stroke();
}
function toRad(degrees) {
return degrees * Math.PI / 180;
}
function toCanvasSpace(p) {
var ret = {};
ret.x = p.x + can.width / 2;
ret.y = p.y + can.height / 2;
return ret;
}
}
}
}
In this case I get this output:
As you can see there is an imperfection at the bottom.
I really don't understand why there is that imperfection; moreover I don't understand why the same code gives different output.
Any help is appreciated!
Thanks

The lineTo p3 is not needed, because when an arc segment is drawn, the connecting line is drawn automatically, according to the Canvas specifications:
The arc() method is equivalent to the ellipse() method in the case
where the two radii are equal. [...]
When the ellipse() method is invoked, it must proceed as follows.
First, if the object's path has any subpaths, then the method must add
a straight line from the last point in the subpath to the start point
of the arc.
Also, the moveTo p1 is not needed, because it would be done as part of the first arc.
As to why the extra line is being drawn further than the start of the second arc, it could be a bug in Qt (maybe a division by 0 issue - just guessing here), or maybe you simply did not compute its position correctly.

Related

Algorithm for drawing lines with 90-degree angles

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>

How to rotate only a line around it's middle point?

I want to display a windmill on js canvas. For now, I display a green line on a cube. I have a problem with rotating the line around it's middle point. Also, I don't want my cube to move. Save() doesn't seem to work? I don't know what I'm doing wrong. I tried looking the answer online but they don't seem to work or I don't understand them. Elements in my canvas somehow disappear.
var x = 600;
function init()
{
window.requestAnimationFrame(draw);
}
function draw()
{
var ctx = document.getElementById('canvas').getContext('2d');
ctx.clearRect(0, 0, 600, 600);
// sciana przednia
ctx.lineWidth = 1;
ctx.strokeStyle = "#000000";
ctx.strokeRect(x/2,x/2,x/4,x/4);
//sciana gorna
ctx.beginPath();
ctx.lineWidth = 1;
ctx.strokeStyle = "#000000";
ctx.moveTo(x/2,x/2);
ctx.lineTo(x-x/3,x/4+x/6);
ctx.lineTo(x-x/8,x/4+x/6);
ctx.lineTo(x/2+x/4,x/2);
ctx.closePath();
ctx.stroke();
//sciana prawa
ctx.beginPath();
ctx.lineWidth = 1;
ctx.strokeStyle = "#000000";
ctx.moveTo(x/2+x/4,x/2+x/4);
ctx.lineTo(x-x/8,x/2+x/7);
ctx.lineTo(x-x/8,x/4+x/6);
ctx.stroke();
ctx.closePath();
//raczka
ctx.beginPath();
ctx.lineWidth = 5;
ctx.strokeStyle = "#808080";
ctx.moveTo(x/2+x/5,x/2-x/5);
ctx.lineTo(x/2+x/5,x/2-x/8+50);
ctx.stroke();
ctx.closePath();
ctx.save();
//smiglo
ctx.beginPath();
ctx.translate(x/2, x/2);
ctx.rotate( (Math.PI / 180) * 25);
ctx.translate(-x/2, -x/2);
ctx.fillStyle = "#00cc00";
ctx.fillRect(x/2+x/5-100,x/2-x/5,200,10);
ctx.closePath();
ctx.restore();
window.requestAnimationFrame(draw);
}
init();
var canvas = document.getElementById('canvas')
var ctx = canvas.getContext('2d');
var x = 600;
function init() {
window.requestAnimationFrame(draw);
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// sciana przednia
ctx.lineWidth = 1;
ctx.strokeStyle = "#000000";
ctx.strokeRect(x / 2, x / 2, x / 4, x / 4);
//sciana gorna
ctx.beginPath();
ctx.lineWidth = 1;
ctx.strokeStyle = "#000000";
ctx.moveTo(x / 2, x / 2);
ctx.lineTo(x - x / 3, x / 4 + x / 6);
ctx.lineTo(x - x / 8, x / 4 + x / 6);
ctx.lineTo(x / 2 + x / 4, x / 2);
ctx.closePath();
ctx.stroke();
//sciana prawa
ctx.beginPath();
ctx.lineWidth = 1;
ctx.strokeStyle = "#000000";
ctx.moveTo(x / 2 + x / 4, x / 2 + x / 4);
ctx.lineTo(x - x / 8, x / 2 + x / 7);
ctx.lineTo(x - x / 8, x / 4 + x / 6);
ctx.stroke();
ctx.closePath();
//raczka
ctx.beginPath();
ctx.lineWidth = 5;
ctx.strokeStyle = "#808080";
ctx.moveTo(x / 2 + x / 5, x / 2 - x / 5);
ctx.lineTo(x / 2 + x / 5, x / 2 - x / 8 + 50);
ctx.stroke();
ctx.closePath();
ctx.save();
//smiglo
ctx.beginPath();
ctx.translate(x / 2, x / 2);
ctx.rotate((Math.PI / 180) * 25);
ctx.translate(-x / 2, -x / 2);
ctx.fillStyle = "#00cc00";
ctx.fillRect(x / 2 + x / 5 - 100, x / 2 - x / 5, 200, 10);
ctx.closePath();
ctx.restore();
window.requestAnimationFrame(draw);
}
init();
<canvas id="canvas" width=600 height=600></canvas>
Edit: I understand now how to rotate around a certain point. I still don't know how to rotate only the line not the whole thing.
Talking about the rotation, I think that you did well: the green line is rotated by 25 degrees in your example. You just need to rotate its middle point.
But to do so, I think it's better if you make other changes in your code: the part that draws the cube is difficult to handle, whenever you want to edit your code, this part will cause issues. I suggest to isolate it in a drawCube() function and to use proper (x,y) coordinates.
According to me, it should look like this:
function draw() {
angle += 5;
ctx.save();
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.translate(canvas.width / 2, canvas.height / 2);
ctx.rotate(Math.PI / 180 * angle);
ctx.translate(-canvas.width / 2, -canvas.height / 2);
// It rotates
drawLine();
ctx.restore();
// It doesn't rotate
drawCube();
}
Then, your code will work. All you will have to do is to make the line rotate around its middle point, but you said that you know how to do it.
Good luck!
EDIT: I added a snippet with an working example, and with a cube like yours as well, may help.
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var btnExample = document.getElementById('btnExample');
var btnCube = document.getElementById('btnCube');
var angle = 0;
var fps = 1000 / 60;
var propellerWidth = 100;
var propellerHeight = 10;
var towerWidth = 2;
var towerHeight = 100;
var mode = {
CUBE: 'CUBE',
EXAMPLE: 'EXAMPLE'
}
var currentMode = mode.EXAMPLE;
btnExample.onclick = function() {
currentMode = mode.EXAMPLE;
}
btnCube.onclick = function() {
currentMode = mode.CUBE;
}
setInterval(function() {
angle += 3;
draw(canvas, ctx, (canvas.width - propellerWidth) / 2, (canvas.height - propellerWidth) / 2, angle);
}, fps);
function draw(canvas, ctx, cx, cy, angle) {
var towerX = cx + propellerWidth / 2 - towerWidth / 2;
var towerY = cy + propellerWidth / 2;
var propellerX = (canvas.width - propellerWidth) / 2;
var propellerY = (canvas.height - propellerHeight) / 2;
ctx.save();
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw other things that don't rotate
if (currentMode === mode.EXAMPLE) {
drawHelp(canvas, ctx);
}
ctx.translate(canvas.width / 2, canvas.height / 2);
ctx.rotate(Math.PI / 180 * angle);
ctx.translate(-canvas.width / 2, -canvas.height / 2);
// Draw things that rotate
drawPropeller(ctx, propellerX, propellerY, propellerWidth, propellerHeight);
ctx.restore();
// Draw other things that don't rotate
if (currentMode === mode.EXAMPLE) {
drawTower(ctx, towerX, towerY, towerWidth, towerHeight);
} else if (currentMode === mode.CUBE) {
drawCube(ctx, towerX, towerY, 30);
}
}
function drawPropeller(ctx, propellerX, propellerY, propellerWidth, propellerHeight) {
ctx.fillStyle = 'black';
ctx.fillRect(propellerX, propellerY, propellerWidth, propellerHeight);
}
function drawTower(ctx, towerX, towerY, towerWidth, towerHeight) {
ctx.fillStyle = 'red';
ctx.fillRect(towerX, towerY, towerWidth, towerHeight);
}
function drawCube(ctx, x, y, size) {
ctx.strokeStyle = 'black';
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(x, y + size);
ctx.closePath();
ctx.stroke();
var x1 = x - size + (size / 4) + (size / 16);
var y1 = y + (size * 1.25);
var x2 = x1 + (size / 3);
var y2 = y1 - (size * 2 / 3);
var x3 = x2 + size;
var y3 = y2;
var x4 = x3 - (size / 3);
var y4 = y1;
var x5 = x4;
var y5 = y4 + size;
var x6 = x3;
var y6 = y3 + size;
ctx.strokeRect(x1, y1, size, size);
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.closePath();
ctx.stroke();
ctx.beginPath();
ctx.moveTo(x2, y2);
ctx.lineTo(x3, y3);
ctx.closePath();
ctx.stroke();
ctx.beginPath();
ctx.moveTo(x3, y3);
ctx.lineTo(x4, y4);
ctx.closePath();
ctx.stroke();
ctx.beginPath();
ctx.moveTo(x3, y3);
ctx.lineTo(x6, y6);
ctx.closePath();
ctx.stroke();
ctx.beginPath();
ctx.moveTo(x5, y5);
ctx.lineTo(x6, y6);
ctx.closePath();
ctx.stroke();
}
function drawHelp(canvas, ctx) {
ctx.globalAlpha = 0.2;
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(canvas.width, canvas.height);
ctx.moveTo(canvas.width, 0);
ctx.lineTo(0, canvas.height);
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.arc(canvas.width / 2, canvas.height / 2, propellerWidth / 2, 0, 2 * Math.PI);
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.arc(canvas.width / 2, canvas.height / 2, propellerWidth / 2, 0, 2 * Math.PI);
ctx.stroke();
ctx.closePath();
ctx.globalAlpha = 1;
}
canvas {
border: 1px solid black;
}
<canvas id="canvas" width=200 height=200></canvas>
<br>
<button id="btnExample">Example</button>
<button id="btnCube">Cube</button>

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

How to implement rotate method for html5 canvas

I've created and some shapes (pacman looking objects) using javascript and I am animating them using matrix transformations built into the canvas. The objects can move in four directions (left, right, up, down) and the shapes essentially will move back and forth across the canvas until another button is pressed. These functionalities work properly when I use the built in canvas transformation functions; however, when I try to implement my own transformations some weird things are happening.
In the normal cases (when I use the canvas transformations) I first am translating the canvas origin to the point that is being rotated around, performing the rotation around this "new" origin, and then translating the origin back to what it was before, seen in the following code:
function renderContent(pm) {
var t = new Transform();
context.save();
context.beginPath();
context.fillStyle = "Yellow";
context.strokeStyle = "Yellow";
context.save();
var tCopy1 = t;
context.translate(pm.posX, pm.posY);
context.rotate(-pm.direction * Math.PI / 180);
context.translate(-pm.posX, -pm.posY);
context.arc(pm.posX, pm.posY, pm.size, (pm.startAngle) * Math.PI, (pm.endAngle) * Math.PI);
context.lineTo(pm.posX, pm.posY);
context.stroke();
context.fill();
var m = t.m;
t.transform(m);
context.restore();
context.restore();
}
To implement my own transformations, I have a transform class with functions including:
function Transform() {
this.identity();
}
Transform.prototype.identity = function () {
this.m = [1, 0, 0, 1, 0, 0];
};
Transform.prototype.rotate = function (rad) {
var c = Math.cos(rad);
var s = Math.sin(rad);
var m11 = this.m[0] * c + this.m[2] * s;
var m12 = this.m[1] * c + this.m[3] * s;
var m21 = this.m[0] * -s + this.m[2] * c;
var m22 = this.m[1] * -s + this.m[3] * c;
this.m[0] = m11;
this.m[1] = m12;
this.m[2] = m21;
this.m[3] = m22;
};
Transform.prototype.translate = function (x, y) {
this.m[4] += this.m[0] * x + this.m[2] * y;
this.m[5] += this.m[1] * x + this.m[3] * y;
};
Transform.prototype.scale = function (sx, sy) {
this.m[0] *= sx;
this.m[1] *= sx;
this.m[2] *= sy;
this.m[3] *= sy;
};
In the renderContext() function, I use essentially the same function calls for translate() and rotate() (which enable the pacman objects to turn around when they hit the canvas borders) but for some reason, with my implementation, the objects don't turn around (180 degrees).
For my transformations I essentially instantiate a transform object, call the transformation operations on the object, make a copy of the transform matrix and then set the canvas with the resultant matrix from the transformations:
E.G.:
var t = new Transform();
var tCopy1 = t;
tCopy1.translate(pm.posX, pm.posY);
t.rotate(-pm.direction * Math.PI / 180);
t.translate(-pm.posX, -pm.posY);
...
var m = t.m;
t.transform(m);
This is probably a lot of code to look at, but what is the correct way of implementing the rotation matrix transformation (as the canvas.rotate() is working)?
Here's the pacman rotating clockwise about the direction angle.
update
Now showing transform error in purple. Rotation looks correct but translation is off.
update 2
I think my previous test was bad. The transform looks good and there was only some minor adjustment of to_radians. The new test shows the context ranslation/rotation followed by the Transform class.
var can = document.getElementById('can');
var context = can.getContext('2d');
var to_rad = Math.PI / 180; // to radians
function main() {
context.fillStyle="black";
context.fillRect(0, 0, can.width, can.height);
var pm = {
direction: 0,
posX: 50,
posY: 100,
size: 20,
startAngle: 45,
endAngle: 315
};
var i = 0;
function loopTest() {
pm.direction = i * 90;
pm.posX = 50 * (i+1);
renderContent(pm);
setTimeout(function(){
renderContent2(pm);
}, 1000);
i++;
if (i < 4) {
setTimeout(loopTest, 2000);
}
}
loopTest();
}
function renderContent(pm) {
context.save();
context.beginPath();
context.fillStyle = "Yellow";
context.strokeStyle = "Yellow";
context.translate(pm.posX, pm.posY);
context.rotate(pm.direction * to_rad);
context.translate(-pm.posX, -pm.posY);
context.arc(pm.posX, pm.posY, pm.size, pm.startAngle * to_rad, pm.endAngle * to_rad);
context.lineTo(pm.posX, pm.posY);
context.stroke();
context.fill();
context.fillStyle="red";
context.font="16px Arial";
context.textAlign="center";
context.fillText(pm.direction, pm.posX,pm.posY+pm.size);
context.restore();
}
function renderContent2(pm) {
var t = new Transform();
context.save();
context.beginPath();
context.fillStyle = "#990099";
context.strokeStyle = "#990099";
t.translate(pm.posX, pm.posY);
t.rotate(pm.direction * to_rad);
t.translate(-pm.posX, -pm.posY);
context.transform.apply(context, t.m);
context.arc(pm.posX, pm.posY, pm.size, pm.startAngle * to_rad, pm.endAngle * to_rad);
context.lineTo(pm.posX, pm.posY);
context.stroke();
context.fill();
context.fillStyle="White";
context.font="16px Arial";
context.textAlign="center";
context.fillText(pm.direction, pm.posX,pm.posY+pm.size);
context.restore();
}
function Transform() {
this.identity();
}
Transform.prototype.identity = function () {
this.m = [1, 0, 0, 1, 0, 0];
};
Transform.prototype.rotate = function (rad) {
var c = Math.cos(rad);
var s = Math.sin(rad);
var m11 = this.m[0] * c + this.m[2] * s;
var m12 = this.m[1] * c + this.m[3] * s;
var m21 = this.m[0] * -s + this.m[2] * c;
var m22 = this.m[1] * -s + this.m[3] * c;
this.m[0] = m11;
this.m[1] = m12;
this.m[2] = m21;
this.m[3] = m22;
};
Transform.prototype.translate = function (x, y) {
this.m[4] += this.m[0] * x + this.m[2] * y;
this.m[5] += this.m[1] * x + this.m[3] * y;
};
Transform.prototype.scale = function (sx, sy) {
this.m[0] *= sx;
this.m[1] *= sx;
this.m[2] *= sy;
this.m[3] *= sy;
};
main();
canvas {
border:3px solid red;
}
<canvas id="can" width="300" height="200"></canvas>

Draw arrow on canvas tag

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>

Categories