Added d3 force graph to GUI2 topology

Change-Id: I6860472efaf51ea27fad74e630e687f0c6abad3d
diff --git a/web/gui2/BUILD b/web/gui2/BUILD
index 7ce7cf7..fb6a2c5 100644
--- a/web/gui2/BUILD
+++ b/web/gui2/BUILD
@@ -17,9 +17,9 @@
 """
     Rules to build the ONOS GUI 2
 
-    The GUI2 Angular 6 elements are built here with Angular CLI 'ng'
+    The GUI2 Angular 7 elements are built here with Angular CLI 'ng'
     Some work is being done in the Bazel community to integrate Bazel and
-    Angular 6, (Angular Buildtools Convergence -
+    Angular 7, (Angular Buildtools Convergence -
     https://docs.google.com/document/d/1OlyiUnoTirUj4gecGxJeZBcjHcFr36RvLsvpBl2mxA8/preview)
     but it is in the very early stages (Aug'18) and not yet fit
     for production and at present it works as a replacement for Angular CLI
@@ -103,7 +103,7 @@
     See bazel-genfiles/web/gui2/onos-gui2-ng-build-prod.log for details of the Angular CLI output
 
     To avoid the overhead of having several "npm install" invocations, we just do
-    it once in the //web/gui2-fw-lib which is really the core for the whole Angular 6
+    it once in the //web/gui2-fw-lib which is really the core for the whole Angular 7
     structure in ONOS. This copies files in to node_modules, but because the gui2-fw-lib
     has not been generated at that time we copy it in separately below with the 'tar' cmd
     and then 'mv'
@@ -151,9 +151,8 @@
           " export PATH=$$ROOT/$$(dirname $${NODE}):$$ROOT/web/gui2/node_modules/@angular/cli/bin:$$PATH &&" +
           " node -v > ../../$(location onos-gui2-ng-build-prod.log) &&" +
           " npm -v >> ../../$(location onos-gui2-ng-build-prod.log) &&" +
-          " ng -v >> ../../$(location onos-gui2-ng-build-prod.log) &&" +
-          # Build it in production mode - optimization is turned off because of Angular CLI 6.0.x bug https://github.com/angular/angular-cli/issues/7799
-          " ng build --extract-css --prod --optimization=false --preserve-symlinks" +
+          " ng version >> ../../$(location onos-gui2-ng-build-prod.log) &&" +
+          " ng build --extract-css --prod --preserve-symlinks" +
           "   --base-href /onos/ui2/ --deploy-url /onos/ui2/ >> $$ROOT/$(location onos-gui2-ng-build-prod.log) 2>&1 ||" +
           " if [ $$? -eq 0 ]; then echo 'Successfully ran build';" +
           " else " +
@@ -163,7 +162,7 @@
           "   exit 1;" +
           " fi;" +
           " cd src/main/webapp/dist && jar Mcf $$ROOT/$(location onos-gui2-ng-build.jar) .",
-    message = "Angular CLI 6 build",
+    message = "Angular CLI 7 build",
 )
 
 """
@@ -213,7 +212,7 @@
           " export PATH=$$ROOT/$$(dirname $${NODE}):$$ROOT/web/gui2/node_modules/@angular/cli/bin:$$PATH &&" +
           " node -v > ../../$(location onos-gui2-ng-ver.log) &&" +
           " npm -v >> ../../$(location onos-gui2-ng-ver.log) &&" +
-          " ng -v >> ../../$(location onos-gui2-ng-ver.log) &&" +
+          " ng version >> ../../$(location onos-gui2-ng-ver.log) &&" +
           " ng lint > ../../$(location onos-gui2-ng-lint.log);" +
           " if [ -f /usr/bin/chromium-browser ]; then " +  # Add to this for Mac and Chrome
           "   export CHROME_BIN=/usr/bin/chromium-browser; " +
@@ -236,7 +235,7 @@
           #"   tail -n 100 ../../$(location onos-gui2-ng-test.log) >&2;" +
           "   exit 1;" +
           " fi;",
