I have a web app where I would like the user to draw a line in the following way: When he clicks on Point1 and he moves the mouse, draw the line from Point1 to the current mouse position and, when clicks to Point2 draw the final line from Point1 to Point2.
How can I do it using jQuery and/or one of its plugins?
Challenge accepted.
I tried to do it with CSS transforms and a bunch of Math in Javascript - after half an hour I have this:
http://jsfiddle.net/VnDrb/2/
Make 2 clicks into the gray square and a line should be drawn.
There is still a small bug that draws the line wrong when the angle is > 45 degree. Maybe someone else knows how to fix that. Maybe instead of using Math.asin (arcsinus), use a other trigonometrical function, but I'm really not good at it.
I thought I'd post it even there is a small bug, I think it's a good start for you.
I've tried a number of different approaches this weekend and the solution that worked best for me is from Adam Sanderson: http://monkeyandcrow.com/blog/drawing_lines_with_css3/
His demo is here: http://monkeyandcrow.com/samples/css_lines/
The core of it is very simple, which is always good.
div.line{
transform-origin: 0 100%;
height: 3px; /* Line width of 3 */
background: #000; /* Black fill */
}
function createLine(x1,y1, x2,y2){
var length = Math.sqrt((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2));
var angle = Math.atan2(y2 - y1, x2 - x1) * 180 / Math.PI;
var transform = 'rotate('+angle+'deg)';
var line = $('<div>')
.appendTo('#page')
.addClass('line')
.css({
'position': 'absolute',
'transform': transform
})
.width(length)
.offset({left: x1, top: y1});
return line;
}
You can not do it with jQuery and classic HTML.
You can do it using SVG (+svgweb for IE8- http://code.google.com/p/svgweb/ )
SVG can be dynamically created. jQuery + svgweb are working perfectly, you just need to know how to create SVG nodes and you need only jquerify this nodes. After jquerifiing in most cases used only one method attr()
You can do it using Raphael http://raphaeljs.com/ (based on SVG and VML)
You can do it using Canvas ( http://flashcanvas.net/ for IE8- )
For SVG programming will be this way:
Moment of creating first point: you create empty line var Line (also this points coordinates will be x1 and y1)
Then you bind on mousemove repaint of x2, y2 properties of Line
On mousedown after mousemove you freeze last line position.
UPDATE
You can do it with CSS/JS, but main problem is in calculations for IE8-, that has only Matrix filter for transformations.
Been using a modified version of this for a while now. Works well.
http://www.ofdream.com/code/css/xline2.php
So on first click, drop and object there as a placeholder div, maybe a little circle, then either keep redrawing a line as they move their mouse, or draw it when they click the second time, using the original placeholder as a guide.
I recently made another helper function for this, because my tool involves moving lines around:
function setLinePos(x1, y1, x2, y2, id) {
if (x2 < x1) {
var temp = x1;
x1 = x2;
x2 = temp;
temp = y1;
y1 = y2;
y2 = temp;
}
var line = $('#line' + id);
var length = Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
line.css('width', length + "px");
var angle = Math.atan((y2 - y1) / (x2 - x1));
line.css('top', y1 + 0.5 * length * Math.sin(angle) + "px");
line.css('left', x1 - 0.5 * length * (1 - Math.cos(angle)) + "px");
line.css('-moz-transform', "rotate(" + angle + "rad)");
line.css('-webkit-transform', "rotate(" + angle + "rad)");
line.css('-o-transform', "rotate(" + angle + "rad)");
}
That is the jquery version, and in this iteration I have no IE requirement so I ignore it. I could be adapted from the original function pretty easily.
The class
function getXY(evt, element) {
var rect = element.getBoundingClientRect();
var scrollTop = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop;
var scrollLeft = document.documentElement.scrollLeft ? document.documentElement.scrollLeft : document.body.scrollLeft;
var elementLeft = rect.left + scrollLeft;
var elementTop = rect.top + scrollTop;
x = evt.pageX - elementLeft;
y = evt.pageY - elementTop;
return { x: x, y: y };
}
var LineDrawer = {
LineHTML: `<div style="cursor: pointer;transform-origin:center; position:absolute;width:200px;height:2px; background-color:blue"></div>`,
isDown: false,
pStart: {},
pCurrent :{},
containerID: "",
JLine: {},
angle: 0,
afterLineCallback: null,
Init: function (containerID, afterLineCallback) {
LineDrawer.containerID = containerID;
LineDrawer.afterLineCallback = afterLineCallback;
LineDrawer.JLine = $(LineDrawer.LineHTML).appendTo("#" + LineDrawer.containerID);
LineDrawer.JLine.css("transform-origin", "top left");
LineDrawer.JLine.hide();
//LineDrawer.JLine.draggable({ containment: "#" + LineDrawer.containerID });
$("#" + LineDrawer.containerID).mousedown(LineDrawer.LineDrawer_mousedown);
$("#" + LineDrawer.containerID).mousemove(LineDrawer.LineDrawer_mousemove);
$("#" + LineDrawer.containerID).mouseup(LineDrawer.LineDrawer_mouseup);
},
LineDrawer_mousedown: function (e) {
if (e.target === LineDrawer.JLine[0]) return false;
LineDrawer.isDown = true;
let p = LineDrawer.pStart = getXY(e, e.target);
LineDrawer.JLine.css({ "left": p.x, "top": p.y, "width": 1});
LineDrawer.JLine.show();
},
LineDrawer_mousemove: function (e) {
if (!LineDrawer.isDown) return;
LineDrawer.pCurrent = getXY(e, document.getElementById("jim"));
let w = Math.sqrt(((LineDrawer.pStart.x - LineDrawer.pCurrent.x) * (LineDrawer.pStart.x - LineDrawer.pCurrent.x)) + ((LineDrawer.pStart.y - LineDrawer.pCurrent.y) * (LineDrawer.pStart.y - LineDrawer.pCurrent.y)));
LineDrawer.JLine.css("width", w - 2);
LineDrawer.angle = Math.atan2((LineDrawer.pStart.y - LineDrawer.pCurrent.y), (LineDrawer.pStart.x - LineDrawer.pCurrent.x)) * (180.0 / Math.PI);
//the below ensures that angle moves from 0 to -360
if (LineDrawer.angle < 0) {
LineDrawer.angle *= -1;
LineDrawer.angle += 180;
}
else LineDrawer.angle = 180 - LineDrawer.angle;
LineDrawer.angle *= -1;
LineDrawer.JLine.css("transform", "rotate(" + LineDrawer.angle + "deg");
},
LineDrawer_mouseup: function (e) {
LineDrawer.isDown = false;
if (LineDrawer.afterLineCallback == null || LineDrawer.afterLineCallback == undefined) return;
LineDrawer.afterLine(LineDrawer.angle, LineDrawer.pStart, LineDrawer.pCurrent);
},
};
Usage:
var ECApp = {
start_action: function () {
LineDrawer.Init("jim", ECApp.afterLine);
},
afterLine(angle, pStart, pEnd) {
//$("#angle").text("angle : " + angle);
let disp = "angle = " + angle;
disp += " Start = " + JSON.stringify(pStart) + " End = " + JSON.stringify(pEnd);
//alert(disp);
$("#angle").text("angle : " + disp);
}
}
$(document).ready(ECApp.start_action);
HTML
<div class="row">
<div class="col">
<div id="jim" style="position:relative;width:1200px;height:800px;background-color:lightblue;">
</div>
</div>
Related
I am working to add filter functionality to my d3 graph. When the user searches for a specific node based on label or id, I want to re-render the graph and show the entire graph again but I want the filtered node to sit in the center of the svg element.
here is what I have the helped it to be centered:
// I get the width and height of the SVG element:
var svgWidth = parseInt(svg.style("width").replace(/px/, ""), 10);
var svgHeight = parseInt(svg.style("height").replace(/px/, ""), 10);
// I get the center of the svg:
var centerX = svgWidth / 2;
var centerY = svgHeight / 2;
_.forEach(nodes, function(e) {
// get the full node (with x and y coordinates) based on the id
var nodeObject = g.node(nodeId);
// I look for matches between the nodeId or label and search word
if (searchInput) {
if (nodeObject.id === parseInt(searchInput, 10) || nodeObject.label.toUpperCase().indexOf(searchInput.toUpperCase()) > -1) {
searchedNodes.push(nodeObject);
console.log(searchedNodes);
}
}
}
// after looping through all the nodes rendered
if (searchedNodes.length > 0) {
//var width = searchedNodes[0].elem.getBBox().width;
//var height = searchedNodes[0].elem.getBBox().height;
ctrl.selectedNode = searchedNodes[0];
var offsetX = centerX - searchedNodes[0].x;
var offsetY = centerY - searchedNodes[0].y;
svgGroup.attr("transform", "translate(" + offsetX + "," + offsetY + ")" + "scale(" + 3 + ")");
// this line here is incorrect syntax and breaks the build, essentially stopping the script from running
// the graph renders correctly when this line is here
svgGroup.attr("transform", "translate(" + offsetX + "," + offsetY + ")").scale(2).event;
}
This is what the graph looks like with the line above that breaks the script included.
When I removed that line, it doesn't center, almost looking like over-renders the graph. Obviously I will need to remove the line of code above that is incorrect but does anybody no why the graph doesn't render correctly in this case?:
// get the user input and re-render the graph
elem.find(".search").bind("keyup", function (e:any) {
var searchInput;
if (e["keyCode"] === 13) {
searchedNodes = [];
searchInput = scope["searchInput"];
currentFilteredNode = null;
enterKeyPressed = true;
renderGraph(searchInput);
}
if (e["keyCode"] === 8) {
searchedNodes = [];
searchInput = scope["searchInput"];
currentFilteredNode = null;
renderGraph(searchInput);
}
});
// if there is searchInput and at least one matching node sort the nodes
// by id and then select and center the first matching one
if (searchInput && searchedNodes.length > 0) {
searchedNodes.sort(function (node1:any, node2:any) {
return node1.id - node2.id;
});
// make sure the noResultsMessage does not get shown on the screen if there are matching results
scope.$apply(function() {
scope["noResultsMessage"] = false;
});
ctrl.selectedNode = searchedNodes[0];
offsetX = centerX - searchedNodes[0].x;
offsetY = centerY - searchedNodes[0].y;
svgGroup.attr("transform", "translate(" + offsetX + "," + offsetY + ")" + "scale(" + 3 + ")");
}
// the only other zoom and this runs just on page load
zoom = d3.behavior.zoom();
zoom.on("zoom", function() {
svgGroup.attr("transform", "translate(" + (<any>d3.event).translate + ")" + "scale(" + (<any>d3.event).scale + ")");
// this scales the graph - it runs on page load and whenever the user enters a search input, which re-renders the whole graph
var scaleGraph = function(useAnimation:any) {
var graphWidth = g.graph().width + 4;
var graphHeight = g.graph().height + 4;
var width = parseInt(svg.style("width").replace(/px/, ""), 10);
var height = parseInt(svg.style("height").replace(/px/, ""), 10);
var zoomScale = originalZoomScale;
// Zoom and scale to fit
if (ctrl.autoResizeGraph === "disabled") {
zoomScale = 1;
} else {
// always scale to canvas if set to fill or if auto (when larger than canvas)
if (ctrl.autoResizeGraph === "fill" || (graphWidth > width || graphHeight > height)) {
zoomScale = Math.min(width / graphWidth, height / graphHeight);
}
}
var translate;
if (direction.toUpperCase() === "TB") {
// Center horizontal + align top (offset 1px)
translate = [(width / 2) - ((graphWidth * zoomScale) / 2) + 2, 1];
} else if (direction.toUpperCase() === "BT") {
// Center horizontal + align top (offset 1px)
translate = [(width / 2) - ((graphWidth * zoomScale) / 4) + 2, 1];
} else if (direction.toUpperCase() === "LR") {
// Center vertical (offset 1px)
translate = [1, (height / 2) - ((graphHeight * zoomScale) / 2)];
} else if (direction.toUpperCase() === "RL") {
// Center vertical (offset 1px)
translate = [1, (height / 2) - ((graphHeight * zoomScale) / 4)];
} else {
// Center horizontal and vertical
translate = [(width / 2) - ((graphWidth * zoomScale) / 2), (height / 2) - ((graphHeight * zoomScale) / 2)];
}
zoom.center([width / 2, height / 2]);
zoom.size([width, height]);
zoom.translate(translate);
zoom.scale(zoomScale);
// If rendering the first time, then don't use animation
zoom.event(useAnimation ? svg.transition().duration(500) : svg);
};
CODE FOR FILTERING THE NODES:
// move to the left of the searchedNodes array when the left arrow is clicked
scope["filterNodesLeft"] = function () {
filterNodesIndex--;
if (filterNodesIndex < 0) {
filterNodesIndex = searchedNodes.length - 1;
}
currentFilteredNode = searchedNodes[filterNodesIndex];
runScaleGraph = true;
number = 1;
renderGraph();
};
// move to the right of the searchNodes array when the right arrow is clicked
scope["filterNodesRight"] = function () {
filterNodesIndex++;
if (filterNodesIndex > searchedNodes.length - 1) {
filterNodesIndex = 0;
}
currentFilteredNode = searchedNodes[filterNodesIndex];
runScaleGraph = true;
number = 1;
renderGraph();
};
// get the current filteredNode in the searchNodes array and center it
// when the graph is re-rendered
if (currentFilteredNode) {
ctrl.selectedNode = currentFilteredNode;
offsetX = centerX - currentFilteredNode.x;
offsetY = centerY - currentFilteredNode.y;
svgGroup.attr("transform", "translate(" + offsetX + "," + offsetY + ")");
runScaleGraph = false;
}
You will want to find the x and y coordinates of your target node, and set the transform attribute of your group with class 'output' accordingly. You will also need to know the width and height of 'output' in order to position it such that your target node is in the center.
//when diagram is initially displayed
var output = d3.select('.output');
var bbox = output.getBBox();
var centerX = bbox.width * .5;
var centerY = bbox.height * .5;
//in your block where you find a node matches the filter
if (node.label.toUpperCase().indexOf(searchString.toUpperCase()) > -1) {
var offsetX = centerX - node.x;
var offsetY = centerY - node.y;
output.attr('transform', 'translate(' + offsetX + ',' + offsetY + ')');
}
Depending on the node's registration point, you may also need to take in to account the node's width and height to make sure we are directly centered on the node. For example, if the registration point is the top left of the node, you would want to add half the nodes width and half the nodes height to the offset.
-- Edit --
In the following line:
svgGroup.attr("transform", "translate(" + offsetX + "," + offsetY + ")" + "scale(" + 3 + ")");
by including "scale(" + 3 + ")" so you are scaling your entire graph - you are not 'zooming in' on the place you have centered, rather the content itself is bigger and so offsetX and offsetY are not the correct cordinates to center on.
The reason things look better when you add that other line, is that you are removing the scale.
svgGroup.attr("transform", "translate(" + offsetX + "," + offsetY + ")");
So, we are back to the default scale, immediately prior to your error being thrown.
If you want to scale, you'll need to multiply offsetX and offsetY by whatever you want to scale by.
If you do not want to scale, just remove
"scale(" + 3 + ")"
Here's how I solved it:
// zoom in on the searched or filtered node
function zoomOnNode (node:any) {
// get the width and height of the svg
var svgWidth = parseInt(svg.style("width").replace(/px/, ""), 10);
var svgHeight = parseInt(svg.style("height").replace(/px/, ""), 10);
// loop through all the rendered nodes (these nodes have x and y coordinates)
for (var i = 0; i < renderedNodes.length; i++) {
// if the first matching node passed into the function
// and the renderedNode's id match get the
// x and y coordinates from that rendered node and use it to calculate the svg transition
if (node.id === renderedNodes[i].id) {
var translate = [svgWidth / 2 - renderedNodes[i].x, svgHeight / 2 - renderedNodes[i].y];
var scale = 1;
svg.transition().duration(750).call(zoom.translate(translate).scale(scale).event);
}
}
}
// listen for the enter key press, get all matching nodes and pass in the first matching node in the array to the zoomOnNode function
elem.find(".search").bind("keyup", function (e:any) {
var searchInput;
if (e["keyCode"] === 13) {
searchedNodes = [];
searchInput = scope["searchInput"];
enterKeyPressed = true;
if (searchInput) {
// recursively get all matching nodes based on search input
getMatchingNodes(ctrl.nodes, searchInput);
scope.$apply(function() {
// show the toggle icons if searchedNodes.length is greater then 1
scope["matchingNodes"] = searchedNodes.length;
scope["noResultsMessage"] = false;
if (searchedNodes.length > 0) {
var firstNode = searchedNodes[0];
ctrl.selectedNode = firstNode;
zoomOnNode(firstNode);
} else if (searchedNodes.length === 0) {
ctrl.selectedNode = null;
// add the noResultsMessage to the screen
scope["noResultsMessage"] = true;
}
});
}
}
}
I want to display random numbers inside a div at random positions without overlapping.
I am able to display random number at random position but its going outside the box and overlapping each other.
Here is my code:
JS Fiddle
var width = $('.container').innerWidth();
var height = $('.container').innerHeight();
(function generate() { // vary size for fun
for (i = 0; i < 15; i++) {
var divsize = 12;
var color = '#' + Math.round(0xffffff * Math.random()).toString(16);
$newdiv = $('<div/>').css({
'width': divsize + 'px',
'height': divsize + 'px'
});
// make position sensitive to size and document's width
var posx = (Math.random() * (width - divsize)).toFixed();
var posy = (Math.random() * (height - divsize)).toFixed();
$newdiv.css({
'position': 'absolute',
'left': posx + 'px',
'top': posy + 'px',
'float': 'left'
}).appendTo('.container').html(Math.floor(Math.random() * 9));
}
})();
How can I do this?
You've got most of it figured out. You just need to think of the .container div as a grid to avoid any overlap or outlying items.
Just check out this fiddle.
Here's what the code looks like:
var tilesize = 18, tilecount = 15;
var gRows = Math.floor($(".container").innerWidth()/tilesize);
var gCols = Math.floor($('.container').innerHeight()/tilesize);
var vals = _.shuffle(_.range(tilecount));
var xpos = _.shuffle(_.range(gRows));
var ypos = _.shuffle(_.range(gCols));
_.each(vals, function(d,i){
var $newdiv = $('<div/>').addClass("tile");
$newdiv.css({
'position':'absolute',
'left':(xpos[i] * tilesize)+'px',
'top':(ypos[i] * tilesize)+'px'
}).appendTo( '.container' ).html(d);
});
PS:I have used underscore in my fiddle to make things easier for me and because I personally hate writing for loops.
If the number of divs you need to create is small enough (i.e. you're not risking that they won't fit) then a simple algorithm is:
pick a random position (x0, y0)-(x1, y1)
check if any previously selected rect overlaps
if none overlaps then add the rect, otherwise loop back and choose another random position
in code
var selected = [];
for (var i=0; i<num_divs; i++) {
while (true) {
var x0 = Math.floor(Math.random() * (width - sz));
var y0 = Math.floor(Math.random() * (height - sz));
var x1 = x0 + sz;
var y1 = y0 + sz;
var i = 0;
while (i < selected.length &&
(x0 >= selected[i].x1 ||
y0 >= selected[i].y1 ||
x1 <= selected[i].x0 ||
y1 <= selected[i].y0)) {
i++;
}
if (i == selected.length) {
// Spot is safe, add it to the selection
selected.push({x0:x0, y0:y0, x1:x1, y1:y1});
break;
}
// The choice collided with a previously added div
// just remain in the loop so a new attempt is done
}
}
In case the elements are many and it's possible to place n-1 of them so that there's no position where to put n-th element then things are a lot more complex.
For the solution of the 1-dimensional version of this problem see this answer.
You can add to array position of each number. And then when ou generate new position for digit you should check if posx posy in array, if false place number there, if true generate new posx and posy
Boy it was hard to give this problem a name...
I've been working on this "progress bar" logic, that when ever the user moves
his/her mouse - the indicator (in this case its progress bar) shows how close cursor is to the wanted object.
Basically it's like "hot 'n cold" kind of thing.
Here's the fiddle
...and this is the problem part
relativeDistance = ((maxMouseDistance - distance) / maxDistance);
if ((maxMouseDistance - distance) > maxDistance){
relativeDistance = 1- (((maxMouseDistance) / maxDistance) -1);
}
Since my code and distance measurements are based on trigonometry, it has a small problem: There's actually atleast two points on the screen, where the wanted distances are equal.
Try it and you'll notice what I mean.
Any ideas on how I could get rid of that...It's propably because of the logics, but I just don't see it.
Does this jsFiddle do what you want?
It uses the nearest corner to the mouse rather than the farthest corner. It will show 0% when the mouse is in any corner, and a positive percentage as the mouse approaches the target, even if the target is off-centre.
(function () {
var mX
, mY
, distance
, $distance = $('#distance')
, $element = $('#thetarget')
, maxMouseDistance
, relativeDistance;
var theWidth = $(document).width();
var theHeight = $(document).height();
$("#theWidth").text(theWidth);
$("#theHeight").text(theHeight);
function pythagoras(length, height) {
var length2 = length * length
, height2 = height * height
return Math.sqrt((length2 + height2));
}
/**/
var target = $("#thetarget");
target.css({
cursor: "default"
, border: "1px solid black"
, margin: 0});
var position = target.position(); // top left of target element
var tX = Math.floor(position.left)
var tY = Math.floor(position.top)
$("#targetPosition").text(tX + ":" + tY);
var corners = [
[0, 0]
, [theWidth, 0]
, [theWidth, theHeight]
, [0, theHeight]
]
function distanceToNearestCorner(x, y) {
var cornerX = x < tX ? 0 : theWidth
var cornerY = y < tY ? 0 : theHeight
return pythagoras(cornerX - tX, cornerY - tY)
}
/*Mouse movement tracking*/
$(document).mousemove(function (e) {
/*Get mouse coordinates*/
mX = e.pageX;
mY = e.pageY;
/*calculate distance between mouse and element*/
distance = pythagoras(tX - mX, tY - mY);
maxMouseDistance = distanceToNearestCorner(mX, mY)
relativeDistance = ((maxMouseDistance - distance) / maxMouseDistance);
$distance.text(distance);
var decimals = distance / 100;
var percents = 100 - (distance / 100);
$("#mouse").text(mX + ":" + mY);
//$("#distanceDecimals").text(decimals);
//$("#dFarCorner").text(maxDistance);
$("#md2FarCorner").text(maxMouseDistance);
$("#formula").text("(E to C max / M to C max) / (M to E distance/100)");
$("#theNumber").text(relativeDistance);
$('.fill').width((relativeDistance * 100) + "%");
});
})();
It doesn't update all the fields, but it does update the progress bar.
Original answer
You seem to have plenty of functions in there which are not being called.
Here's one that I have rewritten... but it doesn't get called:
function calculateDistance(elem, mouseX, mouseY) {
var deltaX = elem.offset().left - mouseX;
var deltaY = elem.offset().top - mouseY;
var delta2 = deltaX * deltaX + deltaY * deltaY;
var delta = Math.floor(Math.sqrt(delta2))
return delta
}
var elem = document.getElementById("targetPosition")
var relativeDistance = calculateDistance(elem , mX, mY)
In my implementation, elem is the HTML element that you consider to be the target. My function is an application of Pythagoras' theorem: it returns the square root of the sum of the distance from the target along the x and y axes, giving the length of the shortest line between the mouse and the target.
When I insert this into your jsFiddle, I see 0 appearing in the M2E Distance field when my cursor is just above the "T" of "Target".
Is this what you are looking for?
Your logic is correct. It's called a locus. http://www.bbc.co.uk/schools/gcsebitesize/maths/geometry/locirev1.shtml
I have the following problem. I have a .one("click") function with a variable that raises itself, and I need to add a function, that triggers, when the variable hits the wanted point. I mean, in the following code I need to use the connect function for the last 'last' img with the 'home' img, after the Result variable turns 9, which will produce a line between them, and I'll have a complete circle. Please read the code and try clicking on all img-es so that you can understand, what i need to achieve. Thanks in advance.
Here's the code:
$(document).ready(function() {
var Result = 0;
$('img').one("click", function(){
if( $('img.home').length == 0 ){
$(this).addClass('home');
}
if(Result <= 9){
var $elem2 = $('span.last');
var $elem1 = $(this).parent();
$(this).toggleClass('selected');
if ($elem2.length > 0) {
connect($elem1[0], $elem2[0], "#0F0", 5);
}
else {
$elem1.addClass('last');
}
$('span').removeClass('last');
$elem1.addClass('last');
Result++;
}
});
});
function connect(div1, div2, color, thickness) {
var off1 = getOffset(div1);
var off2 = getOffset(div2);
// bottom right
var x1 = off1.left + off1.width;
var y1 = off1.top + off1.height;
// top right
var x2 = off2.left + off2.width;
var y2 = off2.top;
// distance
var length = Math.sqrt(((x2-x1) * (x2-x1)) + ((y2-y1) * (y2-y1)));
distanz += parseInt(length);
// center
var cx = ((x1 + x2) / 2) - (length / 2);
var cy = ((y1 + y2) / 2) - (thickness / 2);
// angle
var angle = Math.atan2((y1-y2),(x1-x2))*(180/Math.PI);
// make hr
var htmlLine = "<div style='padding:0px; margin:0px; height:" + thickness + "px; background-color:" + color + "; line-height:1px; position:absolute; left:" + cx + "px; top:" + cy + "px; width:" + length + "px; -moz-transform:rotate(" + angle + "deg); -webkit-transform:rotate(" + angle + "deg); -o-transform:rotate(" + angle + "deg); -ms-transform:rotate(" + angle + "deg); transform:rotate(" + angle + "deg);' />";
htmlLine = $(htmlLine);
$('body').append(htmlLine);
return htmlLine;
}
function getOffset( el ) {
var x = 0;
var y = 0;
var w = el.offsetWidth|0;
var h = el.offsetHeight|0;
while( el && !isNaN( el.offsetLeft ) && !isNaN( el.offsetTop ) ) {
x += el.offsetLeft - el.scrollLeft;
y += el.offsetTop - el.scrollTop;
el = el.offsetParent;
}
return { top: y, left: x, width: w, height: h };
}
jsfiddle: http://jsfiddle.net/CDQhX/4/
I want to execute the connect(home and span images) after the last line is done and the Result is 9. This can't be done inside the click function, since I'm not clicking anywhere to trigger it. My knowledge don't let me work the problem around. So I appreciate any help. I'll be really glad to receive answers.
First of all, what do you mean you are not clicking anywhere and you want it executed when the last img is clicked. Can't you just check if the img is last when it is clicked, and execute what you need?
Also, on the other hand, you can try setTimeout and setInterval as described here: http://www.w3.org/TR/2011/WD-html5-20110525/timers.html
So, for example, you could do something like this:
setInterval( function() {
//do your thing
}, 500);
Which will execute the function every 500 milliseconds, so you could use that for periodic checking of your variables.
I want to display random numbers inside a div at random positions without overlapping.
I am able to display random number at random position but its going outside the box and overlapping each other.
Here is my code:
JS Fiddle
var width = $('.container').innerWidth();
var height = $('.container').innerHeight();
(function generate() { // vary size for fun
for (i = 0; i < 15; i++) {
var divsize = 12;
var color = '#' + Math.round(0xffffff * Math.random()).toString(16);
$newdiv = $('<div/>').css({
'width': divsize + 'px',
'height': divsize + 'px'
});
// make position sensitive to size and document's width
var posx = (Math.random() * (width - divsize)).toFixed();
var posy = (Math.random() * (height - divsize)).toFixed();
$newdiv.css({
'position': 'absolute',
'left': posx + 'px',
'top': posy + 'px',
'float': 'left'
}).appendTo('.container').html(Math.floor(Math.random() * 9));
}
})();
How can I do this?
You've got most of it figured out. You just need to think of the .container div as a grid to avoid any overlap or outlying items.
Just check out this fiddle.
Here's what the code looks like:
var tilesize = 18, tilecount = 15;
var gRows = Math.floor($(".container").innerWidth()/tilesize);
var gCols = Math.floor($('.container').innerHeight()/tilesize);
var vals = _.shuffle(_.range(tilecount));
var xpos = _.shuffle(_.range(gRows));
var ypos = _.shuffle(_.range(gCols));
_.each(vals, function(d,i){
var $newdiv = $('<div/>').addClass("tile");
$newdiv.css({
'position':'absolute',
'left':(xpos[i] * tilesize)+'px',
'top':(ypos[i] * tilesize)+'px'
}).appendTo( '.container' ).html(d);
});
PS:I have used underscore in my fiddle to make things easier for me and because I personally hate writing for loops.
If the number of divs you need to create is small enough (i.e. you're not risking that they won't fit) then a simple algorithm is:
pick a random position (x0, y0)-(x1, y1)
check if any previously selected rect overlaps
if none overlaps then add the rect, otherwise loop back and choose another random position
in code
var selected = [];
for (var i=0; i<num_divs; i++) {
while (true) {
var x0 = Math.floor(Math.random() * (width - sz));
var y0 = Math.floor(Math.random() * (height - sz));
var x1 = x0 + sz;
var y1 = y0 + sz;
var i = 0;
while (i < selected.length &&
(x0 >= selected[i].x1 ||
y0 >= selected[i].y1 ||
x1 <= selected[i].x0 ||
y1 <= selected[i].y0)) {
i++;
}
if (i == selected.length) {
// Spot is safe, add it to the selection
selected.push({x0:x0, y0:y0, x1:x1, y1:y1});
break;
}
// The choice collided with a previously added div
// just remain in the loop so a new attempt is done
}
}
In case the elements are many and it's possible to place n-1 of them so that there's no position where to put n-th element then things are a lot more complex.
For the solution of the 1-dimensional version of this problem see this answer.
You can add to array position of each number. And then when ou generate new position for digit you should check if posx posy in array, if false place number there, if true generate new posx and posy