Calculating offsets after square rotated from corner - javascript

I am wanting to calculate the 4 offsets from the point of rotation when I rotate a square.
The axis of rotation is initially the top left of the square. When I perform a rotation I would like to know how far the shape will spead in all 4 directions (minX, minY, maxX, maxy).
I currently have the general math:
const rotation = .35 // radians = 20 degrees
const size = 50 // size of original square
const o1 = Math.round(size * Math.sin(rotation))
const o2 = Math.round(size * Math.cos(rotation))
Using these numbers I see how I can use them to create an array of offsets
const offsets = [o1, 0, o2, o1 + o2]
When I rotate my square from 20, 110, 200 and 290 degrees it will rotate around the axis marked by the black dot on image.
For each of the 4 rotations I have the offests array as well as the actual numbers that I desire. As you can see the numbers are sort of there but... I initially thought an array shift was all I needed but its more than that.
// 20 degrees
console.log(offsets) // [17, 0, 47, 64]
// The dimensions I actually need
// minX: -17,
// minY: 0
// maxX: 47
// maxY: -64
// 110 degrees
console.log(offsets) // [47, 0, -17, 30]
// The dimensions I actually need
// minX: -64,
// minY: -17,
// maxX: 0,
// maxY: 47
// 200 degrees
console.log(offsets) // [-17, 0, -47, -64]
// The dimensions I actually need
// minX: -47,
// minY: -64,
// maxX: 17,
// maxY: 0
// 290 degrees
console.log(offsets) // [-47, 0, 17, -30]
// The dimensions I actually need
// minX: 0,
// minY: -47,
// maxX: 64,
// maxY: 17
I can certainly shift the array if needed (say for every 90deg) but how can I get the correct numbers? I'm looking for the magic formula for any angle.

