Reputation: 1594
Alright, I would call myself a pretty good programmer who never really followed the academic way. Therefor my mathematical knowledge is rather limited, which is a little burden for things I sometimes need to accomplish. I know some of you are pretty clever minds and enjoy calculating this (for you most likely easy) thing:
I need to realize a few diagrams and charts for a customer. I choosed OCanvas as a drawing library. It has really helped and Everything I need to do works fine, but there is one caveat.
I have two circles, and can pass the Objects OCanvas makes for me to a function that draws a line between circle 1's center to the center of circle 2. Now I want to add triangles to the line to make it an arrow.
I know I need to calculate the tilt of the line, to properly align the triangle. I would also need to calculate the position of the intersection between circle border and the line, to position the triangle.
I would provide you some code, but I honestly have no clue on how to calculate this. Known values for me are: Line startX, startY, endX, endY as well as circle 1 X, Y and radius/circle 2 X, Y and radius (X and Y represent the center of the circles)
Thanks in advance!
EDIT:
A visual representation, where the red triangle is needed:
Upvotes: 1
Views: 575
Reputation: 2294
Not complete but it might give you a good start.
let c = document.getElementById("myCanvas"),
ctx = c.getContext("2d"),
startX = 50,
startY = 50,
endX = 250,
endY = 150,
rS = 30,
rE = 50,
alpha = Math.atan((startY - endY) / (startX - endX)),
dir = 0, // start 0 end 1
side = 10;
ctx.beginPath();
ctx.arc(startX, startY, rS, 0, 2 * Math.PI);
ctx.stroke();
ctx.beginPath();
ctx.arc(endX, endY, rE, 0, 2 * Math.PI);
ctx.stroke();
if (startX < endX) {
endcx = endX + (rE + side/2) * Math.cos(alpha + Math.PI);
endcy = endY + (rE + side/2) * Math.sin(alpha + Math.PI);
startcx = startX - (rS + side/2) * Math.cos(alpha - Math.PI);
startcy = startY - (rS + side/2) * Math.sin(alpha - Math.PI);
dir = 0;
} else if (startX >= endX) {
endcx = endX - (rE + side/2) * Math.cos(alpha + Math.PI);
endcy = endY - (rE + side/2) * Math.sin(alpha + Math.PI);
startcx = startX + (rS + side/2) * Math.cos(alpha - Math.PI);
startcy = startY + (rS + side/2) * Math.sin(alpha - Math.PI);
dir = 1;
}
ctx.beginPath();
ctx.moveTo(startcx, startcy);
ctx.lineTo(endcx, endcy);
ctx.lineWidth=3;
ctx.stroke();
drawEqTriangle(ctx, side, startcx, startcy, dir?0:1);
drawEqTriangle(ctx, side, endcx, endcy, dir);
function drawEqTriangle(ctx, side, cx, cy, dir) {
var h = side * (Math.sqrt(3) / 2);
ctx.save();
ctx.strokeStyle = "#000";
ctx.fillStyle = "#000";
ctx.translate(cx, cy);
ctx.rotate(alpha + Math.PI / 2);
ctx.beginPath();
ctx.lineWidth=1;
if (dir === 0) {
ctx.moveTo(0, -h / 2);
ctx.lineTo(-side / 2, h / 2);
ctx.lineTo(side / 2, h / 2);
ctx.lineTo(0, -h / 2);
} else if (dir === 1) {
ctx.moveTo(0, h / 2);
ctx.lineTo(-side / 2, -h / 2);
ctx.lineTo(side / 2, -h / 2);
ctx.lineTo(0, h / 2);
}
ctx.stroke();
ctx.fill();
ctx.closePath();
ctx.restore();
}
<!DOCTYPE html>
<html>
<body>
<canvas id="myCanvas" width="400" height="300" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.</canvas>
</body>
</html>
Upvotes: 2
Reputation: 2294
Another idea you could use for simplify things (and maybe @Blindman67 implemented above but I don't understand it as such) is to think things like this:
No matter where circles are positioned you can rotate the context in such a way that their centres are on a horizontal, or vertical if you prefer, this way all points of interest are on a single coordinate, linear. Draw what you need and then rotate the context back.
Upvotes: 0
Reputation: 54041
If you were using pure canvas it would be a little less fiddly.
First the two circles
const circle1 = {
x : ?,
y : ?,
r : ?, // Radius
lineWidth : ?,
}
const circle2 = {
x : ?,
y : ?,
r : ?,
lineWidth : ?,
}
Then the arrow description
const arrow = {
width : ?,
depth : ?, // tip to back of arrow head
}
The the math.
Get the vector from circle1 to circle2
var vx = circle2.x - circle1.x;
var vy = circle2.y - circle1.y;
The the distance between them
var dist = Math.sqrt(vx * vx + vy * vy);
You now need to normalise the vector by dividing the vector by the length. This makes the vector one unit long
vx /= dist;
vy /= dist;
Now you can render the arrow. The tip is one the edge of circle 2. To find that point subtract the radius and half the line width from the distance
const aDist = dist - (circle2.r + circle2.lineWidth / 2);
The normalised vector can be multiplied by a distance to find the position, or if using the canvas 2D API you use the normal to set the transform and then it is all aligned to the x Axis.
ctx.beginPath(); // drawing the arrow head
ctx.lineTo(circle1.x + vx * aDist, circle1.y + vy * aDist); // tip of arrow
We need to move back away from the tip and out from the line at 90deg to do this you can rotate a vector 90 deg by swapping the x,y parts and negating the new x.
ctx.lineTo(
circle1.x + vx * (aDist - arrow.depth) - vy * arrow.width,
circle1.y + vy * (aDist - arrow.depth) + vx * arrow.width
// ^------- along line -----^ ^--Out from line--^
);
// and the other side
ctx.lineTo(
circle1.x + vx * (aDist - arrow.depth) + vy * arrow.width,
circle1.y + vy * (aDist - arrow.depth) - vx * arrow.width
// ^------- along line -----^ ^--Out from line--^
);
// ctx.closePath();
ctx.fill();
Or if you use the transform
ctx.setTransform(vx,vy,-vy,vx,circle1.x,circle1.y); // set the circle1 as origin
// and use the normal to
// align the x axis
ctx.beginPath(); // drawing the arrow head
ctx.lineTo(aDist,0); // tip of arrow
ctx.lineTo(aDist - arrow.depth, arrow.width);
ctx.lineTo(aDist - arrow.depth, -arrow.width);
// ctx.closePath();
ctx.fill();
ctx.setTransform(1,0,0,1,0,0); // restore default transform.
const ctx = canvas.getContext("2d");
const circle1 = {
x : 50,
y : 100,
r : 45, // Radius
lineWidth : 3,
}
const circle2 = {
x : 250,
y : 50,
r : 45,
lineWidth : 3,
}
const arrow = {
width : 10,
depth : 20, // tip to back of arrow head
}
var vx = circle2.x - circle1.x;
var vy = circle2.y - circle1.y;
var dist = Math.sqrt(vx * vx + vy * vy);
vx /= dist;
vy /= dist;
const aDist = dist - (circle2.r + circle2.lineWidth / 2);
ctx.lineWidth = circle1.lineWidth;
ctx.beginPath();
ctx.arc(circle1.x, circle1.y, circle1.r, 0, 2 * Math.PI);
ctx.moveTo(circle2.x + circle2.r, circle2.y);
ctx.arc(circle2.x, circle2.y, circle2.r, 0, 2 * Math.PI);
ctx.moveTo(circle1.x, circle1.y);
ctx.lineTo(circle2.x, circle2.y);
ctx.stroke();
ctx.beginPath(); // drawing the arrow head
ctx.lineTo(circle1.x + vx * aDist, circle1.y + vy * aDist); // tip of arrow
ctx.lineTo(
circle1.x + vx * (aDist - arrow.depth) - vy * arrow.width,
circle1.y + vy * (aDist - arrow.depth) + vx * arrow.width
// ^------- along line -----^ ^--Out from line--^
);
// and the other side
ctx.lineTo(
circle1.x + vx * (aDist - arrow.depth) + vy * arrow.width,
circle1.y + vy * (aDist - arrow.depth) - vx * arrow.width
// ^------- along line -----^ ^--Out from line--^
);
// ctx.closePath();
ctx.fill();
canvas { border : 2px solid black; }
const ctx = canvas.getContext("2d");
<canvas id="canvas"></canvas>
setTransform
This uses setTransform to reduce the amount of math. also this example uses the line width to but the arrow head to the circle
const ctx = canvas.getContext("2d");
const circle1 = {
x : 50,
y : 100,
r : 45, // Radius
lineWidth : 3,
}
const circle2 = {
x : 250,
y : 50,
r : 45,
lineWidth : 3,
}
const arrow = {
width : 10,
depth : 20, // tip to back of arrow head
}
var vx = circle2.x - circle1.x;
var vy = circle2.y - circle1.y;
var dist = Math.sqrt(vx * vx + vy * vy);
vx /= dist;
vy /= dist;
const aDist = dist - (circle2.r + circle2.lineWidth / 2);
ctx.lineWidth = circle1.lineWidth;
ctx.beginPath();
ctx.arc(circle1.x, circle1.y, circle1.r, 0, 2 * Math.PI);
ctx.moveTo(circle2.x + circle2.r, circle2.y);
ctx.arc(circle2.x, circle2.y, circle2.r, 0, 2 * Math.PI);
ctx.moveTo(circle1.x, circle1.y);
ctx.lineTo(circle2.x, circle2.y);
ctx.stroke();
ctx.beginPath(); // drawing the arrow head
ctx.setTransform(vx, vy, -vy, vx, circle1.x, circle1.y); // set the circle1 as origin
// and use the normal to
// align the x axis
ctx.beginPath(); // drawing the arrow head
ctx.lineTo(aDist,-circle1.lineWidth/2); // tip of arrow circle1.linewidth is same for line between circles
ctx.lineTo(aDist,circle1.lineWidth/2); // tip of arrow circle1.linewidth is same for line between circles
ctx.lineTo(aDist - arrow.depth, arrow.width + circle1.lineWidth/2);
ctx.lineTo(aDist - arrow.depth, -arrow.width - circle1.lineWidth/2);
// ctx.closePath();
ctx.fill();
ctx.setTransform(1,0,0,1,0,0); // restore default transform.
ctx.fill();
canvas { border : 2px solid black; }
const ctx = canvas.getContext("2d");
<canvas id="canvas"></canvas>
Upvotes: 2
Reputation: 5203
Trying to help you with your problem:
Edit:
By integrating @bluehipy answer, maybe this is going to be closer
//Objects
Circle = function(oX, oY, r){
this.oX = oX;
this.oY = oY;
this.r = r;
context.beginPath();
context.arc(oX, oY, r, 0, 2 * Math.PI, false);
context.fillStyle = 'red';
context.fill();
context.lineWidth = 2;
context.strokeStyle = '#003300';
context.stroke();
}
//Functions
drawArrowFromCirclesOrigins = function(side,c1, c2){
context.beginPath();
//line
context.moveTo(c1.oX, c1.oY);
context.lineTo(c2.oX, c2.oY);
context.stroke();
//triangle
var alpha = Math.tan((c2.oY-c1.oY) / (c2.oX-c1.oX));
var h = side * (Math.sqrt(3) / 2);
context.translate(c2.oX/alpha+c2.r-(-(side/2)), c2.oY/alpha+c2.r-(-(side/2)));
context.rotate(alpha + Math.PI / 2);
context.beginPath();
context.moveTo(0, -h / 2);
context.lineTo(-side / 2, h / 2);
context.lineTo(side / 2, h / 2);
context.lineTo(0, -h / 2);
context.fillStyle = 'green';
context.fill();
context.stroke();
}
//Variables
canvas = document.getElementById('canvas');
context = canvas.getContext('2d');
//Program
var cir1 = new Circle(20, 20, 10);
var cir2 = new Circle(40, 40, 10);
drawArrowFromCirclesOrigins(-11,cir1, cir2);
canvas {
border: 2px solid black;
}
<canvas id="canvas" width="200" height="100"></canvas>
Upvotes: 1
Reputation: 323
To get the angle of the tilt for the triangle you need the following:
Assuming (x1,y1)
are the coordinates of the start of the line, and (x2,y2)
are the coords of the ending of the line, and you want to draw the triangle at the end of the line:
alpha = arctan((y2-y2) / (x2-x1))
Then you need to create the triangle tilted with that angle (alpha).
The other triangle would be alpha + 180 degrees
or alpha - 180 degrees
(produce the same).
Upvotes: 1