Get x,y coord of a div when rotation is 0 - javascript

Hi I need some help with math for my problem. I have a situation where I need to access the left,top coord of a div whenever a touchstart event is fired. But I need to get it based on the rotation of the div being 0 degree.
I have attached a fiddle which shows my condition. Right now when you press the button, the value is 'x:169, y:195'. But I am looking for some formula to get 'x: 208, y: 234'
document.querySelectorAll('input')[0].addEventListener('click',function() {
var x = Math.round(document.querySelectorAll('#knob')[0].getBoundingClientRect().left),
y = Math.round(document.querySelectorAll('#knob')[0].getBoundingClientRect().top)
alert('x: '+x+', y: '+y);
/*
* Alerts x: 208, y: 234 when angle is 0.
* Need to get this value all the time irrespective of the rotation.
*/
});
Is that possible? I did a lot of search but couldn't find the apt answer. And my math isn't good enough to modify the formulas for the other answers in SO.

Note that the centre of the red square will not change when rotated. So we can find its coordinates first (by taking averages), then figure out the top left corner (based off the fact that the size of the red square is given beforehand; in this case, it was 256x256). Here's a simple implementation:
document.querySelectorAll('input')[0].addEventListener('click',function() {
var left = Math.round(document.querySelectorAll('#knob')[0].getBoundingClientRect().left),
right = Math.round(document.querySelectorAll('#knob')[0].getBoundingClientRect().right),
top = Math.round(document.querySelectorAll('#knob')[0].getBoundingClientRect().top),
bottom = Math.round(document.querySelectorAll('#knob')[0].getBoundingClientRect().bottom),
x_mid = (left+right)/2.0,
y_mid = (top+bottom)/2.0,
x = x_mid - 256/2, // Subtract by half the knob's width.
y = y_mid - 256/2 // Subtract by half the knob's height.
alert('left: '+left+', right: '+right+', top: '+top+', bottom: '+bottom+'\n'
+'centre: ('+x_mid+', '+y_mid+')\n'
+'x: '+x+', y: '+y);
//Alerts x: 208, y: 234 when angle is 0. Need to get this value all the time irrespective of the rotation.
});

Use this code for any figure
document.querySelectorAll('input')[0].addEventListener('click', function () {
var left = Math.round(document.querySelectorAll('#knob')[0].getBoundingClientRect().left),
right = Math.round(document.querySelectorAll('#knob')[0].getBoundingClientRect().right),
top = Math.round(document.querySelectorAll('#knob')[0].getBoundingClientRect().top),
bottom = Math.round(document.querySelectorAll('#knob')[0].getBoundingClientRect().bottom),
width = Math.round(document.querySelectorAll('#knob')[0].offsetWidth),
height = Math.round(document.querySelectorAll('#knob')[0].offsetHeight),
x_mid = (left + right) / 2.0,
y_mid = (top + bottom) / 2.0,
x = x_mid - width / 2, // Subtract by half the knob's width.
y = y_mid - height / 2 // Subtract by half the knob's height.
alert('left: ' + left + ', right: ' + right + ', top: ' + top + ', bottom: ' + bottom + '\n' + 'centre: (' + x_mid + ', ' + y_mid + ')\n' +'dimen; w:' +width + ',h:'+ height + 'x: ' + x + ', y: ' + y);
});

Related

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/

javascript find same position in rotated element