-    message = "Angular CLI 6 lint and test",
+    message = "Angular CLI 7 lint and test",
 )
 
 """
diff --git a/web/gui2/README.md b/web/gui2/README.md
index c3cbed3..8f97312 100644
--- a/web/gui2/README.md
+++ b/web/gui2/README.md
@@ -73,9 +73,9 @@
 
 If you make any changes here or are using it for the first time it will need to be built
 ```text
-cd ~/onos/web/gui2-fw-lib && \
+pushd ~/onos/web/gui2-fw-lib && \
 ng build gui2-fw-lib && \
-pushd dist/gui2-fw-lib && \
+cd dist/gui2-fw-lib && \
 npm pack && \
 popd
 ```
diff --git a/web/gui2/package-lock.json b/web/gui2/package-lock.json
index 604c7e4..7cf1766 100644
--- a/web/gui2/package-lock.json
+++ b/web/gui2/package-lock.json
@@ -4704,8 +4704,8 @@
       }
     },
     "fm-gui2-lib": {
-      "version": "file:../../apps/faultmanagement/fm-gui2-lib/dist/fm-gui2-lib/fm-gui2-lib-1.15.0.tgz",
-      "integrity": "sha512-5qRtSAZDtFeafTTaJ9UHwyP+E7Sj6nx5RrAORCzt1DswqI4YFZqW1nSLa9co0hWqi9lem5sKspp+BH787TiXwQ==",
+      "version": "file:../../apps/faultmanagement/fm-gui2-lib/dist/fm-gui2-lib/fm-gui2-lib-2.0.0.tgz",
+      "integrity": "sha512-AYRLoCF5jSYYAvi6fYJCepKtCm9Woregjte79YTQvgzL/+2AvTtXf9m2ezj/wmUnZOfFH6wTqVdW1OLzK/TQiQ==",
       "requires": {
         "tslib": "1.9.3"
       }
@@ -5559,8 +5559,8 @@
       "dev": true
     },
     "gui2-fw-lib": {
-      "version": "file:../gui2-fw-lib/dist/gui2-fw-lib/gui2-fw-lib-0.14.0.tgz",
-      "integrity": "sha512-8uT9TL5Ye4uRSkseDyI6fsGHb1d5/SwjSYeTVhftmyKIgF4O2HywOa0gdFopx6Ft+JjhhP5k1n+WAPvoEjnXKA==",
+      "version": "file:../gui2-fw-lib/dist/gui2-fw-lib/gui2-fw-lib-2.0.0.tgz",
+      "integrity": "sha512-wAhrApE0c7FWqRDgB/8itVppmsd1Z4T+j8woxmDzkYwIkEqSvc7pvF3+QIOF0IQDkRCU2P31Ms1AtZ5cQliXTA==",
       "requires": {
         "tslib": "1.9.3"
       }
diff --git a/web/gui2/src/main/webapp/app/onos-routing.module.ts b/web/gui2/src/main/webapp/app/onos-routing.module.ts
index 962f5a3..77bce5a 100644
--- a/web/gui2/src/main/webapp/app/onos-routing.module.ts
+++ b/web/gui2/src/main/webapp/app/onos-routing.module.ts
@@ -89,7 +89,7 @@
     },
     {
         path: '',
-        redirectTo: 'device', // Default to devices view - change to topo in future
+        redirectTo: 'topo2', // Default to Topology view
         pathMatch: 'full'
     }
 ];
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/backgroundsvg/backgroundsvg.component.html b/web/gui2/src/main/webapp/app/view/topology/layer/backgroundsvg/backgroundsvg.component.html
index 389ec9c..d745ce8 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/backgroundsvg/backgroundsvg.component.html
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/backgroundsvg/backgroundsvg.component.html
@@ -13,8 +13,8 @@
 ~ See the License for the specific language governing permissions and
 ~ limitations under the License.
 -->
-<svg:g onos-mapsvg />
-<svg:g>
+<svg:g xmlns:svg="http://www.w3.org/2000/svg" onos-mapsvg />
+<svg:g  xmlns:svg="http://www.w3.org/2000/svg">
     <svg:text>Layout: {{ layoutData.id }} {{ layoutData.bgDesc }}</svg:text>
     <svg:text>Region: {{ layoutData.region }} {{ layoutData.regionName }}</svg:text>
     <svg:text>Parent {{ layoutData.parent }}</svg:text>
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/backgroundsvg/backgroundsvg.component.ts b/web/gui2/src/main/webapp/app/view/topology/layer/backgroundsvg/backgroundsvg.component.ts
index ff6f99a..e07f304 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/backgroundsvg/backgroundsvg.component.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/backgroundsvg/backgroundsvg.component.ts
@@ -44,6 +44,7 @@
  * Enum of the topo2CurrentRegion location type from Location below
  */
 export enum LocationType {
+    NONE = 'none',
     GEO = 'geo',
     GRID = 'grid'
 }
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/draggable/draggable.directive.spec.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/draggable/draggable.directive.spec.ts
new file mode 100644
index 0000000..bb6b4d0
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/draggable/draggable.directive.spec.ts
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { DraggableDirective } from './draggable.directive';
+import {inject, TestBed} from '@angular/core/testing';
+import {ElementRef} from '@angular/core';
+
+export class MockElementRef extends ElementRef {
+    nativeElement = {};
+}
+
+describe('DraggableDirective', () => {
+    let mockWindow: Window;
+
+    beforeEach(() => {
+        mockWindow = <any>{
+            navigator: {
+                userAgent: 'HeadlessChrome',
+                vendor: 'Google Inc.'
+            }
+        };
+
+        TestBed.configureTestingModule({
+            providers: [DraggableDirective,
+                { provide: 'Window', useFactory: (() => mockWindow ) },
+                { provide: ElementRef, useValue: mockWindow }
+            ]
+        });
+    });
+
+    it('should create an instance', inject([DraggableDirective], (directive: DraggableDirective) => {
+
+        expect(directive).toBeTruthy();
+    }));
+});
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/draggable/draggable.directive.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/draggable/draggable.directive.ts
new file mode 100644
index 0000000..88faa37
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/draggable/draggable.directive.ts
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { Directive, ElementRef, Input, OnChanges } from '@angular/core';
+import { ForceDirectedGraph, Node } from '../models';
+import * as d3 from 'd3';
+
+@Directive({
+  selector: '[onosDraggableNode]'
+})
+export class DraggableDirective implements OnChanges {
+    @Input() draggableNode: Node;
+    @Input() draggableInGraph: ForceDirectedGraph;
+
+    constructor(
+        private _element: ElementRef
+    ) {
+    }
+
+    ngOnChanges() {
+        this.applyDraggableBehaviour(
+            this._element.nativeElement,
+            this.draggableNode,
+            this.draggableInGraph);
+    }
+
+    /**
+     * A method to bind a draggable behaviour to an svg element
+     */
+    applyDraggableBehaviour(element, node: Node, graph: ForceDirectedGraph) {
+        const d3element = d3.select(element);
+
+        function started() {
+            /** Preventing propagation of dragstart to parent elements */
+            d3.event.sourceEvent.stopPropagation();
+
+            if (!d3.event.active) {
+                graph.simulation.alphaTarget(0.3).restart();
+            }
+
+            d3.event.on('drag', dragged).on('end', ended);
+
+            function dragged() {
+                node.fx = d3.event.x;
+                node.fy = d3.event.y;
+            }
+
+            function ended() {
+                if (!d3.event.active) {
+                    graph.simulation.alphaTarget(0);
+                }
+
+                node.fx = null;
+                node.fy = null;
+            }
+        }
+
+        d3element.call(d3.drag()
+            .on('start', started));
+    }
+}
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/forcesvg.component.html b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/forcesvg.component.html
index 0c2f0aa..f02b3a1 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/forcesvg.component.html
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/forcesvg.component.html
@@ -13,10 +13,40 @@
 ~ See the License for the specific language governing permissions and
 ~ limitations under the License.
 -->
-<svg:g class="topo2-force">
-    <svg:g class="topo2-links" />
-    <svg:g class="topo2-linkLabels" />
-    <svg:g class="topo2-numLinkLabels" />
-    <svg:g class="topo2-nodes" />
-    <svg:g class="topo2-portLabels" />
+<svg:g class="topo2-force" xmlns:svg="http://www.w3.org/2000/svg">
+    <svg:g id="new-zoom-layer">
+        <svg:g class="topo2-links">
+            <svg:g onos-linkvisual [link]="link" *ngFor="let link of regionData.links">
+            </svg:g>
+        </svg:g>
+        <svg:g class="topo2-linkLabels" />
+        <svg:g class="topo2-numLinkLabels" />
+        <svg:g class="topo2-nodes">
+            <svg:g onos-devicenodesvg [device]="device"
+                   *ngFor="let device of regionData.devices[visibleLayerIdx()]"
+                   onosDraggableNode [draggableNode]="device" [draggableInGraph]="graph"
+                   (selectedEvent)="updateSelected($event)">
+            </svg:g>
+            <svg:g onos-hostnodesvg [host]="host"
+                   *ngFor="let host of regionData.hosts[visibleLayerIdx()]"
+                   onosDraggableNode [draggableNode]="host" [draggableInGraph]="graph">
+            </svg:g>
+            <svg:g onos-subregionnodesvg [subRegion]="subRegion"
+                   *ngFor="let subRegion of regionData.subregions"
+                   onosDraggableNode [draggableNode]="host" [draggableInGraph]="graph">
+            </svg:g>
+        </svg:g>
+    </svg:g>
+    <!--<svg:g class="topo2-portLabels">-->
+        <!--&lt;!&ndash;TODO make each of these in to a component that can be inserted &ndash;&gt;-->
+        <!--<svg:g *ngIf="selectedLink !== null" id="topo-port-src" class="portLabel" opacity="1">-->
+            <!--<rect x="0" y="0" width="1" height="1" [ngStyle]="{'transform': 'scale(1)'}"></rect>-->
+            <!--<text dy="0.3em" [ngStyle]="{'transform': 'scale(1)'}">{{ selectedLink.portA }}</text>-->
+        <!--</svg:g>-->
+
+        <!--<svg:g *ngIf="selectedLink !== null" id="topo-port-tgt" class="portLabel" opacity="1">-->
+            <!--<rect x="0" y="0" width="1" height="1" [ngStyle]="{'transform': 'scale(1)'}"></rect>-->
+            <!--<text dy="0.3em" [ngStyle]="{'transform': 'scale(1)'}">{{ selectedLink.portA }}</text>-->
+        <!--</svg:g>-->
+    <!--</svg:g>-->
 </svg:g>
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/forcesvg.component.spec.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/forcesvg.component.spec.ts
index 505c528..ffbe667 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/forcesvg.component.spec.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/forcesvg.component.spec.ts
@@ -16,21 +16,71 @@
 import { async, ComponentFixture, TestBed } from '@angular/core/testing';
 
 import { ForceSvgComponent } from './forcesvg.component';
+import {IconService, LogService} from 'gui2-fw-lib';
+import {
+    DeviceNodeSvgComponent,
+    HostNodeSvgComponent, LinkVisualComponent,
+    SubRegionNodeSvgComponent
+} from './visuals';
+import {DraggableDirective} from './draggable/draggable.directive';
+import {ActivatedRoute, Params} from '@angular/router';
+import {of} from 'rxjs';
+
+class MockActivatedRoute extends ActivatedRoute {
+    constructor(params: Params) {
+        super();
+        this.queryParams = of(params);
+    }
+}
+
+class MockIconService {
+    loadIconDef() { }
+}
 
 describe('ForceSvgComponent', () => {
+    let ar: MockActivatedRoute;
+    let windowMock: Window;
+    let logServiceSpy: jasmine.SpyObj<LogService>;
     let component: ForceSvgComponent;
     let fixture: ComponentFixture<ForceSvgComponent>;
 
     beforeEach(async(() => {
+        const logSpy = jasmine.createSpyObj('LogService', ['info', 'debug', 'warn', 'error']);
+        ar = new MockActivatedRoute({ 'debug': 'txrx' });
+
+        windowMock = <any>{
+            location: <any>{
+                hostname: 'foo',
+                host: 'foo',
+                port: '80',
+                protocol: 'http',
+                search: { debug: 'true' },
+                href: 'ws://foo:123/onos/ui/websock/path',
+                absUrl: 'ws://foo:123/onos/ui/websock/path'
+            }
+        };
+
         TestBed.configureTestingModule({
-          declarations: [ ForceSvgComponent ]
+            declarations: [
+                ForceSvgComponent,
+                DeviceNodeSvgComponent,
+                HostNodeSvgComponent,
+                SubRegionNodeSvgComponent,
+                LinkVisualComponent,
+                DraggableDirective
+            ],
+            providers: [
+                { provide: LogService, useValue: logSpy },
+                { provide: IconService, useClass: MockIconService },
+            ]
         })
         .compileComponents();
+        logServiceSpy = TestBed.get(LogService);
     }));
 
     beforeEach(() => {
         fixture = TestBed.createComponent(ForceSvgComponent);
-        component = fixture.componentInstance;
+        component = fixture.debugElement.componentInstance;
         fixture.detectChanges();
     });
 
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/forcesvg.component.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/forcesvg.component.ts
index d159f06..97c9e1e 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/forcesvg.component.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/forcesvg.component.ts
@@ -13,143 +13,189 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {Component, Input, OnInit} from '@angular/core';
-import { LocationType } from '../backgroundsvg/backgroundsvg.component';
+import {
+    ChangeDetectionStrategy,
+    ChangeDetectorRef,
+    Component,
+    EventEmitter,
+    HostListener,
+    Input,
+    OnChanges,
+    OnInit,
+    Output, QueryList, SimpleChange,
+    SimpleChanges, ViewChildren
+} from '@angular/core';
+import {IconService, LogService} from 'gui2-fw-lib';
+import {
+    Device,
+    ForceDirectedGraph,
+    Host,
+    LabelToggle,
+    LayerType,
+    Region,
+    RegionLink,
+    SubRegion
+} from './models';
+import {DeviceNodeSvgComponent} from './visuals';
 
-/**
- * Enum of the topo2CurrentRegion node type from SubRegion below
- */
-export enum NodeType {
-    REGION = 'region',
-    DEVICE = 'device'
-}
-
-/**
- * Enum of the topo2CurrentRegion layerOrder from Region below
- */
-export enum LayerOrder {
-    LAYER_OPTICAL = 'opt',
-    LAYER_PACKET = 'pkt',
-    LAYER_DEFAULT = 'def'
-}
-
-/**
- * model of the topo2CurrentRegion location from SubRegion below
- */
-export interface Location {
-    locType: LocationType;
-    latOrY: number;
-    longOrX: number;
-}
-
-/**
- * model of the topo2CurrentRegion props from SubRegion below
- */
-export interface RegionProps {
-    latitude: number;
-    longitude: number;
-    name: string;
-    peerLocations: string;
-}
-
-/**
- * model of the topo2CurrentRegion subregion from Region below
- */
-export interface SubRegion {
-    id: string;
-    location: Location;
-    nDevs: number;
-    nHosts: number;
-    name: string;
-    nodeType: NodeType;
-    props: RegionProps;
-}
-
-export enum LinkType {
-    UiRegionLink,
-    UiDeviceLink
-}
-
-/**
- * model of the topo2CurrentRegion region rollup from Region below
- */
-export interface RegionRollup {
-    id: string;
-    epA: string;
-    epB: string;
-    portA: string;
-    portB: string;
-    type: LinkType;
-}
-
-/**
- * model of the topo2CurrentRegion region link from Region below
- */
-export interface RegionLink {
-    id: string;
-    epA: string;
-    epB: string;
-    rollup: RegionRollup[];
-    type: LinkType;
-}
-
-/**
- * model of the topo2CurrentRegion device props from Device below
- */
-export interface DeviceProps {
-    latitude: number;
-    longitude: number;
-    name: string;
-    locType: LocationType;
-}
-
-export interface Device {
-    id: string;
-    layer: LayerOrder;
-    location: LocationType;
-    master: string;
-    nodeType: NodeType;
-    online: boolean;
-    props: DeviceProps;
-    type: string;
-}
-
-/**
- * model of the topo2CurrentRegion WebSocket response
- */
-export interface Region {
-    note?: string;
-    id: string;
-    devices: Device[][];
-    hosts: Object[];
-    links: RegionLink[];
-    layerOrder: LayerOrder[];
-    peerLocations?: Location[];
-    subregions: SubRegion[];
-}
-
-/**
- * model of the topo2PeerRegions WebSocket response
- */
-export interface Peer {
-    peers: SubRegion[];
-}
 
 /**
  * ONOS GUI -- Topology Forces Graph Layer View.
+ *
+ * The regionData is set by Topology Service on WebSocket topo2CurrentRegion callback
+ * This drives the whole Force graph
  */
 @Component({
     selector: '[onos-forcesvg]',
     templateUrl: './forcesvg.component.html',
-    styleUrls: ['./forcesvg.component.css']
+    styleUrls: ['./forcesvg.component.css'],
+    changeDetection: ChangeDetectionStrategy.OnPush,
 })
-export class ForceSvgComponent implements OnInit {
+export class ForceSvgComponent implements OnInit, OnChanges {
     @Input() onosInstMastership: string = '';
-    regionData: Region;
+    @Input() visibleLayer: LayerType = LayerType.LAYER_DEFAULT;
+    @Output() linkSelected = new EventEmitter<RegionLink>();
+    @Output() selectedNodeEvent = new EventEmitter<Device>();
+    @Input() selectedLink: RegionLink = null;
+    private graph: ForceDirectedGraph;
 
-    constructor() { }
+    @Input() regionData: Region = <Region>{devices: [ [], [], [] ], hosts: [ [], [], [] ], links: []};
+    private _options: { width, height } = { width: 800, height: 600 };
 
-    ngOnInit() {
+    @ViewChildren(DeviceNodeSvgComponent) devices: QueryList<DeviceNodeSvgComponent>;
+
+    @HostListener('window:resize', ['$event'])
+    onResize(event) {
+        this.graph.initSimulation(this.options);
+        this.log.debug('Simulation reinit after resize', event);
     }
 
+    constructor(
+        protected log: LogService,
+        protected is: IconService,
+        private ref: ChangeDetectorRef
+    ) {
+        this.selectedLink = null;
+        this.log.debug('ForceSvgComponent constructed');
+    }
+
+    /**
+     * After the component is initialized create the Force simulation
+     */
+    ngOnInit() {
+        // Receiving an initialized simulated graph from our custom d3 service
+        this.graph = new ForceDirectedGraph(this.options);
+
+        /** Binding change detection check on each tick
+         * This along with an onPush change detection strategy should enforce checking only when relevant!
+         * This improves scripting computation duration in a couple of tests I've made, consistently.
+         * Also, it makes sense to avoid unnecessary checks when we are dealing only with simulations data binding.
+         */
+        this.graph.ticker.subscribe((simulation) => {
+            // this.log.debug("Force simulation has ticked", simulation);
+            this.ref.markForCheck();
+        });
+        this.log.debug('ForceSvgComponent initialized - waiting for nodes and links');
+
+        this.is.loadIconDef('m_switch');
+    }
+
+    /**
+     * When any one of the inputs get changed by a containing component, this gets called automatically
+     * In addition this is called manually by topology.service when a response
+     * is received from the WebSocket from the server
+     *
+     * The Devices, Hosts and SubRegions are all added to the Node list for the simulation
+     * The Links are added to the Link list of the simulation.
+     * Before they are added the Links are associated with Nodes based on their endPt
+     *
+     * @param changes - a list of changed @Input(s)
+     */
+    ngOnChanges(changes: SimpleChanges) {
+        if (changes['regionData']) {
+            const devices: Device[] =
+                changes['regionData'].currentValue.devices[this.visibleLayerIdx()];
+            const hosts: Host[] =
+                changes['regionData'].currentValue.hosts[this.visibleLayerIdx()];
+            const subRegions: SubRegion[] = changes['regionData'].currentValue.subRegion;
+            this.graph.nodes = [];
+            if (devices) {
+                this.graph.nodes = devices;
+            }
+            if (hosts) {
+                this.graph.nodes = this.graph.nodes.concat(hosts);
+            }
+            if (subRegions) {
+                this.graph.nodes = this.graph.nodes.concat(subRegions);
+            }
+
+            // Associate the endpoints of each link with a real node
+            this.graph.links = [];
+            for (const linkIdx of Object.keys(this.regionData.links)) {
+                this.regionData.links[linkIdx].source =
+                    this.graph.nodes.find((node) =>
+                        node.id === this.regionData.links[linkIdx].epA);
+                this.regionData.links[linkIdx].target =
+                    this.graph.nodes.find((node) =>
+                        node.id === this.regionData.links[linkIdx].epB);
+                this.regionData.links[linkIdx].index = Number(linkIdx);
+            }
+
+            this.graph.links = this.regionData.links;
+
+            this.graph.initSimulation(this.options);
+            this.graph.initNodes();
+            this.log.debug('ForceSvgComponent input changed',
+                this.graph.nodes.length, 'nodes,', this.graph.links.length, 'links');
+        }
+    }
+
+    /**
+     * Get the index of LayerType so it can drive the visibility of nodes and
+     * hosts on layers
+     */
+    visibleLayerIdx(): number {
+        const layerKeys: string[] = Object.keys(LayerType);
+        for (const idx in layerKeys) {
+            if (LayerType[layerKeys[idx]] === this.visibleLayer) {
+                return Number(idx);
+            }
+        }
+        return -1;
+    }
+
+    selectLink(link: RegionLink): void {
+        this.selectedLink = link;
+        this.linkSelected.emit(link);
+    }
+
+    get options() {
+        return this._options = {
+            width: window.innerWidth,
+            height: window.innerHeight
+        };
+    }
+
+    updateDeviceLabelToggle() {
+        this.devices.forEach((d) => {
+            const old: LabelToggle = d.labelToggle;
+            const next = LabelToggle.next(old);
+            d.ngOnChanges({'labelToggle': new SimpleChange(old, next, false)});
+        });
+    }
+
+    updateSelected(selectedNodeId: string): void {
+        this.log.debug('Device selected', selectedNodeId);
+        this.devices.filter((d) => d.device.id !== selectedNodeId).forEach((d) => {
+            d.deselect();
+        });
+        const selectedDevice: DeviceNodeSvgComponent =
+            (this.devices.find((d) => d.device.id === selectedNodeId));
+        if (selectedDevice) {
+            this.selectedNodeEvent.emit(selectedDevice.device);
+        } else {
+            this.selectedNodeEvent.emit();
+        }
+    }
 }
+
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
new file mode 100644
index 0000000..767e094
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/force-directed-graph.spec.ts
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the 'License');
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an 'AS IS' BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {ForceDirectedGraph, Options} from './force-directed-graph';
+import {Node} from './node';
+import {Link} from './link';
+
+export class TestNode extends Node {
+    constructor(id: string) {
+        super(id);
+    }
+}
+
+export class TestLink extends Link {
+    constructor(source: Node, target: Node) {
+        super(source, target);
+    }
+}
+
+/**
+ * ONOS GUI -- ForceDirectedGraph - Unit Tests
+ */
+describe('ForceDirectedGraph', () => {
+    let fdg: ForceDirectedGraph;
+    const options: Options = {width: 1000, height: 1000};
+
+    beforeEach(() => {
+        const nodes: Node[] = [];
+        const links: Link[] = [];
+        fdg = new ForceDirectedGraph(options);
+
+        for (let i = 0; i < 10; i++) {
+            const newNode: TestNode = new TestNode('id' + i);
+            nodes.push(newNode);
+        }
+        for (let j = 1; j < 10; j++) {
+            const newLink = new TestLink(nodes[0], nodes[j]);
+            links.push(newLink);
+        }
+        fdg.nodes = nodes;
+        fdg.links = links;
+        fdg.initSimulation(options);
+        fdg.initNodes();
+
+    });
+
+    afterEach(() => {
+        fdg.stopSimulation();
+        fdg.nodes = [];
+        fdg.links = [];
+        fdg.initSimulation(options);
+    });
+
+    it('should be created', () => {
+        expect(fdg).toBeTruthy();
+    });
+
+    it('should have simulation', () => {
+        expect(fdg.simulation).toBeTruthy();
+    });
+
+    it('should have 10 nodes', () => {
+        expect(fdg.nodes.length).toEqual(10);
+    });
+
+    it('should have 10 links', () => {
+        expect(fdg.links.length).toEqual(9);
+    });
+
+    // TODO fix these up to listen for tick
+    // it('nodes should not be at zero', () => {
+    //     expect(nodes[0].x).toBeGreaterThan(0);
+    // });
+    // it('ticker should emit', () => {
+    //     let tickMe = jasmine.createSpy("tickMe() spy");
+    //     fdg.ticker.subscribe((simulation) => tickMe());
+    //     expect(tickMe).toHaveBeenCalled();
+    // });
+
+    // it('init links chould be called ', () => {
+    //     spyOn(fdg, 'initLinks');
+    //     // expect(fdg).toBeTruthy();
+    //     fdg.initSimulation(options);
+    //     expect(fdg.initLinks).toHaveBeenCalled();
+    // });
+
+    it ('throws error on no options', () => {
+        expect(fdg.initSimulation).toThrowError('missing options when initializing simulation');
+    });
+
+
+
+});
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
new file mode 100644
index 0000000..3db25ae
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/force-directed-graph.ts
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the 'License');
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an 'AS IS' BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { EventEmitter } from '@angular/core';
+import { Link } from './link';
+import { Node } from './node';
+import * as d3 from 'd3';
+
+const FORCES = {
+    LINKS: 1 / 50,
+    COLLISION: 1,
+    CHARGE: -1
+};
+
+export interface Options {
+    width: number;
+    height: number;
+}
+
+export class ForceDirectedGraph {
+    public ticker: EventEmitter<d3.Simulation<Node, Link>> = new EventEmitter();
+    public simulation: d3.Simulation<any, any>;
+
+    public nodes: Node[] = [];
+    public links: Link[] = [];
+
+    constructor(options: Options) {
+        this.initSimulation(options);
+    }
+
+    initNodes() {
+        if (!this.simulation) {
+            throw new Error('simulation was not initialized yet');
+        }
+
+        this.simulation.nodes(this.nodes);
+    }
+
+    initLinks() {
+        if (!this.simulation) {
+            throw new Error('simulation was not initialized yet');
+        }
+
+        // Initializing the links force simulation
+        this.simulation.force('links',
+            d3.forceLink(this.links)
+                .strength(FORCES.LINKS)
+        );
+    }
+
+    initSimulation(options: Options) {
+        if (!options || !options.width || !options.height) {
+            throw new Error('missing options when initializing simulation');
+        }
+
+        /** Creating the simulation */
+        if (!this.simulation) {
+            const ticker = this.ticker;
+
+            // Creating the force simulation and defining the charges
+            this.simulation = d3.forceSimulation()
+                .force('charge',
+                    d3.forceManyBody()
+                        .strength(FORCES.CHARGE)
+                );
+
+            // Connecting the d3 ticker to an angular event emitter
+            this.simulation.on('tick', function () {
+                ticker.emit(this);
+            });
+
+            this.initNodes();
+            this.initLinks();
+        }
+
+        /** Updating the central force of the simulation */
+        this.simulation.force('centers', d3.forceCenter(options.width / 2, options.height / 2));
+
+        /** Restarting the simulation internal timer */
+        this.simulation.restart();
+    }
+
+    stopSimulation() {
+        this.simulation.stop();
+    }
+}
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/index.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/index.ts
new file mode 100644
index 0000000..36fd2e7
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/index.ts
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the 'License');
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an 'AS IS' BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export * from './node';
+export * from './link';
+export * from './regions';
+
+export * from './force-directed-graph';
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
new file mode 100644
index 0000000..e4d7768
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/link.ts
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the 'License');
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an 'AS IS' BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { Node } from './node';
+import * as d3 from 'd3';
+
+export enum LinkType {
+    UiRegionLink,
+    UiDeviceLink,
+    UiEdgeLink
+}
+
+/**
+ * model of the topo2CurrentRegion region rollup from Region below
+ *
+ */
+export interface RegionRollup {
+    id: string;
+    epA: string;
+    epB: string;
+    portA: string;
+    portB: string;
+    type: LinkType;
+}
+
+/**
+ * Implementing SimulationLinkDatum interface into our custom Link class
+ */
+export class Link implements d3.SimulationLinkDatum<Node> {
+    // Optional - defining optional implementation properties - required for relevant typing assistance
+    index?: number;
+
+    // Must - defining enforced implementation properties
+    source: Node;
+    target: Node;
+
+    constructor(source, target) {
+        this.source = source;
+        this.target = target;
+    }
+}
+
+/**
+ * model of the topo2CurrentRegion region link from Region below
+ */
+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);
+    }
+}
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
new file mode 100644
index 0000000..d19de88
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/node.ts
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the 'License');
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an 'AS IS' BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import * as d3 from 'd3';
+import {LocationType} from '../../backgroundsvg/backgroundsvg.component';
+import {
+    LayerType,
+    Location,
+    NodeType,
+    RegionProps
+} from './regions';
+
+/**
+ * Toggle state for how labels should be displayed
+ */
+export enum LabelToggle {
+    NONE,
+    ID,
+    NAME
+}
+
+export namespace LabelToggle {
+    export function next(current: LabelToggle) {
+        if (current === LabelToggle.NONE) {
+            return LabelToggle.ID;
+        } else if (current === LabelToggle.ID) {
+            return LabelToggle.NAME;
+        } else if (current === LabelToggle.NAME) {
+            return LabelToggle.NONE;
+        }
+    }
+}
+
+/**
+ * model of the topo2CurrentRegion device props from Device below
+ */
+export interface DeviceProps {
+    latitude: number;
+    longitude: number;
+    name: string;
+    locType: LocationType;
+}
+
+/**
+ * model of the topo2CurrentRegion Loc part of the MetaUi below
+ */
+export interface LocMeta {
+    lng: number;
+    lat: number;
+}
+
+/**
+ * model of the topo2CurrentRegion MetaUi from Device below
+ */
+export interface MetaUi {
+    equivLoc: LocMeta;
+    x: number;
+    y: number;
+}
+
+export interface HostProps {
+    gridX: number;
+    gridY: number;
+    latitude: number;
+    longitude: number;
+    locType: LocationType;
+    name: string;
+}
+
+/**
+ * Implementing SimulationNodeDatum interface into our custom Node class
+ */
+export abstract class Node implements d3.SimulationNodeDatum {
+    // Optional - defining optional implementation properties - required for relevant typing assistance
+    index?: number;
+    x: number;
+    y: number;
+    vx?: number;
+    vy?: number;
+    fx?: number | null;
+    fy?: number | null;
+
+    id: string;
+
+    protected constructor(id) {
+        this.id = id;
+        this.x = 0;
+        this.y = 0;
+    }
+}
+
+/**
+ * model of the topo2CurrentRegion device from Region below
+ */
+export class Device extends Node {
+    id: string;
+    layer: LayerType;
+    location: LocationType;
+    metaUi: MetaUi;
+    master: string;
+    nodeType: NodeType;
+    online: boolean;
+    props: DeviceProps;
+    type: string;
+
+    constructor(id: string) {
+        super(id);
+    }
+}
+
+export class Host extends Node {
+    configured: boolean;
+    id: string;
+    ips: string[];
+    layer: LayerType;
+    nodeType: NodeType;
+    props: HostProps;
+
+    constructor(id: string) {
+        super(id);
+    }
+}
+
+
+/**
+ * model of the topo2CurrentRegion subregion from Region below
+ */
+export class SubRegion extends Node {
+    id: string;
+    location: Location;
+    nDevs: number;
+    nHosts: number;
+    name: string;
+    nodeType: NodeType;
+    props: RegionProps;
+
+    constructor(id: string) {
+        super(id);
+    }
+}
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/regions.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/regions.ts
new file mode 100644
index 0000000..3c1894b
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/regions.ts
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the 'License');
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an 'AS IS' BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * Enum of the topo2CurrentRegion node type from SubRegion below
+ */
+import {LocationType} from '../../backgroundsvg/backgroundsvg.component';
+import {Device, Host, SubRegion} from './node';
+import {RegionLink} from './link';
+
+export enum NodeType {
+    REGION = 'region',
+    DEVICE = 'device',
+    HOST = 'host',
+}
+
+/**
+ * Enum of the topo2CurrentRegion layerOrder from Region below
+ */
+export enum LayerType {
+    LAYER_OPTICAL = 'opt',
+    LAYER_PACKET = 'pkt',
+    LAYER_DEFAULT = 'def'
+}
+
+/**
+ * model of the topo2CurrentRegion location from SubRegion below
+ */
+export interface Location {
+    locType: LocationType;
+    latOrY: number;
+    longOrX: number;
+}
+
+/**
+ * model of the topo2CurrentRegion props from SubRegion below
+ */
+export interface RegionProps {
+    latitude: number;
+    longitude: number;
+    name: string;
+    peerLocations: string;
+}
+
+/**
+ * model of the topo2CurrentRegion WebSocket response
+ *
+ * The Devices are in a 2D array - 1st order is layer type, 2nd order is
+ * devices in that layer
+ */
+export interface Region {
+    note?: string;
+    id: string;
+    devices: Device[][];
+    hosts: Host[][];
+    links: RegionLink[];
+    layerOrder: LayerType[];
+    peerLocations?: Location[];
+    subregions: SubRegion[];
+}
+
+/**
+ * model of the topo2PeerRegions WebSocket response
+ */
+export interface Peer {
+    peers: SubRegion[];
+}
+
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
new file mode 100644
index 0000000..cf965ac
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.css
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+/*
+ ONOS GUI -- Topology View (forces device visual) -- CSS file
+ */
+g.node.device rect {
+    fill: #f0f0f0;
+}
+g.node.device text {
+    fill: #bbb;
+}
+g.node.device use {
+    fill: #777;
+}
+
+
+g.node.device.online rect {
+    fill: #ffffff;
+}
+g.node.device.online text {
+    fill: #3c3a3a;
+}
+g.node.device.online use {
+    /* NOTE: this gets overridden programatically */
+    fill: #454545;
+}
+
+g.node.selected .node-container {
+    stroke-width: 2.0;
+    stroke: #009fdb;
+}
+
+g.node.hovered .node-container {
+    stroke-width: 2.0;
+    stroke: #454545;
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..d00e906
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.html
@@ -0,0 +1,30 @@
+<!--
+~ Copyright 2018-present Open Networking Foundation
+~
+~ Licensed under the Apache License, Version 2.0 (the "License");
+~ you may not use this file except in compliance with the License.
+~ You may obtain a copy of the License at
+~
+~     http://www.apache.org/licenses/LICENSE-2.0
+~
+~ Unless required by applicable law or agreed to in writing, software
+~ distributed under the License is distributed on an "AS IS" BASIS,
+~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+~ 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
+       [attr.transform]="'translate(' + device?.x + ',' + device?.y + '), scale(' + scale + ')'"
+        [ngClass]="['node', 'device', device.online?'online':'', selected?'selected':'']"
+        (click)="toggleSelected()">
+    <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:rect>
+    <svg:rect x="-16" y="-16" width="32" height="32" [ngStyle]="{'fill': 'rgb(91, 153, 210)'}">
+    </svg:rect>
+    <svg:text #devText text-anchor="start" y="0.3em" x="22" [ngStyle]="{'transform': 'scale(' + scale + ')'}">
+        {{ labelToggle == 0 ? '': labelToggle == 1 ? device.id:device.props.name }}
+    </svg:text>
+    <svg:use xlink:href="#m_switch" width="36" height="36" x="-18" y="-18"></svg:use>
+</svg:g>
\ No newline at end of file
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
new file mode 100644
index 0000000..0f4feee
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.spec.ts
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the 'License');
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an 'AS IS' BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { DeviceNodeSvgComponent } from './devicenodesvg.component';
+import {LogService} from 'gui2-fw-lib';
+import {ActivatedRoute, Params} from '@angular/router';
+import {of} from 'rxjs';
+import {ChangeDetectorRef} from '@angular/core';
+import {Device} from '../../models';
+
+class MockActivatedRoute extends ActivatedRoute {
+    constructor(params: Params) {
+        super();
+        this.queryParams = of(params);
+    }
+}
+
+describe('DeviceNodeSvgComponent', () => {
+    let logServiceSpy: jasmine.SpyObj<LogService>;
+    let component: DeviceNodeSvgComponent;
+    let fixture: ComponentFixture<DeviceNodeSvgComponent>;
+    let ar: MockActivatedRoute;
+    let testDevice: Device;
+
+
+    beforeEach(async(() => {
+        const logSpy = jasmine.createSpyObj('LogService', ['info', 'debug', 'warn', 'error']);
+        ar = new MockActivatedRoute({ 'debug': 'txrx' });
+        testDevice = new Device('test:1');
+        testDevice.online = true;
+
+        TestBed.configureTestingModule({
+            declarations: [ DeviceNodeSvgComponent ],
+            providers: [
+                { provide: LogService, useValue: logSpy },
+                { provide: ActivatedRoute, useValue: ar },
+                { provide: ChangeDetectorRef, useClass: ChangeDetectorRef }
+            ]
+        })
+        .compileComponents();
+        logServiceSpy = TestBed.get(LogService);
+    }));
+
+    beforeEach(() => {
+        fixture = TestBed.createComponent(DeviceNodeSvgComponent);
+        component = fixture.componentInstance;
+        component.device = testDevice;
+        fixture.detectChanges();
+    });
+
+    it('should create', () => {
+        expect(component).toBeTruthy();
+    });
+});
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
new file mode 100644
index 0000000..bec4529
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.ts
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the 'License');
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an 'AS IS' BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {
+    ChangeDetectorRef,
+    Component,
+    ElementRef, EventEmitter,
+    Input,
+    OnChanges, Output,
+    SimpleChanges,
+    ViewChild
+} from '@angular/core';
+import {Device, LabelToggle} from '../../models';
+import {LogService} from 'gui2-fw-lib';
+
+/**
+ * The Device node in the force graph
+ *
+ * Note: here the selector is given square brackets [] so that it can be
+ * inserted in SVG element like a directive
+ */
+@Component({
+    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'))
+    //     ])
+    // ]
+})
+export class DeviceNodeSvgComponent implements OnChanges {
+    @Input() device: Device;
+    @Input() scale: number = 1.0;
+    @Input() labelToggle: LabelToggle = LabelToggle.NONE;
+    selected: boolean;
+    @Output() selectedEvent = new EventEmitter<string>();
+    textWidth: number = 36;
+    @ViewChild('idTxt') idTxt: ElementRef;
+
+    constructor(
+        protected log: LogService,
+        private ref: ChangeDetectorRef
+    ) {
+    }
+
+    /**
+     * Called by parent (forcesvg) when a change happens
+     *
+     * There is a difficulty in passing the SVG text object to the animation
+     * directly, to get its width, so we capture it here and update textWidth
+     * local variable here and use it in the animation
+     */
+    ngOnChanges(changes: SimpleChanges) {
+        if (changes['device']) {
+            if (!this.device.x) {
+                this.device.x = 0;
+                this.device.y = 0;
+            }
+        }
+        if (changes['labelToggle']) {
+            this.labelToggle = changes['labelToggle'].currentValue;
+        }
+        this.ref.markForCheck();
+    }
+
+    toggleSelected() {
+        this.selected = !this.selected;
+        if (this.selected) {
+            this.selectedEvent.emit(this.device.id);
+        } else {
+            this.selectedEvent.emit();
+        }
+    }
+
+    deselect() {
+        this.selected = false;
+    }
+
+}
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/hostnodesvg/hostnodesvg.component.css b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/hostnodesvg/hostnodesvg.component.css
new file mode 100644
index 0000000..60cedab
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/hostnodesvg/hostnodesvg.component.css
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+/*
+ ONOS GUI -- Topology View (forces host visual) -- CSS file
+ */
\ No newline at end of file
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/hostnodesvg/hostnodesvg.component.html b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/hostnodesvg/hostnodesvg.component.html
new file mode 100644
index 0000000..8626cf5
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/hostnodesvg/hostnodesvg.component.html
@@ -0,0 +1,23 @@
+<!--
+~ Copyright 2018-present Open Networking Foundation
+~
+~ Licensed under the Apache License, Version 2.0 (the "License");
+~ you may not use this file except in compliance with the License.
+~ You may obtain a copy of the License at
+~
+~     http://www.apache.org/licenses/LICENSE-2.0
+~
+~ Unless required by applicable law or agreed to in writing, software
+~ distributed under the License is distributed on an "AS IS" BASIS,
+~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+~ See the License for the specific language governing permissions and
+~ limitations under the License.
+-->
+<svg:g  xmlns:svg="http://www.w3.org/2000/svg" [attr.transform]="'translate(' + host?.x + ',' + host?.y + ')'">>
+  <svg:circle
+          cx="0"
+          cy="0"
+          r="5">
+  </svg:circle>
+  <svg:text>{{host?.id}}</svg:text>
+</svg:g>
\ No newline at end of file
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/hostnodesvg/hostnodesvg.component.spec.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/hostnodesvg/hostnodesvg.component.spec.ts
new file mode 100644
index 0000000..bb791b9
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/hostnodesvg/hostnodesvg.component.spec.ts
@@ -0,0 +1,25 @@
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { HostNodeSvgComponent } from './hostnodesvg.component';
+
+describe('HostNodeSvgComponent', () => {
+  let component: HostNodeSvgComponent;
+  let fixture: ComponentFixture<HostNodeSvgComponent>;
+
+  beforeEach(async(() => {
+    TestBed.configureTestingModule({
+      declarations: [ HostNodeSvgComponent ]
+    })
+    .compileComponents();
+  }));
+
+  beforeEach(() => {
+    fixture = TestBed.createComponent(HostNodeSvgComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/hostnodesvg/hostnodesvg.component.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/hostnodesvg/hostnodesvg.component.ts
new file mode 100644
index 0000000..4b1f4ab
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/hostnodesvg/hostnodesvg.component.ts
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the 'License');
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an 'AS IS' BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {
+    Component,
+    Input,
+    OnChanges,
+    SimpleChanges
+} from '@angular/core';
+import { Host } from '../../models';
+import {LogService} from 'gui2-fw-lib';
+
+/**
+ * The Host node in the force graph
+ *
+ * Note 1: here the selector is given square brackets [] so that it can be
+ * inserted in SVG element like a directive
+ * Note 2: the selector is exactly the same as the @Input alias to make this
+ * directive trick work
+ */
+@Component({
+    selector: '[onos-hostnodesvg]',
+    templateUrl: './hostnodesvg.component.html',
+    styleUrls: ['./hostnodesvg.component.css']
+})
+export class HostNodeSvgComponent implements OnChanges {
+    @Input() host: Host;
+
+    constructor(
+        protected log: LogService
+    ) {
+    }
+
+    ngOnChanges(changes: SimpleChanges) {
+        if (!this.host.x) {
+            this.host.x = 0;
+            this.host.y = 0;
+        }
+    }
+}
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/index.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/index.ts
new file mode 100644
index 0000000..0723986
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/index.ts
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the 'License');
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an 'AS IS' BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export * from './linkvisual.component';
+export * from './devicenodesvg/devicenodesvg.component';
+export * from './hostnodesvg/hostnodesvg.component';
+export * from './subregionnodesvg/subregionnodesvg.component';
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/linkvisual.component.css b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/linkvisual.component.css
new file mode 100644
index 0000000..52e5df3
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/linkvisual.component.css
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+/*
+ ONOS GUI -- Topology View (forces link visual) -- CSS file
+ */
\ No newline at end of file
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/linkvisual.component.html b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/linkvisual.component.html
new file mode 100644
index 0000000..6c4a6bd
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/linkvisual.component.html
@@ -0,0 +1,28 @@
+<!--
+~ Copyright 2018-present Open Networking Foundation
+~
+~ Licensed under the Apache License, Version 2.0 (the "License");
+~ you may not use this file except in compliance with the License.
+~ You may obtain a copy of the License at
+~
+~     http://www.apache.org/licenses/LICENSE-2.0
+~
+~ Unless required by applicable law or agreed to in writing, software
+~ distributed under the License is distributed on an "AS IS" BASIS,
+~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+~ See the License for the specific language governing permissions and
+~ limitations under the License.
+-->
+<svg:line xmlns:svg="http://www.w3.org/2000/svg"
+        [attr.x1]="(link && link.source) ? link.source.x : 0"
+        [attr.y1]="(link && link.source) ? link.source.y : 0"
+        [attr.x2]="(link && link.target) ? link.target.x : 0"
+        [attr.y2]="(link && link.target) ? link.target.y : 0"
+          [ngStyle]="{'stroke':'black'}"
+>
+    <!--<svg:line *ngFor="let link of regionData.links"-->
+    <!--x1="0" y1="0" x2="0" y2="0"-->
+    <!--stroke="#939598" stroke-width="1"-->
+    <!--[ngClass]="['link', 'direct']"-->
+    <!--[ngStyle]="{'stroke-width': 1+'px'}" [attr.click()]="selectLink(link)" />-->
+</svg:line>
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/linkvisual.component.spec.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/linkvisual.component.spec.ts
new file mode 100644
index 0000000..c060aab
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/linkvisual.component.spec.ts
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the 'License');
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an 'AS IS' BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { LinkVisualComponent } from './linkvisual.component';
+
+describe('LinkVisualComponent', () => {
+    let component: LinkVisualComponent;
+    let fixture: ComponentFixture<LinkVisualComponent>;
+
+    beforeEach(async(() => {
+        TestBed.configureTestingModule({
+          declarations: [ LinkVisualComponent ]
+        })
+        .compileComponents();
+    }));
+
+    beforeEach(() => {
+        fixture = TestBed.createComponent(LinkVisualComponent);
+        component = fixture.componentInstance;
+        fixture.detectChanges();
+    });
+
+    it('should create', () => {
+        expect(component).toBeTruthy();
+    });
+});
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/linkvisual.component.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/linkvisual.component.ts
new file mode 100644
index 0000000..edde2aa
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/linkvisual.component.ts
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the 'License');
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an 'AS IS' BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {
+    Component,
+    Input,
+} from '@angular/core';
+import { Link } from '../models';
+import {LogService} from 'gui2-fw-lib';
+
+@Component({
+    selector: '[onos-linkvisual]',
+    templateUrl: './linkvisual.component.html',
+    styleUrls: ['./linkvisual.component.css']
+})
+export class LinkVisualComponent {
+    @Input() link: Link;
+
+    constructor(
+        protected log: LogService
+    ) {
+    }
+
+}
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/subregionnodesvg/subregionnodesvg.component.css b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/subregionnodesvg/subregionnodesvg.component.css
new file mode 100644
index 0000000..87c23bd
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/subregionnodesvg/subregionnodesvg.component.css
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+/*
+ ONOS GUI -- Topology View (forces subRegion visual) -- CSS file
+ */
\ No newline at end of file
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/subregionnodesvg/subregionnodesvg.component.html b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/subregionnodesvg/subregionnodesvg.component.html
new file mode 100644
index 0000000..5760634
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/subregionnodesvg/subregionnodesvg.component.html
@@ -0,0 +1,23 @@
+<!--
+~ Copyright 2018-present Open Networking Foundation
+~
+~ Licensed under the Apache License, Version 2.0 (the "License");
+~ you may not use this file except in compliance with the License.
+~ You may obtain a copy of the License at
+~
+~     http://www.apache.org/licenses/LICENSE-2.0
+~
+~ Unless required by applicable law or agreed to in writing, software
+~ distributed under the License is distributed on an "AS IS" BASIS,
+~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+~ See the License for the specific language governing permissions and
+~ limitations under the License.
+-->
+<svg:g  xmlns:svg="http://www.w3.org/2000/svg" [attr.transform]="'translate(' + subRegion?.x + ',' + subRegion?.y + ')'">>
+  <svg:circle
+          cx="0"
+          cy="0"
+          r="5">
+  </svg:circle>
+  <svg:text>{{subRegion?.id}}</svg:text>
+</svg:g>
\ No newline at end of file
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/subregionnodesvg/subregionnodesvg.component.spec.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/subregionnodesvg/subregionnodesvg.component.spec.ts
new file mode 100644
index 0000000..d6f6446
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/subregionnodesvg/subregionnodesvg.component.spec.ts
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { SubRegionNodeSvgComponent } from './subregionnodesvg.component';
+import {SubRegion} from '../../models';
+
+describe('SubRegionNodeSvgComponent', () => {
+    let component: SubRegionNodeSvgComponent;
+    let fixture: ComponentFixture<SubRegionNodeSvgComponent>;
+
+    beforeEach(async(() => {
+        TestBed.configureTestingModule({
+            declarations: [ SubRegionNodeSvgComponent ]
+        })
+        .compileComponents();
+    }));
+
+    beforeEach(() => {
+        fixture = TestBed.createComponent(SubRegionNodeSvgComponent);
+        component = fixture.debugElement.componentInstance;
+        fixture.detectChanges();
+    });
+
+    it('should create', () => {
+        expect(component).toBeTruthy();
+    });
+
+    it('should create with an input', () => {
+        component.subRegion = new SubRegion('testId');
+        expect(component).toBeTruthy();
+    });
+});
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/subregionnodesvg/subregionnodesvg.component.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/subregionnodesvg/subregionnodesvg.component.ts
new file mode 100644
index 0000000..5dd4736
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/subregionnodesvg/subregionnodesvg.component.ts
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the 'License');
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an 'AS IS' BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {Component, Input, OnChanges, SimpleChanges} from '@angular/core';
+import {SubRegion} from '../../models';
+
+/**
+ * The SubRegion node in the force graph
+ *
+ * Note 1: here the selector is given square brackets [] so that it can be
+ * inserted in SVG element like a directive
+ * Note 2: the selector is exactly the same as the @Input alias to make this
+ * directive trick work
+ */
+@Component({
+    selector: '[onos-subregionnodesvg]',
+    templateUrl: './subregionnodesvg.component.html',
+    styleUrls: ['./subregionnodesvg.component.css']
+})
+export class SubRegionNodeSvgComponent implements OnChanges {
+    @Input() subRegion: SubRegion;
+
+    ngOnChanges(changes: SimpleChanges) {
+        if (!this.subRegion.x) {
+            this.subRegion.x = 0;
+            this.subRegion.y = 0;
+        }
+    }
+
+}
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/mapsvg/mapsvg.component.html b/web/gui2/src/main/webapp/app/view/topology/layer/mapsvg/mapsvg.component.html
index 4f3b14f..157a87b 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/mapsvg/mapsvg.component.html
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/mapsvg/mapsvg.component.html
@@ -13,5 +13,5 @@
 ~ See the License for the specific language governing permissions and
 ~ limitations under the License.
 -->
-<svg:text>Map of Croatia</svg:text>
-<svg:path d="m 187.50819,219.30177 -3.14986,-1.57795 z M 45.764161,56.635237 48.914029,55.022914 Z m 62.997349,-32.313055 6.29973,4.85593 6.29974,11.31816 v 11.300949 h 9.4496 l 12.59947,8.061616 6.29973,12.880483 v 8.039036 l 6.29974,14.448536 -3.14987,9.616908 -6.29973,12.80342 v 14.37787 l 3.14986,7.97587 -9.4496,3.18799 3.14987,9.55592 6.29973,4.77344 v 7.94906 l -6.29973,1.58881 3.14987,15.86987 6.29973,6.33869 v 6.33341 l 6.29974,1.58253 v 20.54314 h 6.29973 18.89921 l 3.14986,4.73289 v 1.57699 6.30469 l -3.14986,4.72511 6.29973,6.29564 v 1.57311 l 3.14987,7.8607 -9.4496,17.26536 h -9.44961 l -6.29973,4.70203 v 14.08899 l -6.29974,3.12741 v 15.61816 l -3.14986,6.23849 -22.04907,3.11737 -3.14987,7.78797 h -6.29974 l 9.44961,17.10623 -3.14987,6.21118 -6.29974,-1.55233 v 10.85988 l -6.29973,7.74786 -18.89921,-6.19768 v -20.17635 l -6.299731,-3.10867 v -6.22107 l -6.299735,-10.8988 -9.449602,-3.11675 v -4.67746 l -9.449602,-1.55978 -6.299735,-6.24224 -9.449602,-3.123 -9.449602,-12.5046 -3.149867,-14.09183 12.599469,12.52734 3.149868,-12.52734 -9.449602,-9.40886 6.299734,1.56894 v -7.84788 l -6.299734,1.57021 -6.299735,-9.4261 3.149867,-4.71738 V 228.7627 l 3.149868,-9.46093 6.299734,-3.15624 v -31.63431 l 6.299735,-7.92914 -3.149867,-6.34927 H 52.063896 V 155.9275 l 9.449602,-9.5519 -9.449602,-7.96914 h 12.599469 v -27.15822 l -12.599469,-6.40444 -6.299735,1.60162 V 82.385126 l 3.149868,-3.213886 6.299734,1.607116 3.149868,-8.039036 -12.59947,-9.658277 6.299735,-9.6708 -3.149867,-1.613022 6.299734,-11.300949 6.299735,-4.848528 v -6.469632 l 6.299735,-3.236933 v -8.098523 l 9.449602,-4.863368 3.149867,3.2426 v 14.574161 l 6.299735,3.234816 12.59947,-3.234816 z"></svg:path>
\ No newline at end of file
+<svg:text xmlns:svg="http://www.w3.org/2000/svg">Map of Croatia</svg:text>
+<svg:path xmlns:svg="http://www.w3.org/2000/svg" d="m 187.50819,219.30177 -3.14986,-1.57795 z M 45.764161,56.635237 48.914029,55.022914 Z m 62.997349,-32.313055 6.29973,4.85593 6.29974,11.31816 v 11.300949 h 9.4496 l 12.59947,8.061616 6.29973,12.880483 v 8.039036 l 6.29974,14.448536 -3.14987,9.616908 -6.29973,12.80342 v 14.37787 l 3.14986,7.97587 -9.4496,3.18799 3.14987,9.55592 6.29973,4.77344 v 7.94906 l -6.29973,1.58881 3.14987,15.86987 6.29973,6.33869 v 6.33341 l 6.29974,1.58253 v 20.54314 h 6.29973 18.89921 l 3.14986,4.73289 v 1.57699 6.30469 l -3.14986,4.72511 6.29973,6.29564 v 1.57311 l 3.14987,7.8607 -9.4496,17.26536 h -9.44961 l -6.29973,4.70203 v 14.08899 l -6.29974,3.12741 v 15.61816 l -3.14986,6.23849 -22.04907,3.11737 -3.14987,7.78797 h -6.29974 l 9.44961,17.10623 -3.14987,6.21118 -6.29974,-1.55233 v 10.85988 l -6.29973,7.74786 -18.89921,-6.19768 v -20.17635 l -6.299731,-3.10867 v -6.22107 l -6.299735,-10.8988 -9.449602,-3.11675 v -4.67746 l -9.449602,-1.55978 -6.299735,-6.24224 -9.449602,-3.123 -9.449602,-12.5046 -3.149867,-14.09183 12.599469,12.52734 3.149868,-12.52734 -9.449602,-9.40886 6.299734,1.56894 v -7.84788 l -6.299734,1.57021 -6.299735,-9.4261 3.149867,-4.71738 V 228.7627 l 3.149868,-9.46093 6.299734,-3.15624 v -31.63431 l 6.299735,-7.92914 -3.149867,-6.34927 H 52.063896 V 155.9275 l 9.449602,-9.5519 -9.449602,-7.96914 h 12.599469 v -27.15822 l -12.599469,-6.40444 -6.299735,1.60162 V 82.385126 l 3.149868,-3.213886 6.299734,1.607116 3.149868,-8.039036 -12.59947,-9.658277 6.299735,-9.6708 -3.149867,-1.613022 6.299734,-11.300949 6.299735,-4.848528 v -6.469632 l 6.299735,-3.236933 v -8.098523 l 9.449602,-4.863368 3.149867,3.2426 v 14.574161 l 6.299735,3.234816 12.59947,-3.234816 z"></svg:path>
\ No newline at end of file
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/nodeviceconnectedsvg/nodeviceconnectedsvg.component.html b/web/gui2/src/main/webapp/app/view/topology/layer/nodeviceconnectedsvg/nodeviceconnectedsvg.component.html
index d201c8a..9233713 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/nodeviceconnectedsvg/nodeviceconnectedsvg.component.html
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/nodeviceconnectedsvg/nodeviceconnectedsvg.component.html
@@ -13,7 +13,7 @@
 ~ See the License for the specific language governing permissions and
 ~ limitations under the License.
 -->
-<svg:g id="topo-noDevsLayer" transform="translate(500,500)" style="visibility: visible;">
+<svg:g xmlns:svg="http://www.w3.org/2000/svg" id="topo-noDevsLayer" transform="translate(500,500)" style="visibility: visible;">
     <svg:g id="reposition" #reposition [attr.transform]="centre(reposition.getBBox())">
         <svg:use width="100" height="100" class="noDevsBird" href="#bird"></svg:use>
         <svg:text x="120" y="80">No Devices Are Connected</svg:text>
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/zoomable.directive.spec.ts b/web/gui2/src/main/webapp/app/view/topology/layer/zoomable.directive.spec.ts
new file mode 100644
index 0000000..f298bf5
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/zoomable.directive.spec.ts
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { ZoomableDirective } from './zoomable.directive';
+import {inject, TestBed} from '@angular/core/testing';
+import {LogService, ConsoleLoggerService} from 'gui2-fw-lib';
+import {ElementRef} from '@angular/core';
+
+describe('ZoomableDirective', () => {
+    let log: LogService;
+    let mockWindow: Window;
+
+    beforeEach(() => {
+        log = new ConsoleLoggerService();
+
+        mockWindow = <any>{
+            navigator: {
+                userAgent: 'HeadlessChrome',
+                vendor: 'Google Inc.'
+            }
+        };
+
+        TestBed.configureTestingModule({
+            providers: [ZoomableDirective,
+                {provide: LogService, useValue: log},
+                { provide: ElementRef, useValue: mockWindow }
+            ]
+        });
+    });
+
+    it('should create an instance', inject([ZoomableDirective], (directive: ZoomableDirective) => {
+
+        expect(directive).toBeTruthy();
+    }));
+});
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/zoomable.directive.ts b/web/gui2/src/main/webapp/app/view/topology/layer/zoomable.directive.ts
new file mode 100644
index 0000000..c956627
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/zoomable.directive.ts
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {Directive, ElementRef, Input, OnChanges} from '@angular/core';
+import {LogService} from 'gui2-fw-lib';
+import * as d3 from 'd3';
+
+@Directive({
+  selector: '[onosZoomableOf]'
+})
+export class ZoomableDirective implements OnChanges {
+    @Input() zoomableOf: ElementRef;
+
+    constructor(
+        private _element: ElementRef,
+        private log: LogService,
+    ) {}
+
+    ngOnChanges() {
+        let zoomed, zoom;
+
+        const svg = d3.select(this.zoomableOf);
+        const container = d3.select(this._element.nativeElement);
+
+        zoomed = () => {
+            const transform = d3.event.transform;
+            container.attr('transform', 'translate(' + transform.x + ',' + transform.y + ') scale(' + transform.k + ')');
+        };
+
+        zoom = d3.zoom().on('zoom', zoomed);
+        svg.call(zoom);
+        this.log.debug('Applying zoomable behaviour on', this.zoomableOf, this._element.nativeElement);
+    }
+
+}
diff --git a/web/gui2/src/main/webapp/app/view/topology/panel/details/details.component.html b/web/gui2/src/main/webapp/app/view/topology/panel/details/details.component.html
index af8f6a0..d79954b 100644
--- a/web/gui2/src/main/webapp/app/view/topology/panel/details/details.component.html
+++ b/web/gui2/src/main/webapp/app/view/topology/panel/details/details.component.html
@@ -21,14 +21,14 @@
                 <use width="26" height="26" class="glyph" xlink:href="#m_switch"></use>
             </svg>
         </div>
-        <h2 class="clickable">rest:10.1.2.2:443</h2>
+        <h2 class="clickable">{{ selectedNode?.id }}</h2>
     </div>
     <div class="body">
         <table>
             <tbody>
             <tr>
                 <td class="label">URI :</td>
-                <td class="value">null:0000000000000002</td>
+                <td class="value">{{ selectedNode?.id }}</td>
             </tr>
             <tr>
                 <td class="label">Vendor :</td>
diff --git a/web/gui2/src/main/webapp/app/view/topology/panel/details/details.component.ts b/web/gui2/src/main/webapp/app/view/topology/panel/details/details.component.ts
index 0fcf179..935b2ce 100644
--- a/web/gui2/src/main/webapp/app/view/topology/panel/details/details.component.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/panel/details/details.component.ts
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import { Component, OnInit } from '@angular/core';
+import {Component, Input, OnInit} from '@angular/core';
 import { animate, state, style, transition, trigger } from '@angular/animations';
 import {
     LogService,
@@ -22,6 +22,7 @@
     DetailsPanelBaseImpl,
     WebSocketService
 } from 'gui2-fw-lib';
+import {Node} from '../../layer/forcesvg/models';
 
 /*
  ONOS GUI -- Topology Details Panel.
@@ -51,6 +52,7 @@
     ]
 })
 export class DetailsComponent extends DetailsPanelBaseImpl implements OnInit {
+    @Input() selectedNode: Node = undefined;
 
     constructor(
         protected fs: FnService,
diff --git a/web/gui2/src/main/webapp/app/view/topology/panel/instance/instance.component.html b/web/gui2/src/main/webapp/app/view/topology/panel/instance/instance.component.html
index f856213..0cb4a0b 100644
--- a/web/gui2/src/main/webapp/app/view/topology/panel/instance/instance.component.html
+++ b/web/gui2/src/main/webapp/app/view/topology/panel/instance/instance.component.html
@@ -17,7 +17,7 @@
     <div *ngFor="let inst of onosInstances | keyvalue ; let i=index"
          [ngClass]="['onosInst', inst.value.online?'online':'', inst.value.ready? 'ready': '', mastership?'mastership':'', 'affinity']"
             (click)="chooseMastership(inst.value.id)">
-        <svg width="170" height="85" viewBox="0 0 170 85">
+        <svg xmlns="http://www.w3.org/2000/svg" width="170" height="85" viewBox="0 0 170 85">
             <!-- The following blue-glow effect is applied (through CSS) when mastership style is activated on a rectangle -->
             <filter x="-50%" y="-50%" width="200%" height="200%" id="blue-glow">
                 <feColorMatrix type="matrix" values="0 0 0 0  0 0 0 0 0  0 0 0 0 0  0.7 0 0 0 1  0 "></feColorMatrix>
diff --git a/web/gui2/src/main/webapp/app/view/topology/panel/summary/summary.component.css b/web/gui2/src/main/webapp/app/view/topology/panel/summary/summary.component.css
index 56f0cc9..894b815 100644
--- a/web/gui2/src/main/webapp/app/view/topology/panel/summary/summary.component.css
+++ b/web/gui2/src/main/webapp/app/view/topology/panel/summary/summary.component.css
@@ -20,6 +20,7 @@
  */
 #topo2-p-summary {
     padding: 16px;
+    top: 100px;
 }
 
 #topo2-p-summary  td.label {
diff --git a/web/gui2/src/main/webapp/app/view/topology/topology.module.ts b/web/gui2/src/main/webapp/app/view/topology/topology.module.ts
index a954827..d4d2bbf 100644
--- a/web/gui2/src/main/webapp/app/view/topology/topology.module.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/topology.module.ts
@@ -28,6 +28,14 @@
 import { ForceSvgComponent } from './layer/forcesvg/forcesvg.component';
 import { MapSvgComponent } from './layer/mapsvg/mapsvg.component';
 import { TopologyService } from './topology.service';
+import { DraggableDirective } from './layer/forcesvg/draggable/draggable.directive';
+import { ZoomableDirective } from './layer/zoomable.directive';
+import {
+    LinkVisualComponent,
+    SubRegionNodeSvgComponent,
+    DeviceNodeSvgComponent,
+    HostNodeSvgComponent,
+} from './layer/forcesvg/visuals';
 
 /**
  * ONOS GUI -- Topology View Module
@@ -51,7 +59,13 @@
         DetailsComponent,
         BackgroundSvgComponent,
         ForceSvgComponent,
-        MapSvgComponent
+        MapSvgComponent,
+        ZoomableDirective,
+        DraggableDirective,
+        LinkVisualComponent,
+        DeviceNodeSvgComponent,
+        HostNodeSvgComponent,
+        SubRegionNodeSvgComponent
     ],
     providers: [
         TopologyService
diff --git a/web/gui2/src/main/webapp/app/view/topology/topology.service.ts b/web/gui2/src/main/webapp/app/view/topology/topology.service.ts
index 1d85a08..3572b7c 100644
--- a/web/gui2/src/main/webapp/app/view/topology/topology.service.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/topology.service.ts
@@ -13,13 +13,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import { Injectable } from '@angular/core';
+import {Injectable, SimpleChanges, SimpleChange} from '@angular/core';
 import {
     LogService, WebSocketService,
 } from 'gui2-fw-lib';
 import { InstanceComponent } from './panel/instance/instance.component';
 import { BackgroundSvgComponent } from './layer/backgroundsvg/backgroundsvg.component';
 import { ForceSvgComponent } from './layer/forcesvg/forcesvg.component';
+import {Region} from './layer/forcesvg/models';
 
 /**
  * ONOS GUI -- Topology Service Module.
@@ -54,8 +55,11 @@
                 }
             ],
             ['topo2CurrentRegion', (data) => {
-                    this.log.warn('Add fn for topo2CurrentRegion callback', data);
                     force.regionData = data;
+                    force.ngOnChanges({
+                        'regionData' : new SimpleChange(<Region>{}, data, true)
+                    });
+                    this.log.warn('Add fn for topo2CurrentRegion callback', force.regionData);
                 }
             ],
             ['topo2PeerRegions', (data) => { this.log.warn('Add fn for topo2PeerRegions callback', data); } ],
diff --git a/web/gui2/src/main/webapp/app/view/topology/topology/topology.component.html b/web/gui2/src/main/webapp/app/view/topology/topology/topology.component.html
index d74991c..d72a2e0 100644
--- a/web/gui2/src/main/webapp/app/view/topology/topology/topology.component.html
+++ b/web/gui2/src/main/webapp/app/view/topology/topology/topology.component.html
@@ -21,13 +21,13 @@
 <onos-details #details></onos-details>
 
 <div id="ov-topo2">
-    <svg viewBox="0 0 1000 1000" id="topo2">
+    <svg:svg #svgZoom xmlns:svg="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000" id="topo2">
         <svg:g onos-nodeviceconnected />
-        <svg:g id="topo-zoomlayer">
+        <svg:g id="topo-zoomlayer" onosZoomableOf [zoomableOf]="svgZoom">
             <svg:g #background onos-backgroundsvg/>
-            <svg:g #force onos-forcesvg/>
+            <svg:g #force onos-forcesvg (selectedNodeEvent)="nodeSelected($event)"/>
         </svg:g>
-    </svg>
+    </svg:svg>
 </div>
 
 <div id="breadcrumbs"></div>
\ No newline at end of file
diff --git a/web/gui2/src/main/webapp/app/view/topology/topology/topology.component.spec.ts b/web/gui2/src/main/webapp/app/view/topology/topology/topology.component.spec.ts
index d98a55c..a9c6194 100644
--- a/web/gui2/src/main/webapp/app/view/topology/topology/topology.component.spec.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/topology/topology.component.spec.ts
@@ -34,6 +34,7 @@
     FnService,
     LogService
 } from 'gui2-fw-lib';
+import {ZoomableDirective} from '../layer/zoomable.directive';
 
 
 class MockActivatedRoute extends ActivatedRoute {
@@ -107,14 +108,15 @@
                 SummaryComponent,
                 ToolbarComponent,
                 DetailsComponent,
-                FlashComponent
+                FlashComponent,
+                ZoomableDirective
             ],
             providers: [
                 { provide: FnService, useValue: fs },
                 { provide: LogService, useValue: logSpy },
                 { provide: 'Window', useValue: windowMock },
                 { provide: HttpClient, useClass: MockHttpClient },
-                { provide: TopologyService, useClass: MockTopologyService }
+                { provide: TopologyService, useClass: MockTopologyService },
             ]
         }).compileComponents();
         logServiceSpy = TestBed.get(LogService);
diff --git a/web/gui2/src/main/webapp/app/view/topology/topology/topology.component.ts b/web/gui2/src/main/webapp/app/view/topology/topology/topology.component.ts
index 728347d..d4decb0 100644
--- a/web/gui2/src/main/webapp/app/view/topology/topology/topology.component.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/topology/topology.component.ts
@@ -13,20 +13,32 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-import {Component, OnDestroy, OnInit, ViewChild} from '@angular/core';
+import {
+    Component,
+    OnDestroy,
+    OnInit,
+    ViewChild
+} from '@angular/core';
 import * as d3 from 'd3';
 import {
     FnService,
-    KeysService, KeysToken,
-    LogService, PrefsService,
-    SvgUtilService, WebSocketService, Zoomer, ZoomOpts, ZoomService
+    KeysService,
+    KeysToken,
+    LogService,
+    PrefsService,
+    SvgUtilService,
+    WebSocketService,
+    Zoomer,
+    ZoomOpts,
+    ZoomService
 } from 'gui2-fw-lib';
-import { InstanceComponent } from '../panel/instance/instance.component';
-import { SummaryComponent } from '../panel/summary/summary.component';
-import { DetailsComponent } from '../panel/details/details.component';
-import { BackgroundSvgComponent } from '../layer/backgroundsvg/backgroundsvg.component';
-import { ForceSvgComponent } from '../layer/forcesvg/forcesvg.component';
-import { TopologyService } from '../topology.service';
+import {InstanceComponent} from '../panel/instance/instance.component';
+import {SummaryComponent} from '../panel/summary/summary.component';
+import {DetailsComponent} from '../panel/details/details.component';
+import {BackgroundSvgComponent} from '../layer/backgroundsvg/backgroundsvg.component';
+import {ForceSvgComponent} from '../layer/forcesvg/forcesvg.component';
+import {TopologyService} from '../topology.service';
+import {Node} from '../layer/forcesvg/models';
 
 /**
  * ONOS GUI Topology View
@@ -236,6 +248,7 @@
     protected cycleDeviceLabels() {
         this.log.debug('Cycling device labels');
         // TODO: Reinstate with components
+        this.force.updateDeviceLabelToggle();
         // let deviceLabelIndex = t2ps.get('dlbls') + 1;
         // let newDeviceLabelIndex = deviceLabelIndex % 3;
         //
@@ -262,9 +275,12 @@
     }
 
     protected toggleDetails(token: KeysToken) {
-        this.flashMsg = 'Toggling details';
-        this.details.togglePanel(() => {});
-        this.log.debug('Toggling details', token);
+        if (this.details.selectedNode) {
+            this.flashMsg = 'Toggling details';
+            this.details.togglePanel(() => {
+            });
+            this.log.debug('Toggling details', token);
+        }
     }
 
     protected toggleInstancePanel(token: KeysToken) {
@@ -426,4 +442,9 @@
         this.zoomer.panZoom(translate, scale, transition);
     }
 
+    nodeSelected(node: Node) {
+        this.details.selectedNode = node;
+        this.details.on = Boolean(node);
+    }
+
 }