Rotate evenly-distributed elements placed on a circle with jQuery - javascript

I have a JavaScript function which allows me to generate DOM elements and plot them on a circle with (good enough) even distribution around the circle. The code is as follows (I'm using jQuery):
function createFields(numberOfItems, className, radius) {
var container = $('#container');
for(var i = 0; i < +numberOfItems; i++) {
$('<div/>', {
'class': 'field ' + className,
'text': i + 1
}).appendTo(container);
}
var fields = $('.' + className),
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);
var y = Math.round(height/2 + radius * Math.sin(angle) - $(this).height()/2);
if(window.console) {
console.log($(this).text(), x, y);
}
$(this).css({
left: x + 'px',
top: y + 'px'
});
angle += step;
});
}
createFields(5, 'outer', 200);
createFields(4, 'inner', 120);
Fiddle: http://jsfiddle.net/z79gj8a7/
You'll notice that the generated elements begin at 90 degrees to the vertical. I'd like to plot them so that they begin at 0 degrees. Essentially, if you imagine this as a clock, I want to plot all of the items 3hrs earlier. I've tried modifying the angle in the script to -90 and also subtracting 90 from the angle += step line but it's not having the desired effect.
Could anyone who's better at maths than I suggest a way to get the elements to be plotted -90 degrees from where they are now? (I'm aware I could just rotate the #container but that seems like a hack as I'd have to rotate the elements to compensate to keep their content in the correct orientation).
Many thanks.

The script is working in radians not degrees :) here's what you want (I think) http://jsfiddle.net/z79gj8a7/1/
You need to shift the angle by pi/2
var x = Math.round(width/2 + radius * Math.cos(angle - (Math.PI/2)) - $(this).width()/2);
var y = Math.round(height/2 + radius * Math.sin(angle - (Math.PI/2)) - $(this).height()/2);
Or even better (having read the script properly) don't change the calculation of x and y but change the angle to start at -pi/2: http://jsfiddle.net/z79gj8a7/2/
angle = -Math.PI/2,

jsFiddle demo
function createFields(numberOfItems, className, radius) {
var container = $('#container'),
centerX = container.width()/2,
centerY = container.height()/2,
angle = 0;
for(var i = 0; i < +numberOfItems; i++) {
$('<div/>', {
'class': 'field ' + className,
'text': i + 1
}).appendTo(container);
}
var fields = $('.' + className),
tot = fields.length;
fields.each(function(i, e) {
var w2 = $(e).outerWidth(true)/2,
h2 = $(e).outerHeight(true)/2,
angle = 360/tot*i,
x = Math.round(centerX+radius * Math.sin(angle*Math.PI/180)),
y = Math.round(centerY+radius * -Math.cos(angle*Math.PI/180));
$(e).css({left:x-w2, top:y-h2}).text( i+1 );
});
}
createFields(5, 'outer', 200);
createFields(4, 'inner', 120);

Related

Numbers that result in a more rounded corner when graphing in Javascript

I have a for loop that returns a decimal between 0 and 1. I'd like to make a curve that appears more like a rounded corner than it is now. I'd also like to have it start ramping up only after 0.25. I can't quite figure out how to do it with the math I have now. I'm using Math.log and a linear conversion function, but maybe I need something more related to a parabolic curve.
for (i = 1; i < 101; ++i) {
var dec = i / 100
if (dec >= 0.25) {
console.log("dec = " + dec);
var large = i * 100
var log = Math.log(large);
console.log("log = " + log);
var linCon = applyLinearConversion(log, 2.8, 9.2104, -2.7, 1)
console.log("linCon " + i + " = " + linCon);
document.getElementById("graph").innerHTML += "<div style='background-color:#000000; width:" + (linCon * 1000) + "px; height:5px;'></div>";
}
}
function applyLinearConversion(OldValue, OldMin, OldMax, NewMin, NewMax) {
OldRange = (OldMax - OldMin)
if (OldRange == 0)
NewValue = NewMin
else {
NewRange = (NewMax - NewMin)
NewValue = (((OldValue - OldMin) * NewRange) / OldRange) + NewMin
}
return NewValue
}
<div id="graph"></div>
I have it populating a div with more styled divs.
Mine is like this:
I want mine more like this:
You can use the formula of the half circle graph which is:
It results in the following graph:
Since you are using horizontal divs that are stacked vertically to draw the graph, the x and y coordinates will be reversed and the left quarter of the circle will be used from the above graph.
var width = 200; // the width of the graph
var height = 200; // the height of the graph
var xOffset = 0.25 * width; // the x offset at which the graph will start ramping up (this offset is added to the graph width)
var html = ""; // to accumulate the generated html before assigning it to innerHTML (it's not a good idea to constantly update innerHTML)
for (i = 1; i < 101; ++i) {
var x = 1 - i / 100; // the x coordinate, we are using the left side of the graph so x should be negative going from -1 to 0
var y = Math.sqrt(1 - x * x); // the y coordinate as per the formula (this will be used for the width)
html += "<div style='background-color:#000000; width:" + (xOffset + y * width) + "px; height:" + (height / 100) + "px;'></div>";
}
document.getElementById("graph").innerHTML = html;
<div id="graph"></div>

