Enable link functionality in GUI2 Topology View

Change-Id: I1b88080ecdf8c9b6f8a60af4832a12441186d508
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/force-directed-graph.spec.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/force-directed-graph.spec.ts
index 767e094..bdcd5c5 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/force-directed-graph.spec.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/force-directed-graph.spec.ts
@@ -16,6 +16,8 @@
 import {ForceDirectedGraph, Options} from './force-directed-graph';
 import {Node} from './node';
 import {Link} from './link';
+import {LogService} from 'gui2-fw-lib';
+import {TestBed} from '@angular/core/testing';
 
 export class TestNode extends Node {
     constructor(id: string) {
@@ -33,13 +35,15 @@
  * ONOS GUI -- ForceDirectedGraph - Unit Tests
  */
 describe('ForceDirectedGraph', () => {
+    let logServiceSpy: jasmine.SpyObj<LogService>;
     let fdg: ForceDirectedGraph;
     const options: Options = {width: 1000, height: 1000};
 
     beforeEach(() => {
+        const logSpy = jasmine.createSpyObj('LogService', ['info', 'debug', 'warn', 'error']);
         const nodes: Node[] = [];
         const links: Link[] = [];
-        fdg = new ForceDirectedGraph(options);
+        fdg = new ForceDirectedGraph(options, logSpy);
 
         for (let i = 0; i < 10; i++) {
             const newNode: TestNode = new TestNode('id' + i);
@@ -53,7 +57,7 @@
         fdg.links = links;
         fdg.initSimulation(options);
         fdg.initNodes();
-
+        logServiceSpy = TestBed.get(LogService);
     });
 
     afterEach(() => {
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/force-directed-graph.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/force-directed-graph.ts
index 46d3ba7..b511886 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/force-directed-graph.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/force-directed-graph.ts
@@ -16,12 +16,35 @@
 import { EventEmitter } from '@angular/core';
 import { Link } from './link';
 import { Node } from './node';
-import * as d3 from 'd3';
+import * as d3 from 'd3-force';
+import {LogService} from 'gui2-fw-lib';
 
 const FORCES = {
     LINKS: 1 / 50,
     COLLISION: 1,
-    CHARGE: -10
+    GRAVITY: 0.4,
+    FRICTION: 0.7
+};
+
+const CHARGES = {
+    device: -80,
+    host: -200,
+    region: -80,
+    _def_: -120
+};
+
+const LINK_DISTANCE = {
+    // note: key is link.type
+    direct: 100,
+    optical: 120,
+    UiEdgeLink: 100,
+    _def_: 50,
+};
+
+const LINK_STRENGTH = {
+    // note: key is link.type
+    // range: {0.0 ... 1.0}
+    _def_: 0.1
 };
 
 export interface Options {
@@ -36,7 +59,7 @@
     public nodes: Node[] = [];
     public links: Link[] = [];
 
-    constructor(options: Options) {
+    constructor(options: Options, public log: LogService) {
         this.initSimulation(options);
     }
 
@@ -56,10 +79,26 @@
         // Initializing the links force simulation
         this.simulation.force('links',
             d3.forceLink(this.links)
-                .strength(FORCES.LINKS)
+                .strength(this.strength.bind(this))
+                .distance(this.distance.bind(this))
         );
     }
 
+    charges(node) {
+        const nodeType = node.nodeType;
+        return CHARGES[nodeType] || CHARGES._def_;
+    }
+
+    distance(node) {
+        const nodeType = node.nodeType;
+        return LINK_DISTANCE[nodeType] || LINK_DISTANCE._def_;
+    }
+
+    strength(node) {
+        const nodeType = node.nodeType;
+        return LINK_STRENGTH[nodeType] || LINK_STRENGTH._def_;
+    }
+
     initSimulation(options: Options) {
         if (!options || !options.width || !options.height) {
             throw new Error('missing options when initializing simulation');
@@ -72,9 +111,12 @@
             // Creating the force simulation and defining the charges
             this.simulation = d3.forceSimulation()
                 .force('charge',
-                    d3.forceManyBody()
-                        .strength(FORCES.CHARGE)
-                );
+                    d3.forceManyBody().strength(this.charges.bind(this)))
+                        // .distanceMin(100).distanceMax(500))
+                .force('gravity',
+                    d3.forceManyBody().strength(FORCES.GRAVITY))
+                .force('friction',
+                    d3.forceManyBody().strength(FORCES.FRICTION));
 
             // Connecting the d3 ticker to an angular event emitter
             this.simulation.on('tick', function () {
@@ -82,7 +124,7 @@
             });
 
             this.initNodes();
-            this.initLinks();
+            // this.initLinks();
         }
 
         /** Updating the central force of the simulation */
@@ -94,5 +136,11 @@
 
     stopSimulation() {
         this.simulation.stop();
+        this.log.debug('Simulation stopped');
+    }
+
+    restartSimulation() {
+        this.simulation.restart();
+        this.log.debug('Simulation restarted');
     }
 }
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/link.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/link.ts
index e4d7768..eb6d340 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/link.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/link.ts
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import { Node } from './node';
+import {Node, UiElement} from './node';
 import * as d3 from 'd3';
 
 export enum LinkType {
@@ -38,9 +38,15 @@
 /**
  * Implementing SimulationLinkDatum interface into our custom Link class
  */
-export class Link implements d3.SimulationLinkDatum<Node> {
+export class Link implements UiElement, d3.SimulationLinkDatum<Node> {
     // Optional - defining optional implementation properties - required for relevant typing assistance
     index?: number;
+    id: string; // The id of the link in the format epA/portA~epB/portB
+    epA: string; // The name of the device or host at one end
+    epB: string; // The name of the device or host at the other end
+    portA: string; // The number of the port at one end
+    portB: string; // The number of the port at the other end
+    type: LinkType;
 
     // Must - defining enforced implementation properties
     source: Node;
@@ -50,21 +56,29 @@
         this.source = source;
         this.target = target;
     }
+
+    linkTypeStr(): string {
+        return LinkType[this.type];
+    }
 }
 
 /**
- * model of the topo2CurrentRegion region link from Region below
+ * model of the topo2CurrentRegion region link from Region
  */
 export class RegionLink extends Link {
-    id: string; // The id of the link in the format epA/portA~epB/portB
-    epA: string; // The name of the device or host at one end
-    epB: string; // The name of the device or host at the other end
-    portA: string; // The number of the port at one end
-    portB: string; // The number of the port at the other end
     rollup: RegionRollup[]; // Links in sub regions represented by this one link
-    type: LinkType;
 
     constructor(type: LinkType, nodeA: Node, nodeB: Node) {
         super(nodeA, nodeB);
+        this.type = type;
     }
 }
+
+/**
+ * model of the highlights that are sent back from WebSocket when traffic is shown
+ */
+export interface LinkHighlight {
+    id: string;
+    css: string;
+    label: string;
+}
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/node.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/node.ts
index a7a7cb1..a5b4f3a 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/node.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/node.ts
@@ -22,6 +22,11 @@
     RegionProps
 } from './regions';
 
+export interface UiElement {
+    index?: number;
+    id: string;
+}
+
 /**
  * Toggle state for how device labels should be displayed
  */
@@ -112,7 +117,7 @@
 /**
  * Implementing SimulationNodeDatum interface into our custom Node class
  */
-export abstract class Node implements d3.SimulationNodeDatum {
+export abstract class Node implements UiElement, d3.SimulationNodeDatum {
     // Optional - defining optional implementation properties - required for relevant typing assistance
     index?: number;
     x: number;
@@ -121,7 +126,7 @@
     vy?: number;
     fx?: number | null;
     fy?: number | null;
-
+    nodeType: NodeType;
     id: string;
 
     protected constructor(id) {
@@ -140,7 +145,6 @@
     location: LocationType;
     metaUi: MetaUi;
     master: string;
-    nodeType: NodeType;
     online: boolean;
     props: DeviceProps;
     type: string;
@@ -150,12 +154,14 @@
     }
 }
 
+/**
+ * Model of the ONOS Host element in the topology
+ */
 export class Host extends Node {
     configured: boolean;
     id: string;
     ips: string[];
     layer: LayerType;
-    nodeType: NodeType;
     props: HostProps;
 
     constructor(id: string) {
@@ -173,10 +179,30 @@
     nDevs: number;
     nHosts: number;
     name: string;
-    nodeType: NodeType;
     props: RegionProps;
 
     constructor(id: string) {
         super(id);
     }
 }
+
+/**
+ * Enumerated values for topology update event types
+ */
+export enum ModelEventType {
+    HOST_ADDED_OR_UPDATED,
+    LINK_ADDED_OR_UPDATED,
+    DEVICE_ADDED_OR_UPDATED,
+    DEVICE_REMOVED,
+    HOST_REMOVED
+}
+
+/**
+ * Enumerated values for topology update event memo field
+ */
+export enum ModelEventMemo {
+    ADDED = 'added',
+    REMOVED = 'removed',
+    UPDATED = 'updated'
+}
+