Javascript sin function does some funky stuff - javascript

When I wrote this script to calculate the angle from 2 points (I know I can use atan2, but experiments and math learning), I got some weird results when I combined it with a triangle function I wrote. It suddenly turns around when the halfway point on the x axis is passed with the mouse, and as I don't know much about trigonometery I didn't know how to fix this issue... Here's my code:
const can = document.getElementById('can');
const ctx = can.getContext('2d');
can.width = innerWidth;
can.height = innerHeight;
var length = 200;
var origin = {x: can.width / 2, y: can.height / 2};
var mousey;
var mousex;
function triangle(x1, y1, x2, y2){
ctx.strokeStyle = "blue";
ctx.lineWidth = "2";
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(x2, y2);
ctx.lineTo(x2, y2 + (y1 - y2));
ctx.strokeStyle = "#33cc33";
ctx.stroke();
ctx.beginPath();
ctx.moveTo(x2, y2 + (y1 - y2));
ctx.lineTo(x1, y1);
ctx.strokeStyle = "red";
ctx.stroke();
}
(function loop(){
ctx.clearRect(0, 0, can.width, can.height);
calc2(mousex, mousey);
requestAnimationFrame(loop);
})();
function update(){
calc2(mousex, mousey);
}
function calc2(x1, y1){
let x2 = origin.x;
let y2 = origin.y;
let opposite = y1 - y2;
let hypotenuse = Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
let a = Math.asin(opposite / hypotenuse);
triangle(x2, y2, x2 + Math.cos(a) * length, y2 + Math.sin(a) * length);
}
addEventListener('mousemove', (e)=>{
mousey = e.clientY;
mousex = e.clientX;
});
<canvas id="can" width="400" height="400"></canvas>
Thanks in advance!

In function calc2(x1, y1), variable 'hypotenuse' is always a non-negative number, it can't tell if your mouse is on the left or right side. You need a boolean to store this.
The range of Math.asin() is from -pi/2 to pi/2 (-90 degrees to 90 degrees). Cos() this angle is always greater or equal to 0. In your calculations, the -90 deg relative to the horizontal red line is a straight downward line. Rotating counterclockwise from there, -45 deg is a line to lower right corner, 0 deg is the red line itself, 45 deg to upper right corner, 90 deg is straight upward. That means all possible blue lines are from starting point to the right.
The y-axis is correct. To let cos(x) produce a negative number, x should be pi/2 to pi (90 deg to 180 deg). As cos(pi-x) == -cos(x), we will flip this angle when mouse is on the left side.
const can = document.getElementById('can');
const ctx = can.getContext('2d');
can.width = innerWidth;
can.height = innerHeight;
var length = 200;
var origin = {x: can.width / 2, y: can.height / 2};
var mousey;
var mousex;
function triangle(x1, y1, x2, y2){
ctx.strokeStyle = "blue";
ctx.lineWidth = "2";
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(x2, y2);
ctx.lineTo(x2, y2 + (y1 - y2));
ctx.strokeStyle = "#33cc33";
ctx.stroke();
ctx.beginPath();
ctx.moveTo(x2, y2 + (y1 - y2));
ctx.lineTo(x1, y1);
ctx.strokeStyle = "red";
ctx.stroke();
}
(function loop(){
ctx.clearRect(0, 0, can.width, can.height);
calc2(mousex, mousey);
requestAnimationFrame(loop);
})();
function update(){
calc2(mousex, mousey);
}
function calc2(x1, y1){
let x2 = origin.x;
let y2 = origin.y;
let opposite = y1 - y2;
let hypotenuse = Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
let onLeft = x1 < x2 ? true : false;
let a = Math.asin(opposite / hypotenuse);
triangle(x2, y2, x2 + Math.cos(onLeft ? Math.PI - a : a) * length, y2 + Math.sin(a) * length)
}
addEventListener('mousemove', (e)=>{
mousey = e.clientY;
mousex = e.clientX;
});
<canvas id="can" width="400" height="400"></canvas>

Related

Canvas determine if point is on line

