how i get a coordinate of each rectangle? - javascript

i have tried this,
public drawNumbers(ctx, x1, y1, length, count) {
let angle = 0;
for (let i = 0; i <= count; i++ ) {
angle += 2 * Math.PI / (count );
const x2 = x1 + length * Math.cos(angle),
y2 = y1 + length * Math.sin(angle);
ctx.beginPath();
ctx.fillRect(x2, y2, 10, 20);
ctx.stroke();
}
}
this.canvas.drawNumbers(ctx, this.midX, this.midY, 160, 60);
output:
expected result:
i want to calculate a four coordinate(rectangle) of rotated axis.
How do i detect click event on each rectangle?

Using setTransform
Salix alba answer is a solution though a few too many steps.
It can be done in a single transform using setTransform and applying the translate and rotations in one step. Also the second translation is where you draw the box relative to its origin. When using transforms always draw objects around the center of rotation.
ctx.strokeRect(-10,-10,20,20); // rotation is always around 0,0
const ctx = canvas.getContext("2d");
const centerX = 250;
const centerY = 250;
const radius = 200;
const boxWidth = 10;
const bobLength = 20;
// draw boxs around circle center at cx,cy and radius rad
// box width bw, and box height bh
// spacing optional is the distance between boxes
function drawCircleOfBoxes(cx,cy,rad,bw,bh,spacing = 5){
var steps = ((rad - bw /2) * Math.PI * 2) / (bw + spacing) | 0; // get number boxes that will fit circle
ctx.beginPath();
for(var i = 0; i < steps; i ++){
const ang = (i / steps) * Math.PI * 2;
var xAxisX = Math.cos(ang); // get the direction of he xAxis
var xAxisY = Math.sin(ang);
// set the transform to circle center x Axis out towards box
// y axis at 90 deg to x axis
ctx.setTransform(xAxisX, xAxisY, -xAxisY, xAxisX, cx, cy);
// draw box offset from the center so its center is distance radius
ctx.rect(rad - bh / 2, -bw / 2, bh, bw);
}
ctx.fill();
ctx.stroke();
ctx.setTransform(1,0,0,1,0,0); // reset transform
}
ctx.fillStyle = "#FCD";
ctx.strokeStyle = "#000";
drawCircleOfBoxes(centerX, centerY, radius, boxWidth, bobLength);
<canvas id="canvas" width="500" height="500"></canvas>
Manually apply the transform to a point
If you wish to transform the box in code you can use the transform applied in the above and apply it directly to a set of points. You can not apply it to the ctx.rect function that needs the API transform.
To transform a point px,py you need the the direction of the rotated x axis
const xAx = Math.cos(dirOfXAxis);
const xAy = Math.sin(dirOfXAxis);
You can then move the point px distance along the xAxis and then turn 90 deg and move py distance along the y axis
var x = px * xAx; // move px dist along x axis
var y = px * xAy;
x += py * -xAy; // move px dist along y axis
y += py * xAx;
Then just add the translation
x += translateX;
y += translateY;
Or done in one go
var x = px * xAx - py * xAy + translateX; // move px dist along x axis
var y = px * xAy + py * xAx + translateY;
The snippet shows it in action
const ctx = canvas.getContext("2d");
const centerX = 250;
const centerY = 250;
const radius = 200;
const boxWidth = 10;
const boxLength = 20;
// draw boxs around circle center at cx,cy and radius rad
// box width bw, and box height bh
// spacing optional is the distance between boxes
function drawCircleOfBoxes(cx,cy,rad,bw,bh,spacing = 5){
var points = [ // setout points of box with coord (0,0) as center
{x : bh / 2, y : -bw / 2},
{x : bh / 2 + bh, y : -bw / 2},
{x : bh / 2 + bh, y : -bw / 2 + bw},
{x : bh / 2, y : -bw / 2 + bw},
];
var steps = (((rad - bw /2) * Math.PI * 2) / (bw + spacing) )+ 4| 0; // get number boxes that will fit circle
ctx.beginPath();
for(var i = 0; i < steps; i ++){
const ang = (i / steps) * Math.PI * 2;
const xAx = Math.cos(ang); // get the direction of he xAxis
const xAy = Math.sin(ang);
var first = true
for(const p of points){ // for each point
// Apply the transform to the point after moving it
// to the circle (the p.x + rad)
const x = (p.x + rad) * xAx - p.y * xAy + cx;
const y = (p.x + rad) * xAy + p.y * xAx + cy;
if(first){
ctx.moveTo(x,y);
first = false;
}else{
ctx.lineTo(x,y);
}
}
ctx.closePath();
}
ctx.fill();
ctx.stroke();
}
ctx.fillStyle = "#CFD";
ctx.strokeStyle = "#000";
for(var i = boxLength + 5; i < radius; i += boxLength + 5){
drawCircleOfBoxes(centerX, centerY, i , boxWidth, boxLength);
}
<canvas id="canvas" width="500" height="500"></canvas>

