Enable link functionality in GUI2 Topology View

Change-Id: I1b88080ecdf8c9b6f8a60af4832a12441186d508
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.css b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.css
index cf965ac..57a2bd3 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.css
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.css
@@ -19,7 +19,7 @@
  ONOS GUI -- Topology View (forces device visual) -- CSS file
  */
 g.node.device rect {
-    fill: #f0f0f0;
+    fill: #f0f0f070;
 }
 g.node.device text {
     fill: #bbb;
@@ -30,7 +30,7 @@
 
 
 g.node.device.online rect {
-    fill: #ffffff;
+    fill: #fafafad0;
 }
 g.node.device.online text {
     fill: #3c3a3a;
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.html b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.html
index e329874..70d4d5b 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.html
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.html
@@ -13,17 +13,75 @@
 ~ See the License for the specific language governing permissions and
 ~ limitations under the License.
 -->
-<svg:g xmlns:svg="http://www.w3.org/2000/svg" #devSvg
+<svg:defs xmlns:svg="http://www.w3.org/2000/svg">
+    <!-- Template explanation: Define an SVG Filter that in
+        line 1) render the target object in to a bit map and apply a blur to it
+            based on its alpha channel
+        line 2) take that blurred layer and shift it down and to the right by 4
+        line 3) Merge this blurred and shifted layer and overlay it with the
+            original target object
+    -->
+    <svg:filter id="drop-shadow">
+        <svg:feGaussianBlur in="SourceAlpha" stdDeviation="2" result="blur" />
+        <svg:feOffset in="blur" dx="4" dy="4" result="offsetBlur"/>
+        <svg:feMerge >
+            <svg:feMergeNode in="offsetBlur" />
+            <svg:feMergeNode in="SourceGraphic" />
+        </svg:feMerge>
+    </svg:filter>
+    <svg:linearGradient id="diagonal_blue" x1="0%" y1="0%" x2="100%" y2="100%">
+        <svg:stop offset= "0%" style="stop-color: #5b99d2;" />
+        <svg:stop offset= "100%" style="stop-color: #3b79b2;" />
+    </svg:linearGradient>
+</svg:defs>
+<!-- Template explanation: Creates an SVG Group and in
+    line 1) transform it to the position calculated by the d3 force graph engine
+    line 2) Give it various CSS styles depending on attributes
+    line 3) When it is clicked, call the method that toggles the selection and
+        emits an event.
+    Other child objects have their own description
+-->
+<svg:g xmlns:svg="http://www.w3.org/2000/svg"
        [attr.transform]="'translate(' + device?.x + ',' + device?.y + '), scale(' + scale + ')'"
         [ngClass]="['node', 'device', device.online?'online':'', selected?'selected':'']"
         (click)="toggleSelected(device)">
-    <svg:rect class="node-container" x="-18" y="-18" width="36" height="36">
-              <!--[attr.width]="devText.getComputedTextLength()+36" -->
-              <!--[@deviceLabelToggle]="{ value: labelToggle, params: {txtWidth: devSvg.getBBox().width+'px' }}">-->
+    <svg:desc>Device {{device.id}}</svg:desc>
+    <!-- Template explanation: Creates an SVG Rectangle and in
+        line 1) set a css style and shift so that it's centred
+        line 2) set the initial width and height - width changes with label
+        line 3) link to the animation 'deviceLabelToggle', pass in to it a width
+            calculated from the width of the text, and additional padding at the end
+        line 4) Apply the filter defined above to this rectangle (even as its
+            width changes
+    -->
+    <svg:rect
+            class="node-container" x="-18" y="-18"
+            width="36" height="36"
+            [@deviceLabelToggle]="{ value: labelToggle, params: {txtWidth: (36 + labelTextLen() * 1.05)+'px' }}"
+            filter= "url(#drop-shadow)">
     </svg:rect>
-    <svg:rect x="-16" y="-16" width="32" height="32" [ngStyle]="{'fill': 'rgb(91, 153, 210)'}">
+    <!-- Template explanation: Creates an SVG Rectangle slightly smaller and
+        overlaid on the above. This is the blue box, and its width and height does
+        not change
+    -->
+    <svg:rect x="-16" y="-16" width="32" height="32" style="fill: url(#diagonal_blue)">
     </svg:rect>
-    <svg:text #devText text-anchor="start" y="0.3em" x="22" [ngStyle]="{'transform': 'scale(' + scale + ')'}">
+    <!-- Template explanation: Creates an SVG Text element and in
+        line 1) make it left aligned and slightly down and to the right of the last rect
+        line 2) set its text length to be the calculated value - see that function
+        line 3) because of kerning the actual text might be shorter or longer than
+            the pre-calculated value - if so change the spacing between the letters
+            (and not the letter width to compensate)
+        line 4) link to the animation deviceLabelToggleTxt, so that the text appears
+            in gently
+        line 5) The text will be one of 3 values - blank, the id or the name
+    -->
+    <svg:text
+            text-anchor="start" y="0.3em" x="22"
+            [attr.textLength]= "labelTextLen()"
+            lengthAdjust= "spacing"
+            [ngStyle]="{'transform': 'scale(' + scale + ')'}"
+            [@deviceLabelToggleTxt]="labelToggle">
         {{ labelToggle == 0 ? '': labelToggle == 1 ? device.id:device.props.name }}
     </svg:text>
     <!-- It's not possible to drive the following xref dynamically -->
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.spec.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.spec.ts
index 0f4feee..32ed2ef 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.spec.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.spec.ts
@@ -21,6 +21,7 @@
 import {of} from 'rxjs';
 import {ChangeDetectorRef} from '@angular/core';
 import {Device} from '../../models';