I am trying to design a line chart which will show a tool tip when the user hovers over a line. Below is a minimal example showing my attempt at determining whether a point lies on a line (the line can be considered a rectangle with height 5 and width 80 in this case).
I don't understand why isPointInPath can only find the points at exactly the start and end points of the line. How can I determine if a point lies anywhere on the line?
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
ctx.lineWidth = 5;
ctx.moveTo(20, 20);
ctx.lineTo(100, 20);
ctx.stroke();
console.log(ctx.isPointInPath(20, 20)); // beginning: true
console.log(ctx.isPointInPath(100, 20)); // end: true
console.log(ctx.isPointInPath(60, 20)); // middle: false
console.log(ctx.isPointInPath(100, 21)); // end offset: false
isPointInPath does use the fill region to make its check. You want isPointInStroke() which also takes the lineWidth into consideration:
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
const path = new Path2D();
let x = -1;
let y = -1;
for (let i = 0; i<20; i++) {
path.lineTo(
Math.random() * canvas.width,
Math.random() * canvas.height
);
}
ctx.lineWidth = 5;
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.strokeStyle = ctx.isPointInStroke(path, x, y)
? "red"
: "green";
ctx.stroke(path);
}
draw();
addEventListener("mousemove", (evt) => {
const rect = canvas.getBoundingClientRect();
x = evt.clientX - rect.left;
y = evt.clientY - rect.top;
draw();
});
<canvas></canvas>
As to why isPointInPath() finds the points that are explicitly set in the Path declaration and not the ones that would logically lie on it... That's quite unclear I'm afraid, the specs don't have a clear fill algorithm, and I'll open a specs issue to fix that since there is actually an interop-issue with Safari acting as you'd expect, and Firefox + Chrome ignoring the fill area entirely when it doesn't produce a pixel, but still keeping these points.
If you want a function that tells you if a point is on a line with a width of 5px, we should use some analytical geometry: a point distance from a line. I copied formula from wikipedia
function isPointOnLine(px, py, x1, y1, x2, y2, width) {
return distancePointFromLine(px, py, x1, y1, x2, y2, width) <= width / 2
}
function distancePointFromLine(x0, y0, x1, y1, x2, y2) {
return Math.abs((x2 - x1) * (y1 - y0) - (x1 - x0) * (y2 - y1)) / Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
}
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
ctx.lineWidth = 5;
x1 = y1 = y2 = 20
x2 = 100
ctx.moveTo(20, 20);
ctx.lineTo(100, 20);
ctx.stroke();
console.log(isPointOnLine(20, 20, x1, y1, x2, y2, 5));
console.log(isPointOnLine(100, 20, x1, y1, x2, y2, 5));
console.log(isPointOnLine(60, 20, x1, y1, x2, y2, 5));
console.log(isPointOnLine(100, 21, x1, y1, x2, y2, 5));
<canvas></canvas>

Circle is not connecting through lines properly using Canvas

I am trying to create 11 circles which connected through lines with a middle circle. I am trying to draw the circles. Here I have doing some r&d but I could not able to make lines. Please help me to complete this.
var canvas, ctx;
var circlePoints = [];
function createCanvasPainting() {
canvas = document.getElementById('myCanvas');
if (!canvas || !canvas.getContext) {
return false;
}
canvas.width = 600;
canvas.height = 600;
ctx = canvas.getContext('2d');
ctx.strokeStyle = '#B8D9FE';
ctx.fillStyle = '#B8D9FE';
ctx.translate(300, 250);
ctx.arc(0, 0, 50, 0, Math.PI * 2); //center circle
ctx.stroke();
ctx.fill();
var angleRotate = 0;
for (var i=0; i<11; i++) {
if (i > 0) {
angleRotate += 32.72;
}
lineToAngle(ctx, 0, 0, 200, angleRotate);
}
}
function lineToAngle(ctx, x1, y1, length, angle) {
angle *= Math.PI / 180;
var x2 = x1 + length * Math.cos(angle),
y2 = y1 + length * Math.sin(angle);
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.lineWidth = 1;
ctx.arc(x2, y2, 40, 0, Math.PI * 2);
ctx.fill();
ctx.stroke();
circlePoints.push({x: x2, y: y2});
// console.log(circlePoints);
}
createCanvasPainting();
<canvas id="myCanvas"></canvas>
Here is my JSFiddle Link
See below I removed all the "noise" from your code.
Just circles with lines connecting with a middle circle.
canvas = document.getElementById('myCanvas');
canvas.width = canvas.height = 200;
ctx = canvas.getContext('2d');
ctx.lineWidth = 1;
ctx.translate(99, 99);
angle = 0;
function draw() {
ctx.clearRect(-99, -99, 200, 200);
ctx.beginPath();
ctx.arc(0, 0, 35 + Math.cos(angle / 3000), 0, Math.PI * 2);
ctx.stroke();
ctx.fill();
for (var i = 0; i < 11; i++) {
a = angle * Math.PI / 180;
x = 80 * Math.cos(a)
y = 80 * Math.sin(a)
ctx.beginPath();
ctx.arc(x, y, 18, 0, Math.PI * 2);
ctx.moveTo(x, y);
ctx.lineTo(0, 0);
ctx.fill();
ctx.stroke();
angle += 32.7;
}
}
setInterval(draw, 10);
<canvas id="myCanvas"></canvas>

