Showing
3 changed files
with
192 additions
and
6 deletions
web/gui/src/main/webapp/geometry.js
0 → 100644
| 1 | +/* | ||
| 2 | + Geometry library - based on work by Mike Bostock. | ||
| 3 | + */ | ||
| 4 | + | ||
| 5 | +(function() { | ||
| 6 | + | ||
| 7 | + if (typeof geo == 'undefined') { | ||
| 8 | + geo = {}; | ||
| 9 | + } | ||
| 10 | + | ||
| 11 | + var tolerance = 1e-10; | ||
| 12 | + | ||
| 13 | + function eq(a, b) { | ||
| 14 | + return (Math.abs(a - b) < tolerance); | ||
| 15 | + } | ||
| 16 | + | ||
| 17 | + function gt(a, b) { | ||
| 18 | + return (a - b > -tolerance); | ||
| 19 | + } | ||
| 20 | + | ||
| 21 | + function lt(a, b) { | ||
| 22 | + return gt(b, a); | ||
| 23 | + } | ||
| 24 | + | ||
| 25 | + geo.eq = eq; | ||
| 26 | + geo.gt = gt; | ||
| 27 | + geo.lt = lt; | ||
| 28 | + | ||
| 29 | + geo.LineSegment = function(x1, y1, x2, y2) { | ||
| 30 | + this.x1 = x1; | ||
| 31 | + this.y1 = y1; | ||
| 32 | + this.x2 = x2; | ||
| 33 | + this.y2 = y2; | ||
| 34 | + | ||
| 35 | + // Ax + By = C | ||
| 36 | + this.a = y2 - y1; | ||
| 37 | + this.b = x1 - x2; | ||
| 38 | + this.c = x1 * this.a + y1 * this.b; | ||
| 39 | + | ||
| 40 | + if (eq(this.a, 0) && eq(this.b, 0)) { | ||
| 41 | + throw new Error( | ||
| 42 | + 'Cannot construct a LineSegment with two equal endpoints.'); | ||
| 43 | + } | ||
| 44 | + }; | ||
| 45 | + | ||
| 46 | + geo.LineSegment.prototype.intersect = function(that) { | ||
| 47 | + var d = (this.x1 - this.x2) * (that.y1 - that.y2) - | ||
| 48 | + (this.y1 - this.y2) * (that.x1 - that.x2); | ||
| 49 | + | ||
| 50 | + if (eq(d, 0)) { | ||
| 51 | + // The two lines are parallel or very close. | ||
| 52 | + return { | ||
| 53 | + x : NaN, | ||
| 54 | + y : NaN | ||
| 55 | + }; | ||
| 56 | + } | ||
| 57 | + | ||
| 58 | + var t1 = this.x1 * this.y2 - this.y1 * this.x2, | ||
| 59 | + t2 = that.x1 * that.y2 - that.y1 * that.x2, | ||
| 60 | + x = (t1 * (that.x1 - that.x2) - t2 * (this.x1 - this.x2)) / d, | ||
| 61 | + y = (t1 * (that.y1 - that.y2) - t2 * (this.y1 - this.y2)) / d, | ||
| 62 | + in1 = (gt(x, Math.min(this.x1, this.x2)) && lt(x, Math.max(this.x1, this.x2)) && | ||
| 63 | + gt(y, Math.min(this.y1, this.y2)) && lt(y, Math.max(this.y1, this.y2))), | ||
| 64 | + in2 = (gt(x, Math.min(that.x1, that.x2)) && lt(x, Math.max(that.x1, that.x2)) && | ||
| 65 | + gt(y, Math.min(that.y1, that.y2)) && lt(y, Math.max(that.y1, that.y2))); | ||
| 66 | + | ||
| 67 | + return { | ||
| 68 | + x : x, | ||
| 69 | + y : y, | ||
| 70 | + in1 : in1, | ||
| 71 | + in2 : in2 | ||
| 72 | + }; | ||
| 73 | + }; | ||
| 74 | + | ||
| 75 | + geo.LineSegment.prototype.x = function(y) { | ||
| 76 | + // x = (C - By) / a; | ||
| 77 | + if (this.a) { | ||
| 78 | + return (this.c - this.b * y) / this.a; | ||
| 79 | + } else { | ||
| 80 | + // a == 0 -> horizontal line | ||
| 81 | + return NaN; | ||
| 82 | + } | ||
| 83 | + }; | ||
| 84 | + | ||
| 85 | + geo.LineSegment.prototype.y = function(x) { | ||
| 86 | + // y = (C - Ax) / b; | ||
| 87 | + if (this.b) { | ||
| 88 | + return (this.c - this.a * x) / this.b; | ||
| 89 | + } else { | ||
| 90 | + // b == 0 -> vertical line | ||
| 91 | + return NaN; | ||
| 92 | + } | ||
| 93 | + }; | ||
| 94 | + | ||
| 95 | + geo.LineSegment.prototype.length = function() { | ||
| 96 | + return Math.sqrt( | ||
| 97 | + (this.y2 - this.y1) * (this.y2 - this.y1) + | ||
| 98 | + (this.x2 - this.x1) * (this.x2 - this.x1)); | ||
| 99 | + }; | ||
| 100 | + | ||
| 101 | + geo.LineSegment.prototype.offset = function(x, y) { | ||
| 102 | + return new geo.LineSegment( | ||
| 103 | + this.x1 + x, this.y1 + y, | ||
| 104 | + this.x2 + x, this.y2 + y); | ||
| 105 | + }; | ||
| 106 | + | ||
| 107 | +})(); |
| ... | @@ -14,6 +14,7 @@ | ... | @@ -14,6 +14,7 @@ |
| 14 | <link rel="stylesheet" href="base.css"> | 14 | <link rel="stylesheet" href="base.css"> |
| 15 | <link rel="stylesheet" href="onos.css"> | 15 | <link rel="stylesheet" href="onos.css"> |
| 16 | 16 | ||
| 17 | + <script src="geometry.js"></script> | ||
| 17 | <script src="onosui.js"></script> | 18 | <script src="onosui.js"></script> |
| 18 | 19 | ||
| 19 | </head> | 20 | </head> | ... | ... |
| ... | @@ -353,13 +353,28 @@ | ... | @@ -353,13 +353,28 @@ |
| 353 | 353 | ||
| 354 | node.select('image') | 354 | node.select('image') |
| 355 | .attr('x', bounds.x1); | 355 | .attr('x', bounds.x1); |
| 356 | + | ||
| 357 | + d.extent = { | ||
| 358 | + left: bounds.x1 - lab.marginLR, | ||
| 359 | + right: bounds.x2 + lab.marginLR, | ||
| 360 | + top: bounds.y1 - lab.marginTB, | ||
| 361 | + bottom: bounds.y2 + lab.marginTB | ||
| 362 | + }; | ||
| 363 | + | ||
| 364 | + d.edge = { | ||
| 365 | + left : new geo.LineSegment(bounds.x1, bounds.y1, bounds.x1, bounds.y2), | ||
| 366 | + right : new geo.LineSegment(bounds.x2, bounds.y1, bounds.x2, bounds.y2), | ||
| 367 | + top : new geo.LineSegment(bounds.x1, bounds.y1, bounds.x2, bounds.y1), | ||
| 368 | + bottom : new geo.LineSegment(bounds.x1, bounds.y2, bounds.x2, bounds.y2) | ||
| 369 | + }; | ||
| 370 | + | ||
| 356 | // ==== | 371 | // ==== |
| 357 | }); | 372 | }); |
| 358 | 373 | ||
| 359 | network.numTicks = 0; | 374 | network.numTicks = 0; |
| 360 | network.preventCollisions = false; | 375 | network.preventCollisions = false; |
| 361 | network.force.start(); | 376 | network.force.start(); |
| 362 | - for (var i = 0; i < config.ticksWithoutCollisions; i++) { | 377 | + for (var i = 0; i < config.force.ticksWithoutCollisions; i++) { |
| 363 | network.force.tick(); | 378 | network.force.tick(); |
| 364 | } | 379 | } |
| 365 | network.preventCollisions = true; | 380 | network.preventCollisions = true; |
| ... | @@ -376,6 +391,51 @@ | ... | @@ -376,6 +391,51 @@ |
| 376 | return 'translate(' + x + ',' + y + ')'; | 391 | return 'translate(' + x + ',' + y + ')'; |
| 377 | } | 392 | } |
| 378 | 393 | ||
| 394 | + function preventCollisions() { | ||
| 395 | + var quadtree = d3.geom.quadtree(network.nodes); | ||
| 396 | + | ||
| 397 | + network.nodes.forEach(function(n) { | ||
| 398 | + var nx1 = n.x + n.extent.left, | ||
| 399 | + nx2 = n.x + n.extent.right, | ||
| 400 | + ny1 = n.y + n.extent.top, | ||
| 401 | + ny2 = n.y + n.extent.bottom; | ||
| 402 | + | ||
| 403 | + quadtree.visit(function(quad, x1, y1, x2, y2) { | ||
| 404 | + if (quad.point && quad.point !== n) { | ||
| 405 | + // check if the rectangles intersect | ||
| 406 | + var p = quad.point, | ||
| 407 | + px1 = p.x + p.extent.left, | ||
| 408 | + px2 = p.x + p.extent.right, | ||
| 409 | + py1 = p.y + p.extent.top, | ||
| 410 | + py2 = p.y + p.extent.bottom, | ||
| 411 | + ix = (px1 <= nx2 && nx1 <= px2 && py1 <= ny2 && ny1 <= py2); | ||
| 412 | + if (ix) { | ||
| 413 | + var xa1 = nx2 - px1, // shift n left , p right | ||
| 414 | + xa2 = px2 - nx1, // shift n right, p left | ||
| 415 | + ya1 = ny2 - py1, // shift n up , p down | ||
| 416 | + ya2 = py2 - ny1, // shift n down , p up | ||
| 417 | + adj = Math.min(xa1, xa2, ya1, ya2); | ||
| 418 | + | ||
| 419 | + if (adj == xa1) { | ||
| 420 | + n.x -= adj / 2; | ||
| 421 | + p.x += adj / 2; | ||
| 422 | + } else if (adj == xa2) { | ||
| 423 | + n.x += adj / 2; | ||
| 424 | + p.x -= adj / 2; | ||
| 425 | + } else if (adj == ya1) { | ||
| 426 | + n.y -= adj / 2; | ||
| 427 | + p.y += adj / 2; | ||
| 428 | + } else if (adj == ya2) { | ||
| 429 | + n.y += adj / 2; | ||
| 430 | + p.y -= adj / 2; | ||
| 431 | + } | ||
| 432 | + } | ||
| 433 | + return ix; | ||
| 434 | + } | ||
| 435 | + }); | ||
| 436 | + | ||
| 437 | + }); | ||
| 438 | + } | ||
| 379 | 439 | ||
| 380 | function tick(e) { | 440 | function tick(e) { |
| 381 | network.numTicks++; | 441 | network.numTicks++; |
| ... | @@ -390,6 +450,11 @@ | ... | @@ -390,6 +450,11 @@ |
| 390 | }); | 450 | }); |
| 391 | } | 451 | } |
| 392 | 452 | ||
| 453 | + if (network.preventCollisions) { | ||
| 454 | + preventCollisions(); | ||
| 455 | + } | ||
| 456 | + | ||
| 457 | + // TODO: use intersection technique for source end of link also | ||
| 393 | network.link | 458 | network.link |
| 394 | .attr('x1', function(d) { | 459 | .attr('x1', function(d) { |
| 395 | return d.source.x; | 460 | return d.source.x; |
| ... | @@ -397,11 +462,24 @@ | ... | @@ -397,11 +462,24 @@ |
| 397 | .attr('y1', function(d) { | 462 | .attr('y1', function(d) { |
| 398 | return d.source.y; | 463 | return d.source.y; |
| 399 | }) | 464 | }) |
| 400 | - .attr('x2', function(d) { | 465 | + .each(function(d) { |
| 401 | - return d.target.x; | 466 | + var x = d.target.x, |
| 402 | - }) | 467 | + y = d.target.y, |
| 403 | - .attr('y2', function(d) { | 468 | + line = new geo.LineSegment(d.source.x, d.source.y, x, y); |
| 404 | - return d.target.y; | 469 | + |
| 470 | + for (var e in d.target.edge) { | ||
| 471 | + var ix = line.intersect(d.target.edge[e].offset(x,y)); | ||
| 472 | + if (ix.in1 && ix.in2) { | ||
| 473 | + x = ix.x; | ||
| 474 | + y = ix.y; | ||
| 475 | + break; | ||
| 476 | + } | ||
| 477 | + } | ||
| 478 | + | ||
| 479 | + d3.select(this) | ||
| 480 | + .attr('x2', x) | ||
| 481 | + .attr('y2', y); | ||
| 482 | + | ||
| 405 | }); | 483 | }); |
| 406 | 484 | ||
| 407 | network.node | 485 | network.node | ... | ... |
-
Please register or login to post a comment