Javascript create SVG path bottom up vs top down

I have a page that shows a grid of job positions and I am showing the progression from one to another by using SVG + paths to draw the connection between boxes.
My code is working just fine when I am connecting an element at the top to one at the bottom. It is finding the XY of the top box and the XY of the bottom box and connects the two.
My issue is I want to flip this code and go from the bottom up. This means I need the top XY of the bottom element and the bottom XY of the top element and draw the path.
I have been trying to flip offsets around and basically do the opposite of what is working but I think my math is wrong somewhere.
Here is what the top down approach looks like. Works just fine.
The bottom up approach however is not correct. Theres some math errors somewhere and the calculations are causing the SVG to be cut off.
I believe the answer lies within the connectElements() function as that is where the coordinates are determined.
Any thoughts on how I can get these calculations corrected?
Fiddle: http://jsfiddle.net/Ly59a2hf/2/
JS Code:
function getOffset(el) {
var rect = el.getBoundingClientRect();
return {
left: rect.left + window.pageXOffset,
top: rect.top + window.pageYOffset,
width: rect.width || el.offsetWidth,
height: rect.height || el.offsetHeight
};
}
function drawPath(svg, path, startX, startY, endX, endY) {
// get the path's stroke width (if one wanted to be really precize, one could use half the stroke size)
var style = getComputedStyle(path)
var stroke = parseFloat(style.strokeWidth);
// check if the svg is big enough to draw the path, if not, set heigh/width
if (svg.getAttribute("height") < endY) svg.setAttribute("height", endY);
if (svg.getAttribute("width") < (startX + stroke)) svg.setAttribute("width", (startX + stroke));
if (svg.getAttribute("width") < (endX + stroke * 3)) svg.setAttribute("width", (endX + stroke * 3));
var deltaX = (endX - startX) * 0.15;
var deltaY = (endY - startY) * 0.15;
// for further calculations which ever is the shortest distance
var delta = deltaY < absolute(deltaX) ? deltaY : absolute(deltaX);
// set sweep-flag (counter/clock-wise)
// if start element is closer to the left edge,
// draw the first arc counter-clockwise, and the second one clock-wise
var arc1 = 0;
var arc2 = 1;
if (startX > endX) {
arc1 = 1;
arc2 = 0;
}
// draw tha pipe-like path
// 1. move a bit down, 2. arch, 3. move a bit to the right, 4.arch, 5. move down to the end
path.setAttribute("d", "M" + startX + " " + startY +
" V" + (startY + delta) +
" A" + delta + " " + delta + " 0 0 " + arc1 + " " + (startX + delta * signum(deltaX)) + " " + (startY + 2 * delta) +
" H" + (endX - delta * signum(deltaX)) +
" A" + delta + " " + delta + " 0 0 " + arc2 + " " + endX + " " + (startY + 3 * delta) +
" V" + (endY - 30));
}
function connectElements(svg, path, startElem, endElem, type, direction) {
// Define our container
var svgContainer = document.getElementById('svgContainer'),
svgTop = getOffset(svgContainer).top,
svgLeft = getOffset(svgContainer).left,
startX,
startY,
endX,
endY,
startCoord = startElem,
endCoord = endElem;
console.log(svg, path, startElem, endElem, type, direction)
/**
* bottomUp - This means we need the top XY of the starting box and the bottom XY of the destination box
* topDown - This means we need the bottom XY of the starting box and the top XY of the destination box
*/
switch (direction) {
case 'bottomUp': // Not Working
// Calculate path's start (x,y) coords
// We want the x coordinate to visually result in the element's mid point
startX = getOffset(startCoord).left + 0.5 * getOffset(startElem).width - svgLeft; // x = left offset + 0.5*width - svg's left offset
startY = getOffset(startCoord).top + getOffset(startElem).height - svgTop; // y = top offset + height - svg's top offset
// Calculate path's end (x,y) coords
endX = endCoord.getBoundingClientRect().left + 0.5 * endElem.offsetWidth - svgLeft;
endY = endCoord.getBoundingClientRect().top - svgTop;
break;
case 'topDown': // Working
// If first element is lower than the second, swap!
if (startElem.offsetTop > endElem.offsetTop) {
var temp = startElem;
startElem = endElem;
endElem = temp;
}
// Calculate path's start (x,y) coords
// We want the x coordinate to visually result in the element's mid point
startX = getOffset(startCoord).left + 0.5 * getOffset(startElem).width - svgLeft; // x = left offset + 0.5*width - svg's left offset
startY = getOffset(startCoord).top + getOffset(startElem).height - svgTop; // y = top offset + height - svg's top offset
// Calculate path's end (x,y) coords
endX = endCoord.getBoundingClientRect().left + 0.5 * endElem.offsetWidth - svgLeft;
endY = endCoord.getBoundingClientRect().top - svgTop;
break;
}
// Call function for drawing the path
drawPath(svg, path, startX, startY, endX, endY, type);
}
function connectAll(direction) {
var svg = document.getElementById('svg1'),
path = document.getElementById('path1');
// This is just to help with example.
if (direction == 'topDown') {
var div1 = document.getElementById('box_1'),
div2 = document.getElementById('box_20');
} else {
var div1 = document.getElementById('box_20'),
div2 = document.getElementById('box_1');
}
// connect all the paths you want!
connectElements(svg, path, div1, div2, 'line', direction);
}
//connectAll('topDown'); // Works fine. Path goes from the bottom of box_1 to the top of box_20
connectAll('bottomUp'); // Doesn't work. I expect path to go from top of box_20 to the bottom of box_1
IMO, you can simplify things by making the SVG the exact right size. Ie. fit it between the two elements vertically, and have it start at the leftmost X coord.
If you do that, the path starts and ends at either:
X: 0 or svgWidth
Y: 0 or svgHeight.
Then as far as drawing the path goes, it's just a matter of using the relative directions (startX -> endX and startY -> endY) in your calculations. I've called these variables xSign and ySign. If you are consistent with those, everything works out correctly.
The last remaining complication is working out which direction the arcs for the rounded corners have to go - clockwise or anticlockwise. You just have to work out the first one, and the other one is the opposite.
function getOffset(el) {
var rect = el.getBoundingClientRect();
return {
left: rect.left + window.pageXOffset,
top: rect.top + window.pageYOffset,
width: rect.width || el.offsetWidth,
height: rect.height || el.offsetHeight
};
}
function drawPath(svg, path, start, end) {
// get the path's stroke width (if one wanted to be really precise, one could use half the stroke size)
var style = getComputedStyle(path)
var stroke = parseFloat(style.strokeWidth);
var arrowHeadLength = stroke * 3;
var deltaX = (end.x - start.x) * 0.15;
var deltaY = (end.y - start.y) * 0.15;
// for further calculations which ever is the shortest distance
var delta = Math.min(Math.abs(deltaX), Math.abs(deltaY));
var xSign = Math.sign(deltaX);
var ySign = Math.sign(deltaY);
// set sweep-flag (counter/clock-wise)
// If xSign and ySign are opposite, then the first turn is clockwise
var arc1 = (xSign !== ySign) ? 1 : 0;
var arc2 = 1 - arc1;
// draw tha pipe-like path
// 1. move a bit vertically, 2. arc, 3. move a bit to the horizontally, 4.arc, 5. move vertically to the end
path.setAttribute("d", ["M", start.x, start.y,
"V", start.y + delta * ySign,
"A", delta, delta, 0, 0, arc1, start.x + delta * xSign, start.y + 2 * delta * ySign,
"H", end.x - delta * xSign,
"A", delta, delta, 0, 0, arc2, end.x, start.y + 3 * delta * ySign,
"V", end.y - arrowHeadLength * ySign].join(" "));
}
function connectElements(svg, path, startElem, endElem, type, direction) {
// Define our container
var svgContainer = document.getElementById('svgContainer');
// Calculate SVG size and position
// SVG is sized to fit between the elements vertically, start at the left edge of the leftmost
// element and end at the right edge of the rightmost element
var startRect = getOffset(startElem),
endRect = getOffset(endElem),
pathStartX = startRect.left + startRect.width / 2,
pathEndX = endRect.left + endRect.width / 2,
startElemBottom = startRect.top + startRect.height,
svgTop = Math.min(startElemBottom, endRect.top + endRect.height),
svgBottom = Math.max(startRect.top, endRect.top),
svgLeft = Math.min(pathStartX, pathEndX),
svgHeight = svgBottom - svgTop;
// Position the SVG
svg.style.left = svgLeft + 'px';
svg.style.top = svgTop + 'px';
svg.style.width = Math.abs(pathEndX - pathStartX) + 'px';
svg.style.height = svgHeight + 'px';
// Call function for drawing the path
var pathStart = {x: pathStartX - svgLeft, y: (svgTop === startElemBottom) ? 0 : svgHeight};
var pathEnd = {x: pathEndX - svgLeft, y: (svgTop === startElemBottom) ? svgHeight : 0};
drawPath(svg, path, pathStart, pathEnd);
}
function connectAll(direction) {
var svg = document.getElementById('svg1'),
path = document.getElementById('path1');
// This is just to help with example.
if (direction == 'topDown') {
var div1 = document.getElementById('box_1'),
div2 = document.getElementById('box_20');
} else {
var div1 = document.getElementById('box_20'),
div2 = document.getElementById('box_1');
}
// connect all the paths you want!
connectElements(svg, path, div1, div2, 'line');
}
//connectAll('topDown');
connectAll('bottomUp');
http://jsfiddle.net/93Le85tk/3/