To get rotated rectangles you need to use the transform() method of the graphics context.
Imagine a set of axis at the top left of the drawing area. Any drawing will be done relative to these axis which we can move with transform.
To translate by xshift, yshift
ctx.transform(1,0,0,1, xshift, yshift);
ctx.fillRect(0,0,100,100);
To rotate by angle ang in radians
ctx.transform(Math.cos(ang),Math.sin(ang),
-Math.sin(ang),Math.cos(ang), 0,0);
We can combine things with three transformations. The first moves the origin to the center of the circle. Then rotate the axes about this point,
then shift the axes to where you want the shape to appear. Finally, draw the shape.
for(deg = 0; deg < 360; deg+=20) {
ctx.setTransform(1,0,0,1,0,0); // reset transformation
ang = deg * Math.PI/180;
ctx.transform(1,0,0,1,100,100); // shift origin
ctx.transform(Math.cos(ang),Math.sin(ang),
-Math.sin(ang),Math.cos(ang), 0,0);
ctx.transform(1,0,0,1,50,0);
ctx.fillRect(0,0,30,10);
}
You can achieve the same this using the translate and rotate
for(deg = 0; deg < 360; deg+=20) {
ctx.setTransform(1,0,0,1,0,0); // reset transformation
ang = deg * Math.PI/180;
ctx.translate(100,100); // shift origin
ctx.rotate(ang);
ctx.translate(50,0);
ctx.fillRect(0,0,30,10);
}

Related

How to get an angle by using tangent in javascript?

The red circle is at a known angle of 130°, then I want to draw the navy line from the center to 130° using x and y of the red circle but it looks like I missed the calculation.
Currently, the angle of the Navy line is a reflection to the angle of the red line and if I add minus sign ➖ to *diffX * at line13, it'll work as expected but Why do I need to do that by myself, why can't the Calculations at line 10 and 13 figured out if x should be minus ➖ or plus.
I couldn't figure out where I was wrong..any help/suggestions are appreciated!
let ctx, W = innerWidth,
H = innerHeight;
// params for the red circle
let hypothenus = 100;
let knownAngle = (-130 * Math.PI) / 180;
let x = (W / 2) + Math.cos(knownAngle) * hypothenus;
let y = (H / 2) + Math.sin(knownAngle) * hypothenus;
// params for navy line
let diffX = x - (W / 2);
let diffY = (H / 2) - y;
let dist = Math.hypot(diffX, diffY); // pythagoras
let unknownAngle = -Math.atan2(diffY, diffX);
let newX = (W / 2) + Math.cos(unknownAngle) * dist;
let newY = (H / 2) + Math.sin(unknownAngle) * dist;
let angInDegree1 = ~~Math.abs(knownAngle * 180 / Math.PI);
let angInDegree2 = ~~Math.abs(unknownAngle * 180 / Math.PI) | 0;
const msg = document.getElementById("msg")
msg.innerHTML = `Hypothenus1: ${hypothenus}, angle: ${angInDegree1}<br>`;
msg.innerHTML +=`Hypothenus2: ${dist}, angle: ${angInDegree2}`;
// everything to be rendered to the screen
const update = () => {
if (ctx == null) return;
// drawing the red line
draw.line([W / 2, 0], [W / 2, H], 6, "red");
draw.line([0, H / 2], [W, H / 2], 6, "red");
// the red circle
draw.circle([x, y], 10, "red");
// draw line
draw.line([W / 2, H / 2], [newX, newY], 4, "navy");
}
// utility object for drawing
const draw = {
line(from, to, width, color) {
with(ctx) {
beginPath();
lineWidth = width;
strokeStyle = color;
moveTo(...from);
lineTo(...to);
stroke();
closePath();
}
},
circle(pos, radius, color) {
ctx.beginPath();
ctx.fillStyle = color;
ctx.arc(...pos, radius, 0, 2 * Math.PI);
ctx.fill();
ctx.closePath();
}
}
// init function
const init = () => {
ctx = document.querySelector("#cvs").getContext("2d");
W = ctx.canvas.width = innerWidth;
H = ctx.canvas.height = innerHeight;
update();
}
window.addEventListener("load", init);
<div id="msg"></div>
<canvas id="cvs"></canvas>
Seems you are using too much minuses.
At first, you define angle -130 degrees, close to -3Pi/4. Cosine and sine values for this angle are about -0.7, using hypothenus = 100, we get x =W/2-70, y = H/2-70
diffX = x - W/2 = -70
diffY = y - H/2 = -70
atan2(-70, -70) gives -2.3561 radians = -3/4*Pi = -135 degrees
When you change sign of diffY (note - diffY formula is wrong, not difX one!), you make reflection against OX axis, and change angle sign - that is why another minus before Math.atan2 is required
Corrected code:
let diffX = x - (W / 2);
let diffY = y - (H / 2);
let dist = Math.hypot(diffX, diffY); // pythagoras
let unknownAngle = Math.atan2(diffY, diffX);

How to get axis-aligned bounding box of an ellipse with all given parameters?