I have a "base" reference rect (red)
Inside a rotated div (#map), I need a clone rect (yellow), it has to be same size and position of "base" rect, independent of its parent (#map) rotation.
This is where I am so far, any help would be welcoming.
http://codepen.io/christianpugliese/pen/oXKOda
var controls = { degrees: 0, rectX:125, rectY:55 };
var wBounds = document.getElementById("wrapper").getBoundingClientRect(),
mapBounds = document.getElementById("map").getBoundingClientRect(),
rectBounds = document.getElementById("rect").getBoundingClientRect();
var _x = ((mapBounds.width - wBounds.width) / 2) + $('#rect').position().left,
_y = ((mapBounds.height - wBounds.height) / 2) + $('#rect').position().top;
$('#rect').css({top: controls.rectY+'px', left:controls.rectX+'px'});
$('#mapRect').width(rectBounds.width);
$('#mapRect').height(rectBounds.height);
$('#mapRect').css({top: _y+'px',
left:_x+'px',
'transform': 'rotate('+ Math.round(-controls.degrees) +'deg)'});
$('#map').css('transform', 'rotate('+ Math.round(controls.degrees) +'deg)');
Since you're rotating the #mapRect an equal amount in the opposite direction you're getting rotation/orientation right but not the origin. The transform-origin would be the center of the #mapBounds, but relative to the #rect;
Fork of your pen: http://codepen.io/MisterCurtis/pen/vNBYZJ?editors=101
Since there is some rounding/subpixel positioning happening the yellow rect doesn't align pixel perfect.
function updateUI(){
var _x = ((mapBounds.width - wBounds.width) / 2) + $('#rect').position().left,
_y = ((mapBounds.height - wBounds.height) / 2) + $('#rect').position().top,
_ox = mapBounds.width/2 - _x, // origin x
_oy = mapBounds.height/2 - _y; // origin y
...
$('#mapRect').css({
'transform-origin': _ox + 'px ' + _oy + 'px', // now it rotates by the bounds
top: _y + 'px',
left: _x + 'px',
'transform': 'rotate(' + Math.round(-controls.degrees) + 'deg)'
});
}
Edit: Updated the pen. You'll have to ditch using Rectangle and instead use Polygon. This way you can use a plugin like https://github.com/ahmadnassri/google-maps-polygon-rotate to perform the rotation along the map center.

Highcharts. Pie chart. DataLabels formatter

I have a pie chart. And I need to format DataLabels in someway. So I use:
dataLabels: {
useHTML : true,
formatter: function () {
if(this.point.id == 'razr') {
return '<div><b>' + this.point.y + '</b></div><div style="left: -140px; text-align: center; position: absolute"><b>sum is:<br/>' + this.point.summ + ' </b></div>';
} else return '<b>' + this.point.y + '<b>';
}
}
And what I got:
My problem is here style="left: -140px;. The position is static. I can't find any way to position my sum label at the center of a green point of a chart. I've been searching the properties of a point (like plotX, graphic attr), nothing helps. If I remove style="left: -140px; datalabel will moves to the right. How can I get coordinates of my green point?
To be honest, it's not easy. I see two possible solutions:
1) Easy (but dirty workaround): create second pie chart under the first one with the same values, but render just one label. Then the second pie chart can have dataLabel inside the slice.
2) Hard (more generic solution): calculate required top/left offsets. It's hard because you don't know bounding box of the label. I suggest to set fixed width+height for that part of the label - so you can find center of that label's part. Then calculate position using series.center and point.labelPos arrays. These are inner options and can change between versions, so keep an eye on these options before upgrading. Example: http://jsfiddle.net/zwb5toe9/2/
dataLabels: {
useHTML: true,
formatter: function () {
var point = this.point,
width = 50,
height = 50,
series = point.series,
center = series.center, //center of the pie
startX = point.labelPos[4], //connector X starts here
endX = point.labelPos[0] + 5, //connector X ends here
left = -(endX - startX + width / 2), //center label over right edge
startY = point.labelPos[5], //connector Y starts here
endY = point.labelPos[1] - 5, //connector Y ends here
top = -(endY - startY + height / 2); //center label over right edge
// now move inside the slice:
left += (center[0] - startX)/2;
top += (center[1] - startY)/2;
if (point.id == 'razr') {
console.log(this);
return '<div><b>' + point.y + '</b></div><div style="width: ' + width + 'px; left:' + left + 'px;top:' + top + 'px; text-align: center; position: absolute"><b>sum is:<br/>' + point.summ + ' </b></div>';
} else return '<b>' + point.y + '<b>';
}
}

Rotate evenly-distributed elements placed on a circle with jQuery

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);

Find new left and top corresponding to original left and top on CSS rotate

Suppose my div has left:200px and top:400px, after I apply a rotate transform of suppose 90 deg the above top and left positions no more point to the old positions. Now how can we calculate the new top and left for the transformed div which are equivalent to the left and top positions of the non-transformed div after rotation.
Edited answer
Besides the starting position of the corner point (top-left in your example), and the rotation angle, we also need to know the position of the reference point of the rotation. This is the point around which we rotate the div (CSS calls it transform-origin). If you don't specify it, then normally, the centre of mass of the element is used.
I don't know of any JavaScript method that simply calculates it for you, but I can show you its Math, and a simple JS implementation.
Math
P: original position of the corner point, with (Px, Py) coordinates
O: reference point of the rotation, with (Ox, Oy) coordinates
Calculate the original position of P, relative to O.
x = Px - Ox
y = Py - Oy
Calculate the rotated position of P, relative to O.
x' = x * cos(angle) - y * sin(angle)
y' = x * sin(angle) + y * cos(angle)
Convert this position back to the original coordinate system.
Px' = x' + Ox
Py' = y' + Oy
If you're not aware of the formulas in step #2, you can find an explanation here.
JavaScript implementation
function rotatedPosition(pLeft, pTop, oLeft, oTop, angle){
// 1
var x = pLeft - oLeft;
var y = pTop - oTop;
// 2
var xRot = x * Math.cos(angle) - y * Math.sin(angle);
var yRot = x * Math.sin(angle) + y * Math.cos(angle);
// 3
var pLeftRot = xRot + oLeft;
var pTopRot = yRot + oTop
return {left: pLeftRot, top: pTopRot};
}
rotatedPosition requires you to define the original position of the point and the reference point, plus the angle.
In case you need a method which takes only a single argument, the div element itself, and computes the rest for you, then you can do something like:
function divTopLeftRotatedPosition(div){
var pLeft = // ...
var pTop = // ...
var width = // ...
var height = // ...
var angle = // ...
return rotatedPosition(pLeft, pTop, pLeft + width / 2, pTop + height / 2, angle);
}

Categories