Reputation: 1525
I am trying to position 15 div
elements evenly in a circle with a radius of 150px
. I'm using the following code, which seems to give an oddly eccentric ellipse that overlaps.
// Hold a global reference to the div#main element. Initially assign it ... somewhere useful :)
var main = document.getElementById('main');
var circleArray = [];
// Move a circle based on the distance of the approaching mouse
var moveCircle = function(circle, dx, dy) {
};
// Look at all the circle elements, and figure out if any of them have to move.
var checkMove = function() {
};
var setup = function() {
for (var i = 0; i < 15; i++) {
//create element, add it to the array, and assign it's coordinates trigonometrically.
//Then add it to the "main" div
var circle = document.createElement('div');
circle.className = 'circle number' + i;
circleArray.push(circle);
circleArray[i].posx = Math.round((150 * Math.cos(i * (2 * Math.PI / 15)))) + 'px';
circleArray[i].posy = Math.round((150 * Math.sin(i * (2 * Math.PI / 15)))) + 'px';
circleArray[i].style.position = "relative";
circleArray[i].style.top = circleArray[i].posy;
circleArray[i].style.left = circleArray[i].posx;
main.appendChild(circleArray[i]);
}
};
setup();
window.addEventListener('load', function() {
});
div {
box-sizing: border-box;
}
div#main {
position: absolute;
left: 50%;
top: 50%;
}
div.circle {
position: absolute;
width: 20px;
height: 20px;
border: 2px solid black;
border-radius: 10px;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
}
<div id="main"></div>
Any suggestions as to what I might be doing wrong?
Upvotes: 25
Views: 21295
Reputation: 176
Here's another take at it learning from the existing answers, 10 years later.
<div class="circle" id="main"></div>
// cache
let c = document.querySelector("#main");
// constants
const pi = Math.PI;
let R = 180; // let, because we can dynamically change this
let count = 19;
let DELTA = 360 / count;
let TRANSPOSE_OFFSET = R + 50;
// helper functions
const pxfy = (n) => `${n}px`;
const radians = (theta) => (theta * pi) / 180;
const make = (e) => document.createElement(e);
const r_cosA = (r, a) => Math.cos(a) * r;
const r_sinA = (r, a) => Math.sin(a) * r;
const transpose = (p, delta) => {
// our circle is drawn at the origin
// transpose its center to where you like
let x = p.x + delta.x;
let y = p.y + delta.y;
return { x, y };
};
const get_point = (r, a) => {
// {x,y} = {r * cos(a), r * sin(a)}
let x = r_cosA(r, a);
let y = r_sinA(r, a);
// where do you want to move the center?
let Dx = TRANSPOSE_OFFSET + 10;
let Dy = TRANSPOSE_OFFSET;
return transpose({ x, y }, { x: Dx, y: Dy });
};
// THE CODE TO DRAW CIRCLES
const setup = () => {
// additional decorations
let text = "This is JavaScript";
text = text.padStart(count, '*');
let text_split = text.split('');
// ---
// reference range array
let range = [...Array(count).keys()];
range.forEach((i) => {
let circle = make("div");
circle.id = `c_${i}`;
circle.textContent = `${i + 1}`;
// additional decorations
let elem = make('div');
elem.classList.add('anno');
elem.textContent = text_split[i];
circle.append(elem);
// ---
let ANGLE = i * DELTA + 60;
let RADIUS = R;
let p = get_point(RADIUS, radians(ANGLE));
circle.style.left = pxfy(p.x);
circle.style.top = pxfy(p.y);
// following transpositions applied to angles are entirely arbitrary.
// use what you like or just use ${ANGLE}
circle.style.backgroundColor = `hsl(${ANGLE * 2.3}deg, 90%, 70%)`;
circle.style.color = `hsl(${ANGLE * 1.5 + 270}deg, 80%, 20%)`;
// additional bells and whisles
circle.style.transform = `rotate(${ANGLE + 90}deg)`;
// ---
c.appendChild(circle);
});
};
setup();
.circle#main {
--rad: 36px;
position: absolute;
width: 500px;
height: 500px;
top: 240px;
left: 50%;
transform: translate(-50%, -28%);
}
.circle#main div {
position: absolute;
border: 1px solid black;
width: var(--rad);
height: var(--rad);
border-radius: var(--rad);
/* everything below this is to style the text which can be removed */
display: flex;
justify-content: center;
align-items: center;
font-family: sans-serif;
font-weight: bold;
font-size: 11px;
pointer-events: none;
opacity: 1;
transition: opacity 3s ease-in-out;
}
#main div > .anno {
position: absolute;
top: -2.8rem;
font-size: 2rem;
font-family: serif;
text-align: center;
text-transform: uppercase;
font-weight: bold;
text-rendering: geometricPrecision;
border-color: white;
border-bottom-color: currentColor;
}
Upvotes: 0
Reputation: 22992
First of all, the equation for a co-ordinate on a circle is simply:
(x, y) = (r * cos(θ), r * sin(θ))
where, r
is the radius of a circle and θ
is the angle in radians.
The reason why your code is creating an eccentric ellipse is because when you assign the .top
and .left
CSS values, you are not considering that it will actually take the top-left corner as its reference. I've fixed your code and now it creates a perfect circle.
Added an array theta
that holds all the angles.
var theta = [0, Math.PI / 6, Math.PI / 4, Math.PI / 3, Math.PI / 2, 2 * (Math.PI / 3), 3 * (Math.PI / 4), 5 * (Math.PI / 6), Math.PI, 7 * (Math.PI / 6), 5 * (Math.PI / 4), 4 * (Math.PI / 3), 3 * (Math.PI / 2), 5 * (Math.PI / 3), 7 * (Math.PI / 4), 11 * (Math.PI / 6)];
The image below shows all the angles I've used.
Added an array colors
that holds different colors.
var colors = ['red', 'green', 'purple', 'black', 'orange', 'yellow', 'maroon', 'grey', 'lightblue', 'tomato', 'pink', 'maroon', 'cyan', 'magenta', 'blue', 'chocolate', 'DarkSlateBlue'];
Made changes to your trigonometric equations.
circleArray[i].posx = Math.round(radius * (Math.cos(theta[i]))) + 'px';
circleArray[i].posy = Math.round(radius * (Math.sin(theta[i]))) + 'px';
Changed the way .top
and .left
are assigned.
circleArray[i].style.top = ((mainHeight / 2) - parseInt(circleArray[i].posy.slice(0, -2))) + 'px';
circleArray[i].style.left = ((mainHeight / 2) + parseInt(circleArray[i].posx.slice(0, -2))) + 'px';
where mainHeight
is the height of the #main
div
.
div
svar setup = function() {
var radius = 150;
var main = document.getElementById('main');
var mainHeight = parseInt(window.getComputedStyle(main).height.slice(0, -2));
var theta = [0, Math.PI / 6, Math.PI / 4, Math.PI / 3, Math.PI / 2, 2 * (Math.PI / 3), 3 * (Math.PI / 4), 5 * (Math.PI / 6), Math.PI, 7 * (Math.PI / 6), 5 * (Math.PI / 4), 4 * (Math.PI / 3), 3 * (Math.PI / 2), 5 * (Math.PI / 3), 7 * (Math.PI / 4), 11 * (Math.PI / 6)];
var circleArray = [];
var colors = ['red', 'green', 'purple', 'black', 'orange', 'yellow', 'maroon', 'grey', 'lightblue', 'tomato', 'pink', 'maroon', 'cyan', 'magenta', 'blue', 'chocolate', 'DarkSlateBlue'];
for (var i = 0; i < 16; i++) {
var circle = document.createElement('div');
circle.className = 'circle number' + i;
circleArray.push(circle);
circleArray[i].posx = Math.round(radius * (Math.cos(theta[i]))) + 'px';
circleArray[i].posy = Math.round(radius * (Math.sin(theta[i]))) + 'px';
circleArray[i].style.position = "absolute";
circleArray[i].style.backgroundColor = colors[i];
circleArray[i].style.top = ((mainHeight / 2) - parseInt(circleArray[i].posy.slice(0, -2))) + 'px';
circleArray[i].style.left = ((mainHeight / 2) + parseInt(circleArray[i].posx.slice(0, -2))) + 'px';
main.appendChild(circleArray[i]);
}
};
setup();
div#main {
height: 300px;
width: 300px;
position: absolute;
margin: 0 auto;
transform: translate(-50%, -50%);
top: 50%;
left: 50%;
}
div.circle {
position: absolute;
width: 20px;
height: 20px;
border: 2px solid black;
border-radius: 50%;
}
body {
margin: 0 auto;
background: papayawhip;
}
<div id="main"></div>
div
s Positioned Evenlyvar setup = function() {
var radius = 150;
var main = document.getElementById('main');
var mainHeight = parseInt(window.getComputedStyle(main).height.slice(0, -2));
var theta = [0, (2 * (Math.PI / 15)), (4 * (Math.PI / 15)), (2 * (Math.PI / 5)), (8 * (Math.PI / 15)), (2 * (Math.PI / 3)), (4 * (Math.PI / 5)), (14 * (Math.PI / 15)), (16 * (Math.PI / 15)), (6 * (Math.PI / 5)), (4 * (Math.PI / 3)), (22 * (Math.PI / 15)), (8 * (Math.PI / 5)), (26 * (Math.PI / 15)), (28 * (Math.PI / 15))];
var circleArray = [];
var colors = ['red', 'green', 'purple', 'black', 'orange', 'yellow', 'maroon', 'grey', 'lightblue', 'tomato', 'pink', 'maroon', 'cyan', 'magenta', 'blue', 'chocolate', 'DarkSlateBlue'];
for (var i = 0; i < 15; i++) {
var circle = document.createElement('div');
circle.className = 'circle number' + i;
circleArray.push(circle);
circleArray[i].posx = Math.round(radius * (Math.cos(theta[i]))) + 'px';
circleArray[i].posy = Math.round(radius * (Math.sin(theta[i]))) + 'px';
circleArray[i].style.position = "absolute";
circleArray[i].style.backgroundColor = colors[i];
circleArray[i].style.top = ((mainHeight / 2) - parseInt(circleArray[i].posy.slice(0, -2))) + 'px';
circleArray[i].style.left = ((mainHeight / 2) + parseInt(circleArray[i].posx.slice(0, -2))) + 'px';
main.appendChild(circleArray[i]);
}
};
setup();
div#main {
height: 300px;
width: 300px;
position: absolute;
margin: 0 auto;
transform: translate(-50%, -50%);
top: 50%;
left: 50%;
}
div.circle {
position: absolute;
width: 20px;
height: 20px;
border: 2px solid black;
border-radius: 50%;
}
body {
margin: 0 auto;
background: papayawhip;
}
<div id="main"></div>
div
s on an Ellipse/CircleThe equation for a co-ordinate on an ellipse is:
(x, y) = (rx * cos(θ), ry * sin(θ))
where, rx
is the radius along X-axis and ry
is the radius along Y-axis.
In this case, the function generate(n, rx, ry, id)
takes four arguments, where n
is the number of div
s, rx
and ry
are the radii along the X and Y-axis respectively and finally id
is the id
of the div
that you want to append your elliptically arranged div
s in.
var theta = [];
var setup = function(n, rx, ry, id) {
var main = document.getElementById(id);
var mainHeight = parseInt(window.getComputedStyle(main).height.slice(0, -2));
var circleArray = [];
var colors = ['red', 'green', 'purple', 'black', 'orange', 'yellow', 'maroon', 'grey', 'lightblue', 'tomato', 'pink', 'maroon', 'cyan', 'magenta', 'blue', 'chocolate', 'darkslateblue', 'coral', 'blueviolet', 'burlywood', 'cornflowerblue', 'crimson', 'darkgoldenrod', 'olive', 'sienna', 'red', 'green', 'purple', 'black', 'orange', 'yellow', 'maroon', 'grey', 'lightblue', 'tomato', 'pink', 'maroon', 'cyan', 'magenta', 'blue', 'chocolate', 'darkslateblue', 'coral', 'blueviolet', 'burlywood', 'cornflowerblue', 'crimson', 'darkgoldenrod', 'olive', 'sienna'];
for (var i = 0; i < n; i++) {
var circle = document.createElement('div');
circle.className = 'circle number' + i;
circleArray.push(circle);
circleArray[i].posx = Math.round(rx * (Math.cos(theta[i]))) + 'px';
circleArray[i].posy = Math.round(ry * (Math.sin(theta[i]))) + 'px';
circleArray[i].style.position = "absolute";
circleArray[i].style.backgroundColor = colors[i];
circleArray[i].style.top = ((mainHeight / 2) - parseInt(circleArray[i].posy.slice(0, -2))) + 'px';
circleArray[i].style.left = ((mainHeight / 2) + parseInt(circleArray[i].posx.slice(0, -2))) + 'px';
main.appendChild(circleArray[i]);
}
};
var generate = function(n, rx, ry, id) {
var frags = 360 / n;
for (var i = 0; i <= n; i++) {
theta.push((frags / 180) * i * Math.PI);
}
setup(n, rx, ry, id)
}
generate(16, 150, 75, 'main');
div#main {
height: 300px;
width: 300px;
position: absolute;
margin: 0 auto;
transform: translate(-50%, -50%);
top: 50%;
left: 50%;
}
div.circle {
position: absolute;
width: 20px;
height: 20px;
border: 2px solid black;
border-radius: 50%;
}
body {
margin: 0 auto;
background: papayawhip;
}
<div id="main"></div>
Here's a more flexible version with start offset, clock wise and anti-clock wise functionality.
/*
Usage: Position.ellipse(n, rx, ry, so, wh, idd, cls, cw);
where n = number of divs,
rx = radius along X-axis,
ry = radius along Y-axis,
so = startOffset,
wh = width/height of divs,
idd = id of main div(ellipse),
cls = className of divs;
cw = clockwise(true/false)
*/
var Position = {
ellipse: function(n, rx, ry, so, wh, idd, cls, cw) {
var m = document.createElement('div'),
ss = document.styleSheets;
ss[0].insertRule('#' + idd + ' { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); border-radius: 50%; box-shadow: inset 0 0 ' + wh + 'px ' + wh / 4 + 'px black; background: rgba(0, 0, 0, 0.2); width: ' + String((rx * 2) + wh) + 'px; height: ' + String((ry * 2) + wh) + 'px; }', 1);
ss[0].insertRule('.' + cls + '{ position: absolute; background: black; color: papayawhip; text-align: center; font-family: "Open Sans Condensed", sans-serif; border-radius: 50%; transition: transform 0.2s ease; width: ' + wh + 'px; height: ' + wh + 'px; line-height: ' + wh + 'px;}', 1);
ss[0].insertRule('.' + cls + ':hover { transform: scale(1.2); cursor: pointer; background: rgba(0, 0, 0, 0.8); }', 1);
m.id = idd;
for (var i = 0; i < n; i++) {
var c = document.createElement('div');
c.className = cls;
c.innerHTML = i + 1;
c.style.top = String(ry + -ry * Math.cos((360 / n / 180) * (i + so) * Math.PI)) + 'px';
c.style.left = String(rx + rx * (cw ? Math.sin((360 / n / 180) * (i + so) * Math.PI) : -Math.sin((360 / n / 180) * (i + so) * Math.PI))) + 'px';
m.appendChild(c);
}
document.body.appendChild(m);
}
}
Position.ellipse(20, 150, 150, 0, 42, 'main', 'circle', true);
@import url(http://fonts.googleapis.com/css?family=Open+Sans+Condensed:300);
body {
margin: 0 auto;
background: rgb(198, 193, 173);
}
Upvotes: 83
Reputation: 31
Yet another solution, based on ideas from other solutions I've seen
http://jsfiddle.net/0hr1n7a2/6/
(function() {
var radians, radius;
radius = 150;
var totalItems = 48
var item = 0;
function positionTarget()
{
var x, y, angle = 0, step = (2*Math.PI) / totalItems;
var width = $('#container').width()/2;
var height = $('#container').height()/2;
var itemW = 20, itemH = 2;
var deg = 0;
while(item <= totalItems)
{
x = Math.round(width + radius * Math.cos(angle) - itemW/2);
y = Math.round(height + radius * Math.sin(angle) - itemH/2);
//console.log(x + "," + y);
$('#container').append('<div id="'+ item +'"/>')
$('div#'+item).css('position', 'absolute')
.css('width', itemW+'px').css('height', itemH+'px')
.css('left', x+'px').css('top', y+'px')
.css('background-color', 'blue')
.css('transform-origin', x+'px' -y+'px')
.css('transform', 'rotate('+ deg +'deg)')
.css('border', 'solid 1px #000');
angle += step;
++item;
deg += 360/totalItems;
//console.log(deg)
}
}
$('#theButton').on('click', function()
{
positionTarget();
})
})();
#container { width: 600px; height: 600px; border: 1px solid #000; position: relative; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input type="button" id="theButton" value="Draw">
<div id="container">
</div>
Upvotes: 2
Reputation: 103750
chipChocolate.py's anser is pretty complete but there is an other way to achieve your aim. It is simpler and doesn't require JS.
The point is to think "circle" and rotation rather than rely on [x,y]
coordinates :
You need to nest all the elements and apply a rotation to them. As they are nested the n + 1
element will rotate according to it's direct parent's rotation. Here is a DEMO :
.circle, .circle div {
width:24px; height:300px;
position:absolute;
left:50%; top:50px;
}
.circle:before, .circle div:before {
content:'';
display:block;
width:20px; height:20px;
border: 2px solid black;
border-radius: 100%;
}
.circle div {
top:0; left:0;
-webkit-transform : rotate(24deg);
-ms-transform : rotate(24deg);
transform : rotate(24deg);
}
<div class="circle">
<div><div><div><div><div><div><div><div><div><div><div><div><div><div><div>
</div></div></div></div></div></div></div></div></div></div></div></div></div></div></div>
</div>
The diameter of the circle is controled by the height of the elements (in the demo height:300px
) you can make that a percentage to make the circle responsive (see below).
The rotation must be set according to the number of elements you want around the circle. In the demo 15 elements so rotation = 360 / 15 = 24deg
.
If you have a dynamic number of elements, you may use JS to add them and to calculate the rotation angle needed.
.circle{
position:relative;
width:5%;padding-bottom:50%;
margin-left:47.5%;
}
.circle div {
position:absolute;
top:0; left:0;
width:100%; height:100%;
-webkit-transform : rotate(24deg);
-ms-transform : rotate(24deg);
transform : rotate(24deg);
}
.circle:before, .circle div:before {
content:'';
position:absolute;
top:0; left:0;
width:100%; padding-bottom:100%;
border-radius: 100%;
border: 2px solid teal;
background:gold;
}
<div class="circle">
<div><div><div><div><div><div><div><div><div><div><div><div><div><div><div>
</div></div></div></div></div></div></div></div></div></div></div></div></div></div></div>
</div>
Upvotes: 18
Reputation: 101
Set the position to "absolute". This will allow "top" and "left" to position the divs from (0, 0). Using "relative" will position the divs from where they would normally be laid out.
Change the center point of your circle from (0, 0) to something else, like (250, 250).
circleArray[i].posx = 250 + Math.round((150*Math.cos(i*(2*Math.PI/15)))) + 'px';
circleArray[i].posy = 250 + Math.round((150*Math.sin(i*(2*Math.PI/15)))) + 'px';
circleArray[i].style.position = "absolute";
Upvotes: 0