Transforming points
The easiest way to do this is create a simple rotation matrix. This is just the direction of the x and y axis as vectors each with a length the size of a pixel (or unit whatever that may be) and the location of the origin.
To rotate a point
First define the point
var x = ?; // the point to rotate
var y = ?;
Then the origin and rotation
const ox = ?; // location of origin
const oy = ?;
const rotation = ?; // in radians
From the rotation we calculate to vector that is the direction of the x axis
var xAxisX = Math.cos(rotation);
var xAxisY = Math.sin(rotation);
Optionally you could have a scale as well
const scale = ?;
that would change the length of the x and y axis so the x axis calculation is
var xAxisX = Math.cos(rotation) * scale;
var xAxisY = Math.sin(rotation) * scale;
No we can apply the rotation to the point. First move the point relative to the origin.
x -= ox;
y -= oy;
Then move the point x distance along the x axis
var rx = x * xAxisX;
var ry = x * xAxisY;
Then move y distance along the y axis. The y axis is at 90 deg clockwise from the x. To rotate any vector 90deg you swap the x and y and negate the new x. Thus moving along the y axis is as follows
rx -= y * xAxisY; // use x axis y for y axis x and negate
ry += y * xAxisX; // use x axis x for y axis y
Now the point has been rotated but is still relative to the origin, we need to move it back to the world space. To do that just add the origin
rx += ox;
ry += oy;
And rx,ry is the rotated point around the origin, and scaled if you did that.
Match rotation in 2D context
You can get the 2D context to do the same for you
ctx.setTransform(xAxisX, xAxisY, -xAxisY, xAxisX, ox, oy);
ctx.fillRect(x,y,1,1); // draw the rotated pixel
ctx.setTransform(1, 0, 0, 1, 0, 0); // restore default transform
Or you can add the rotation via a function call
ctx.setTransform(1, 0, 0, 1, ox, oy);
ctx.rotate(rotation);
// and if scale then
// ctx.scale(scale,scale)
ctx.fillRect(x,y,1,1); // draw the rotated pixel
ctx.setTransform(1, 0, 0, 1, 0, 0); // restore default transform
The various steps above can be compacted, the next part of the answer rotates a rectangle using the above method.
Rotating a rectangle
The following function will return the 4 rotated corners.
// angle is the amount of rotation in radians
// ox,oy is the origin (center of rotation)
// x,y is the top left of the rectangle
// w,h is the width and height of the rectangle
// returns an array of points as arrays [[x,y],[x1,y1],...]
// Order of returned points topLeft, topRight, bottomRight, bottomLeft
function rotateRect(angle,ox,oy,x,y,w,h){
const xAx = Math.cos(angle); // x axis x
const xAy = Math.sin(angle); // x axis y
x -= ox; // move rectangle onto origin
y -= oy;
return [[ // return array holding the resulting points
x * xAx - y * xAy + ox, // Get the top left rotated position
x * xAy + y * xAx + oy, // and move it back to the origin
], [
(x + w) * xAx - y * xAy + ox, // Get the top right rotated position
(x + w) * xAy + y * xAx + oy,
], [
(x + w) * xAx - (y + h) * xAy + ox, // Get the bottom right rotated position
(x + w) * xAy + (y + h) * xAx + oy,
], [
x * xAx - (y + h) * xAy + ox, // Get the bottom left rotated position
x * xAy + (y + h) * xAx + oy,
]
];
}
Finding the offsets
To use the function
var angle = 1; // amount to rotate in radians
var ox = 0; // origin top left of rectangle
var oy = 0;
const rotatedRect = rotateRect(angle,ox,oy,0,0,50,50);
const r = rotatedRect; // alias to make following code more readable
var leftOfOrigin = Math.min(r[0][0],r[1][0],r[2][0],r[3][0]) - ox;
var rightOfOrigin = Math.max(r[0][0],r[1][0],r[2][0],r[3][0]) - ox;
var aboveOrigin = Math.min(r[0][1],r[1][1],r[2][1],r[3][1]) - oy;
var belowOrigin = Math.max(r[0][1],r[1][1],r[2][1],r[3][1]) - oy;
I keep the distance calcs outside the function as that is a little more useful as you may want more information about the rotated points.
DEMO
As an example
const ctx = canvas.getContext("2d");
canvas.width = 512;
canvas.height = 512;
// angle is the amount of rotation in radians
// ox,oy is the origin (center of rotation)
// x,y is the top left of the rectangle
// w,h is the width and height of the rectangle
// returns an array of points as arrays [[x,y],[x1,y1],...]
// Order of returned points topLeft, topRight, bottomRight, bottomLeft
function rotateRect(angle,ox,oy,x,y,w,h){
const xAx = Math.cos(angle); // x axis x
const xAy = Math.sin(angle); // x axis y
x -= ox; // move rectangle onto origin
y -= oy;
return [[ // return array holding the resulting points
x * xAx - y * xAy + ox, // Get the top left rotated position
x * xAy + y * xAx + oy, // and move it back to the origin
], [
(x + w) * xAx - y * xAy + ox, // Get the top right rotated position
(x + w) * xAy + y * xAx + oy,
], [
(x + w) * xAx - (y + h) * xAy + ox, // Get the bottom right rotated position
(x + w) * xAy + (y + h) * xAx + oy,
], [
x * xAx - (y + h) * xAy + ox, // Get the bottom left rotated position
x * xAy + (y + h) * xAx + oy,
]
];
}
function drawRectangle(angle, ox, oy, rect){
ctx.strokeStyle = "red";
ctx.lineWidth = 2;
ctx.setTransform(1,0,0,1,ox,oy);
ctx.rotate(angle);
ctx.strokeRect(rect.x - ox, rect.y - oy, rect.w, rect.h);
ctx.setTransform(1,0,0,1,0,0); // restore transform to default
}
function drawBounds(rotatedRect){
const r = rotatedRect; // alias to make following code more readable
const left = Math.min(r[0][0], r[1][0], r[2][0], r[3][0]);
const right = Math.max(r[0][0], r[1][0], r[2][0], r[3][0]);
const top = Math.min(r[0][1], r[1][1], r[2][1], r[3][1]);
const bottom = Math.max(r[0][1], r[1][1], r[2][1], r[3][1]);
ctx.strokeStyle = "#999";
ctx.lineWidth = 2;
ctx.strokeRect(left, top, right - left, bottom - top);
}
function drawDistance(text,x,y,dist,direction,textOverflowDir){
if(dist.toFixed(2) == 0) { return }
function drawArrows(){
ctx.strokeStyle = "blue";
ctx.lineWidth = 2;
ctx.beginPath();
ctx.lineTo(8,-12);
ctx.lineTo(0,-7);
ctx.lineTo(8,-2);
ctx.moveTo(dist - 8, -12);
ctx.lineTo(dist, -7);
ctx.lineTo(dist - 8, -2);
ctx.stroke();
}
ctx.setTransform(1,0,0,1,x,y);
ctx.rotate(direction);
const width = ctx.measureText(text).width;
ctx.fillStyle = "blue";
ctx.fillRect(-1, - 16, 2, 14);
ctx.fillRect(dist -1, - 16, 2, 14);
if(width + 8 > dist){
ctx.fillRect(1, -8, dist - 2, 2);
drawArrows();
ctx.fillStyle = "black";
if(textOverflowDir < 0){
ctx.fillText(text, - width / 2 - 4, - 9);
}else{
ctx.fillText(text,dist + width / 2 + 6, - 9);
}
}else{
ctx.fillRect(-1, - 8, (dist - width) / 2 - 4, 2);
ctx.fillRect(dist - 1 - ((dist - width) / 2 - 4), - 8, (dist - width) / 2 - 4, 2);
drawArrows();
ctx.fillStyle = "black";
ctx.fillText(text, dist / 2, - 9);
}
ctx.setTransform(1,0,0,1,0,0); //restore default transform
}
// set up the font
ctx.font = "16px arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
var angle = 3.2; // amount to rotate in radians
var ox = 256; // origin top left of rectangle
var oy = 256;
const rect = {
x : 256,
y : 256,
w : 164,
h : 164,
}
function mainLoop(){
ctx.clearRect(0,0,512,512);
angle += 0.01; // slowly rotate
// draw origin
ctx.fillStyle = "#FA2";
ctx.fillRect(ox-1,0,2,512);
ctx.fillRect(0,oy-1,512,2);
const rotatedRect = rotateRect(angle, ox, oy, rect.x, rect.y, rect.w, rect.h);
drawBounds(rotatedRect);
drawRectangle(angle, ox, oy, rect);
const r = rotatedRect; // alias to make following code more readable
var leftOfOrigin = Math.min(r[0][0],r[1][0],r[2][0],r[3][0]) - ox;
var rightOfOrigin = Math.max(r[0][0],r[1][0],r[2][0],r[3][0]) - ox;
var aboveOrigin = Math.min(r[0][1],r[1][1],r[2][1],r[3][1]) - oy;
var belowOrigin = Math.max(r[0][1],r[1][1],r[2][1],r[3][1]) - oy;
// draw distances
drawDistance(leftOfOrigin.toFixed(2), ox + leftOfOrigin, oy +aboveOrigin, - leftOfOrigin, 0, -1);
drawDistance(rightOfOrigin.toFixed(2), ox, oy + aboveOrigin, rightOfOrigin, 0, 1);
drawDistance(belowOrigin.toFixed(2), ox + leftOfOrigin, oy + belowOrigin, belowOrigin, - Math.PI / 2, -1);
drawDistance(aboveOrigin.toFixed(2), ox + leftOfOrigin, oy, - aboveOrigin, - Math.PI / 2, 1);
requestAnimationFrame(mainLoop);
}
requestAnimationFrame(mainLoop);
canvas { border : 2px solid black; }
<canvas id="canvas"></canvas>

