Merge remote-tracking branch 'origin/master'
diff --git a/web/gui/src/main/webapp/geometry.js b/web/gui/src/main/webapp/geometry.js
new file mode 100644
index 0000000..71533cb
--- /dev/null
+++ b/web/gui/src/main/webapp/geometry.js
@@ -0,0 +1,107 @@
+/*
+ Geometry library - based on work by Mike Bostock.
+ */
+
+(function() {
+
+    if (typeof geo == 'undefined') {
+        geo = {};
+    }
+
+    var tolerance = 1e-10;
+
+    function eq(a, b) {
+        return (Math.abs(a - b) < tolerance);
+    }
+
+    function gt(a, b) {
+        return (a - b > -tolerance);
+    }
+
+    function lt(a, b) {
+        return gt(b, a);
+    }
+
+    geo.eq = eq;
+    geo.gt = gt;
+    geo.lt = lt;
+
+    geo.LineSegment = function(x1, y1, x2, y2) {
+        this.x1 = x1;
+        this.y1 = y1;
+        this.x2 = x2;
+        this.y2 = y2;
+
+        // Ax + By = C
+        this.a = y2 - y1;
+        this.b = x1 - x2;
+        this.c = x1 * this.a + y1 * this.b;
+
+        if (eq(this.a, 0) && eq(this.b, 0)) {
+            throw new Error(
+                'Cannot construct a LineSegment with two equal endpoints.');
+        }
+    };
+
+    geo.LineSegment.prototype.intersect = function(that) {
+        var d = (this.x1 - this.x2) * (that.y1 - that.y2) -
+            (this.y1 - this.y2) * (that.x1 - that.x2);
+
+        if (eq(d, 0)) {
+            // The two lines are parallel or very close.
+            return {
+                x : NaN,
+                y : NaN
+            };
+        }
+
+        var t1  = this.x1 * this.y2 - this.y1 * this.x2,
+            t2  = that.x1 * that.y2 - that.y1 * that.x2,
+            x   = (t1 * (that.x1 - that.x2) - t2 * (this.x1 - this.x2)) / d,
+            y   = (t1 * (that.y1 - that.y2) - t2 * (this.y1 - this.y2)) / d,
+            in1 = (gt(x, Math.min(this.x1, this.x2)) && lt(x, Math.max(this.x1, this.x2)) &&
+                gt(y, Math.min(this.y1, this.y2)) && lt(y, Math.max(this.y1, this.y2))),
+            in2 = (gt(x, Math.min(that.x1, that.x2)) && lt(x, Math.max(that.x1, that.x2)) &&
+                gt(y, Math.min(that.y1, that.y2)) && lt(y, Math.max(that.y1, that.y2)));
+
+        return {
+            x   : x,
+            y   : y,
+            in1 : in1,
+            in2 : in2
+        };
+    };
+
+    geo.LineSegment.prototype.x = function(y) {
+        // x = (C - By) / a;
+        if (this.a) {
+            return (this.c - this.b * y) / this.a;
+        } else {
+            // a == 0 -> horizontal line
+            return NaN;
+        }
+    };
+
+    geo.LineSegment.prototype.y = function(x) {
+        // y = (C - Ax) / b;
+        if (this.b) {
+            return (this.c - this.a * x) / this.b;
+        } else {
+            // b == 0 -> vertical line
+            return NaN;
+        }
+    };
+
+    geo.LineSegment.prototype.length = function() {
+        return Math.sqrt(
+                (this.y2 - this.y1) * (this.y2 - this.y1) +
+                (this.x2 - this.x1) * (this.x2 - this.x1));
+    };
+
+    geo.LineSegment.prototype.offset = function(x, y) {
+        return new geo.LineSegment(
+                this.x1 + x, this.y1 + y,
+                this.x2 + x, this.y2 + y);
+    };
+
+})();
diff --git a/web/gui/src/main/webapp/index.html b/web/gui/src/main/webapp/index.html
index c2cb8f8..ebf25c5 100644
--- a/web/gui/src/main/webapp/index.html
+++ b/web/gui/src/main/webapp/index.html
@@ -14,6 +14,7 @@
     <link rel="stylesheet" href="base.css">
     <link rel="stylesheet" href="onos.css">
 
+    <script src="geometry.js"></script>
     <script src="onosui.js"></script>
 
 </head>
