diff --git a/mindplot/src/main/javascript/nlayout/BalancedSorter.js b/mindplot/src/main/javascript/nlayout/BalancedSorter.js new file mode 100644 index 00000000..e1f371b6 --- /dev/null +++ b/mindplot/src/main/javascript/nlayout/BalancedSorter.js @@ -0,0 +1,175 @@ +mindplot.nlayout.BalancedSorter = new Class({ + Extends: mindplot.nlayout.ChildrenSorterStrategy, + + initialize: function() { + + }, + + computeChildrenIdByHeights: function(treeSet, node) { + var result = {}; + this._computeChildrenHeight(treeSet, node, result); + return result; + }, + + _computeChildrenHeight : function(treeSet, node, heightCache) { + var height = node.getSize().height + (mindplot.nlayout.BalancedSorter.INTERNODE_VERTICAL_PADDING * 2); // 2* Top and down padding; + + var result; + var children = treeSet.getChildren(node); + if (children.length == 0) { + result = height; + } else { + var childrenHeight = 0; + children.forEach(function(child) { + childrenHeight += this._computeChildrenHeight(treeSet, child, heightCache); + }, this); + + result = Math.max(height, childrenHeight); + } + + if (heightCache) { + heightCache[node.getId()] = result; + } + + return result; + }, + + predict : function(parent, graph, position) { + + // No children... + var children = graph.getChildren(parent); + if (children.length == 0) { + return [0,parent.getPosition()]; // @Todo:Change x ... + } + + // Try to fit within ... + // + // - Order is change if the position top position is changed ... + // - Suggested position is the middle between the two topics... + // + var result = null; + children.forEach(function(child) { + var cpos = child.getPosition(); + if (position.y > cpos.y) { + result = [child.getOrder(),{x:cpos.x,y:cpos.y + child.getSize().height}]; + } + }); + + // Ok, no overlap. Suggest a new order. + if (result) { + var last = children.getLast(); + result = [last.getOrder() + 1,{x:cpos.x,y:cpos.y - (mindplot.nlayout.BalancedSorter.INTERNODE_VERTICAL_PADDING * 4)}]; + } + + return result; + }, + + insert: function(treeSet, parent, child, order) { + var children = this._getSortedChildren(treeSet, parent); + $assert(order <= children.length, "Order must be continues and can not have holes. Order:" + order); + $assert(order <= children.length, "Order must be continues and can not have holes. Order:" + order); + + // Shift all the elements in one . + for (var i = order; i < children.length; i++) { + var node = children[i]; + node.setOrder(i + 1); + } + child.setOrder(order); + }, + + detach:function(treeSet, node) { + var parent = treeSet.getParent(node); + var children = this._getSortedChildren(treeSet, parent); + var order = node.getOrder(); + $assert(children[order] === node, "Node seems not to be in the right position"); + + // Shift all the nodes ... + for (var i = node.getOrder() + 1; i < children.length; i++) { + var child = children[i]; + child.setOrder(child.getOrder() - 1); + } + node.setOrder(0); + }, + + _getSortedChildren:function(treeSet, node) { + var result = treeSet.getChildren(node); + result.sort(function(a, b) { + return a.getOrder() - b.getOrder() + }); + return result; + }, + + verify:function(treeSet, node) { + // Check that all is consistent ... + var children = this._getSortedChildren(treeSet, node); + + for (var i = 0; i < children.length; i++) { + $assert(children[i].getOrder() == i, "missing order elements"); + } + }, + + computeOffsets:function(treeSet, node) { + $assert(treeSet, "treeSet can no be null."); + $assert(node, "node can no be null."); + + var children = this._getSortedChildren(treeSet, node); + + // Compute heights ... + var heights = children.map(function(child) { + return {id:child.getId(), order:child.getOrder(), height:this._computeChildrenHeight(treeSet, child)}; + }, this); + + + // Compute the center of the branch ... + var totalPHeight = 0; + var totalNHeight = 0; + //TODO(gb): optimize using filter + heights.forEach(function(elem) { + if (elem.order % 2 == 0) { + totalPHeight += elem.height; + } else { + totalNHeight += elem.height; + } + }); + var psum = totalPHeight/2; + var nsum = totalNHeight/2; + var totalDifference = totalPHeight - totalNHeight; + var ysum = 0; + + // Calculate the offsets ... + var result = {}; + for (var i = 0; i < heights.length; i++) { + var direction = heights[i].order % 2 ? -1 : 1; + + if (direction > 0) { + psum = psum - heights[i].height; + ysum = psum; + } else { + nsum = nsum - heights[i].height; + ysum = nsum; + } + +// console.log("node {id: " + heights[i].id + ", order: " + heights[i].order + ", psum: " + psum + ", nsum: " + nsum + "}"); //TODO(gb): Remove trace!!! +// console.log("totalPHeight: " + totalPHeight + ", totalNHeight: " + totalNHeight); //TODO(gb): Remove trace!!! + console.log("node {id: " + heights[i].id + ", totalDifference: " + totalDifference + ", direction: " + direction + "}"); //TODO(gb): Remove trace!!! + + var yOffset = ysum + heights[i].height/2; + var xOffset = direction * (node.getSize().width + mindplot.nlayout.SymetricSorter.INTERNODE_HORIZONTAL_PADDING); + + $assert(!isNaN(xOffset), "xOffset can not be null"); + $assert(!isNaN(yOffset), "yOffset can not be null"); + + result[heights[i].id] = {x:xOffset,y:yOffset}; + } + console.log("----------------------\n"); //TODO(gb): Remove trace!!! + return result; + }, + + toString:function() { + return "Balanced Sorter"; + } +}); + +mindplot.nlayout.BalancedSorter.INTERNODE_VERTICAL_PADDING = 5; +mindplot.nlayout.BalancedSorter.INTERNODE_HORIZONTAL_PADDING = 5; +mindplot.nlayout.BalancedSorter.INITIAL_HEIGHT = 100; diff --git a/mindplot/src/main/javascript/nlayout/LayoutManager.js b/mindplot/src/main/javascript/nlayout/LayoutManager.js index 2cbb6385..d29657f5 100644 --- a/mindplot/src/main/javascript/nlayout/LayoutManager.js +++ b/mindplot/src/main/javascript/nlayout/LayoutManager.js @@ -39,10 +39,14 @@ mindplot.nlayout.LayoutManager = new Class({ // @Todo: finish... }, - find: function(id) { + find: function(id) { return this._treeSet.find(id); }, + move: function() { + //TODO(gb): implement + }, + connectNode: function(parentId, childId, order) { $assert($defined(parentId), "parentId can not be null"); $assert($defined(childId), "childId can not be null"); @@ -92,10 +96,10 @@ mindplot.nlayout.LayoutManager = new Class({ plot: function(containerId, size) { $assert(containerId, "containerId cannot be null"); - size = size || {w:200,h:200}; + size = size || {width:200,height:200}; var squaresize = 10; - var canvas = Raphael(containerId, size.w, size.h); - canvas.drawGrid(0, 0, size.w, size.h, size.w/squaresize, size.h/squaresize); + var canvas = Raphael(containerId, size.width, size.height); + canvas.drawGrid(0, 0, size.width, size.height, size.width/squaresize, size.height/squaresize); this._treeSet.plot(canvas); }, diff --git a/mindplot/src/main/javascript/nlayout/OriginalLayout.js b/mindplot/src/main/javascript/nlayout/OriginalLayout.js index c1d42974..5d117d45 100644 --- a/mindplot/src/main/javascript/nlayout/OriginalLayout.js +++ b/mindplot/src/main/javascript/nlayout/OriginalLayout.js @@ -26,7 +26,9 @@ mindplot.nlayout.OriginalLayout = new Class({ $assert(position, "position can not be null"); $assert(type, "type can not be null"); - var strategy = type === 'root' ? mindplot.nlayout.OriginalLayout.GRID_SORTER : mindplot.nlayout.OriginalLayout.SYMETRIC_SORTER; + var strategy = type === 'root' ? + mindplot.nlayout.OriginalLayout.BALANCED_SORTER : + mindplot.nlayout.OriginalLayout.SYMETRIC_SORTER; return new mindplot.nlayout.Node(id, size, position, strategy); }, @@ -120,6 +122,7 @@ mindplot.nlayout.OriginalLayout = new Class({ mindplot.nlayout.OriginalLayout.SYMETRIC_SORTER = new mindplot.nlayout.SymetricSorter(); mindplot.nlayout.OriginalLayout.GRID_SORTER = new mindplot.nlayout.GridSorter(); +mindplot.nlayout.OriginalLayout.BALANCED_SORTER = new mindplot.nlayout.BalancedSorter(); diff --git a/mindplot/src/main/javascript/nlayout/SymetricSorter.js b/mindplot/src/main/javascript/nlayout/SymetricSorter.js index 83c4aa6f..c83fa946 100644 --- a/mindplot/src/main/javascript/nlayout/SymetricSorter.js +++ b/mindplot/src/main/javascript/nlayout/SymetricSorter.js @@ -127,7 +127,6 @@ mindplot.nlayout.SymetricSorter = new Class({ computeOffsets:function(treeSet, node) { $assert(treeSet, "treeSet can no be null."); $assert(node, "node can no be null."); - $assert("order can no be null."); var children = this._getSortedChildren(treeSet, node); diff --git a/mindplot/src/main/javascript/nlayout/TestSuite.js b/mindplot/src/main/javascript/nlayout/TestSuite.js index 22cee90c..60ed9218 100644 --- a/mindplot/src/main/javascript/nlayout/TestSuite.js +++ b/mindplot/src/main/javascript/nlayout/TestSuite.js @@ -18,14 +18,16 @@ mindplot.nlayout.TestSuite = new Class({ Extends: mindplot.nlayout.ChildrenSorterStrategy, initialize:function() { - this.testAligned(); - this.testSymmetry(); - this.testGrid(); - this.testEvents(); - this.testEventsComplex(); - this.testDisconnect(); - this.testReconnect(); - this.testRemoveNode(); +// this.testAligned(); +// this.testSymmetry(); + this.testBalanced(); +// this.testGrid(); +// this.testEvents(); +// this.testEventsComplex(); +// this.testDisconnect(); +// this.testReconnect(); +// this.testRemoveNode(); +// this.testFreePosition(); }, testAligned: function() { @@ -45,7 +47,7 @@ mindplot.nlayout.TestSuite = new Class({ manager.layout(); manager.dump(); - manager.plot("testAligned", {w:300,h:200}); + manager.plot("testAligned", {width:300,height:200}); // All nodes should be vertically aligned $assert(manager.find(0).getPosition().y == manager.find(1).getPosition().y, "Nodes are not aligned"); @@ -90,11 +92,50 @@ mindplot.nlayout.TestSuite = new Class({ manager.layout(); manager.dump(); - manager.plot("testSymmetry",{w:500, h:300}); + manager.plot("testSymmetry",{width:500, height:300}); //TODO(gb): make asserts }, + testBalanced: function() { +// var size = {width:80, height:30}; + var size = {width:25, height:25}; + var plotsize = {width: 400, height:280}; + var position = {x:0, y:0}; +// var manager = new mindplot.nlayout.LayoutManager(0, {width:120, height:40}); + var manager = new mindplot.nlayout.LayoutManager(0, size); + + manager.addNode(1, size, position); + manager.connectNode(0, 1, 0); + manager.layout(); + manager.plot("testBalanced1", plotsize); + + manager.addNode(2, size, position); + manager.connectNode(0, 2, 1); + manager.layout(); + manager.plot("testBalanced2", plotsize); + + manager.addNode(3, size, position); + manager.connectNode(0, 3, 2); + manager.layout(); + manager.plot("testBalanced3", plotsize); + + manager.addNode(4, size, position); + manager.connectNode(0, 4, 3); + manager.layout(); + manager.plot("testBalanced4", plotsize); + + manager.addNode(5, size, position); + manager.connectNode(0, 5, 4); + manager.layout(); + manager.plot("testBalanced5", plotsize); + + manager.addNode(6, size, position); + manager.connectNode(0, 6, 5); + manager.layout(); + manager.plot("testBalanced6", plotsize); + }, + testGrid: function() { var size = {width:25,height:25}; var position = {x:0,y:0}; @@ -127,7 +168,7 @@ mindplot.nlayout.TestSuite = new Class({ manager.connectNode(2, 6, 1); manager.connectNode(6, 7, 0); manager.layout(); - manager.plot("testGrid5", {w:300, h:300}); + manager.plot("testGrid5", {width:300, height:300}); manager.dump(); @@ -236,16 +277,16 @@ mindplot.nlayout.TestSuite = new Class({ events.push(event); }); manager.layout(true); -// manager.dump(); - manager.plot("testDisconnect1", {w:300, h:200}); + manager.dump(); + manager.plot("testDisconnect1", {width:300, height:200}); // Now, disconnect one node ... console.log("--- Disconnect a single node ---"); events.empty(); manager.disconnectNode(2); manager.layout(true); -// manager.dump(); - manager.plot("testDisconnect2", {w:300, h:200}); + manager.dump(); + manager.plot("testDisconnect2", {width:300, height:200}); $assert(events.some( function(event) { @@ -256,8 +297,8 @@ mindplot.nlayout.TestSuite = new Class({ console.log("--- Disconnect a node with children ---"); manager.disconnectNode(3); manager.layout(true); -// manager.dump(); - manager.plot("testDisconnect3", {w:300, h:200}); + manager.dump(); + manager.plot("testDisconnect3", {width:300, height:200}); $assert(events.some( function(event) { @@ -297,14 +338,14 @@ mindplot.nlayout.TestSuite = new Class({ manager.layout(); manager.dump(); - manager.plot("testReconnect1",{w:400, h:300}); + manager.plot("testReconnect1",{width:400, height:300}); // Reconnect node 6 to node 4 manager.disconnectNode(6); manager.connectNode(4,6,0); manager.layout(); manager.dump(); - manager.plot("testReconnect2",{w:400, h:300}); + manager.plot("testReconnect2",{width:400, height:300}); //TODO(gb): make asserts }, @@ -334,7 +375,7 @@ mindplot.nlayout.TestSuite = new Class({ }); manager.layout(true); manager.dump(); - manager.plot("testRemoveNode1", {w:300, h:200}); + manager.plot("testRemoveNode1", {width:300, height:200}); // Test removal of a connected node ... console.log("--- Remove node 3 ---"); @@ -342,6 +383,26 @@ mindplot.nlayout.TestSuite = new Class({ manager.layout(true); manager.dump(); manager.plot("testRemoveNode2"); + }, + + testFreePosition: function() { + var size = {width:25,height:25}; + var position = {x:0,y:0}; + var manager = new mindplot.nlayout.LayoutManager(0, size); + + // Prepare a sample graph ... + manager.addNode(1, size, position); + manager.addNode(2, size, position); + manager.addNode(3, size, position); + manager.addNode(4, size, position); + + manager.connectNode(0, 4, 0); + manager.connectNode(4, 1, 0); + manager.connectNode(4, 2, 1); + manager.connectNode(4, 3, 2); + + manager.layout(); + manager.plot("testFreePosition"); } diff --git a/mindplot/src/test/javascript/static/layout.html b/mindplot/src/test/javascript/static/layout.html index 4caf8ed3..e0fc5a73 100644 --- a/mindplot/src/test/javascript/static/layout.html +++ b/mindplot/src/test/javascript/static/layout.html @@ -14,6 +14,7 @@ + @@ -57,6 +58,14 @@
+