How to get coordinates of every circle from this Canvas

I need to create a pattern where 5 circles connected by lines to a middle main circle.
So I have created dynamically by rotating in some certain angle. Now I need each and every circle's x and y axis coordinates for capturing the click events on every circle.
Please help me how to find out of coordinates of every circle?
var canvas, ctx;
function createCanvasPainting() {
canvas = document.getElementById('myCanvas');
if (!canvas || !canvas.getContext) {
return false;
}
canvas.width = 600;
canvas.height = 600;
ctx = canvas.getContext('2d');
ctx.strokeStyle = '#B8D9FE';
ctx.fillStyle = '#B8D9FE';
ctx.translate(300, 250);
ctx.arc(0, 0, 50, 0, Math.PI * 2); //center circle
ctx.stroke();
ctx.fill();
drawChildCircles(5);
fillTextMultiLine('Test Data', 0, 0);
drawTextInsideCircles(5);
}
function drawTextInsideCircles(n) {
let ang_unit = Math.PI * 2 / n;
ctx.save();
for (var i = 0; i < n; i++) {
ctx.rotate(ang_unit);
//ctx.moveTo(0,0);
fillTextMultiLine('Test Data', 200, 0);
ctx.strokeStyle = '#B8D9FE';
ctx.fillStyle = '#B8D9FE';
}
ctx.restore();
}
function drawChildCircles(n) {
let ang_unit = Math.PI * 2 / n;
ctx.save();
for (var i = 0; i < n; ++i) {
ctx.rotate(ang_unit);
ctx.beginPath();
ctx.moveTo(0,0);
ctx.lineTo(100,0);
ctx.arc(200, 0, 40, 0, Math.PI * 2);
let newW = ctx.fill();
ctx.stroke();
}
ctx.restore();
}
function fillTextMultiLine(text, x, y) {
ctx.font = 'bold 13pt Calibri';
ctx.textAlign = 'center';
ctx.fillStyle = "#FFFFFF";
// Defining the `textBaseline`…
ctx.textBaseline = "middle";
var lineHeight = ctx.measureText("M").width * 1.2;
var lines = text.split("\n");
for (var i = 0; i < lines.length; ++i) {
// console.log(lines);
if (lines.length > 1) {
if (i == 0) {
y -= lineHeight;
} else {
y += lineHeight;
}
}
ctx.fillText(lines[i], x, y);
}
}
createCanvasPainting();
<canvas id="myCanvas"></canvas>
The problem here is that you are rotating the canvas matrix and your circles are not aware of their absolute positions.
Why don't you use some simple trigonometry to determine the center of your circle and the ending of the connecting lines ?
function lineToAngle(ctx, x1, y1, length, angle) {
angle *= Math.PI / 180;
var x2 = x1 + length * Math.cos(angle),
y2 = y1 + length * Math.sin(angle);
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
return {x: x2, y: y2};
}
Ref: Finding coordinates after canvas Rotation
After that, given the xy center of your circles, calculating if a coord is inside a circle, you can apply the following formula:
Math.sqrt((x1-x0)*(x1-x0) + (y1-y0)*(y1-y0)) < r
Ref: Detect if user clicks inside a circle

How can I draw arrows on a canvas with mouse