void ctx.ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle [, anticlockwise]);
The canvas context 2D API ellipse() method creates an elliptical arc centered at (x, y) with the radii radiusX and radiusY. The path starts at startAngle and ends at endAngle, and travels in the direction given by anticlockwise.
How to get the axis-aligned bounding box of a ellipse with the given parameters:x, y, radiusX, radiusY, rotation, startAngle, endAngle , anticlockwise?
Two solutions
This answer contains two exact solutions it is not an approximation.
The solutions are
boundEllipseAll will find the bounds of the full ellipse. If is a lot less complex than the full solution but you need to ensure that the x radius is greater than the y radius (eg rotate the ellipse 90 deg and swap the x, y radius)
boundEllipse Will find the bounds for a segment of an ellipse. It will work for all ellipses however I have not included the CCW flag. To get the bounds for CCW ellipse swap the start and end angles.
It works by first finding the x, y coords at the start and end points calculating the min and max along each axis. It then calculates the extrema angles, first for the x axis extremes, then the y axis extremes.
If the extrema angle is between the start and end angle, the x,y position of that angle is calculated and the point tested against the min and max extent.
There is a lot of room to optimize as many of the points need only the x, or y parts, and the inner while loop in function extrema can exit early if min and max are change for the axis it is working on.
Example
The example ensures I have not made any mistakes, and uses the second solution, animating an ellipse by moving the start and end angles, rotation, and y axis radius. Drawing the bounding box and the ellipse it bounds.
Update April 2022
Example shows use of both full ellipse boundEllipseAll and ellipse segment boundEllipse.
Note that boundEllipse is only for ellipse segment where endAngle n and startAngle m fit the rule {m <= n <= m + 2Pi}
Fixed bug in boundEllipse that did not show full ellipse when endAngle == startAngle + 2 * Math.PI
const ctx = canvas.getContext("2d");
const W = 200, H= 180;
const TAU = Math.PI * 2;
const ellipse = {
x: W / 2,
y: H / 2,
rx: W / 3,
ry: W / 3,
rotate: 0,
startAng: 0,
endAng: Math.PI * 2,
dir: false,
};
function boundEllipseAll({x, y, rx, ry, rotate}) {
const xAx = Math.cos(rotate);
const xAy = Math.sin(rotate);
const w = ((rx * xAx) ** 2 + (ry * xAy) ** 2) ** 0.5;
const h = ((rx * xAy) ** 2 + (ry * xAx) ** 2) ** 0.5;
return {x: -w + x, y: -h + y, w: w * 2, h: h * 2};
}
function boundEllipse({x, y, rx, ry, rotate, startAng, endAng}) {
const normalizeAng = ang => (ang % TAU + TAU) % TAU;
const getPoint = ang => {
const cA = Math.cos(ang);
const sA = Math.sin(ang);
return [cA * rx * xAx - sA * ry * xAy, cA * rx * xAy + sA * ry * xAx];
}
const extrema = a => { // from angle
var i = 0;
while(i < 4) {
const ang = normalizeAng(a + Math.PI * (i / 2));
if ((ang > startAng && ang < endAng) || (ang + TAU > startAng && ang + TAU < endAng)) {
const [xx, yy] = getPoint(ang);
minX = Math.min(minX, xx);
maxX = Math.max(maxX, xx);
minY = Math.min(minY, yy);
maxY = Math.max(maxY, yy);
}
i ++;
}
}
// UPDATE bug fix (1) for full ellipse
const checkFull = startAng !== endAng; // Update fix (1)
startAng = normalizeAng(startAng);
endAng = normalizeAng(endAng);
(checkFull && startAng === endAng) && (endAng += TAU); // Update fix (1)
const xAx = Math.cos(rotate);
const xAy = Math.sin(rotate);
endAng += endAng < startAng ? TAU : 0;
const [sx, sy] = getPoint(startAng);
const [ex, ey] = getPoint(endAng);
var minX = Math.min(sx, ex);
var maxX = Math.max(sx, ex);
var minY = Math.min(sy, ey);
var maxY = Math.max(sy, ey);
extrema(-Math.atan((ry * xAy) / (rx * xAx))); // Add x Axis extremas
extrema(-Math.atan((rx * xAy) / (ry * xAx))); // Add y Axis extremas
return {x: minX + x, y: minY + y, w: maxX - minX, h: maxY - minY};
}
function drawExtent({x,y,w,h}) {
ctx.moveTo(x,y);
ctx.rect(x, y, w, h);
}
function drawEllipse({x, y, rx, ry, rotate, startAng, endAng, dir}) {
ctx.ellipse(x, y, rx, ry, rotate, startAng, endAng, dir);
}
function drawFullEllipse({x, y, rx, ry, rotate, dir}) {
ctx.ellipse(x, y, rx, ry, rotate, 0, TAU, dir);
}
mainLoop(0);
function mainLoop(time) {
ctx.clearRect(0, 0, W, H);
// Animate ellipse
ellipse.startAng = time / 1000;
ellipse.endAng = time / 2000;
ellipse.rotate = Math.cos(time / 14000) * Math.PI * 2;
ellipse.ry = Math.cos(time / 6000) * (W / 4 - 10) + (W / 4);
// Draw full ellipse and bounding box.
ctx.strokeStyle = "#F008";
ctx.beginPath();
drawFullEllipse(ellipse);
drawExtent(boundEllipseAll(ellipse));
ctx.stroke();
// Draw ellipse segment and bounding box.
ctx.strokeStyle = "#0008";
ctx.beginPath();
drawEllipse(ellipse);
drawExtent(boundEllipse(ellipse));
ctx.stroke();
requestAnimationFrame(mainLoop)
}
canvas { border: 1px solid black }
<canvas id="canvas" width="200" height="180"></canvas>

Apply noise on circle according to mouseX and mouseY

