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