Topo2: Implemented Link Labels
JIRA-Tasks; ONOS-6387

Change-Id: I6d0292846349d73d6d274ae806d14736b2d3eb7c
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2-theme.css b/web/gui/src/main/webapp/app/view/topo2/topo2-theme.css
index a9ba89e..34422f7 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2-theme.css
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2-theme.css
@@ -293,35 +293,45 @@
 /* TODO: Review for not-permitted links */
 #ov-topo2 svg .link.not-permitted {
     stroke: rgb(255,0,0);
-    stroke-width: 5.0;
     stroke-dasharray: 8 4;
 }
 
 #ov-topo2 svg .link.secondary {
-    stroke-width: 3px;
     stroke: rgba(0,153,51,0.5);
 }
 
+#ov-topo2 svg .link.secondary.port-traffic-green {
+    stroke: rgb(0,153,51);
+}
+
+#ov-topo2 svg .link.secondary.port-traffic-yellow {
+    stroke: rgb(128,145,27);
+}
+
+#ov-topo2 svg .link.secondary.port-traffic-orange {
+    stroke: rgb(255, 137, 3);
+}
+
+#ov-topo2 svg .link.secondary.port-traffic-red {
+    stroke: rgb(183, 30, 21);
+}
+
 /* Port traffic color visualization for Kbps, Mbps, and Gbps */
 
 #ov-topo2 svg .link.secondary.port-traffic-Kbps {
     stroke: rgb(0,153,51);
-    stroke-width: 5.0;
 }
 
 #ov-topo2 svg .link.secondary.port-traffic-Mbps {
     stroke: rgb(128,145,27);
-    stroke-width: 6.5;
 }
 
 #ov-topo2 svg .link.secondary.port-traffic-Gbps {
     stroke: rgb(255, 137, 3);
-    stroke-width: 8.0;
 }
 
 #ov-topo2 svg .link.secondary.port-traffic-Gbps-choked {
     stroke: rgb(183, 30, 21);