I have created a blob with small points. I want my blob to show noise on its surface according to mouseX and mouseY. I want it to show high noise in the quadrant in which the mouse lies. I want it to be wavy. Below is my code.
var ctx = document.querySelector("canvas").getContext("2d");
var cx = 200;
var cy = 200;
var radius = 50;
var amp = 2;
var mouseX = 0;
var mouseY = 0;
document.querySelector("canvas").addEventListener("mousemove", function (e) {
mouseX = e.clientX;
mouseY = e.clientY;
});
function drawTheBlob() {
ctx.fillStyle = "#000";
ctx.fillRect(0, 0, 400, 400);
ctx.beginPath();
ctx.strokeStyle = "#fff";
for (var a = 0; a < 360; a ++) {
var angle = a * Math.PI/180;
var x = cx + radius * Math.cos(angle) + Math.random() * amp;
var y = cy + radius * Math.sin(angle) + Math.random() * amp;
ctx.lineTo(x, y);
}
ctx.stroke();
ctx.closePath();
requestAnimationFrame(drawTheBlob);
}
drawTheBlob();
<canvas width="400" height="400"></canvas>
Adds a sin wave on the circle. Use ctx.arc to draw the flat part of the circle for speed as drawing many circles with lines will be slow. See code for comments on what is done.
var ctx = document.querySelector("canvas").getContext("2d");
ctx.lineWidth = 3;
ctx.lineJoin = "round";
var cx = 100;
var cy = 100;
var radius = 50;
var mouseX = 0;
var mouseY = 0;
const quadWidth = Math.PI / 2; // area of effect PI/2 is 90 degree
const steps = radius / quadWidth; // number steps around the circle matches 1 pixel per step,
const noiseAmpMax = 5; // in pixels
const noiseWaveMoveSpeed = 2; // speed of waves on circle in radians per second
const noiseWaveFreq = 16; // how many waves per 360 deg
document.querySelector("canvas").addEventListener("mousemove", function(e) {
mouseX = e.clientX;
mouseY = e.clientY;
});
function drawTheBlob(time) { // time is passed from the requestAnimationFrame call
var amp = 0; // amplitude of noise
var wavePos = ((time / 1000) * Math.PI) * noiseWaveMoveSpeed;
var mouseDir = Math.atan2(mouseY - cy, mouseX - cx);
ctx.fillStyle = "#000";
ctx.fillRect(0, 0, 400, 400);
ctx.beginPath();
ctx.strokeStyle = "#fff";
ctx.fillStyle = "red";
// draw arc for parts that have no noise as it is a log quicker
ctx.arc(cx, cy, radius, mouseDir + quadWidth / 2, mouseDir + Math.PI * 2 - quadWidth / 2);
for (var a = 0; a < 1; a += 1 / steps) {
var angle = (mouseDir - quadWidth / 2) + a * quadWidth;
var angDist = Math.abs(angle - mouseDir); // find angular distance from mouse
// as a positive value, it does not mater
// what the sign is
if (angDist < quadWidth / 2) { // is angle distance within the range of effect
// normalise the distance (make it 0 to 1)
amp = 1 - angDist / (quadWidth / 2);
} else {
amp = 0; // no noise
}
// amp will be zero if away from mouse direction and 0 to 1 the closer to
// mouse angle it gets.
// add a sin wave to the radius and scale it by amp
var dist = radius + Math.sin(wavePos + noiseWaveFreq * angle) * noiseAmpMax * amp;
var x = cx + dist * Math.cos(angle);
var y = cy + dist * Math.sin(angle);
ctx.lineTo(x, y);
}
ctx.closePath(); // use close path to close the gap (only needed if you need to draw a line from the end to the start. It is not needed to match beginPath
ctx.fill();
ctx.stroke();
requestAnimationFrame(drawTheBlob);
}
requestAnimationFrame(drawTheBlob); // start this way so that you get the time argument
<canvas width="200" height="200"></canvas>
How it works.
Mouse direction
First we need the direction from the circle to the mouse. To do that we use the function Math.atan2 It takes the vector from the circle to the mouse and returns the direction in radians. The function is a little weird as it takes y first, then x.
var mouseDir = Math.atan2(mouseY - cy, mouseX - cx);
Draw arc to save CPU time
Now that we have the direction to the mouse we can draw the parts of the circle that has no noise using arc .
ctx.arc(cx, cy, radius, mouseDir + quadWidth / 2, mouseDir + Math.PI * 2 - quadWidth / 2);
The variable quadWidth is angular size of the noise bit so from the mouseDir we add half that angular width and draw the arc around to mouseDir plus 360deg take half the quadWidth.
Quick word on Radians
Almost all programming languages use radians to define angles, 360deg is equal to 2 * PI or 2 * 3.1415, which can be hard to get your head around, but there is good reason to use radians. For now just remember that a full circle in radians is 2 * Math.PI = 360deg, Math.PI = 180deg, Math.PI / 2 = 90deg, Math.PI / 4 = 45Deg and Math.PI / 180 = 1deg. You dont have to remember the digits just Math.PI is half a circle.
quadWidth from above is a constant defined as const quadWidth = Math.PI / 2; which is 90deg.
The for loop
The for loop only draws the (Math.PI / 2) 90deg section around the mouseDir, from 45 deg left to 45 right. or whatever you set quadWidth to.
for (var a = 0; a < 1; a += 1 / steps) {
I loop from 0 to 1 the number of steps that give a reasonably smooth curve. We can find what part of the noisy arc we are drawing by multiplying the value a *
quadWidth and adding that to the mouseDir - quadWidth / 2. This means that we start at mouseDir - 45deg and move clock wise to mouseDir + 45deg
var angle = (mouseDir - quadWidth / 2) + a * quadWidth;
Next i find how far that angle is from the mouseDir (could optimize it here a bit here but this way is a little more flexible, if you want to draw more noise on the other part of the arc)
var angDist = Math.abs(angle - mouseDir);
If that number is less than quadWidth / 2 convert the value to the range 0 to 1 where 0 is at the angle furthest from the mouse direction and 1 closest.
if (angDist < quadWidth / 2) {
amp = 1 - angDist / (quadWidth / 2);
} else {
amp = 0;
}
The sin wave
Now we calculate the radius of the circle at the current angle and add a sin wave to it. First the radius then the sin wave multiplied by the amp calculated in the last step. Where amp is zero none of the sin wave is added, where amp is 1 (in the direction of the mouse) the full sin wave is added.
var dist = radius + Math.sin(wavePos + noiseWaveFreq * angle) * noiseAmpMax * amp
The values wavePos, noiseWaveFreq, and noiseAmpMax control the animation of the sin wave. Play around with these values to get a feel of what they do, wavePos is calculated based on the time at the start of the function.
With dist we can calculate the x,y position for the next line of the circle
var x = cx + dist * Math.cos(angle);
var y = cy + dist * Math.sin(angle);
ctx.lineTo(x, y);
Experiment
I added some constants
const quadWidth = Math.PI / 2; // area of effect PI/2 is 90 degree
const steps = radius / quadWidth; // number steps around the circle matches 1 pixel per step,
const noiseAmpMax = 5; // in pixels
const noiseWaveMoveSpeed = 2; // speed of waves on circle in radians per second
const noiseWaveFreq = 16; // how many waves per 360 deg
To get a understanding what they do experiment and change the numbers to see what happens.

How to draw herringbone pattern on html canvas

I Have to draw Herringbone pattern on canvas and fill with image
some one please help me I am new to canvas 2d drawing.
I need to draw mixed tiles with cross pattern (Herringbone)
var canvas = this.__canvas = new fabric.Canvas('canvas');
var canvas_objects = canvas._objects;
// create a rectangle with a fill and a different color stroke
var left = 150;
var top = 150;
var x=20;
var y=40;
var rect = new fabric.Rect({
left: left,
top: top,
width: x,
height: y,
angle:45,
fill: 'rgba(255,127,39,1)',
stroke: 'rgba(34,177,76,1)',
strokeWidth:0,
originX:'right',
originY:'top',
centeredRotation: false
});
canvas.add(rect);
for(var i=0;i<15;i++){
var rectangle = fabric.util.object.clone(getLastobject());
if(i%2==0){
rectangle.left = rectangle.oCoords.tr.x;
rectangle.top = rectangle.oCoords.tr.y;
rectangle.originX='right';
rectangle.originY='top';
rectangle.angle =-45;
}else{
fabric.log('rectangle: ', rectangle.toJSON());
rectangle.left = rectangle.oCoords.tl.x;
rectangle.top = rectangle.oCoords.tl.y;
fabric.log('rectangle: ', rectangle.toJSON());
rectangle.originX='left';
rectangle.originY='top';
rectangle.angle =45;
}
//rectangle.angle -90;
canvas.add(rectangle);
}
fabric.log('rectangle: ', canvas.toJSON());
canvas.renderAll();
function getLastobject(){
var last = null;
if(canvas_objects.length !== 0){
last = canvas_objects[canvas_objects.length -1]; //Get last object
}
return last;
}
How to draw this pattern in canvas using svg or 2d,3d method. If any third party library that also Ok for me.
I don't know where to start and how to draw this complex pattern.
some one please help me to draw this pattern with rectangle fill with dynamic color on canvas.
Here is a sample of the output I need: (herringbone pattern)
I tried something similar using fabric.js library here is my JSFiddle
Trippy disco flooring
To get the pattern you need to draw rectangles one horizontal tiled one space left or right for each row down and the same for the vertical rectangle.
The rectangle has an aspect of width 2 time height.
Drawing the pattern is simple.
Rotating is easy as well the harder part is finding where to draw the tiles for the rotation.
To do that I create a inverse matrix of the rotation (it reverses a rotation). I then apply that rotation to the 4 corners of the canvas 0,0, width,0 width,height and 0,height this gives me 4 points in the rotated space that are at the edges of the canvas.
As I draw the tiles from left to right top to bottom I find the min corners for the top left, and the max corners for the bottom right, expand it out a little so I dont miss any pixels and draw the tiles with a transformation set the the rotation.
As I could not workout what angle you wanted it at the function will draw it at any angle. On is animated, the other is at 60deg clockwise.
Warning demo contains flashing content.
Update The flashing was way to out there, so have made a few changes, now colours are a more pleasing blend and have fixed absolute positions, and have tied the tile origin to the mouse position, clicking the mouse button will cycle through some sizes as well.
const ctx = canvas.getContext("2d");
const colours = []
for(let i = 0; i < 1; i += 1/80){
colours.push(`hsl(${Math.floor(i * 360)},${Math.floor((Math.sin(i * Math.PI *4)+1) * 50)}%,${Math.floor(Math.sin(i * Math.PI *8)* 25 + 50)}%)`)
}
const sizes = [0.04,0.08,0.1,0.2];
var currentSize = 0;
const origin = {x : canvas.width / 2, y : canvas.height / 2};
var size = Math.min(canvas.width * 0.2, canvas.height * 0.2);
function drawPattern(size,origin,ang){
const xAx = Math.cos(ang); // define the direction of xAxis
const xAy = Math.sin(ang);
ctx.setTransform(1,0,0,1,0,0);
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.setTransform(xAx,xAy,-xAy,xAx,origin.x,origin.y);
function getExtent(xAx,xAy,origin){
const im = [1,0,0,1]; // inverse matrix
const dot = xAx * xAx + xAy * xAy;
im[0] = xAx / dot;
im[1] = -xAy / dot;
im[2] = xAy / dot;
im[3] = xAx / dot;
const toWorld = (x,y) => {
var point = {};
var xx = x - origin.x;
var yy = y - origin.y;
point.x = xx * im[0] + yy * im[2];
point.y = xx * im[1] + yy * im[3];
return point;
}
return [
toWorld(0,0),
toWorld(canvas.width,0),
toWorld(canvas.width,canvas.height),
toWorld(0,canvas.height),
]
}
const corners = getExtent(xAx,xAy,origin);
var startX = Math.min(corners[0].x,corners[1].x,corners[2].x,corners[3].x);
var endX = Math.max(corners[0].x,corners[1].x,corners[2].x,corners[3].x);
var startY = Math.min(corners[0].y,corners[1].y,corners[2].y,corners[3].y);
var endY = Math.max(corners[0].y,corners[1].y,corners[2].y,corners[3].y);
startX = Math.floor(startX / size) - 2;
endX = Math.floor(endX / size) + 2;
startY = Math.floor(startY / size) - 2;
endY = Math.floor(endY / size) + 2;
// draw the pattern
ctx.lineWidth = size * 0.1;
ctx.lineJoin = "round";
ctx.strokeStyle = "black";
var colourIndex = 0;
for(var y = startY; y <endY; y+=1){
for(var x = startX; x <endX; x+=1){
if((x + y) % 4 === 0){
colourIndex = Math.floor(Math.abs(Math.sin(x)*size + Math.sin(y) * 20));
ctx.fillStyle = colours[(colourIndex++)% colours.length];
ctx.fillRect(x * size,y * size,size * 2,size);
ctx.strokeRect(x * size,y * size,size * 2,size);
x += 2;
ctx.fillStyle = colours[(colourIndex++)% colours.length];
ctx.fillRect(x * size,y * size, size, size * 2);
ctx.strokeRect(x * size,y * size, size, size * 2);
x += 1;
}
}
}
}
// Animate it all
var update = true; // flag to indecate something needs updating
function mainLoop(time){
// if window size has changed update canvas to new size
if(canvas.width !== innerWidth || canvas.height !== innerHeight || update){
canvas.width = innerWidth;
canvas.height = innerHeight
origin.x = canvas.width / 2;
origin.y = canvas.height / 2;
size = Math.min(canvas.width, canvas.height) * sizes[currentSize % sizes.length];
update = false;
}
if(mouse.buttonRaw !== 0){
mouse.buttonRaw = 0;
currentSize += 1;
update = true;
}
// draw the patter
drawPattern(size,mouse,time/2000);
requestAnimationFrame(mainLoop);
}
requestAnimationFrame(mainLoop);
mouse = (function () {
function preventDefault(e) { e.preventDefault() }
var m; // alias for mouse
var mouse = {
x : 0, y : 0, // mouse position
buttonRaw : 0,
over : false, // true if mouse over the element
buttonOnMasks : [0b1, 0b10, 0b100], // mouse button on masks
buttonOffMasks : [0b110, 0b101, 0b011], // mouse button off masks
bounds : null,
eventNames : "mousemove,mousedown,mouseup,mouseout,mouseover".split(","),
event(e) {
var t = e.type;
m.bounds = m.element.getBoundingClientRect();
m.x = e.pageX - m.bounds.left - scrollX;
m.y = e.pageY - m.bounds.top - scrollY;
if (t === "mousedown") { m.buttonRaw |= m.buttonOnMasks[e.which - 1] }
else if (t === "mouseup") { m.buttonRaw &= m.buttonOffMasks[e.which - 1] }
else if (t === "mouseout") { m.over = false }
else if (t === "mouseover") { m.over = true }
e.preventDefault();
},
start(element) {
if (m.element !== undefined) { m.remove() }
m.element = element === undefined ? document : element;
m.eventNames.forEach(name => document.addEventListener(name, mouse.event) );
document.addEventListener("contextmenu", preventDefault, false);
},
}
m = mouse;
return mouse;
})();
mouse.start(canvas);
canvas {
position : absolute;
top : 0px;
left : 0px;
}
<canvas id=canvas></canvas>
Un-animated version at 60Deg
const ctx = canvas.getContext("2d");
const colours = ["red","green","yellow","orange","blue","cyan","magenta"]
const origin = {x : canvas.width / 2, y : canvas.height / 2};
var size = Math.min(canvas.width * 0.2, canvas.height * 0.2);
function drawPattern(size,origin,ang){
const xAx = Math.cos(ang); // define the direction of xAxis
const xAy = Math.sin(ang);
ctx.setTransform(1,0,0,1,0,0);
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.setTransform(xAx,xAy,-xAy,xAx,origin.x,origin.y);
function getExtent(xAx,xAy,origin){
const im = [1,0,0,1]; // inverse matrix
const dot = xAx * xAx + xAy * xAy;
im[0] = xAx / dot;
im[1] = -xAy / dot;
im[2] = xAy / dot;
im[3] = xAx / dot;
const toWorld = (x,y) => {
var point = {};
var xx = x - origin.x;
var yy = y - origin.y;
point.x = xx * im[0] + yy * im[2];
point.y = xx * im[1] + yy * im[3];
return point;
}
return [
toWorld(0,0),
toWorld(canvas.width,0),
toWorld(canvas.width,canvas.height),
toWorld(0,canvas.height),
]
}
const corners = getExtent(xAx,xAy,origin);
var startX = Math.min(corners[0].x,corners[1].x,corners[2].x,corners[3].x);
var endX = Math.max(corners[0].x,corners[1].x,corners[2].x,corners[3].x);
var startY = Math.min(corners[0].y,corners[1].y,corners[2].y,corners[3].y);
var endY = Math.max(corners[0].y,corners[1].y,corners[2].y,corners[3].y);
startX = Math.floor(startX / size) - 4;
endX = Math.floor(endX / size) + 4;
startY = Math.floor(startY / size) - 4;
endY = Math.floor(endY / size) + 4;
// draw the pattern
ctx.lineWidth = 5;
ctx.lineJoin = "round";
ctx.strokeStyle = "black";
for(var y = startY; y <endY; y+=1){
for(var x = startX; x <endX; x+=1){
ctx.fillStyle = colours[Math.floor(Math.random() * colours.length)];
if((x + y) % 4 === 0){
ctx.fillRect(x * size,y * size,size * 2,size);
ctx.strokeRect(x * size,y * size,size * 2,size);
x += 2;
ctx.fillStyle = colours[Math.floor(Math.random() * colours.length)];
ctx.fillRect(x * size,y * size, size, size * 2);
ctx.strokeRect(x * size,y * size, size, size * 2);
x += 1;
}
}
}
}
canvas.width = innerWidth;
canvas.height = innerHeight
origin.x = canvas.width / 2;
origin.y = canvas.height / 2;
size = Math.min(canvas.width * 0.2, canvas.height * 0.2);
drawPattern(size,origin,Math.PI / 3);
canvas {
position : absolute;
top : 0px;
left : 0px;
}
<canvas id=canvas></canvas>
The best way to approach this is to examine the pattern and analyse its symmetry and how it repeats.
You can look at this several ways. For example, you could rotate the patter 45 degrees so that the tiles are plain orthogonal rectangles. But let's just look at it how it is. I am going to assume you are happy with it with 45deg tiles.
Like the tiles themselves, it turns out the pattern has a 2:1 ratio. If we repeat this pattern horizontally and vertically, we can fill the canvas with the completed pattern.
We can see there are five tiles that overlap with our pattern block. However we don't need to draw them all when we draw each pattern block. We can take advantage of the fact that blocks are repeated, and we can leave the drawing of some tiles to later rows and columns.
Let's assume we are drawing the pattern blocks from left to right and top to bottom. Which tiles do we need to draw, at a minimum, to ensure this pattern block gets completely drawn (taking into account adjacent pattern blocks)?
Since we will be starting at the top left (and moving right and downwards), we'll need to draw tile 2. That's because that tile won't get drawn by either the block below us, or the block to the right of us. The same applies to tile 3.
It turns out those two are all we'll need to draw for each pattern block. Tile 1 and 4 will be drawn when the pattern block below us draws their tile 2 and 3 respectively. Tile 5 will be drawn when the pattern block to the south-east of us draws their tile 1.
We just need to remember that we may need to draw an extra column on the right-hand side, and at the bottom, to ensure those end-of-row and end-of-column pattern blocks get completely drawn.
The last thing to work out is how big our pattern blocks are.
Let's call the short side of the tile a and the long side b. We know that b = 2 * a. And we can work out, using Pythagoras Theorem, that the height of the pattern block will be:
h = sqrt(a^2 + a^2)
= sqrt(2 * a^2)
= sqrt(2) * a
The width of the pattern block we can see will be w = 2 * h.
Now that we've worked out how to draw the pattern, let's implement our algorithm.
const a = 60;
const b = 120;
const h = 50 * Math.sqrt(2);
const w = h * 2;
const h2 = h / 2; // How far tile 1 sticks out to the left of the pattern block
// Set of colours for the tiles
const colours = ["red","cornsilk","black","limegreen","deepskyblue",
"mediumorchid", "lightgrey", "grey"]
const canvas = document.getElementById("herringbone");
const ctx = canvas.getContext("2d");
// Set a universal stroke colour and width
ctx.strokeStyle = "black";
ctx.lineWidth = 4;
// Loop through the pattern block rows
for (var y=0; y < (canvas.height + h); y+=h)
{
// Loop through the pattern block columns
for (var x=0; x < (canvas.width + w); x+=w)
{
// Draw tile "2"
// I'm just going to draw a path for simplicity, rather than
// worrying about drawing a rectangle with rotation and translates
ctx.beginPath();
ctx.moveTo(x - h2, y - h2);
ctx.lineTo(x, y - h);
ctx.lineTo(x + h, y);
ctx.lineTo(x + h2, y + h2);
ctx.closePath();
ctx.fillStyle = colours[Math.floor(Math.random() * colours.length)];
ctx.fill();
ctx.stroke();
// Draw tile "3"
ctx.beginPath();
ctx.moveTo(x + h2, y + h2);
ctx.lineTo(x + w - h2, y - h2);
ctx.lineTo(x + w, y);
ctx.lineTo(x + h, y + h);
ctx.closePath();
ctx.fillStyle = colours[Math.floor(Math.random() * colours.length)];
ctx.fill();
ctx.stroke();
}
}
<canvas id="herringbone" width="500" height="400"></canvas>

Drawing a spiral on an HTML canvas using JavaScript

I have searched and haven't found anything really on how to draw spirals in canvas using JavaScript.
I thought it might be possible to do it with the bezier curve and if that didn't work use lineTo(), but that seemed a lot harder.
Also, to do that I'm guessing I would have to use trigonometry and graphing with polar coordinates and its been a while since I did that. If that is the case could you point me in the right direction on the math.
The Archimedean spiral is expressed as r=a+b(angle). Convert that into x, y coordinate, it will be expressed as x=(a+b*angle)*cos(angle), y=(a+b*angle)*sin(angle). Then you can put angle in a for loop and do something like this:
for (i=0; i< 720; i++) {
angle = 0.1 * i;
x=(1+angle)*Math.cos(angle);
y=(1+angle)*Math.sin(angle);
context.lineTo(x, y);
}
Note the above assumes a = 1 and b = 1.
Here is a jsfiddle link: http://jsfiddle.net/jingshaochen/xJc7M/
This is a slightly-changed, javascript-ified version of a Java spiral I once borrowed from here
It uses lineTo() and its not all that hard.
<!DOCTYPE HTML>
<html><body>
<canvas id="myCanvas" width="300" height="300" style="border:1px solid #c3c3c3;"></canvas>
<script type="text/javascript">
var c=document.getElementById("myCanvas");
var cxt=c.getContext("2d");
var centerX = 150;
var centerY = 150;
cxt.moveTo(centerX, centerY);
var STEPS_PER_ROTATION = 60;
var increment = 2*Math.PI/STEPS_PER_ROTATION;
var theta = increment;
while( theta < 40*Math.PI) {
var newX = centerX + theta * Math.cos(theta);
var newY = centerY + theta * Math.sin(theta);
cxt.lineTo(newX, newY);
theta = theta + increment;
}
cxt.stroke();
</script></body></html>
Here's a function I wrote for drawing Archimedean spirals:
CanvasRenderingContext2D.prototype.drawArchimedeanSpiral =
CanvasRenderingContext2D.prototype.drawArchimedeanSpiral ||
function(centerX, centerY, stepCount, loopCount,
innerDistance, loopSpacing, rotation)
{
this.beginPath();
var stepSize = 2 * Math.PI / stepCount,
endAngle = 2 * Math.PI * loopCount,
finished = false;
for (var angle = 0; !finished; angle += stepSize) {
// Ensure that the spiral finishes at the correct place,
// avoiding any drift introduced by cumulative errors from
// repeatedly adding floating point numbers.
if (angle > endAngle) {
angle = endAngle;
finished = true;
}
var scalar = innerDistance + loopSpacing * angle,
rotatedAngle = angle + rotation,
x = centerX + scalar * Math.cos(rotatedAngle),
y = centerY + scalar * Math.sin(rotatedAngle);
this.lineTo(x, y);
}
this.stroke();
}
there is a fine free tool that will help if you have illustrator
ai2canvas
it will create all the curves to javascript in html canvas tag for you!
(if you are looking for archmedes spiral than you will first have to get it from coreldraw and copy that to illustrator, because the default spiral tool enlarges the angle with each point)
this is example of drawing spiral using function below:
spiral(ctx, {
start: {//starting point of spiral
x: 200,
y: 200
},
angle: 30 * (Math.PI / 180), //angle from starting point
direction: false,
radius: 100, //radius from starting point in direction of angle
number: 3 // number of circles
});
spiral drawing code:
spiral = function(ctx,obj) {
var center, eAngle, increment, newX, newY, progress, sAngle, tempTheta, theta;
sAngle = Math.PI + obj.angle;
eAngle = sAngle + Math.PI * 2 * obj.number;
center = {
x: obj.start.x + Math.cos(obj.angle) * obj.radius,
y: obj.start.y + Math.sin(obj.angle) * obj.radius
};
increment = 2 * Math.PI / 60/*steps per rotation*/;
theta = sAngle;
ctx.beginPath();
ctx.moveTo(center.x, center.y);
while (theta <= eAngle + increment) {
progress = (theta - sAngle) / (eAngle - sAngle);
tempTheta = obj.direction ? theta : -1 * (theta - 2 * obj.angle);
newX = obj.radius * Math.cos(tempTheta) * progress;
newY = obj.radius * Math.sin(tempTheta) * progress;
theta += increment;
ctx.lineTo(center.x + newX, center.y + newY);
}
ctx.stroke();
};
The following code approximates a spiral as a collection of quarters of a circle each with a slightly larger radius. It might look worse than an Archimedes spiral for small turning numbers but it should run faster.
function drawSpiral(ctx, centerx, centery, innerRadius, outerRadius, turns=2, startAngle=0){
ctx.save();
ctx.translate(centerx, centery);
ctx.rotate(startAngle);
let r = innerRadius;
let turns_ = Math.floor(turns*4)/4;
let dr = (outerRadius - innerRadius)/turns_/4;
let cx = 0, cy = 0;
let directionx = 0, directiony = -1;
ctx.beginPath();
let angle=0;
for(; angle < turns_*2*Math.PI; angle += Math.PI/2){
//draw a quarter arc around the center point (x, cy)
ctx.arc( cx, cy, r, angle, angle + Math.PI/2);
//move the center point and increase the radius so we can draw a bigger arc
cx += directionx*dr;
cy += directiony*dr;
r+= dr;
//rotate direction vector by 90 degrees
[directionx, directiony] = [ - directiony, directionx ];
}
//draw the remainder of the last quarter turn
ctx.arc( cx, cy, r, angle, angle + 2*Math.PI*( turns - turns_ ))
ctx.stroke();
ctx.restore();
}
Result:

Categories

Resources