zoom changes slider behavior css/javascript

I have created a circle slider using JavaScript, and I need it to act right if it gets zoomed in or out.
My issue is when the circle slider gets zoomed in eg. (zoom: 0.5) , the mouse event listener for the slider will not act probably.
This issue only happen if I set the the zoom property to less or bigger than 1 .
You can try and see the differences: https://jsfiddle.net/mqgfxkjf/8/
Change:
<div style="zoom: 1.0">
To:
<div style="zoom: 0.5">
And you will find that it's not acting right while moving the slider to all-directions.
Tested on Chrome
You have to scale the mouse position accordingly to the zoom value.
Let's say you have set zoom value to 0.5, you will have to scale the mouse position (x and y) with the same value. So in order to fix this exact problem, you can do something as simple as just dividing by the scale value: var mPos = {x: (e.clientX / 0.5) - elPos.x, y: (e.clientY / 0.5) - elPos.y };.
I highly suggest that you let the script handle the scale / zoom value so that you can set it as a variable in your script. I.e. something like this:
(function () {
var scaleValue = 0.5;
addZoom(scaleValue);
var $container = $('#container');
var $slider = $('#slider');
var sliderW2 = $slider.width()/2;
var sliderH2 = $slider.height()/2;
var radius = 200;
var deg = 0;
var elP = $('#container').offset();
var elPos = { x: elP.left, y: elP.top};
var X = 0, Y = 0;
var mdown = false;
$('#container')
.mousedown(function (e) { mdown = true; })
.mouseup(function (e) { mdown = false; })
.mousemove(function (e) {
if (mdown) {
var mPos = {x: (e.clientX / scaleValue) - elPos.x, y: (e.clientY / scaleValue) - elPos.y };
var atan = Math.atan2(mPos.x-radius, mPos.y-radius);
deg = -atan/(Math.PI/180) + 180; // final (0-360 positive) degrees from mouse position
X = Math.round(radius* Math.sin(deg*Math.PI/180));
Y = Math.round(radius* -Math.cos(deg*Math.PI/180));
$slider.css({ left: X+radius-sliderW2, top: Y+radius-sliderH2 });
// AND FINALLY apply exact degrees to ball rotation
$slider.css({ WebkitTransform: 'rotate(' + deg + 'deg)'});
$slider.css({ '-moz-transform': 'rotate(' + deg + 'deg)'});
//
// PRINT DEGREES
$('#value').html('angle deg= '+deg);
}
});
})();
function addZoom(scaleValue) {
$('#zoom-container').css('zoom', scaleValue);
}
Fiddle:
https://jsfiddle.net/mqgfxkjf/10/