I gave this a try, not claiming it to be efficient or the best way, but I couldn't match your expected values. Either I did something wrong or your first set of expected values is incorrect?
'use strict';
const degToRad = deg => (deg * Math.PI) / 180;
const rotatePoint = (pivot, point, radians) => {
const cosA = Math.cos(radians);
const sinA = Math.sin(radians);
const [x, y] = pivot;
const difX = point[0] - x;
const difY = point[1] - y;
return [
Math.round(((cosA * difX) - (sinA * difY)) + x),
Math.round((sinA * difX) + (cosA * difY) + y),
];
};
const rotateSquare = (square, pivot, angle) => {
const radians = degToRad(angle);
return square.map(point => rotatePoint(pivot, point, radians));
};
const extents = (points, pivot) => points.reduce((acc, point) => {
const [difX, difY] = point.map((value, index) => value - pivot[index]);
return [
Math.min(acc[0], difX),
Math.min(acc[1], difY),
Math.max(acc[2], difX),
Math.max(acc[3], difY),
];
}, [0, 0, 0, 0]);
const createSquare = (x, y, size) => [
[x, y],
[x + size, y],
[x + size, y + size],
[x, y + size],
];
const pivot = [0, 0];
const square = createSquare(...pivot, 50);
const angles = [20, 110, 200, 290];
const rotations = angles.map(angle => rotateSquare(square, pivot, angle));
const offsets = rotations.map(rotation => extents(rotation, pivot));
const expecteds = [
[-17, 0, 47, -64],
[-64, -17, 0, 47],
[-47, -64, 17, 0],
[0, -47, 64, 17],
];
offsets.forEach((offset, index) => {
const actual = JSON.stringify(offset);
const expected = JSON.stringify(expecteds[index]);
console.log(
`Actual:${actual}`,
`Expected:${expected}`,
`Same:${actual === expected}`
);
});