-    stroke-width: 8.0;
 }
 
 
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Collection.js b/web/gui/src/main/webapp/app/view/topo2/topo2Collection.js
index c3662e6..1166fb8 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Collection.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Collection.js
@@ -48,12 +48,14 @@
         addModel: function (data) {
             if (Object.getPrototypeOf(data) !== Object.prototype) {
                 this.models.push(data);
+                data.collection = this;
                 this._byId[data.get('id')] = data;
                 return data;
             }
 
             var CollectionModel = this.model;
             var model = new CollectionModel(data, this);
+            model.collection = this;
 
             this.models.push(model);
             this._byId[data.id] = model;
@@ -96,6 +98,12 @@
         filter: function (comparator) {
             return _.filter(this.models, comparator);
         },
+        empty: function () {
+            _.map(this.models, function (m) {
+                m.remove();
+            });
+            this._reset();
+        },
         _reset: function () {
             this._byId = [];
             this.models = [];
@@ -111,7 +119,6 @@
         .factory('Topo2Collection',
         ['Topo2Model', 'FnService',
             function (_Model_, fn) {
-
                 Collection.extend = fn.extend;
                 Model = _Model_;
                 return Collection;
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Event.js b/web/gui/src/main/webapp/app/view/topo2/topo2Event.js
index a03dab4..e8b8eac 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Event.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Event.js
@@ -27,7 +27,7 @@
     'use strict';
 
     // injected refs
-    var $log, wss, t2fs;
+    var $log, wss, t2fs, t2ovs;
 
     // internal state
     var handlerMap,
@@ -42,7 +42,8 @@
             topo2CurrentRegion: t2fs,
             topo2PeerRegions: t2fs,
 
-            topo2UiModelEvent: t2fs
+            topo2UiModelEvent: t2fs,
+            topo2Highlights: t2ovs.showHighlights,
 
             // Add further event names / module references as needed
         };
@@ -83,12 +84,13 @@
 
     angular.module('ovTopo2')
     .factory('Topo2EventService', [
-        '$log', 'WebSocketService', 'Topo2ForceService',
+        '$log', 'WebSocketService', 'Topo2ForceService', 'Topo2OverlayService',
 
-        function (_$log_, _wss_, _t2fs_) {
+        function (_$log_, _wss_, _t2fs_, _t2ovs_) {
             $log = _$log_;
             wss = _wss_;
             t2fs = _t2fs_;
+            t2ovs = _t2ovs_;
 
             // deferred creation of handler map, so module references are good
             createHandlerMap();
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Label.js b/web/gui/src/main/webapp/app/view/topo2/topo2Label.js
index 8a47424..28f134c 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Label.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Label.js
@@ -50,26 +50,39 @@
                     _iconG: {},
                     _labelG: {},
 
-                    initialize: function (data, node, options) {
+                    initialize: function (data, node) {
                         this.parent = node;
-                        this.options = options || {};
-
                         t2zs.addZoomEventListener(this.setScale.bind(this));
+                        this.beforeRender();
                         this.render();
+                        this.afterRender();
                     },
-                    onChange: function (property, value, options) {
+                    onChange: function (property) {
                         if (property === 'x' || property === 'y') {
                             this._position();
                         }
+
+                        if (property === 'label') {
+                            var width = this._labelG.text.node().getBBox().width + 20,
+                                height = this._labelG.text.node().getBBox().height + 10;
+
+                            this._labelG.text.text(this.get('label'));
+                            this._labelG.rect.attr({
+                                width: width,
+                                height: height
+                            }).style({
+                                transform: sus.translate(-(width/2) + 'px', -(height/2) + 'px')
+                            });
+                        }
                     },
 
                     setPosition: function () {},
                     setScale: function () {},
 
                     applyStyles: function () {
-                        var styles = _.extend({}, defaultStyles, this.get('styles'));
+                        var styles = _.extend({}, defaultStyles, this.get('styles') || {});
 
-                        if (this.get('text')) {
+                        if (this.get('label')) {
                             this._labelG.text.style(styles.label.text);
                             this._labelG.rect.style(styles.label.rect);
                         }
@@ -79,21 +92,17 @@
                             this._iconG.rect.style(styles.icon.rect);
                         }
                     },
-
                     _position: function () {
                         this.el.style('transform', sus.translate(this.get('x') + 'px',
                             this.get('y') + 'px'));
                     },
-                    labelDimensions: function () {
-                        return this.content.node().getBBox();
-                    },
                     renderText: function () {
                         this._labelG.el = this.content.append('g')
                             .attr('class', 'label-group');
 
                         this._labelG.rect = this._labelG.el.append('rect');
                         this._labelG.text = this._labelG.el.append('text')
-                            .text(this.get('text'))
+                            .text(this.get('label'))
                             .attr('y', '0.4em')
                             .style('text-anchor', 'middle');
 
@@ -127,6 +136,7 @@
                             transform: sus.translate(iconX, iconY)
                         });
                     },
+                    beforeRender: function () {},
                     render: function () {
                         this.el = this.parent.append('g')
                             .attr('class', 'topo2-label')
@@ -146,6 +156,10 @@
                         this.applyStyles();
                         this.setPosition();
                         this.setScale();
+                    },
+                    afterRender: function () {},
+                    remove: function () {
+                        this.el.remove();
                     }
                 });
             }
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2LabelCollection.js b/web/gui/src/main/webapp/app/view/topo2/topo2LabelCollection.js
index 895f491..ba05bb5 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2LabelCollection.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2LabelCollection.js
@@ -19,7 +19,6 @@
  A collection of any type of label (Topo2Label, Topo2Badge, Topo2LinkLabel)
  */
 
-
 (function () {
 
     var instance;
@@ -32,12 +31,18 @@
                 var LabelCollection = Collection.extend({
                     initialize: function () {
                         instance = this;
+                    },
+                    addLabel: function (Model, label, targetNode, options) {
+                        if (this._byId[label.id]) {
+                            this.get(label.id).set(label);
+                        } else {
+                            var lab = new Model(label, targetNode, options)
+                            this.add(lab);
+                        }
                     }
                 });
 
                 return instance || new LabelCollection();
             }
         ]);
-
-
-})();
\ No newline at end of file
+})();
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Link.js b/web/gui/src/main/webapp/app/view/topo2/topo2Link.js
index 77892ab..eb0da8f 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Link.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Link.js
@@ -22,7 +22,8 @@
 (function () {
     'use strict';
 
-    var $log, Collection, Model, ts, sus, t2zs, t2vs, t2lps, fn, ps, t2mss;
+    var $log, Collection, Model, ts, sus, t2zs, t2vs, t2lps,
+        fn, ps, t2mss, t2ts;
 
     var linkLabelOffset = '0.35em';
 
@@ -134,7 +135,8 @@
                         enhanced: this.get('enhanced'),
                         selected: this.get('selected'),
                         suppressedmax: this.get('mastership')
-                    }
+                    },
+                    (this.linkLabel) ? this.linkLabel.linkLabelCSSClass() : null
                 );
             },
             expected: function () {
@@ -154,6 +156,7 @@
                 // Update class names when the model changes
                 if (this.el) {
                     this.el.attr('class', this.svgClassName());
+                    this.setScale();
                 }
             },
             enhance: function () {
@@ -239,7 +242,6 @@
                 };
             },
             setPosition: function () {
-
                 var multiline = this.get('multiline');
                 if (multiline) {
                     var offsetAmt = this.amt(multiline.deviceLinks, multiline.index);
@@ -256,6 +258,9 @@
                     this.el.attr(this.get('position'));
                 }
 
+                if (this.linkLabel) {
+                    this.linkLabel.setPosition();
+                }
             },
             updatePortPosition: function () {
                 var sourcePos = this.locatePortLabel(1),
@@ -324,11 +329,39 @@
                 this.setVisibility();
                 this.setScale();
             },
