How to make a Tile/Grid layout like Gridster.js in AS3? - javascript

I want to make a custom layout which behaves like this shown in picture below.
I have already tried Tile Layout and to modify its calculateDropIndex method but not get desired behavior.
Tile layout works well when all the tiles are of same height and width but in my case tile size are different.
http://gridster.net/
Gridster layout screenshot

After a little bit more try i am able make a gridster like drag n drop gird component.
here is a brief summery of how i have made this component.
while adding pods in to container add one more pod at the same position and keep it's visibility off. this extra node will be our drop indicator.
keep all the original nodes in one array say nodes, and all extra nodes in another array say dropIndicators.
for example if add nodes A, B and C in the container. we will add three extra nodes X, Y and Z respectively one for each main node.
and over nodes array will be
node[0] = A;
node[1] = B;
node[2] = C;
and dropIndicators array will be
dropIndicators[0] = X;
dropIndicators[1] = Y;
dropIndicators[2] = Z;
now you will need following function to update nodes position when we drag any node.
CalculateDropLocation - to calculate new drop location while dragging.
CheckCollision - to check collision between two nodes.
FixCollision - if there is a collision we will fix the collision using this function. this function will mode all the colliding nodes downwards recursively.
updateNodes - once the collision is fixed we will update all the nodes using this function. this function will move node upward if there is an empty space.
now when we start dragging A node calculate new drop location for A node and move it's relative extra node i.e. X to the new drop location, check if X is colliding with any other extra node. we will use the dropIndicators array in all the functions.
if collistion than call fixCollision() function, than call update node function.
and at the end once all the nodes are updated set the position of all the extra nodes to it's original nodes i.e. set X's position to A's position , Y's position to B and so on. while updating original nodes position you can use Move animation to move nodes smoothly in the container.
hope this will help. using this method you can make this type of layout in any language.
hope this will help.
for more details and sample application with source visit this link
http://usefulflexcomponents.blogspot.in/2015/12/blog-post.html

Related

CreateJS hittest between a stage object and a container child?

I have a side scrolling game, so instead of moving the character, you have the character centered and everything else movies past him.
The character sprite is added directly to the stage, but the background image + a tree sprite is added to a container and the container is added to the stage.
I want to do a hit test between the character and the tree. The problem is that the tree x and y coordinates do not change because it's attached to the container, so they never overlap with the character's coordinates, thus no collision.
How do you do a hit test between a stage object and a container object?
Or am I doing it wrong and I should add the tree as a stage object and move it separately to the container?

D3 force layout: How to maintain a given minimum distance between nodes?

I am using force layout. New d3 nodes are created by clicking inside a div element. The node is created at the point of click. The nodes are rectangles of size 50 pixels x 50 pixels. Immediately after creating a node, I set its fixed property to true so that it does not move on its own. I am not calling force.drag. The nodes can be moved by holding down ctrl key and dragging the node. An edge can be created by dragging mouse (without holding ctrl key) from one node to the other.
Now, I want to add the following feature.
The closest distance between any two nodes should be more than a certain minimum. You can assume any positive value for the minimum distance. Let us assume 100 pixels. When any new node is created too close to an existing node, then the nodes should move so that the distance between any two nodes becomes more than 100 pixels. Similarly, when one node is moved and brought too close to another, then also the nodes should move to maintain minimum 100 pixels distance.
There is no condition on which nodes to move and in which direction. One way is to check coordinates and distances and then calculate which nodes to move, how much, in what direction and execute code accordingly. But, is there a simpler way in d3?
Consider just using force.linkDistance() and force.linkStrength() to achieve this. linkDistance represents your minimum distance constraint, and linkStrength (in the range of [0, 1]) determines how 'rigid' the link distance is, or how much linkDistance can be overridden by the simulation.
force.linkDistance
force.linkStrength

D3 graph with svg nodes - How to move nodes to arbitrary positions