Related

HTML5 Canvas rotate gradient around centre with best fit

I want to make a gradient that covers the whole canvas whatever the angle of it.
So I used a method found on a Stack Overflow post which is finally incorrect. The solution is almost right but, in fact, the canvas is not totally covered by the gradient.
It is this answer: https://stackoverflow.com/a/45628098/5594331
(You have to look at the last point named "Example of best fit.")
In my code example below, the yellow part should not be visible because it should be covered by the black and white gradient. This is mostly the code written in Blindman67's answer with some adjustments to highlight the problem.
I have drawn in green the control points of the gradient. With the right calculations, these should be stretched to the edges of the canvas at any angle.
var ctx = canvas.getContext("2d");
var w = canvas.width;
var h = canvas.height;
function bestFitGradient(angle){
var dist = Math.sqrt(w * w + h * h) / 2; // get the diagonal length
var diagAngle = Math.asin((h / 2) / dist); // get the diagonal angle
// Do the symmetry on the angle (move to first quad
var a1 = ((angle % (Math.PI *2))+ Math.PI*4) % (Math.PI * 2);
if(a1 > Math.PI){ a1 -= Math.PI }
if(a1 > Math.PI / 2 && a1 <= Math.PI){ a1 = (Math.PI / 2) - (a1 - (Math.PI / 2)) }
// get angles from center to edges for along and right of gradient
var ang1 = Math.PI/2 - diagAngle - Math.abs(a1);
var ang2 = Math.abs(diagAngle - Math.abs(a1));
// get distance from center to horizontal and vertical edges
var dist1 = Math.cos(ang1) * h;
var dist2 = Math.cos(ang2) * w;
// get the max distance
var scale = Math.max(dist2, dist1) / 2;
// get the vector to the start and end of gradient
var dx = Math.cos(angle) * scale;
var dy = Math.sin(angle) * scale;
var x0 = w / 2 + dx;
var y0 = h / 2 + dy;
var x1 = w / 2 - dx;
var y1 = h / 2 - dy;
// create the gradient
const g = ctx.createLinearGradient(x0, y0, x1, y1);
// add colours
g.addColorStop(0, "yellow");
g.addColorStop(0, "white");
g.addColorStop(.5, "black");
g.addColorStop(1, "white");
g.addColorStop(1, "yellow");
return {
g: g,
x0: x0,
y0: y0,
x1: x1,
y1: y1
};
}
function update(timer){
var r = bestFitGradient(timer / 1000);
// draw gradient
ctx.fillStyle = r.g;
ctx.fillRect(0,0,w,h);
// draw points
ctx.lineWidth = 3;
ctx.fillStyle = '#00FF00';
ctx.strokeStyle = '#FF0000';
ctx.beginPath();
ctx.arc(r.x0, r.y0, 5, 0, 2 * Math.PI, false);
ctx.stroke();
ctx.fill();
ctx.beginPath();
ctx.arc(r.x1, r.y1, 5, 0, 2 * Math.PI, false);
ctx.stroke();
ctx.fill();
requestAnimationFrame(update);
}
requestAnimationFrame(update);
canvas {
border : 2px solid red;
}
<canvas id="canvas" width="300" height="200"></canvas>
In this fiddle there is a function that calculates the distance between a rotated line and a point:
function distanceToPoint(px, py, angle) {
const cx = width / 2;
const cy = height / 2;
return Math.abs((Math.cos(angle) * (px - cx)) - (Math.sin(angle) * (py - cy)));
}
Which is then used to find the maximum distance between the line and the corner points (only two points are considered, because the distances to the other two points are mirrored):
const dist = Math.max(
distanceToPoint(0, 0, angle),
distanceToPoint(0, height, angle)
);
Which can be used to calculate offset points for the end of the gradient:
const ox = Math.cos(angle) * dist;
const oy = Math.sin(angle) * dist;
const gradient = context.createLinearGradient(
width / 2 + ox,
height / 2 + oy,
width / 2 - ox,
height / 2 - oy
)