recently I started playing with canvas element. Now I am able to draw lines(as many as I wish) on a canvas with mouse. You can see it here in the code: https://jsfiddle.net/saipavan579/a6L3ka8p/.
var ctx = tempcanvas.getContext('2d'),
mainctx = canvas.getContext('2d'),
w = canvas.width,
h = canvas.height,
x1,
y1,
isDown = false;
tempcanvas.onmousedown = function(e) {
var pos = getPosition(e, canvas);
x1 = pos.x;
y1 = pos.y;
isDown = true;
}
tempcanvas.onmouseup = function() {
isDown = false;
mainctx.drawImage(tempcanvas, 0, 0);
ctx.clearRect(0, 0, w, h);
}
tempcanvas.onmousemove = function(e) {
if (!isDown) return;
var pos = getPosition(e, canvas);
x2 = pos.x;
y2 = pos.y;
ctx.clearRect(0, 0, w, h);
drawEllipse(x1, y1, x2, y2);
}
function drawEllipse(x1, y1, x2, y2) {
var radiusX = (x2 - x1) * 0.5,
radiusY = (y2 - y1) * 0.5,
centerX = x1 + radiusX,
centerY = y1 + radiusY,
step = 0.01,
a = step,
pi2 = Math.PI * 2 - step;
ctx.beginPath();
ctx.moveTo(x1,y1);
for(; a < pi2; a += step) {
ctx.lineTo(x2,y2);
}
ctx.closePath();
ctx.strokeStyle = '#000';
ctx.stroke();
}
function getPosition(e, gCanvasElement) {
var x;
var y;
x = e.pageX;
y = e.pageY;
x -= gCanvasElement.offsetLeft;
y -= gCanvasElement.offsetTop;
return {x:x, y:y};
};
Now I want to draw arrow headed lines(for pointing to some specific point on a image) in the same way as I am drawing the lines. How can do that? Thank you in advance.
You can draw an arrowhead at the end of line segment [p0,p1] like this:
calculate the angle from p0 to p1 using Math.atan2.
Each side of the arrowhead starts at p1, so calculate the 2 arrow endpoints using trigonometry.
draw the [p0,p1] line segment and the 2 arrowhead line segments.
Here's example code and a Demo:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var p0={x:50,y:100};
var p1={x:250,y:50};
drawLineWithArrowhead(p0,p1,15);
function drawLineWithArrowhead(p0,p1,headLength){
// constants (could be declared as globals outside this function)
var PI=Math.PI;
var degreesInRadians225=225*PI/180;
var degreesInRadians135=135*PI/180;
// calc the angle of the line
var dx=p1.x-p0.x;
var dy=p1.y-p0.y;
var angle=Math.atan2(dy,dx);
// calc arrowhead points
var x225=p1.x+headLength*Math.cos(angle+degreesInRadians225);
var y225=p1.y+headLength*Math.sin(angle+degreesInRadians225);
var x135=p1.x+headLength*Math.cos(angle+degreesInRadians135);
var y135=p1.y+headLength*Math.sin(angle+degreesInRadians135);
// draw line plus arrowhead
ctx.beginPath();
// draw the line from p0 to p1
ctx.moveTo(p0.x,p0.y);
ctx.lineTo(p1.x,p1.y);
// draw partial arrowhead at 225 degrees
ctx.moveTo(p1.x,p1.y);
ctx.lineTo(x225,y225);
// draw partial arrowhead at 135 degrees
ctx.moveTo(p1.x,p1.y);
ctx.lineTo(x135,y135);
// stroke the line and arrowhead
ctx.stroke();
}
body{ background-color: ivory; }
canvas{border:1px solid red;}
<canvas id="canvas" width=300 height=300></canvas>

Simple JS/html5 fractal tree with decrementing line width

I would like to make a very simple fractal tree (for learning purposes) using JS. I have been using the following code which i got from a wikipedia article. It is great, only i want the line width to decrement with each iteration. As you can see i tried context.lineWidth = context.lineWidth - 1, but this doesn’t work. Does anybody have any ideas about how this can be acheived?
var elem = document.getElementById('canvas');
var context = elem.getContext('2d');
context.fillStyle = '#000';
context.lineWidth = 20;
var deg_to_rad = Math.PI / 180.0;
var depth = 9;
function drawLine(x1, y1, x2, y2){
context.moveTo(x1, y1);
context.lineTo(x2, y2);
context.lineWidth = context.lineWidth - 1;
}
function drawTree(x1, y1, angle, depth){
if (depth != 0){
var x2 = x1 + (Math.cos(angle * deg_to_rad) * depth * 10.0);
var y2 = y1 + (Math.sin(angle * deg_to_rad) * depth * 10.0);
drawLine(x1, y1, x2, y2);
drawTree(x2, y2, angle - 20, depth - 1);
drawTree(x2, y2, angle + 20, depth - 1);
}
}
context.beginPath();
drawTree(300, 500, -90, depth);
context.closePath();
context.stroke();
It would also be great if there was a way to do this in stages so that when i click on a button it adds a new branch. Anyway, your advice would be greatly appreciated.
I have created and tweaked a fiddle which somehow does what you want.
fiddle
All in all: You need to stroke each time a new line width is set. So the code looks like this:
function drawLine(x1, y1, x2, y2, lw){
context.beginPath();
context.moveTo(x1, y1);
context.lineTo(x2, y2);
context.lineWidth = lw;
context.closePath();
context.stroke();
}
function drawTree(x1, y1, angle, depth, lw){
if (depth != 0){
var x2 = x1 + (Math.cos(angle * deg_to_rad) * depth * 10.0);
var y2 = y1 + (Math.sin(angle * deg_to_rad) * depth * 10.0);
drawLine(x1, y1, x2, y2, lw);
drawTree(x2, y2, angle - 20, depth - 1, lw - 1);
drawTree(x2, y2, angle + 20, depth - 1, lw - 1);
}
}
drawTree(300, 500, -90, depth, depth);

Categories

Resources