I want to make a D3 graph, which should be as follows:
When the html page is loaded, there will be a single node at a fixed location. Let us say top left. Let us call it template node and this node is non-movable.
When the user does mouse down on the template node, a new node is created at the same location as the template node and the user should be able to drag the new node to where he wants. The new node should remain exactly where the user moves it to.
At any time user should be able to move a node. Again the node should remain where the user leaves it.
User should be able to draw link between any two nodes. Let us assume that if he drags from one node to another without holding down ctrl key, then a link is drawn and if he drags while holding down the control key, then the node moves.
When a link is drawn between two nodes, then the nodes should not change positions.
When two nodes are linked and one of them is moved by dragging it, then the link should change in size and orientation as needed.
I am using force layout.
I am able to create a template node but it always goes to the center of the container - I think it is because the center of the container is the center of gravity. But not sure how to fix its position to the top left through code.
I can create links and new nodes. But the nodes move and links resize. May be it is because force layout tries to make link lengths equal to the link distance in the force layout. But I do not know how to use a function for link distance? I am even not sure if that will really help.
So what method should I use? Any idea?
For force layout, you can set the 'fixed' property of a node to true in order to prevent it from being affected by the simulation. After that, you should be able to set it's position manually. You might choose to do this in a function call:
function pinNode(node) {
node.fixed = true;
}
function unpinNode(node) {
node.fixed = false;
}
I believe you could get a node to the upper left corner with a call like this: pinNode(node, 0, 0). As long as the node has its fixed property set to true, it should remain unaffected by the sim. You might find this snippet from the documentation helpful; it describes how the fixed property is affected by force.drag:
Bind a behavior to nodes to allow interactive dragging, either using
the mouse or touch. Use this in conjunction with the call operator on
the nodes; for example, say node.call(force.drag) on initialization.
The drag event sets the fixed attribute of nodes on mouseover, such
that as soon as the mouse is over a node, it stops moving. Fixing on
mouseover, rather than on mousedown, makes it easier to catch moving
nodes. When a mousedown event is received, and on each subsequent
mousemove until mouseup, the node center is set to the current mouse
position. In addition, each mousemove triggers a resume of the force
layout, reheating the simulation. If you want dragged nodes to remain
fixed after dragging, set the fixed attribute to true on dragstart, as
in the sticky force layout example.
force.drag
Also see here: force layout nodes
If you want to use a function for link distance, include it when you create the force layout:
var force = d3.layout.force()
.size(width, height)
.linkStrength(0.5) // how much can link distance be overridedn by the simulation
.linkDistance(function() {return /* some evaluation */;});
// ...
// You might need to defer the calculation of linkDistance until later,
// such as in update(), since nodes might not have the properties
// that you need to check until that point:
function update() {
force
.nodes(nodes)
.links(links)
.linkDistance(function(link) {
// The function gets called for each link in the simulation.
// Each link will be connected to two nodes, source and target,
// which may be useful in determining link distance.
if (link.source.someProperty || link.target.somePropery) {
return /* something */;
} else {
return /* something else */;
}
});
}

Inner div inside an outer rotated div does not follow mouse in case dragging with jQuery UI