Calculate 2D rotation javascript HTML canvas

Implemented a canvas, Drawing a square there and get the calculated coordinates,
Can see on the following pic the drawing:
I'm calculating and getting the upleft point X and Y coordinates,
And for the down right coordinates that i need, I'm adding the height and width, as follows:
{ upLeft: { x: position.x, y: position.y }, downRight: { x: position.x + position.width, y: position.y + position.height } },
Now i want to get the same dimensions when i'm rotating the canvas clockwise or anti-clockwise.
So i have the angle, And i try to calculate via the following function:
function getRotatedCoordinates(cx, cy, x, y, angle) {
let radians = (Math.PI / 180) * angle,
cos = Math.cos(radians),
sin = Math.sin(radians),
nx = (cos * (x - cx)) - (sin * (y - cy)) + cx,
ny = (cos * (y - cy)) + (sin * (x - cx)) + cy;
return [nx, ny];
}
And i'm calling the function via the following args and using it.
let newCoords = getRotatedCoordinates(0, 0, position.x, position.y, angle);
position.x = newCoords[0];
position.y = newCoords[1];
So firstly, I'm not sure that the cx and cy points are correct, I'm always entering 0 for both of them.
Secondly, I'm not getting the desired results, They are getting changed but i'm pretty sure that something is wrong with the x and y, So i guess that the function is wrong.
Thanks.
Here is how I would do it:
function getRectangeCoordinates(x, y, width, height, angle) {
let points = [ [x, y] ]
let radians = (Math.PI / 180) * angle;
for (let i = 0; i < 3; i++) {
x += Math.cos(radians) * ((i == 1) ? height : width);
y += Math.sin(radians) * ((i == 1) ? height : width);
points.push([x, y])
radians += Math.PI / 2
}
return points
}
let canvas = document.createElement("canvas");
canvas.width = canvas.height = 140
let ctx = canvas.getContext('2d');
document.body.appendChild(canvas);
function draw(coords, radius) {
for (let i = 0; i < 4; i++) {
ctx.beginPath();
ctx.arc(coords[i][0], coords[i][1], radius, 0, 8);
ctx.moveTo(coords[i][0], coords[i][1]);
let next = (i + 1) % 4
ctx.lineTo(coords[next][0], coords[next][1]);
ctx.stroke();
}
}
let coords = getRectangeCoordinates(20, 10, 120, 40, 15)
console.log(JSON.stringify(coords))
draw(coords, 3)
ctx.strokeStyle = "red";
coords = getRectangeCoordinates(60, 40, 40, 50, 65)
draw(coords, 5)
ctx.strokeStyle = "blue";
coords = getRectangeCoordinates(120, 3, 20, 20, 45)
draw(coords, 2)
In the getRectangeCoordinates I'm returning all corners of a rectangle and the paraments of the function are the top left corner (x, y) the height and width of the rectangle and last the angle.
I'm drawing a few rectangles with different shapes and angles to show how it looks like
The calculations in the function are simple trigonometry here is a visual representation that could help you remember it the next time you need it:

How to draw triangle pointers inside of circle

