Simon Hunt

Added geometry lib script, and implemented collision prevention of nodes.

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
......