+            linkWidth: function () {
+                var width = widthRatio;
+                if (this.get('enhanced')) { width = 3.5; }
+                if (this.linkLabel) {
+                    var scale = d3.scale.ordinal()
+                            .rangeRoundPoints([4, 8]),
+                        label = this.linkLabel.get('label').split(' ');
+
+                    switch (t2ts.selectedTrafficOverlay()) {
+                        case 'flowStatsBytes':
+                            scale.domain(['KB', 'MB', 'GB']);
+                            width = scale(label[1]);
+                            break;
+                        case 'portStatsBitSec':
+                            scale.domain(['Kbps', 'Mbps', 'Gbps'])
+                            width = scale(label[1]);
+                            break;
+                        case 'portStatsPktSec':
+                            scale = d3.scale.linear()
+                                .domain([1, 10, 100, 1000, 10000])
+                                .range(d3.range(3.5, 9))
+                                .clamp(true);
+                            width = scale(parseInt(label[0]));
+                    }
+                }
+
+                return width;
+            },
             setScale: function () {
 
                 if (!this.el) return;
 
-                var linkWidthRatio = this.get('enhanced') ? 3.5 : widthRatio;
+                var linkWidthRatio = this.linkWidth();
 
                 var width = linkScale(linkWidthRatio) / t2zs.scale();
                 this.el.attr('stroke-width', width + 'px');
@@ -342,6 +375,9 @@
 
                 this.setPosition();
 
+                if (this.linkLabel) {
+                    this.linkLabel.setScale();
+                }
             },
             update: function () {
                 if (this.get('enhanced')) {
@@ -390,9 +426,9 @@
         '$log', 'Topo2Collection', 'Topo2Model',
         'ThemeService', 'SvgUtilService', 'Topo2ZoomService',
         'Topo2ViewService', 'Topo2LinkPanelService', 'FnService', 'PrefsService',
-        'Topo2MastershipService',
+        'Topo2MastershipService', 'Topo2TrafficService',
         function (_$log_, _c_, _Model_, _ts_, _sus_,
-            _t2zs_, _t2vs_, _t2lps_, _fn_, _ps_, _t2mss_) {
+            _t2zs_, _t2vs_, _t2lps_, _fn_, _ps_, _t2mss_, _t2ts_) {
 
             $log = _$log_;
             ts = _ts_;
@@ -405,6 +441,7 @@
             fn = _fn_;
             ps = _ps_;
             t2mss = _t2mss_;
+            t2ts = _t2ts_;
 
             return {
                 createLinkCollection: createLinkCollection
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2LinkLabel.js b/web/gui/src/main/webapp/app/view/topo2/topo2LinkLabel.js
index 6e6d7fa..66b06e5 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2LinkLabel.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2LinkLabel.js
@@ -30,8 +30,21 @@
                     className: 'topo2-linklabel',
                     maxHeight: 30,
                     minHeight: 20,
+                    initialize: function (label, dom, options) {
+                        this.link = options.link;
+                        this.parent = dom;
+                        this.super = this.constructor.__super__;
+                        this.super.initialize.apply(this, arguments);
+                    },
+                    onChange: function () {
+                        this.link.onChange();
+                        this.constructor.__super__.onChange.apply(this, arguments);
+                    },
+                    linkLabelCSSClass: function () {
+                        return this.get('css') || '';
+                    },
                     setPosition: function () {
-                        var link = this.options.link;
+                        var link = this.link;
                         this.set({
                             x: (link.source.x + link.target.x) / 2,
                             y: (link.source.y + link.target.y) / 2
@@ -40,6 +53,14 @@
                     setScale: function () {
                         this.content.style('transform',
                             'scale(' + t2zs.adjustmentScale(20, 30) + ')');
+                    },
+                    beforeRender: function () {
+                        this.link.linkLabel = this;
+                    },
+                    remove: function () {
+                        this.link.linkLabel = null;
+                        this.link.onChange();
+                        this.constructor.__super__.remove.apply(this, arguments);
                     }
                 });
             }
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Model.js b/web/gui/src/main/webapp/app/view/topo2/topo2Model.js
index 31ec0f6..efcd201 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Model.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Model.js
@@ -23,13 +23,8 @@
     'use strict';
 
     function Model(attributes, collection) {
-
-        var attrs = attributes || {};
         this.attributes = {};
-
-        attrs = angular.extend({}, attrs);
-        this.set(attrs, { silent: true });
-        this.collection = collection;
+        this.set(angular.extend({}, attributes || {}), { silent: true });
         this.initialize.apply(this, arguments);
     }
 
@@ -116,6 +111,11 @@
         },
         toJSON: function (options) {
             return angular.copy(this.attributes);
+        },
+        remove: function () {
+            if (this.collection) {
+                this.collection.remove(this);
+            }
         }
     };
 
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Overlay.js b/web/gui/src/main/webapp/app/view/topo2/topo2Overlay.js
index 808ee69..1227c30 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Overlay.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Overlay.js
@@ -22,7 +22,7 @@
     var t2os = 'Topo2OverlayService: ';
 
     // injected refs
-    var $log, $timeout, fs, gs, wss, t2kcs, api;
+    var $log, $timeout, fs, gs, wss, t2kcs, t2rs, t2lc, api, LinkLabel;
 
     // internal state
     var overlays = {},
@@ -134,27 +134,20 @@
     }
 
     function showHighlights(data) {
-        function doHighlight() {
-            _showHighlights(data);
-        }
-
-        // note: this allows the server-side event to add a manual delay
-        //       before invoking the highlight... this was (originally) to
-        //       allow for the re-creation of the DOM model, before trying
-        //       to reference elements. For Topo2, there may be a better
-        //       solution, making this piece of code redundant. Steven??
-
-        if (data.delay) {
-            $timeout(doHighlight, data.delay);
-        } else {
-            doHighlight();
-        }
-
-    }
-
-    function _showHighlights(data) {
-        // TODO: implement the highlighting .. see topoOverlay.js for example
         $log.info('+++ TOPO 2 +++ show highlights', data);
+        t2lc.empty();
+        var linkLabelsDOM = d3.select('.topo2-linkLabels');
+        _.each(data.links, function (link) {
+            // TODO: Inconsistent host id's (currentRegion and LinkLabel)
+            var id = link.id.replace('/None/0', '/None').replace('-', '~'),
+                lab = t2rs.getLink(id);
+                // TODO: There's a bug in backend where link id is in reverse
+                if (lab) {
+                    t2lc.addLabel(LinkLabel, link, linkLabelsDOM, {
+                        link: lab
+                    });
+                }
+        });
     }
 
     // ========================================================================
@@ -162,15 +155,20 @@
     angular.module('ovTopo2')
     .factory('Topo2OverlayService', [
         '$log', '$timeout', 'FnService', 'GlyphService', 'WebSocketService',
-        'Topo2KeyCommandService',
+        'Topo2KeyCommandService', 'Topo2RegionService', 'Topo2LabelCollection',
+        'Topo2LinkLabel',
 
-        function (_$log_, _$timeout_, _fs_, _gs_, _wss_, _t2kcs_) {
+        function (_$log_, _$timeout_, _fs_, _gs_, _wss_, _t2kcs_, _t2rs_,
+            _t2lc_, _t2ll_) {
             $log = _$log_;
             $timeout = _$timeout_;
             fs = _fs_;
             gs = _gs_;
             wss = _wss_;
             t2kcs = _t2kcs_;
+            t2rs = _t2rs_;
+            t2lc = _t2lc_;
+            LinkLabel = _t2ll_;
 
             return {
                 register: register,
diff --git a/web/gui/src/main/webapp/app/view/topo2/topo2Traffic.js b/web/gui/src/main/webapp/app/view/topo2/topo2Traffic.js
index 6ca4d5e..1cb703f 100644
--- a/web/gui/src/main/webapp/app/view/topo2/topo2Traffic.js
+++ b/web/gui/src/main/webapp/app/view/topo2/topo2Traffic.js
@@ -35,6 +35,7 @@
 
     // internal state
     var mode = null,
+        currentIndex = 0,
         allIndex = 0;
 
     // === -----------------------------------------------------
@@ -59,9 +60,14 @@
             trafficType: allTrafficTypes[allIndex]
         });
         flash.flash(allTrafficMsgs[allIndex]);
+        currentIndex = allIndex;
         allIndex = (allIndex + 1) % 3;
     }
 
+    function selectedTrafficOverlay() {
+        return allTrafficTypes[currentIndex];
+    }
+
     // === -----------------------------------------------------
 
     angular.module('ovTopo2')
@@ -80,7 +86,8 @@
 
                     // invoked from toolbar overlay buttons or keystrokes
                     cancelTraffic: cancelTraffic,
-                    showAllTraffic: showAllTraffic
+                    showAllTraffic: showAllTraffic,
+                    selectedTrafficOverlay: selectedTrafficOverlay
                 }
             }
         ]);