I realize this is a simple Trigonometry question, but my high school is failing me right now.
Given an angle, that I have converted into radians to get the first point. How do I figure the next two points of the triangle to draw on the canvas, so as to make a small triangle always point outwards to the circle. So lets say Ive drawn a circle of a given radius already. Now I want a function to plot a triangle that sits on the edge of the circle inside of it, that points outwards no matter the angle. (follows the edge, so to speak)
function drawPointerTriangle(ctx, angle){
var radians = angle * (Math.PI/180)
var startX = this.radius + this.radius/1.34 * Math.cos(radians)
var startY = this.radius - this.radius/1.34 * Math.sin(radians)
// This gives me my starting point on the outer edge of the circle, plotted at the angle I need
ctx.moveTo(startX, startY);
// HOW DO I THEN CALCULATE x1,y1 and x2, y2. So that no matter what angle I enter into this function, the arrow/triangle always points outwards to the circle.
ctx.lineTo(x1, y1);
ctx.lineTo(x2, y2);
}
Example
You don't say what type of triangle you want to draw so I suppose that it is an equilateral triangle.
Take a look at this image (credit here)
I will call 3 points p1, p2, p3 from top right to bottom right, counterclockwise.
You can easily calculate the coordinate of three points of the triangle in the coordinate system with the origin is coincident with the triangle's centroid.
Given a point belongs to the edge of the circle and the point p1 that we just calculated, we can calculate parameters of the translation from our main coordinate system to the triangle's coordinate system. Then, we just have to translate the coordinate of two other points back to our main coordinate system. That is (x1,y1) and (x2,y2).
You can take a look at the demo below that is based on your code.
const w = 300;
const h = 300;
function calculateTrianglePoints(angle, width) {
let r = width / Math.sqrt(3);
let firstPoint = [
r * Math.cos(angle),
r * Math.sin(angle),
]
let secondPoint = [
r * Math.cos(angle + 2 * Math.PI / 3),
r * Math.sin(angle + 2 * Math.PI / 3),
]
let thirdPoint = [
r * Math.cos(angle + 4 * Math.PI / 3),
r * Math.sin(angle + 4 * Math.PI / 3),
]
return [firstPoint, secondPoint, thirdPoint]
}
const radius = 100
const triangleWidth = 20;
function drawPointerTriangle(ctx, angle) {
var radians = angle * (Math.PI / 180)
var startX = radius * Math.cos(radians)
var startY = radius * Math.sin(radians)
var [pt0, pt1, pt2] = calculateTrianglePoints(radians, triangleWidth);
var delta = [
startX - pt0[0],
startY - pt0[1],
]
pt1[0] = pt1[0] + delta[0]
pt1[1] = pt1[1] + delta[1]
pt2[0] = pt2[0] + delta[0]
pt2[1] = pt2[1] + delta[1]
ctx.beginPath();
// This gives me my starting point on the outer edge of the circle, plotted at the angle I need
ctx.moveTo(startX, startY);
[x1, y1] = pt1;
[x2, y2] = pt2;
// HOW DO I THEN CALCULATE x1,y1 and x2, y2. So that no matter what angle I enter into this function, the arrow/triangle always points outwards to the circle.
ctx.lineTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.closePath();
ctx.fillStyle = '#FF0000';
ctx.fill();
}
function drawCircle(ctx, radius) {
ctx.beginPath();
ctx.arc(0, 0, radius, 0, 2 * Math.PI);
ctx.closePath();
ctx.fillStyle = '#000';
ctx.fill();
}
function clear(ctx) {
ctx.fillStyle = '#fff';
ctx.fillRect(-w / 2, -h / 2, w, h);
}
function normalizeAngle(pointCoordinate, angle) {
const [x, y] = pointCoordinate;
if (x > 0 && y > 0) return angle;
else if (x > 0 && y < 0) return 360 + angle;
else if (x < 0 && y < 0) return 180 - angle;
else if (x < 0 && y > 0) return 180 - angle;
}
function getAngleFromPoint(point) {
const [x, y] = point;
if (x == 0 && y == 0) return 0;
else if (x == 0) return 90 * (y > 0 ? 1 : -1);
else if (y == 0) return 180 * (x >= 0 ? 0: 1);
const radians = Math.asin(y / Math.sqrt(
x ** 2 + y ** 2
))
return normalizeAngle(point, radians / (Math.PI / 180))
}
document.addEventListener('DOMContentLoaded', function() {
const canvas = document.querySelector('canvas');
const angleText = document.querySelector('.angle');
const ctx = canvas.getContext('2d');
ctx.translate(w / 2, h / 2);
drawCircle(ctx, radius);
drawPointerTriangle(ctx, 0);
canvas.addEventListener('mousemove', _.throttle(function(ev) {
let mouseCoordinate = [
ev.clientX - w / 2,
ev.clientY - h / 2
]
let degAngle = getAngleFromPoint(mouseCoordinate)
clear(ctx);
drawCircle(ctx, radius);
drawPointerTriangle(ctx, degAngle)
angleText.innerText = Math.floor((360 - degAngle)*100)/100;
}, 15))
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.9.1/underscore-min.js"></script>
<canvas width=300 height=300></canvas>
<div class="angle">0</div>
reduce the radius, change the angle and call again cos/sin:
function drawPointerTriangle(ctx, angle)
{
var radians = angle * (Math.PI/180);
var radius = this.radius/1.34;
var startX = this.center.x + radius * Math.cos(radians);
var startY = this.center.y + radius * Math.sin(radians);
ctx.moveTo(startX, startY);
radius *= 0.9;
radians += 0.1;
var x1 = this.center.x + radius * Math.cos(radians);
var y1 = this.center.y + radius * Math.sin(radians);
radians -= 0.2;
var x1 = this.center.x + radius * Math.cos(radians);
var y1 = this.center.y + radius * Math.sin(radians);
ctx.lineTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.lineTo(startX, startY);
}
the resulting triangle's size is proportional to the size of the circle.
in case you need an equilateral, fixed size triangle, use this:
//get h by pythagoras
h = sqrt( a^2 - (a/2)^2 );)
//get phi using arcustangens:
phi = atan( a/2, radius-h );
//reduced radius h by pythagoras:
radius = sqrt( (radius-h)^2 + (a/2)^2 );
radians += phi;
...
radians -= 2*phi;
...

JS calculting width and height for rotated rectangle

am trying to draw rectangles it different angles , initially i know rectangle position data it 0 degree , then sometime i get angle greater than 0 so i have to draw on that angle , for which i then have to rotate points. so currently i can rotate points but do not know to calculate width and height after rotation.
am doing it like ...
// Main rotation function
function rotate(originX, originY,pointX, pointY, angle) {
angle = angle * Math.PI / 180.0;
return {
x: Math.cos(angle) * (pointX-originX) - Math.sin(angle) * (pointY-originY) + originX,
y: Math.sin(angle) * (pointX-originX) + Math.cos(angle) * (pointY-originY) + originY
};
}
// initial Rectangle
ctx.fillStyle = "red";
var x = 200
var y = 200
var w = 80
var h = 20
var angle = 90 ;
ctx.fillRect(x, y, w, h);
// Calculate Center
var cx = (x + (w/2));
var cy = (y + (h/2));
// highlight Center
ctx.fillStyle = "black";
ctx.fillRect(cx,cy, 5, 5);
// Rotate starting x y at angle xxx
var r = rotate(cx,cy,x,y, angle - h );
// highlight roate points
ctx.fillStyle = "yellow";
ctx.fillRect(r.x, r.y, 5, 5);
// rotate Width and Height
var r2 = rotate(cx,cy,x+w,y+h, angle - h );
// highlight roate points
ctx.fillStyle = "green";
ctx.fillRect(r2.x, r2.y, 5, 5);
ctx.save();
so it end i rotated width and height which is ok for single line , but am interested in full rotation of width of height , like it 90 angle old with will become new height and old height will become new width. so any idea how to do it
Fiddle : https://jsfiddle.net/047txgox/
fixed it , hope will help someone
var x = 40
var y = 80
var w = 80
var h = 20
var angle = 120 ;
// Calculate Center
var cx = (x + (w/2));
var cy = (y + (h/2));
context.setTransform(1,0,0,1,0,0)
context.translate(cx, cy);
context.rotate(angle*Math.PI/180);
context.translate(-cx, -cy);
context.rect(x, y, w, h);
context.fillStyle = "#FF00FF";
context.fill();
// highlight Center
context.fillStyle = "black";
context.fillRect(cx,cy, 5, 5);
context.save();
Fiddle : http://jsfiddle.net/vorjcbz7/

How to draw parallel edges (arrows) between vertices with canvas?

I'm working on a flow-network visualization with Javascript.
Vertices are represented as circles and edges are represented as arrows.
Here is my Edge class:
function Edge(u, v) {
this.u = u; // start vertex
this.v = v; // end vertex
this.draw = function() {
var x1 = u.x;
var y1 = u.y;
var x2 = v.x;
var y2 = v.y;
context.beginPath();
context.moveTo(x1, y1);
context.lineTo(x2, y2);
context.stroke();
var dx = x1 - x2;
var dy = y1 - y2;
var length = Math.sqrt(dx * dx + dy * dy);
x1 = x1 - Math.round(dx / ((length / (radius))));
y1 = y1 - Math.round(dy / ((length / (radius))));
x2 = x2 + Math.round(dx / ((length / (radius))));
y2 = y2 + Math.round(dy / ((length / (radius))));
// calculate the angle of the edge
var deg = (Math.atan(dy / dx)) * 180.0 / Math.PI;
if (dx < 0) {
deg += 180.0;
}
if (deg < 0) {
deg += 360.0;
}
// calculate the angle for the two triangle points
var deg1 = ((deg + 25 + 90) % 360) * Math.PI * 2 / 360.0;
var deg2 = ((deg + 335 + 90) % 360) * Math.PI * 2 / 360.0;
// calculate the triangle points
var arrowx = [];
var arrowy = [];
arrowx[0] = x2;
arrowy[0] = y2;
arrowx[1] = Math.round(x2 + 12 * Math.sin(deg1));
arrowy[1] = Math.round(y2 - 12 * Math.cos(deg1));
arrowx[2] = Math.round(x2 + 12 * Math.sin(deg2));
arrowy[2] = Math.round(y2 - 12 * Math.cos(deg2));
context.beginPath();
context.moveTo(arrowx[0], arrowy[0]);
context.lineTo(arrowx[1], arrowy[1]);
context.lineTo(arrowx[2], arrowy[2]);
context.closePath();
context.stroke();
context.fillStyle = "black";
context.fill();
};
}
Given the code
var canvas = document.getElementById('canvas'); // canvas element
var context = canvas.getContext("2d");
context.lineWidth = 1;
context.strokeStyle = "black";
var radius = 20; // vertex radius
var u = {
x: 50,
y: 80
};
var v = {
x: 150,
y: 200
};
var e = new Edge(u, v);
e.draw();
The draw() function will draw an edge between two vertices like this:
If we add the code
var k = new Edge(v, u);
k.draw();
We will get:
but I want to draw edges both directions as following:
(sorry for my bad paint skills)
Of course the vertices and the edge directions are not fixed.
A working example (with drawing vertex fucntion) on JSFiddle:
https://jsfiddle.net/Romansko/0fu01oec/18/
Aligning axis to a line.
It can make everything a little easier if you rotate the rendering to align with the line. Once you do that it is then easy to draw above or below the line as that is just in the y direction and along the line is the x direction.
Thus if you have a line
const line = {
p1 : { x : ? , y : ? },
p2 : { x : ? , y : ? },
};
Convert it to a vector and normalise that vector
// as vector from p1 to p2
var nx = line.p2.x - line.p1.x;
var ny = line.p2.y - line.p1.y;
// then get length
const len = Math.sqrt(nx * nx + ny * ny);
// use the length to normalise the vector
nx /= len;
ny /= len;
The normalised vector represents the new x axis we want to render along, and the y axis is at 90 deg to that. We can use setTransform to set both axis and the origin (0,0) point at the start of the line.
ctx.setTransform(
nx, ny, // the x axis
-ny, nx, // the y axis at 90 deg to the x axis
line.p1.x, line.p1.y // the origin (0,0)
)
Now rendering the line and arrow heads is easy as they are axis aligned
ctx.beginPath();
ctx.lineTo(0,0); // start of line
ctx.lineTo(len,0); // end of line
ctx.stroke();
// add the arrow head
ctx.beginPath();
ctx.lineTo(len,0); // tip of arrow
ctx.lineTo(len - 10, 10);
ctx.lineTo(len - 10, -10);
ctx.fill();
To render two lines offset from the center
var offset = 10;
ctx.beginPath();
ctx.lineTo(0,offset); // start of line
ctx.lineTo(len,offset); // end of line
ctx.moveTo(0,-offset); // start of second line
ctx.lineTo(len,-offset); // end of second line
ctx.stroke();
// add the arrow head
ctx.beginPath();
ctx.lineTo(len,offset); // tip of arrow
ctx.lineTo(len - 10, offset+10);
ctx.lineTo(len - 10, offset-10);
ctx.fill();
offset = -10;
// add second arrow head
ctx.beginPath();
ctx.lineTo(0,offset); // tip of arrow
ctx.lineTo(10, offset+10);
ctx.lineTo(10, offset-10);
ctx.fill();
And you can reset the transform with
ctx.setTransform(1,0,0,1,0,0); // restore default transform

Categories