I have an inner div inside an outer div. The inner div is draggable and outer is rotated through 40 degree. This is a test case. In an actual case it could be any angle. There is another div called point which is positioned as shown in the figure. ( I am from a flash background . In Flash if I were to drag the inner div it would follow the mouse even if its contained inside an outer rotated div.) But in HTML the inner div does not follow the mouse as it can be seen from the fiddle. I want the div 'point' to exactly follow the mouse. Is this possible. I tried to work it using trignometry but could not get it to work.
http://jsfiddle.net/bobbyfrancisjoseph/kB4ra/8/
Here is my approach to this problem.
http://jsfiddle.net/2X9sT/21/
I put the point outside the rotated div. That way I'm assured that the drag event will produce a normal behavior (no jumping in weird directions). I use the draggable handler to attach the point to the mouse cursor.
In the drag event, I transform the drag offset to reflect the new values. This is done by rotating the offset around the outer div center in the opposite direction of the rotation angle.
I tested it and it seems to be working in IE9, Firefox, and Chrome.
You can try different values for angle and it should work fine.
I also modified the HTML so it is possible to apply the same logic to multiple divs in the page.
Edit:
I updated the script to account for containment behavior as well as cascading rotations as suggested in the comments.
I'm also expirementing with making the outer div draggable inside another div. Right now it is almost working. I just need to be able to update the center of the dragged div to fix the dragging behavior.
Try Dragging the red div.
http://jsfiddle.net/mohdali/kETcE/39/
I am at work now, so I can't do the job for you, but I can explain the mathematics behind the neatest way of solving your problem (likely not the easiest solution, but unlike some of the other hacks it's a lot more flexible once you get it implemented).
First of all you must realize that the rotation plugin you are using is applying a transformation to your element (transform: rotate(30deg)), which in turn is changed into a matrix by your browser (matrix(0.8660254037844387, 0.49999999999999994, -0.49999999999999994, 0.8660254037844387, 0, 0)).
Secondly it is necessary to understand that by rotating an element the axis of the child elements are rotate absolutely and entirely with it (after looking for a long time there isn't any real trick to bypass this, which makes sense), thus the only way would be to take the child out of the parent as some of the other answers suggest, but I am assuming this isn't an option in your application.
Now, what we thus need to do is cancel out the original matrix of the parent, which is a two step process. First we need to find the matrix using code along the following lines:
var styles = window.getComputedStyle(el, null);
var matrix = styles.getPropertyValue("-webkit-transform") ||
styles.getPropertyValue("-moz-transform") ||
styles.getPropertyValue("-ms-transform") ||
styles.getPropertyValue("-o-transform") ||
styles.getPropertyValue("transform");
Next the matrix will be a string as shown above which you would need to parse to an array with which you can work (there are jquery plugins to do that). Once you have done that you will need to take the inverse of the matrix (which boils down to rotate(-30deg) in your example) which can be done using for example this library (or your math book :P).
Lastly you would need to do the inverse matrix times (use the matrix library I mentioned previously) a translation matrix (use this tool to figure out how those look (translations are movements along the x and y axis, a bit like left and top on a relatively positioned element, but hardware accelerated and part of the matrix transform css property)) which will give you a new matrix which you can apply to your child element giving you the a translation on the same axis as your parent element.
Now, you could greatly simplify this by doing this with left, top and manual trigonometry1 for specifically rotations only (bypassing the entire need for inverse matrices or even matrices entirely), but this has the distinct disadvantage that it will only work for normal rotations and will need to be changed depending on each specific situation it's used in.
Oh and, if you are now thinking that flash was a lot easier, believe me, the way the axis are rotated in HTML/CSS make a lot of sense and if you want flash like behavior use this library.
1 This is what Mohamed Ali is doing in his answer for example (the transformOffset function in his jsFiddle).
Disclaimer, it has been awhile since I have been doing this stuff and my understanding of matrices has never been extremely good, so if you see any mistakes, please do point them out/fix them.
For Webkit only, the webkitConvertPointFromPageToNode function handles the missing behavior:
var point = webkitConvertPointFromPageToNode(
document.getElementById("outer"),
new WebKitPoint(event.pageX, event.pageY)
);
jsFiddle: http://jsfiddle.net/kB4ra/108/
To cover other browsers as well, you can use the method described in this StackOverflow answer: https://stackoverflow.com/a/6994825/638544
function coords(event, element) {
function a(width) {
var l = 0, r = 200;
while (r - l > 0.0001) {
var mid = (r + l) / 2;
var a = document.createElement('div');
a.style.cssText = 'position: absolute;left:0;top:0;background: red;z-index: 1000;';
a.style[width ? 'width' : 'height'] = mid.toFixed(3) + '%';
a.style[width ? 'height' : 'width'] = '100%';
element.appendChild(a);
var x = document.elementFromPoint(event.clientX, event.clientY);
element.removeChild(a);
if (x === a) {
r = mid;
} else {
if (r === 200) {
return null;
}
l = mid;
}
}
return mid;
}
var l = a(true),
r = a(false);
return (l && r) ? {
x: l,
y: r
} : null;
}
This has the disadvantage of not working when the mouse is outside of the target element, but it should be possible to extend the area it covers by an arbitrary amount (though it would be rather hard to guarantee that it covers the entire window no matter how large).
jsFiddle: http://jsfiddle.net/kB4ra/122/
This can be extended to apply to #point by adding a mousemove event:
$('#outer').mousemove(function(event){
var point = convertCoordinates(event, $("#outer"));
$("#point").css({left: point.x+1, top: point.y+1});
});
Note that I adjust the x and y coordinates of #point by 1px to prevent it from being directly underneath the mouse; if I didn't do that, then it would block dragging #inner. An alternative fix would be to add handlers to #point that detect mouse events and pass them on to whichever element is directly underneath #point (and stopPropagation, so that they don't run twice on larger page elements).
jsFiddle: http://jsfiddle.net/kB4ra/123/
It seems to me that if you do not rotate the div, the div exactly follows the mouse.
This might be a problem with the plugin..maybe you could simulate the draggable function corretly?
This basically will do what you need though it is buggy. Bind the drag event handler, intercept the ui object and modify it to use the offset X and Y of the parent element. All of the X, Y, top, left etc. are in those objects. I will try to get you a better example sometime when today when I get a bit more time. Good luck!
http://jsfiddle.net/kB4ra/107/
may be this is issue of your jquery library or you can check this by assigning z-order value of inner div and outer div make sure that you give higher number to inner div.

How can this object be appended to a specific tile on an isometric map?

I have an isometric map that has selectable tiles. The map is formed by a matrix such as $('div').gameMap({map:[[{"tile":"tile_0","object":"anObjectOnTile"},{"tile":"tile_3","object":""}], [{...}]],mapsize:3}); If the tile has something within the object:"", then that tile has that object (in my case, an image of an oil derrick) on top of that tile.
There is a Place button which when clicked should place an oil derrick object on top of the currently selected tile.
However, I have run into two problems:
Knowing which tile is currently selected
Adding a single oil derrick
At first I wanted to figure a way to modify the map's matrix to insert the object's name (oilDerrick) into the tile's object parameter, but now I think the better solution would be to append the oilDerrick to the tile. I can't figure out how to append to the particular tile and not every tile.
I've got the code set up to look at and play with: http://jsfiddle.net/briz/jdhPW/13/
There's an oilDerrick object set up in the map's matrix for you to view, which can be removed by deleting the word oilDerrick in the matrix
I played with $(".tile").append($('.oilDerrick')); though I knew that would append to every tile. But I simply do not know enough about appending yet to figure out how to limit it to a single instance.
Each tile has a unique ID. I also tried this in order to add an oilDerrick to a certain tile:
$("#placeButton").click(function()
{
for (var y in config.map)
{
for (var x in config.map[y])
{
document.getElementById(obj.attr('id') + '_tile_' + x + '_' +y).appendChild(".oilDerrick");
}
}
}
But this came to no avail as well. I think I'm heading in the right direction though? Can anyone help?
I did some modifications.
There a little mess in your code. For example, your using jquery but there is a lot of direct DOM manipulation.
.style.background = ...
// should be
.css("background",...)
And the most important:
document.getElementById(...)
// should be
$("#...")
Easier, don't you think? Then, the problem is simple solved by creating a var currentTile and when the user select one tile, it save it. When click in 'place', $(currentTile).append($('.oilDerrick')); And the oil derrick move to where to selected tile.
Anyway, more details you can see in the link.

Categories