+import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
 
 class MockActivatedRoute extends ActivatedRoute {
     constructor(params: Params) {
@@ -44,6 +45,7 @@
         testDevice.online = true;
 
         TestBed.configureTestingModule({
+            imports: [ BrowserAnimationsModule ],
             declarations: [ DeviceNodeSvgComponent ],
             providers: [
                 { provide: LogService, useValue: logSpy },
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.ts
index 036fc08..756769d 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.ts
@@ -14,17 +14,18 @@
  * limitations under the License.
  */
 import {
+    ChangeDetectionStrategy,
     ChangeDetectorRef,
     Component,
-    ElementRef, EventEmitter,
+    EventEmitter,
     Input,
     OnChanges, Output,
     SimpleChanges,
-    ViewChild
 } from '@angular/core';
-import {Node, Device, LabelToggle} from '../../models';
+import {Device, LabelToggle, UiElement} from '../../models';
 import {LogService} from 'gui2-fw-lib';
 import {NodeVisual} from '../nodevisual';
+import {animate, state, style, transition, trigger} from '@angular/animations';
 
 /**
  * The Device node in the force graph
@@ -36,28 +37,38 @@
     selector: '[onos-devicenodesvg]',
     templateUrl: './devicenodesvg.component.html',
     styleUrls: ['./devicenodesvg.component.css'],
-    // changeDetection: ChangeDetectionStrategy.Default,
-    // animations: [
-    //     trigger('deviceLabelToggle', [
-    //         state('0', style({ // none
-    //             width: '36px',
-    //         })),
-    //         state('1, 2', // id
-    //             style({ width: '{{ txtWidth }}'}),
-    //             { params: {'txtWidth': '36px'}}
-    //         ), // default
-    //         transition('0 => *', animate('1000ms ease-in')),
-    //         transition('* => 0', animate('1000ms ease-out'))
-    //     ])
-    // ]
+    changeDetection: ChangeDetectionStrategy.Default,
+    animations: [
+        trigger('deviceLabelToggle', [
+            state('0', style({ // none
+                width: '36px',
+            })),
+            state('1, 2', // id
+                style({ width: '{{ txtWidth }}'}),
+                { params: {'txtWidth': '36px'}}
+            ), // default
+            transition('0 => 1', animate('250ms ease-in')),
+            transition('1 => 2', animate('250ms ease-in')),
+            transition('* => 0', animate('250ms ease-out'))
+        ]),
+        trigger('deviceLabelToggleTxt', [
+            state('0', style( {
+                opacity: 0,
+            })),
+            state( '1,2', style({
+                opacity: 1.0
+            })),
+            transition('0 => 1', animate('250ms ease-in')),
+            transition('* => 0', animate('250ms ease-out'))
+        ])
+    ]
 })
 export class DeviceNodeSvgComponent extends NodeVisual implements OnChanges {
     @Input() device: Device;
     @Input() scale: number = 1.0;
     @Input() labelToggle: LabelToggle = LabelToggle.NONE;
-    @Output() selectedEvent = new EventEmitter<Node>();
+    @Output() selectedEvent = new EventEmitter<UiElement>();
     textWidth: number = 36;
-    @ViewChild('idTxt') idTxt: ElementRef;
     constructor(
         protected log: LogService,
         private ref: ChangeDetectorRef
@@ -84,4 +95,31 @@
         }
         this.ref.markForCheck();
     }
+
+    /**
+     * Calculate the text length in advance as well as possible
+     *
+     * The length of SVG text cannot be exactly estimated, because depending on
+     * the letters kerning might mean that it is shorter or longer than expected
+     *
+     * This takes the approach of 8px width per letter of this size, that on average
+     * evens out over words. A word like 'ilj' will be much shorter than 'wm0'
+     * because of kerning
+     *
+     *
+     * In addition in the template, the <svg:text> properties
+     * textLength and lengthAdjust ensure that the text becomes long with extra
+     * wide spacing created as necessary.
+     *
+     * Other approaches like getBBox() of the text
+     */
+    labelTextLen() {
+        if (this.labelToggle === 1) {
+            return this.device.id.length * 8 * this.scale;
+        } else if (this.labelToggle === 2 && this.device && this.device.props.name && this.device.props.name.trim().length > 0) {
+            return this.device.props.name.length * 8 * this.scale;
+        } else {
+            return 0;
+        }
+    }
 }