diff --git a/web/gui/src/main/webapp/network.js b/web/gui/src/main/webapp/network.js
index 5e38fcc..80d11b7 100644
--- a/web/gui/src/main/webapp/network.js
+++ b/web/gui/src/main/webapp/network.js
@@ -353,13 +353,28 @@
 
                 node.select('image')
                     .attr('x', bounds.x1);
+
+                d.extent = {
+                    left: bounds.x1 - lab.marginLR,
+                    right: bounds.x2 + lab.marginLR,
+                    top: bounds.y1 - lab.marginTB,
+                    bottom: bounds.y2 + lab.marginTB
+                };
+
+                d.edge = {
+                    left   : new geo.LineSegment(bounds.x1, bounds.y1, bounds.x1, bounds.y2),
+                    right  : new geo.LineSegment(bounds.x2, bounds.y1, bounds.x2, bounds.y2),
+                    top    : new geo.LineSegment(bounds.x1, bounds.y1, bounds.x2, bounds.y1),
+                    bottom : new geo.LineSegment(bounds.x1, bounds.y2, bounds.x2, bounds.y2)
+                };
+
                 // ====
             });
 
             network.numTicks = 0;
             network.preventCollisions = false;
             network.force.start();
-            for (var i = 0; i < config.ticksWithoutCollisions; i++) {
+            for (var i = 0; i < config.force.ticksWithoutCollisions; i++) {
                 network.force.tick();
             }
             network.preventCollisions = true;
@@ -376,6 +391,51 @@
         return 'translate(' + x + ',' + y + ')';
     }
 
+    function preventCollisions() {
+        var quadtree = d3.geom.quadtree(network.nodes);
+
+        network.nodes.forEach(function(n) {
+            var nx1 = n.x + n.extent.left,
+                nx2 = n.x + n.extent.right,
+                ny1 = n.y + n.extent.top,
+                ny2 = n.y + n.extent.bottom;
+
+            quadtree.visit(function(quad, x1, y1, x2, y2) {
+                if (quad.point && quad.point !== n) {
+                    // check if the rectangles intersect
+                    var p = quad.point,
+                        px1 = p.x + p.extent.left,
+                        px2 = p.x + p.extent.right,
+                        py1 = p.y + p.extent.top,
+                        py2 = p.y + p.extent.bottom,
+                        ix = (px1 <= nx2 && nx1 <= px2 && py1 <= ny2 && ny1 <= py2);
+                    if (ix) {
+                        var xa1 = nx2 - px1, // shift n left , p right
+                            xa2 = px2 - nx1, // shift n right, p left
+                            ya1 = ny2 - py1, // shift n up   , p down
+                            ya2 = py2 - ny1, // shift n down , p up
+                            adj = Math.min(xa1, xa2, ya1, ya2);
+
+                        if (adj == xa1) {
+                            n.x -= adj / 2;
+                            p.x += adj / 2;
+                        } else if (adj == xa2) {
+                            n.x += adj / 2;
+                            p.x -= adj / 2;
+                        } else if (adj == ya1) {
+                            n.y -= adj / 2;
+                            p.y += adj / 2;
+                        } else if (adj == ya2) {
+                            n.y += adj / 2;
+                            p.y -= adj / 2;
+                        }
+                    }
+                    return ix;
+                }
+            });
+
+        });
+    }
 
     function tick(e) {
         network.numTicks++;
@@ -390,6 +450,11 @@
             });
         }
 
+        if (network.preventCollisions) {
+            preventCollisions();
+        }
+
+        // TODO: use intersection technique for source end of link also
         network.link
             .attr('x1', function(d) {
                 return d.source.x;
@@ -397,11 +462,24 @@
             .attr('y1', function(d) {
                 return d.source.y;
             })
-            .attr('x2', function(d) {
-                return d.target.x;
-            })
-            .attr('y2', function(d) {
-                return d.target.y;
+            .each(function(d) {
+                var x = d.target.x,
+                    y = d.target.y,
+                    line = new geo.LineSegment(d.source.x, d.source.y, x, y);
+
+                for (var e in d.target.edge) {
+                    var ix = line.intersect(d.target.edge[e].offset(x,y));
+                    if (ix.in1 && ix.in2) {
+                        x = ix.x;
+                        y = ix.y;
+                        break;
+                    }
+                }
+
+                d3.select(this)
+                    .attr('x2', x)
+                    .attr('y2', y);
+
             });
 
         network.node