JS display text tangent to a circle

Here is what I have so far
https://jsfiddle.net/0p7zf13x/5/
Here is what I would like to obtain
Text around circle, and tangent to its center
The code is on jsfiddle, but here is a little piece of it.
var count = $("#rollo ul li").length;
var cx = 300;
var cy = 300;
var r = 300;
$("#rollo ul li").each(function(index) {
var theta = 2 * Math.PI * (index / count);
var left = cx + r * Math.sin(theta);
var top = cy - r * Math.cos(theta);
console.log(index, left, top);
$(this).css({ left: left, top: top });
$(this).css({"transform":"rotate("+Math.cos(theta)*-90+"deg)"});
});
I started from this code page
placing divs in a circle
Question : How to make texts around circle and left side of first letter, tangent to that same circle ? All texts evenly distributed.
Thanks
Just use the angle you're generating from:
$(this).css({"transform":"rotate("+(theta-Math.PI/2)+"rad)"});
fiddle

JS/ CSS Matrix cube with axis scale

I have this code that generates the div.
By applying the css transform property using matrix, I would to get the three faces of a cube, aligning the div properly.
The problem is in the left div. Setting array leftArr scale (d * scale), I can not align vertically correctly div left side of the top div.
Can anyone tell me the best way to get a simulation of a cube.
Thank you.
CSS:
.face {
height: 50px;
overflow: hidden;
position: absolute;
width: 50px;
}
JS:
var angle = 45,
r = parseFloat(angle) * (Math.PI / 180),
cos_theta = Math.cos(r),
sin_theta = Math.sin(r);
var a = cos_theta,
b = sin_theta,
c = -sin_theta,
d = cos_theta;
var face = 50, //reference to .face class
k = 0,
j = 100; //constant
var scale = 3;
var dX = face * Math.SQRT2 * scale;
var dY = face * Math.SQRT2;
for(var i = 0; i < 3; i++){
var tx = j + k;
var ty = j;
var lx = j + k - dX/4;
var ly = ty;
var topArr = [a * scale, b, c * scale, d, tx, ty];
var leftArr = [a * scale, b, 0, d * scale, lx, ly];
var top = 'matrix(' + topArr.join(',') + ')';
var left = 'matrix(' + leftArr.join(',') + ')';
k += dX;
$('<div/>', {
id : 'top_'+i,
'class' : 'face',
css : {
'background' : 'hsla( ' + parseInt(Math.random() * 90) + ', 100%, 50%, 0.5 )',
'transform' : top
}
}).appendTo('body');
$('<div/>', {
id : 'left_'+i,
'class' : 'face',
css : {
'background' : 'hsla( ' + parseInt(Math.random() * 90) + ', 100%, 50%, 0.5 )',
'transform' : left
}
}).appendTo('body');
}
Example:
Scale = 1
Scale = 2
Scale = 3
UPDATE:
After some test with:
var ly = ty + dY/2 + ( ( (dY/2)*(scale-1) ) / 2);
the code take sense, but if there are better solution, any help is appreciate.
You are using 2d transforms to rotate in 3d.
If you want an elegant solution, you should use 3d matrices, that are of dimension 4.
Then you would have the left side come from a 90 degrees turn from the down side; and the same translation would be applied to that.
If you want to use 2d transforms, then the best way to go would be to precalculate the 2 d transforms for each face. Then calculate the translation matrix for all the cube (only 1 matrix, you are moving all faces at the same time). The matrices for each face will the product of the translation matrix and the face matrix. (keep in mind that this is not conmutative, the order is important)

Categories