GUI -- TopoView - port numbers now offset along the link.

Change-Id: Ic093e5bb5f53d1d54ef4225917ff1177e4d5eef2
diff --git a/web/gui/src/main/webapp/app/view/topo/topo.css b/web/gui/src/main/webapp/app/view/topo/topo.css
index 5f7e5c1..9c0ebe0 100644
--- a/web/gui/src/main/webapp/app/view/topo/topo.css
+++ b/web/gui/src/main/webapp/app/view/topo/topo.css
@@ -517,13 +517,13 @@
     fill: #eee;
 }
 .dark #ov-topo svg .portLabel rect {
-    fill: #555;
+    fill: #222;
 }
 
 #ov-topo svg .portLabel text {
     text-anchor: middle;
     stroke-width: 0.1;
-    font-size: 9pt;
+    font-size: 11pt;
 }
 .light #ov-topo svg .portLabel text {
     fill: #444;
diff --git a/web/gui/src/main/webapp/app/view/topo/topoD3.js b/web/gui/src/main/webapp/app/view/topo/topoD3.js
index 7017a75..39c3495 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoD3.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoD3.js
@@ -426,15 +426,10 @@
 
             rect.attr(rectAroundText(el));
             text.attr('dy', linkLabelOffset);
-            el.attr('transform', transformPort(d));
+            el.attr('transform', sus.translate(d.x, d.y));
         });
     }
 
-    function transformPort(d) {
-        // TODO: offset along the link, away from the node
-        return sus.translate(d.baseX, d.baseY);
-    }
-
     // ==========================
     // Module definition
 
diff --git a/web/gui/src/main/webapp/app/view/topo/topoLink.js b/web/gui/src/main/webapp/app/view/topo/topoLink.js
index 1b8da1f..f13032c 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoLink.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoLink.js
@@ -190,36 +190,60 @@
     }
 
     function unenhance(d) {
-        d.el.classed('enhanced', false);
+        // guard against link element not set
+        if (d.el) {
+            d.el.classed('enhanced', false);
+        }
         api.portLabelG().selectAll('.portLabel').remove();
     }
 
     function enhance(d) {
+        var data = [],
+            point;
+
+        // guard against link element not set
+        if (!d.el) return;
+
         d.el.classed('enhanced', true);
         $log.debug('[' + (d.srcPort || 'H') + '] ---> [' + d.tgtPort + ']', d.key);
 
         // Define port label data objects.
         // NOTE: src port is absent in the case of host-links.
 
-        var data = [{
+        point = locatePortLabel(d);
+        angular.extend(point, {
             id: 'topo-port-tgt',
-            num: d.tgtPort,
-            baseX: d.target.x,
-            baseY: d.target.y
-        }];
+            num: d.tgtPort
+        });
+        data.push(point);
 
         if (d.srcPort) {
-            data.push({
+            point = locatePortLabel(d, 1);
+            angular.extend(point, {
                 id: 'topo-port-src',
-                num: d.srcPort,
-                baseX: d.source.x,
-                baseY: d.source.y
+                num: d.srcPort
             });
+            data.push(point);
         }
 
         td3.applyPortLabels(data, api.portLabelG());
     }
 
+    function locatePortLabel(link, src) {
+        var near = src ? 'source' : 'target',
+            far = src ? 'target' : 'source',
+            ln = link[near],
+            lf = link[far],
+            offset = 32;
+
+        function dist(x, y) { return Math.sqrt(x*x + y*y); }
+
+        var dx = lf.x - ln.x,
+            dy = lf.y - ln.y,
+            k = offset / dist(dx, dy);
+
+        return {x: k * dx + ln.x, y: k * dy + ln.y};
+    }
 
     // ==========================
     // Module definition