diff --git a/mindplot/src/main/javascript/layout/BalancedSorter.js b/mindplot/src/main/javascript/layout/BalancedSorter.js index 085757e5..41fa4b55 100644 --- a/mindplot/src/main/javascript/layout/BalancedSorter.js +++ b/mindplot/src/main/javascript/layout/BalancedSorter.js @@ -177,6 +177,10 @@ mindplot.layout.BalancedSorter = new Class({ } }, + getDirection: function(treeSet, node) { + return node.getOrder() % 2 == 0 ? 1 : -1; + }, + toString:function() { return "Balanced Sorter"; }, diff --git a/mindplot/src/main/javascript/layout/ChildrenSorterStrategy.js b/mindplot/src/main/javascript/layout/ChildrenSorterStrategy.js index 2b3bc428..bcbccb9f 100644 --- a/mindplot/src/main/javascript/layout/ChildrenSorterStrategy.js +++ b/mindplot/src/main/javascript/layout/ChildrenSorterStrategy.js @@ -44,6 +44,10 @@ mindplot.layout.ChildrenSorterStrategy = new Class({ throw "Method must be implemented"; }, + getDirection: function(treeSet, node) { + throw "Method must be implemented"; + }, + toString:function() { throw "Method must be implemented: print name"; } diff --git a/mindplot/src/main/javascript/layout/LayoutManager.js b/mindplot/src/main/javascript/layout/LayoutManager.js index ea4d3565..e41f45ab 100644 --- a/mindplot/src/main/javascript/layout/LayoutManager.js +++ b/mindplot/src/main/javascript/layout/LayoutManager.js @@ -109,6 +109,8 @@ mindplot.layout.LayoutManager = new Class({ if (free) { $assert($defined(position), "position cannot be null for predict in free positioning"); + + //TODO(gb): check this. Should direction be obtained by the sorter? var rootNode = this._treeSet.getRootNode(parent); var direction = parent.getPosition().x > rootNode.getPosition().x ? 1 : -1; diff --git a/mindplot/src/main/javascript/layout/Node.js b/mindplot/src/main/javascript/layout/Node.js index 1a0225cd..40600562 100644 --- a/mindplot/src/main/javascript/layout/Node.js +++ b/mindplot/src/main/javascript/layout/Node.js @@ -124,6 +124,10 @@ mindplot.layout.Node = new Class({ this._setProperty('freeDisplacement', Object.clone(newDisplacement)); }, + resetFreeDisplacement: function() { + this._setProperty('freeDisplacement', {x:0, y:0}); + }, + getFreeDisplacement: function() { var freeDisplacement = this._getProperty('freeDisplacement'); return (freeDisplacement || {x:0, y:0}); diff --git a/mindplot/src/main/javascript/layout/OriginalLayout.js b/mindplot/src/main/javascript/layout/OriginalLayout.js index a37295f4..f630b1d0 100644 --- a/mindplot/src/main/javascript/layout/OriginalLayout.js +++ b/mindplot/src/main/javascript/layout/OriginalLayout.js @@ -55,7 +55,7 @@ mindplot.layout.OriginalLayout = new Class({ // Make it fixed node.setFree(false); - node.setFreeDisplacement({x:0, y:0}); + node.resetFreeDisplacement(); // Remove from children list. var sorter = parent.getSorter(); @@ -107,6 +107,14 @@ mindplot.layout.OriginalLayout = new Class({ children.forEach(function(child) { var offset = offsetById[child.getId()]; + var childFreeDisplacement = child.getFreeDisplacement(); + var direction = node.getSorter().getDirection(this._treeSet, child); + + if ((direction > 0 && childFreeDisplacement.x < 0) || (direction < 0 && childFreeDisplacement.x > 0)) { + child.resetFreeDisplacement(); + child.setFreeDisplacement({x: -childFreeDisplacement.x, y:childFreeDisplacement.y}); + } + offset.x += child.getFreeDisplacement().x; offset.y += child.getFreeDisplacement().y; diff --git a/mindplot/src/main/javascript/layout/RootedTreeSet.js b/mindplot/src/main/javascript/layout/RootedTreeSet.js index 4af7438a..e7ab4594 100644 --- a/mindplot/src/main/javascript/layout/RootedTreeSet.js +++ b/mindplot/src/main/javascript/layout/RootedTreeSet.js @@ -195,10 +195,10 @@ mindplot.layout.RootedTreeSet = new Class({ var rectPosition = {x: rect.attr("x") - canvas.width/2 + rect.attr("width")/2, y:rect.attr("y") - canvas.height/2 + rect.attr("height")/2}; var rectSize = {width: rect.attr("width"), height:rect.attr("height")}; rect.click(function() { - console.log("[id:" + node.getId() + ", order:" + node.getOrder() + ", position:(" + rectPosition.x + "," + rectPosition.y + "), size:" + rectSize.width + "x" + rectSize.height + ", sorter:" + node.getSorter() +"]"); + console.log("[id:" + node.getId() + ", order:" + node.getOrder() + ", position:(" + rectPosition.x + "," + rectPosition.y + "), size:" + rectSize.width + "x" + rectSize.height + ", freeDisplacement:(" + node.getFreeDisplacement().x + "," + node.getFreeDisplacement().y +")]"); }); text.click(function() { - console.log("[id:" + node.getId() + ", order:" + node.getOrder() + ", position:(" + rectPosition.x + "," + rectPosition.y + "), size:" + rectSize.width + "x" + rectSize.height + ", sorter:" + node.getSorter() +"]"); + console.log("[id:" + node.getId() + ", order:" + node.getOrder() + ", position:(" + rectPosition.x + "," + rectPosition.y + "), size:" + rectSize.width + "x" + rectSize.height + ", freeDisplacement:(" + node.getFreeDisplacement().x + "," + node.getFreeDisplacement().y +")]"); }); for (var i = 0; i < children.length; i++) { diff --git a/mindplot/src/main/javascript/layout/SymmetricSorter.js b/mindplot/src/main/javascript/layout/SymmetricSorter.js index a8596a57..e9db580d 100644 --- a/mindplot/src/main/javascript/layout/SymmetricSorter.js +++ b/mindplot/src/main/javascript/layout/SymmetricSorter.js @@ -137,6 +137,12 @@ mindplot.layout.SymmetricSorter = new Class({ } }, + getDirection: function(treeSet, node) { + var parent = treeSet.getParent(node); + var rootNode = treeSet.getRootNode(node); + return parent.getPosition().x >= rootNode.getPosition().x ? 1 : -1; + }, + toString:function() { return "Symmetric Sorter"; } diff --git a/mindplot/src/test/javascript/static/layout.html b/mindplot/src/test/javascript/static/layout.html index 05fc82df..2e979c7d 100644 --- a/mindplot/src/test/javascript/static/layout.html +++ b/mindplot/src/test/javascript/static/layout.html @@ -154,6 +154,7 @@
+
diff --git a/mindplot/src/test/javascript/static/test/FreeTestSuite.js b/mindplot/src/test/javascript/static/test/FreeTestSuite.js index 2625857c..b85bd48a 100644 --- a/mindplot/src/test/javascript/static/test/FreeTestSuite.js +++ b/mindplot/src/test/javascript/static/test/FreeTestSuite.js @@ -67,48 +67,56 @@ mindplot.layout.FreeTestSuite = new Class({ manager.layout(); manager.plot("testFreePosition1", {width:1400, height:600}); - console.log("\tmove node 12 to (300,30)"); + console.log("\tmove node 12 to (300,30):"); manager.moveNode(12, {x:300, y:30}); - manager.layout(); + manager.layout(true); manager.plot("testFreePosition2", {width:1400, height:600}); + this._assertFreePosition(manager, 12, {x:300, y:30}) - console.log("\tmove node 13 to (340,180)"); + console.log("\tmove node 13 to (340,180):"); manager.moveNode(13, {x:340, y:180}); manager.layout(true); manager.plot("testFreePosition3", {width:1400, height:600}); + this._assertFreePosition(manager, 13, {x:340, y:180}); - console.log("\tmove node 11 to (250,-50)"); + console.log("\tmove node 11 to (250,-50):"); manager.moveNode(11, {x:250, y:-50}); manager.layout(true); manager.plot("testFreePosition4", {width:1400, height:600}); + this._assertFreePosition(manager, 11, {x:250, y:-50}); - console.log("\tmove node 7 to (350,-190)"); + console.log("\tmove node 7 to (350,-190):"); manager.moveNode(7, {x:350, y:-190}); manager.layout(true); manager.plot("testFreePosition5", {width:1400, height:600}); + this._assertFreePosition(manager, 7, {x:350, y:-190}); console.log("\tadd node 23 to 12:"); manager.addNode(23, mindplot.layout.TestSuite.NODE_SIZE, position); manager.connectNode(12,23,3); manager.layout(true); manager.plot("testFreePosition6", {width:1400, height:600}); + this._assertFreePosition(manager, null, null); - console.log("\tmove node 4 to (-300, 190)"); + console.log("\tmove node 4 to (-300, 190):"); manager.moveNode(4, {x:-300, y:190}); manager.layout(true); manager.plot("testFreePosition7", {width:1400, height:600}); + this._assertFreePosition(manager, 4, {x:-300, y:190}); console.log("\tadd node 24 to 3:"); manager.addNode(24, mindplot.layout.TestSuite.NODE_SIZE, position); manager.connectNode(3,24,3); manager.layout(true); manager.plot("testFreePosition8", {width:1400, height:600}); + this._assertFreePosition(manager, null, null); console.log("\tadd node 25 to 17:"); manager.addNode(25, mindplot.layout.TestSuite.NODE_SIZE, position); manager.connectNode(17,25,0); manager.layout(true); manager.plot("testFreePosition9", {width:1400, height:600}); + this._assertFreePosition(manager, null, null); console.log("OK!\n\n"); }, @@ -209,35 +217,96 @@ mindplot.layout.FreeTestSuite = new Class({ manager.moveNode(5, {x:250, y:30}); manager.layout(); manager.plot("testReconnectFreeNode2", {width:1000, height:400}); + this._assertFreePosition(manager, 5, {x:250, y:30}); console.log("\treconnect node 5 to node 2"); manager.disconnectNode(5); manager.connectNode(2,5,2); manager.layout(); manager.plot("testReconnectFreeNode3", {width:1000, height:400}); + $assert(manager.find(5).getPosition().y > manager.find(10).getPosition().y && + manager.find(5).getPosition().x == manager.find(10).getPosition().x, "Node 5 is incorrectly positioned" + ); + $assert(manager.find(5).getOrder() == 2, "Node 5 should have order 2"); console.log("\tmove node 8"); manager.moveNode(8, {x:-370, y:60}); manager.layout(); manager.plot("testReconnectFreeNode4", {width:1000, height:400}); + this._assertFreePosition(manager, 8, {x:-370, y:60}); - //TODO(gb): fix this. node 11 is not positioned correctly console.log("\treconnect node 5 to node 10"); manager.disconnectNode(5); manager.connectNode(10,5,0); manager.layout(); manager.plot("testReconnectFreeNode5", {width:1000, height:400}); + $assert(manager.find(5).getPosition().y == manager.find(10).getPosition().y && + manager.find(5).getPosition().x < manager.find(10).getPosition().x, "Node 5 is incorrectly positioned" + ); + $assert(manager.find(5).getOrder() == 0, "Node 5 should have order 0"); -// console.log("reconnect node 5 to node 3"); -// manager.disconnectNode(5); -// manager.connectNode(3,5,2); -// manager.layout(); -// manager.plot("testReconnectFreeNode6", {width:1000, height:400}); + console.log("reconnect node 5 to node 3"); + manager.disconnectNode(5); + manager.connectNode(3,5,2); + manager.layout(); + manager.plot("testReconnectFreeNode6", {width:1000, height:400}); + $assert(manager.find(5).getPosition().y > manager.find(6).getPosition().y && + manager.find(5).getPosition().x == manager.find(6).getPosition().x, "Node 5 is incorrectly positioned" + ); + $assert(manager.find(5).getOrder() == 2, "Node 5 should have order 2"); -// manager.moveNode(8, {x:370, y:30}); -// manager.layout(); -// manager.plot("testReconnectFreeNode2", {width:1000, height:400}); + console.log("\tmove node 8"); + manager.moveNode(8, {x:370, y:30}); + manager.layout(); + manager.plot("testReconnectFreeNode7", {width:1000, height:400}); + this._assertFreePosition(manager, 8, {x:370, y:30}); console.log("OK!\n\n"); + }, + + _assertFreePosition: function(manager, id, position) { + if (id != null && position.x != null && position.y != null) { + var node = manager.find(id); + $assert(node.getPosition().x == position.x && node.getPosition().y == position.y, + "Freely moved node " + id + " is not left at free position (" + position.x + "," + position.y + "). " + + "Actual position: (" + node.getPosition().x + "," + node.getPosition().y + ")"); + } + + var treeSet = manager._treeSet; + treeSet._rootNodes.forEach(function(rootNode) { + var heightById = rootNode.getSorter().computeChildrenIdByHeights(treeSet, rootNode); + this._assertBranchCollision(treeSet, rootNode, heightById); + }, this); + }, + + _assertBranchCollision: function(treeSet, node, heightById) { + var children = treeSet.getChildren(node); + var childOfRootNode = treeSet._rootNodes.contains(node); + + children.forEach(function(child) { + var height = heightById[child.getId()]; + var siblings = treeSet.getSiblings(child); + if (childOfRootNode) { + siblings = siblings.filter(function(sibling) { + return (child.getOrder() % 2) == (sibling.getOrder() % 2); + }) + } + siblings.forEach(function(sibling) { + this._branchesOverlap(child, sibling, heightById); + }, this); + }, this); + + children.forEach(function(child) { + this._assertBranchCollision(treeSet, child, heightById); + }, this) + }, + + _branchesOverlap: function(branchA, branchB, heightById) { + var topA = branchA.getPosition().y - heightById[branchA.getId()]/2; + var bottomA = branchA.getPosition().y + heightById[branchA.getId()]/2; + var topB = branchB.getPosition().y - heightById[branchB.getId()]/2; + var bottomB = branchB.getPosition().y + heightById[branchB.getId()]/2; + + $assert(topA >= bottomB || bottomA <= topB, "Branches " + branchA.getId() + " and " + branchB.getId() + " overlap"); } }); \ No newline at end of file