I'm trying to draw some charts on a Canvas Area. My Problem is following...
I have 1-4 (or more) circles to draw. The canvas size is like 500 x 400 px. How can i now calculate the max Radius of each circle to place all on this canvas and get the position (center x/y) of each circle? So each circle could be optimal placed on the area with some margin to each other?
here some example screens to show you what i mean...
thanks a lot!
To calculate the maximum radius you can use
var numberOfSections = 4;
var width = 500;
var height = 400;
var R = Math.sqrt((width * height) / numberOfSections) / 2
var MX = Math.round(width / (R * 2)); // max amount of squares that can fit on the width
var MY = Math.round(height / (R * 2)); // max amount of squares that can fit on the height
var skipLast = 0;
var numOfCalculatedCircles = MX*MY;
if(numOfCalculatedCircles != numberOfSections) {
if(numOfCalculatedCircles < numberOfSections) {
console.log('numOfCalculatedCircles',numOfCalculatedCircles);
MX = MX + Math.ceil((numberOfSections - numOfCalculatedCircles)/MY);
if(MX*MY != numberOfSections) {
skipLast = Math.abs(MX*MY - numberOfSections);
}
} else {
skipLast = numOfCalculatedCircles - numberOfSections;;
}
console.log('MX*MY',MX*MY);
}
// recalculate the radius for X
if (R * 2 * MX > width) {
R = (width/2) / MX;
}
// recalculate the radius for Y
if (R * 2 * MY > height) {
R = (height/2) / MY
}
Calculate the margins for X and Y:
var circlesWidth = R * 2 * MX;
var circlesHeight = R * 2 * MY;
var marginX = 0;
var marginY = 0;
if (circlesWidth < width) {
marginX = (width - circlesWidth) / 2
}
if (circlesHeight < height) {
marginY = (height - circlesHeight) / 2
}
After that you can calculate the centers:
var RY = marginY + R;
var radiusPadding = 10;
for (var i = 0; i < MY; i++) {
var RX = marginX + R;
for (var j = 0; j < MX; j++) {
if(i === MY - 1) {
if(j === MX - skipLast) {
break;
}
}
canvas.drawArc({
fromCenter: true,
strokeStyle: 'red',
strokeWidth: 1,
start: 0,
end: 360,
radius: R - radiusPadding,
x: RX,
y: RY
});
RX += 2 * R;
}
RY += 2 * R;
}
Hope this helps.
UPDATE: It is still incomplete but it may work in this particular example: http://jsfiddle.net/dhM96/4/
You are not giving enough of your placement constraints.
Anyway, assuming a free space of F pixels along the rectangle edges and f between the circles, the maximum radius on X is Rx = (Width - 2 F - (Nx-1) f) / 2 and on Y, R y = (Height - 2F - (Ny-1) f) / 2. (Nx circles horizontally, Ny vertically.) Take the smallest of the two.
The centers will be at (F + (2 Rx + f) Ix + Rx, F + (2 Ry + f) Iy + Ry), 0 <= Ix < Nx, 0 <= Iy < Ny.
The Knapsack problem you are asking about is hard to solve. Best approach in your case is to use a given table such as http://www.packomania.com. If you can, restrict yourself to a square.
Related
I am working on a raycaster and followed this tutorial: https://dev.opera.com/articles/3d-games-with-canvas-and-raycasting-part-1/
My questions is about a Bug which draws / calculates Walls in diffrent Width depending on where on the canvas its drawn (so how big the ray angle is to the players point of direction (center view)):
The Walls in drawn in the middle (1.) are small, but those on the left or right (2.) side of the screen are drawn wider. Its easiest to understand when you look at the image. I think I just got the Math wrong, maybe rounded up somewhere I shouldnt but I havent found it yet or could think of any reason this error accures. Its made in a HTML Canvas using JavaScript.
In my first function I am sending out a ray for each x pixel of my canvas:
let resolution = Math.ceil(canvas.width / this.resolution); //canvas width = 1600, resolution = 1
let id = 0;
for (let x = 0; x < resolution; x++) {
let viewDist = (canvas.width / this.resolution) / Math.tan((this.fov / 2)); //fov 90 in rad
let rayx = (-resolution / 2 + x) * this.resolution;
let rayDist = Math.sqrt(rayx * rayx + viewDist * viewDist);
let rayAngle = Math.asin(rayx / rayDist);
let wall = this.castWall(this.pod * Math.PI / 180 + rayAngle);
this.drawWall(x, wall);
}
But I dont think theres anything wrong here. In the second function I am castinbg each ray and giving back the distance to the hit wall. My blocks / walls are 50 wide. My map is stored in and 2D number Array -> this.map.grid, this.map.width holds how many block there are in x direction, this.map.height holds the count in y direction.
castWall(angle) {
const PI2 = Math.PI * 2;
angle %= PI2;
if (angle < 0) {
angle += PI2;
}
let right = angle > PI2 * 0.75 || angle < PI2 * 0.25;
let up = angle < 0 || angle > Math.PI;
let sin = Math.sin(angle);
let cos = Math.cos(angle);
let dist = 0;
let textureX;
let texture;
let slope = sin / cos;
let dXVer = right ? 1 : -1;
let dYVer = dXVer * slope;
let px = this.x / 50;
let py = this.y / 50;
let x = right ? Math.ceil(px) : Math.floor(px);
let y = py + (x - px) * slope;
while (x >= 0 && x < this.map.width && y >= 0 && y < this.map.height) {
let wallX = Math.floor(x + (right ? 0 : -1));
let wallY = Math.floor(y);
if (this.map.grid[wallY][wallX] > 0) {
dist = Math.sqrt(Math.pow(x - px, 2) + Math.pow(y - py, 2));
texture = this.map.grid[wallY][wallX];
textureX = (y * 50) % 50;
if (right) {
textureX = 50 - textureX;
}
break;
}
x += dXVer;
y += dYVer;
}
slope = cos / sin;
let dYHor = up ? -1 : 1;
let dXHor = dYHor * slope;
y = up ? Math.floor(py) : Math.ceil(py);
x = px + (y - py) * slope;
while (x >= 0 && x < this.map.width && y >= 0 && y < this.map.height) {
let wallY = Math.floor(y + (up ? -1 : 0));
let wallX = Math.floor(x);
if (this.map.grid[wallY][wallX] > 0) {
let distHor = Math.sqrt(Math.pow(x - px, 2) + Math.pow(y - py, 2));
if (dist === 0 || distHor < dist) {
dist = distHor;
texture = this.map.grid[wallY][wallX];
textureX = (x * 50) % 50;
if (up) {
textureX = 50 - textureX;
}
}
break;
}
x += dXHor;
y += dYHor;
}
return {
distance: dist,
texture: texture,
textureX: textureX
};`
Ive also tried raycasting with other algorithms (Bresenham & DDA) but I never got them really to work. This is the only one which works for me. If you have any questions about the code feel free to ask.
I'm trying to draw 2 unit vectors and then draw an arc between them. I'm not looking for any solution, rather I want to know why my specific solution is not working.
First I pick 2 unit vectors at random.
function rand(min, max) {
if (max === undefined) {
max = min;
min = 0;
}
return Math.random() * (max - min) + min;
}
var points = [{},{}];
points[0].direction = normalize([rand(-1, 1), rand(-1, 1), 0]);
points[1].direction = normalize([rand(-1, 1), rand(-1, 1), 0]);
Note: the math here is in 3D but I'm using a 2d example by just keeping the vectors in the XY plane
I can draw those 2 unit vectors in a canvas
// move to center of canvas
var scale = ctx.canvas.width / 2 * 0.9;
ctx.transform(ctx.canvas.width / 2, ctx.canvas.height / 2);
ctx.scale(scale, scale); // expand the unit fill the canvas
// draw a line for each unit vector
points.forEach(function(point) {
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(point.direction[0], point.direction[1]);
ctx.strokeStyle = point.color;
ctx.stroke();
});
That works.
Next I want to make a matrix that puts the XY plane with its Y axis aligned with the first unit vector and in the same plane as the plane described by the 2 unit vectors
var zAxis = normalize(cross(points[0].direction, points[1].direction));
var xAxis = normalize(cross(zAxis, points[0].direction));
var yAxis = points[0].direction;
I then draw a unit grid using that matrix
ctx.setTransform(
xAxis[0] * scale, xAxis[1] * scale,
yAxis[0] * scale, yAxis[1] * scale,
ctx.canvas.width / 2, ctx.canvas.height / 2);
ctx.beginPath();
for (var y = 0; y < 20; ++y) {
var v0 = (y + 0) / 20;
var v1 = (y + 1) / 20;
for (var x = 0; x < 20; ++x) {
var u0 = (x + 0) / 20;
var u1 = (x + 1) / 20;
ctx.moveTo(u0, v0);
ctx.lineTo(u1, v0);
ctx.moveTo(u0, v0);
ctx.lineTo(u0, v1);
}
}
ctx.stroke();
That works too. Run the sample below and see the pink unit grid is always aligned with the green unit vector and facing in the direction of the red unit vector.
Finally using the data for the unit grid I want to bend it the correct amount to fill the space between the 2 unit vectors. Given it's a unit grid it seems like I should be able to do this
var cosineOfAngleBetween = dot(points[0].direction, points[1].direction);
var expand = (1 + -cosineOfAngleBetween) / 2 * Math.PI;
var angle = x * expand; // x goes from 0 to 1
var newX = sin(angle) * y; // y goes from 0 to 1
var newY = cos(angle) * y;
And if I plot newX and newY for every grid point it seems like I should get the correct arc between the 2 unit vectors.
Taking the dot product of the two unit vectors should give me the cosine of the angle between them which goes from 1 if they are coincident to -1 if they are opposite. In my case I need expand to go from 0 to PI so (1 + -dot(p0, p1)) / 2 * PI seems like it should work.
But it doesn't. See the blue arc which is the unit grid points as input to the code above.
Some things I checked. I checked zAxis is correct. It's always either [0,0,1] or [0,0,-1] which is correct. I checked xAxis and yAxis are unit vectors. They are. I checked manually setting expand to PI * .5, PI, PI * 2 and it does exactly what I expect. PI * .5 gets a 90 degree arc, 1/4th of the way around from the blue unit vector. PI gets a half circle exactly as I expect. PI * 2 gets a full circle.
That makes it seem like dot(p0,p1) is wrong but looking at the dot function it seems correct and if test it with various easy vectors it returns what I expect dot([1,0,0], [1,0,0]) returns 1. dot([-1,0,0],[1,0,0]) returns -1. dot([1,0,0],[0,1,0]) returns 0. dot([1,0,0],normalize([1,1,0])) returns 0.707...
What am I missing?
Here's the code live
function cross(a, b) {
var dst = []
dst[0] = a[1] * b[2] - a[2] * b[1];
dst[1] = a[2] * b[0] - a[0] * b[2];
dst[2] = a[0] * b[1] - a[1] * b[0];
return dst;
}
function normalize(a) {
var dst = [];
var lenSq = a[0] * a[0] + a[1] * a[1] + a[2] * a[2];
var len = Math.sqrt(lenSq);
if (len > 0.00001) {
dst[0] = a[0] / len;
dst[1] = a[1] / len;
dst[2] = a[2] / len;
} else {
dst[0] = 0;
dst[1] = 0;
dst[2] = 0;
}
return dst;
}
function dot(a, b) {
return (a[0] * b[0]) + (a[1] * b[1]) + (a[2] * b[2]);
}
var canvas = document.querySelector("canvas");
canvas.width = 200;
canvas.height = 200;
var ctx = canvas.getContext("2d");
function rand(min, max) {
if (max === undefined) {
max = min;
min = 0;
}
return Math.random() * (max - min) + min;
}
var points = [
{
direction: [0,0,0],
color: "green",
},
{
direction: [0,0,0],
color: "red",
},
];
var expand = 1;
var scale = ctx.canvas.width / 2 * 0.8;
function pickPoints() {
points[0].direction = normalize([rand(-1, 1), rand(-1, 1), 0]);
points[1].direction = normalize([rand(-1, 1), rand(-1, 1), 0]);
expand = (1 + -dot(points[0].direction, points[1].direction)) / 2 * Math.PI;
console.log("expand:", expand);
render();
}
pickPoints();
function render() {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.save();
ctx.translate(ctx.canvas.width / 2, ctx.canvas.height / 2);
ctx.scale(scale, scale);
ctx.lineWidth = 3 / scale;
points.forEach(function(point) {
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(point.direction[0], point.direction[1]);
ctx.strokeStyle = point.color;
ctx.stroke();
});
var zAxis = normalize(cross(points[0].direction, points[1].direction));
var xAxis = normalize(cross(zAxis, points[0].direction));
var yAxis = points[0].direction;
ctx.setTransform(
xAxis[0] * scale, xAxis[1] * scale,
yAxis[0] * scale, yAxis[1] * scale,
ctx.canvas.width / 2, ctx.canvas.height / 2);
ctx.lineWidth = 0.5 / scale;
ctx.strokeStyle = "pink";
drawPatch(false);
ctx.strokeStyle = "blue";
drawPatch(true);
function drawPatch(curved) {
ctx.beginPath();
for (var y = 0; y < 20; ++y) {
var v0 = (y + 0) / 20;
var v1 = (y + 1) / 20;
for (var x = 0; x < 20; ++x) {
var u0 = (x + 0) / 20;
var u1 = (x + 1) / 20;
if (curved) {
var a0 = u0 * expand;
var x0 = Math.sin(a0) * v0;
var y0 = Math.cos(a0) * v0;
var a1 = u1 * expand;
var x1 = Math.sin(a1) * v0;
var y1 = Math.cos(a1) * v0;
var a2 = u0 * expand;
var x2 = Math.sin(a0) * v1;
var y2 = Math.cos(a0) * v1;
ctx.moveTo(x0, y0);
ctx.lineTo(x1, y1);
ctx.moveTo(x0, y0);
ctx.lineTo(x2, y2);
} else {
ctx.moveTo(u0, v0);
ctx.lineTo(u1, v0);
ctx.moveTo(u0, v0);
ctx.lineTo(u0, v1);
}
}
}
ctx.stroke();
}
ctx.restore();
}
window.addEventListener('click', pickPoints);
canvas {
border: 1px solid black;
}
div {
display: flex;
}
<div><canvas></canvas><p> Click for new points</p></div>
There's nothing wrong with your dot product function. It's the way you're using it:
expand = (1 + -dot(points[0].direction, points[1].direction)) / 2 * Math.PI;
should be:
expand = Math.acos(dot(points[0].direction, points[1].direction));
The expand variable, as you use it, is an angle (in radians). The dot product gives you the cosine of the angle, but not the angle itself. While the cosine of an angle varies between 1 and -1 for input [0,pi], that value does not map linearly back to the angle itself.
In other words, it doesn't work because the cosine of an angle cannot be transformed into the angle itself simply by scaling it. That's what arcsine is for.
Note that in general, you can often get by using your original formula (or any simple formula that maps that [-1,1] domain to a range of [0,pi]) if all you need is an approximation, but it will never give an exact angle except at the extremes.
This can be seen visually by plotting the two functions on top of each other:
I want an array looking like this:
[
[0,0,1,1,1,0,0],
[0,1,1,1,1,1,0],
[1,1,1,1,1,1,1],
[1,1,1,1,1,1,1],
[1,1,1,1,1,1,1],
[0,1,1,1,1,1,0],
[0,0,1,1,1,0,0],
]
My first approach was to get the circumference
var steps = 100;
var coord = [];
var x,y;
for (var i = 0; i < steps; i++) {
var phase = 2 * Math.PI * i / steps;
x = Math.round(cenx + range * Math.cos(phase));
y = Math.round(ceny + range * Math.sin(phase))
if(x>=0 && y >=0){
coord.push([x,y]);
}
}
and with the resulting coords i could have juggled around to get the circular area. but i doubt that would be performant.
So my second approach would be to check every entry of the array whether it has a certain distance (i.e. radius) to the center of my circle. but for huge maps that wouldnt be performant either. perhaps checking only in a reasonable frame would be wiser.
but im certain there is a better approach for this problem.
im needing this for a fog of war implementation.
Your second suggested approach of testing each point in the array will be simple to implement, and can be optimized to just one subtract, one multiply and one test per element in the inner loop.
The basic test is ((x - centerX) * (x - centerX)) + ((y - centerY) * (y - centerY)) > radiusSq, but since ((y - centerY) * (y - centerY)) will be constant for a given row you can move that outside the loop.
Given that you have to visit each element in the array and set it anyway (meaning your algorithm will always be O(n2) on the circle radius), the test is a negligible cost:
// circle generation code:
function makeCircle(centerX, centerY, radius, a, arrayWidth, arrayHeight)
{
var x, y, d, yDiff, threshold, radiusSq;
radius = (radius * 2) + 1;
radiusSq = (radius * radius) / 4;
for(y = 0; y < arrayHeight; y++)
{
yDiff = y - centerY;
threshold = radiusSq - (yDiff * yDiff);
for(x = 0; x < arrayWidth; x++)
{
d = x - centerX;
a[y][x] = ((d * d) > threshold) ? 0 : 1;
}
}
}
// test code:
var width = 7;
var dim = (width * 2) + 1;
var array = new Array(dim);
for(row = 0; row < dim; row++)
array[row] = new Array(dim);
makeCircle(width, width, width, array, dim, dim);
for(var y = 0, s = ""; y < dim; y++)
{
for(var x = 0; x < dim; x++)
{
s += array[y][x];
}
s += "<br>";
}
document.body.innerHTML += s + "<br>";
I would use the mid-point circle algorithm and see the array as a bitmap.
I did this JavaScript implementation a while back, modified here to use an array as target source for the "pixel". Just note that a circle will produce odd widths and heights as the distance is always from a single center point and we can only use integer values in this case.
Tip: For speed improvements you could use typed array instead of a regular one (shown below).
Example
Make sure to use integer values as input, the code will clip values outside the "bitmap"/array -
var width = 7, height = 7,
array = new Uint8Array(width * height);
// "draw" circle into array
circle(3, 3, 3);
renderDOM();
// circle example 2
width = height = 17;
array = new Uint8Array(width * height);
circle(8, 8, 8);
renderDOM();
function circle(xc, yc, r) {
if (r < 1) return;
var x = r, y = 0, // for Bresenham / mid-point circle
cd = 0,
xoff = 0,
yoff = r,
b = -r,
p0, p1, w0, w1;
while (xoff <= yoff) {
p0 = xc - xoff;
p1 = xc - yoff;
w0 = xoff + xoff;
w1 = yoff + yoff;
hl(p0, yc - yoff, yc + yoff, w0); // fill a "line"
hl(p1, yc - xoff, yc + xoff, w1);
if ((b += xoff+++xoff) >= 0) {
b -= --yoff + yoff;
}
}
// for fill
function hl(x, y1, y2, w) {
w++;
var xw = 0;
while (w--) {
xw = x + w;
setPixel(xw, y1);
setPixel(xw, y2);
}
}
function setPixel(x, y) {
if (x < width && y < height && x >= 0 && y >= 0)
array[y * width + x] = 1;
}
}
function renderDOM() {
for(var i = 0, str = ""; i < array.length; i++) {
if (i > 0 && !(i % width)) str += "<br>";
str += array[i];
}
document.body.innerHTML += str + "<br><br>";
}
body {font:18px monospace}
For an odd-sized array (2r+1 x 2r+1),
for (row= 0; row < 2 * r + 1; row++)
{
f= (row + 1) * (row - 2 * r - 1) + r * r + r;
for (col= 0; col < 2 * r + 1; f+= 2 * (col - r) + 1; col++)
{
array[row][col]= f >= 0;
}
}
I'm trying to create a UI that has a lot of items in circles. Sometimes these circles will have related circles that should be displayed around them.
I was able to cobble together something that works, here.
The problem is that the outer circles start near 0 degrees, and I'd like them to start at an angle supplied by the consumer of the function/library. I was never a star at trigonometry, or geometry, so I could use a little help.
As you can see in the consuming code, there is a setting: startingDegree: 270 that the function getPosition should honor, but I haven't been able to figure out how.
Update 04/02/2014:
as I mentioned in my comment to Salix alba, I wasn't clear above, but what I needed was to be able to specify the radius of the satellite circles, and to have them go only partly all the way around. Salix gave a solution that calculates the size the satellites need to be to fit around the center circle uniformly.
Using some of the hints in Salix's answer, I was able to achieve the desired result... and have an extra "mode," thanks to Salix, in the future.
The working, though still rough, solution is here: http://jsfiddle.net/RD4RZ/11/. Here is the entire code (just so it's all on SO):
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<script type="text/javascript" src="//code.jquery.com/jquery-1.10.1.js"></script>
<style type="text/css">
.circle
{
position: absolute;
width: 100px;
height: 100px;
background-repeat: no-repeat;background-position: center center;
border: 80px solid #a19084;
border-radius: 50%;
-moz-border-radius: 50%;
}
.sm
{
border: 2px solid #a19084;
}
</style>
<script type="text/javascript">//<![CDATA[
$(function () {
function sind(x) {
return Math.sin(x * Math.PI / 180);
}
/*the law of cosines:
cc = aa + bb - 2ab cos(C), where c is the satellite diameter a and b are the legs
solving for cos C, cos C = ( aa + bb - cc ) / 2ab
Math.acos((a * a + b * b - c * c) / (2 * a * b)) = C
*/
function solveAngle(a, b, c) { // Returns angle C using law of cosines
var temp = (a * a + b * b - c * c) / (2 * a * b);
if (temp >= -1 && temp <= 1)
return radToDeg(Math.acos(temp));
else
throw "No solution";
}
function radToDeg(x) {
return x / Math.PI * 180;
}
function degToRad(x) {
return x * (Math.PI / 180);
}
var satellite = {
//settings must have: collection (array), itemDiameter (number), minCenterDiameter (number), center (json with x, y numbers)
//optional: itemPadding (number), evenDistribution (boolean), centerPadding (boolean), noOverLap (boolean)
getPosition: function (settings) {
//backwards compat
settings.centerPadding = settings.centerPadding || settings.itemPadding;
settings.noOverLap = typeof settings.noOverLap == 'undefined' ? true : settings.noOverLap;
settings.startingDegree = settings.startingDegree || 270;
settings.startSatellitesOnEdge = typeof settings.startSatellitesOnEdge == 'undefined' ? true : settings.startSatellitesOnEdge;
var itemIndex = $.inArray(settings.item, settings.collection);
var itemCnt = settings.collection.length;
var satelliteSide = settings.itemDiameter + (settings.itemSeparation || 0) + (settings.itemPadding || 0);
var evenDistribution = typeof settings.evenDistribution == 'undefined' ? true : settings.evenDistribution;
var degreeOfSeparation = (360 / itemCnt);
/*
we know all three sides:
one side is the diameter of the satellite itself (plus any padding). the other two
are the parent radius + the radius of the satellite itself (plus any padding).
given that, we need to find the angle of separation using the law of cosines (solveAngle)
*/
//if (!evenDistribution) {
var side1 = ((satelliteSide / 2)) + ((settings.minCenterDiameter + (2 * settings.centerPadding)) / 2);
var side2 = satelliteSide;;
var degreeOfSeparationBasedOnSatellite = solveAngle(side1, side1, side2); //Math.acos(((((side1 * side1) + (side2 * side2)) - (side2 * side2)) / (side2 * side2 * 2)) / 180 * Math.PI) * Math.PI;
degreeOfSeparation = evenDistribution? degreeOfSeparation: settings.noOverLap ? Math.min(degreeOfSeparation, degreeOfSeparationBasedOnSatellite) : degreeOfSeparationBasedOnSatellite;
//}
//angle-angle-side
//a-A-B
var a = satelliteSide;
var A = degreeOfSeparation;
/*
the three angles of any triangle add up to 180. We know one angle (degreeOfSeparation)
and we know the other two are equivalent to each other, so...
*/
var B = (180 - A) / 2;
//b is length necessary to fit all satellites, might be too short to be outside of base circle
var b = a * sind(B) / sind(A);
var offset = (settings.itemDiameter / 2) + (settings.itemPadding || 0); // 1; //
var onBaseCircleLegLength = ((settings.minCenterDiameter / 2) + settings.centerPadding) + offset;
var offBase = false;
if (b > onBaseCircleLegLength) {
offBase = true;
}
b = settings.noOverLap ? Math.max(b, onBaseCircleLegLength) : onBaseCircleLegLength;
var radianDegree = degToRad(degreeOfSeparation);
//log('b=' + b);
//log('settings.center.x=' + settings.center.x);
//log('settings.center.y=' + settings.center.y);
var degreeOffset = settings.startingDegree;
if (settings.startSatellitesOnEdge) {
degreeOffset += ((offBase ? degreeOfSeparation : degreeOfSeparationBasedOnSatellite) / 2);
}
var i = ((Math.PI * degreeOffset) / 180) + (radianDegree * itemIndex);// + (degToRad(degreeOfSeparationBasedOnSatellite) / 2); //(radianDegree) * (itemIndex);
var x = (Math.cos(i) * b) + (settings.center.x - offset);
var y = (Math.sin(i) * b) + (settings.center.y - offset);
return { 'x': Math.round(x), 'y': Math.round(y) };
}
,
/* if we ever want to size satellite by how many need to fit tight around the base circle:
x: function calcCircles(n) {
circles.splice(0); // clear out old circles
var angle = Math.PI / n;
var s = Math.sin(angle);
var r = baseRadius * s / (1 - s);
console.log(angle);
console.log(s);
console.log(r);
console.log(startAngle);
console.log(startAngle / (Math.PI * 2));
for (var i = 0; i < n; ++i) {
var phi = ((Math.PI * startAngle) / 180) + (angle * i * 2);
var cx = 150 + (baseRadius + r) * Math.cos(phi);
var cy = 150 + (baseRadius + r) * Math.sin(phi);
circles.push(new Circle(cx, cy, r));
}
},
*/
//settings must have: collection (array), itemDiameter (number), minCenterDiameter (number), center (json with x, y numbers)
//optional: itemPadding (number), evenDistribution (boolean), centerPadding (boolean), noOverLap (boolean)
getAllPositions: function (settings) {
var point;
var points = [];
var collection = settings.collection;
for (var i = 0; i < collection.length; i++) {
settings.item = collection[i]
points.push(satellite.getPosition(settings));
}
return points;
}
};
var el = $("#center"), cnt = 10, arr = [], itemDiameter= 100;
for (var c = 0; c < cnt; c++) {
arr.push(c);
}
var settings = {
collection: arr,
itemDiameter: itemDiameter,
minCenterDiameter: el.width(),
center: { x: el.width() / 2, y: el.width() / 2 },
itemPadding: 2,
evenDistribution: false,
centerPadding: parseInt(el.css("border-width")),
noOverLap: false,
startingDegree: 270
};
var points = satellite.getAllPositions(settings);
for (var i = 0; i < points.length; i++) {
var $newdiv1 = $("<div></div>");
var div = el.append($newdiv1);
$newdiv1.addClass("circle").addClass("sm");
$newdiv1.text(i);
$newdiv1.css({ left: points[i].x, top: points[i].y, width: itemDiameter +'px', height: itemDiameter +'px' });
}
});//]]>
</script>
</head>
<body>
<div id="center" class="circle" style="left:250px;top:250px" >
</div>
</body>
</html>
The central bit you need to work out is radius of the small circles. If you have R for radius of the central circle and you want to fit n smaller circles around it. Let the as yet unknown radius of the small circle be r. We can construct a right angle triangle with one corner in the center of the big circle one in the center of the small circle and one which is where a line from the center is tangent to the small circle. This will be a right angle. The angle at the center is a the hypotenuse has length R+r the opposite is r and we don't need the adjacent. Using trig
sin(a) = op / hyp = r / (R + r)
rearrange
(R+r) sin(a) = r
R sin(a) + r sin(a) = r
R sin(a) = r - r sin(a)
R sin(a) = (1 - sin(a)) r
r = R sin(a) / ( 1 - sin(a))
once we have r we are pretty much done.
You can see this as a fiddle http://jsfiddle.net/SalixAlba/7mAAS/
// canvas and mousedown related variables
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var $canvas = $("#canvas");
var canvasOffset = $canvas.offset();
var offsetX = canvasOffset.left;
var offsetY = canvasOffset.top;
var scrollX = $canvas.scrollLeft();
var scrollY = $canvas.scrollTop();
// save canvas size to vars b/ they're used often
var canvasWidth = canvas.width;
var canvasHeight = canvas.height;
var baseRadius = 50;
var baseCircle = new Circle(150,150,50);
var nCircles = 7;
var startAngle = 15.0;
function Circle(x,y,r) {
this.x = x;
this.y = y;
this.r = r;
}
Circle.prototype.draw = function() {
ctx.beginPath();
ctx.arc(this.x,this.y,this.r, 0, 2 * Math.PI, false);
ctx.stroke();
}
var circles = new Array();
function calcCircles(n) {
circles.splice(0); // clear out old circles
var angle = Math.PI / n;
var s = Math.sin(angle);
var r = baseRadius * s / (1-s);
console.log(angle);
console.log(s);
console.log(r);
for(var i=0;i<n;++i) {
var phi = startAngle + angle * i * 2;
var cx = 150+(baseRadius + r) * Math.cos(phi);
var cy = 150+(baseRadius + r) * Math.sin(phi);
circles.push(new Circle(cx,cy,r));
}
}
function draw() {
baseCircle.draw();
circles.forEach(function(ele){ele.draw()});
}
calcCircles(7);
draw();
I'm looking for a function to arrange some elements around a circle.
result should be something like :
Here's some code that should help you:
var numElements = 4,
angle = 0
step = (2*Math.PI) / numElements;
for(var i = 0; i < numElements.length; i++) {
var x = container_width/2 + radius * Math.cos(angle);
var y = container_height/2 + radius * Math.sin(angle);
angle += step;
}
It is not complete but should give you a good start.
Update: Here's something that actually works:
var radius = 200; // radius of the circle
var fields = $('.field'),
container = $('#container'),
width = container.width(),
height = container.height(),
angle = 0,
step = (2*Math.PI) / fields.length;
fields.each(function() {
var x = Math.round(width/2 + radius * Math.cos(angle) - $(this).width()/2),
y = Math.round(height/2 + radius * Math.sin(angle) - $(this).height()/2);
$(this).css({
left: x + 'px',
top: y + 'px'
});
angle += step;
});
Demo: http://jsfiddle.net/ThiefMaster/LPh33/
Here's an improved version where you can change the element count.
For an element around a centre at (x, y), distance r, the element's centre should be positioned at:
(x + r cos(2kπ/n), y + r sin(2kπ/n))
where n is the number of elements, and k is the "number" of the element you're currently positioning (between 1 and n inclusive).
I've combined ThiefMaster's fiddle with the jQuery pointAt plugin:
Demo: http://jsfiddle.net/BananaAcid/nytN6/
the code is somewhat like above.
might be interesting to some of you.
Arrange Elements In Circle (Javascript)
function arrangeElementsInCircle (elements, x, y, r) {
for (var i = 0; i < elements.length; i++) {
elements[i].scaleX = 1 / elements.length
elements[i].scaleY = 1 / elements.length
elements[i].x = (x + r * Math.cos((2 * Math.PI) * i/elements.length))
elements[i].y = (y + r * Math.sin((2 * Math.PI) * i/store.length))
}
}
Where x,y is point co-ordinates and elements is array of elements to be placed and r is radius.
Javascript only version of thiefmaster's answer
function distributeFields(deg){
deg = deg || 0;
var radius = 200;
var fields = document.querySelectorAll('.field'), //using queryselector instead of $ to select items
container = document.querySelector('#container'),
width = container.offsetWidth, //offsetWidth gives the width of the container
height = container.offsetHeight,
angle = deg || Math.PI * 3.5,
step = (2 * Math.PI) / fields.length;
console.log(width, height)
//using forEach loop on a NodeList instead of a Jquery .each,
//so we can now use "field" as an iterator instead of $(this)
fields.forEach((field)=>{
var x = Math.round(width / 2 + radius * Math.cos(angle) - field.offsetWidth/2);
var y = Math.round(height / 2 + radius * Math.sin(angle) - field.offsetHeight/2);
console.log(x, y)
field.style.left = x + 'px'; //adding inline style to the document (field)
field.style.top= y + 'px';
angle += step;
})
}
distributeFields();