GUI2 Absolute locations for Devices and Hosts
Change-Id: I172020a19004b559ae740478d30a2cf9ce08091e
diff --git a/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandlerBase.java b/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandlerBase.java
index 3ba641f..9e62410 100644
--- a/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandlerBase.java
+++ b/web/gui/src/main/java/org/onosproject/ui/impl/TopologyViewMessageHandlerBase.java
@@ -66,6 +66,7 @@
import static com.google.common.base.Strings.isNullOrEmpty;
import static org.onosproject.net.AnnotationKeys.DRIVER;
+import static org.onosproject.net.AnnotationKeys.UI_TYPE;
import static org.onosproject.net.PortNumber.portNumber;
import static org.onosproject.net.config.basics.BasicElementConfig.LOC_TYPE_GEO;
import static org.onosproject.net.config.basics.BasicElementConfig.LOC_TYPE_GRID;
@@ -438,7 +439,7 @@
// Create models of the data to return, that overlays can adjust / augment
private String lookupGlyph(Device device) {
- String uiType = device.annotations().value("uiType");
+ String uiType = device.annotations().value(UI_TYPE);
if (uiType != null && !uiType.equalsIgnoreCase("undefined")) {
return uiType;
} else {
diff --git a/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/Topo.properties b/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/Topo.properties
index 8e45d49..58be4ab 100644
--- a/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/Topo.properties
+++ b/web/gui/src/main/resources/org/onosproject/ui/lion/core/view/Topo.properties
@@ -37,6 +37,7 @@
tbtt_cyc_layers=Cycle node layers
tbtt_cyc_dev_labs=Cycle device labels
tbtt_cyc_host_labs=Cycle host labels
+tbtt_cyc_grid_display=Cycle grid display
tbtt_unpin_node=Unpin node (hover mouse over)
tbtt_reset_zoom=Reset pan / zoom
tbtt_tog_toolbar=Toggle Toolbar
@@ -63,6 +64,11 @@
fl_host_labels_show_mac=Show host MAC addresses
fl_host_labels_hide=Hide host labels
+fl_grid_display_hide=Hide grid
+fl_grid_display_1000=Show XY grid
+fl_grid_display_geo=Show Geo grid
+fl_grid_display_both=Show both grids
+
fl_offline_devices=Offline Devices
fl_bad_links=Bad Links
fl_reset_node_locations=Reset Node Locations
diff --git a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/glyphdata.service.ts b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/glyphdata.service.ts
index a3cbb43..be2ddb4 100644
--- a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/glyphdata.service.ts
+++ b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/glyphdata.service.ts
@@ -877,6 +877,20 @@
'M11.6,51.4a1.2,1.2,0,0,0-1.2,1.2V63.2a1.2,1.2,0,0,0,1.2,1.2' +
'H33.8A1.2,1.2,0,0,0,35,63.2V52.6a1.2,1.2,0,0,0-1.2-1.2H11.6Z'],
+ ['m_cycleGridDisplay', 'M 78.5,74.7 A 34.2,34.2 0 0 1 30.8,81.5 ' +
+ 'L 30.5,81.2 30.4,81 v -0.2 a 1.8,1.8 0 0 1 0.8,-2.5 L 31.9,78 26.7,74.6 ' +
+ 'v 6.2 l 0.7,-0.3 a 1.8560711,1.8560711 0 1 1 1.7,3.3 ' +
+ 'l -3.4,1.8 -0.9,0.2 -1,-0.3 A 1.9,1.9 0 0 1 22.9,83.9 V 71.1 ' +
+ 'a 1.8,1.8 0 0 1 2.8,-1.5 l 10.7,7 a 1.9,1.9 0 0 1 0.8,1.6 1.9,1.9 0 0 1 -1,1.5 ' +
+ 'l -0.7,0.4 a 30.5,30.5 0 0 0 40.1,-7.7 1.8506756,1.8506756 0 0 1 2.9,2.3 z ' +
+ 'M 18.500163,7.4998005 V 20.50004 H 2.5001058 v 2.999817 H 18.500163 V 50.499758 ' +
+ 'H 2.5001058 v 3.000335 H 18.500163 v 13.999663 h 2.999816 V 53.500093 h 26.999902 ' +
+ 'v 13.999663 h 3.000333 V 53.500093 h 26.999903 v 13.999663 h 2.999817 V 53.500093 ' +
+ 'H 97.499992 V 50.499758 H 81.499934 V 23.499857 H 97.499992 V 20.50004 H 81.499934 ' +
+ 'V 7.4998005 H 78.500117 V 20.50004 H 51.500214 V 7.4998005 H 48.499881 V 20.50004 ' +
+ 'H 21.499979 V 7.4998005 Z M 21.499979,23.499857 H 48.499881 V 50.499758 H 21.499979 ' +
+ 'Z m 30.000235,0 H 78.500117 V 50.499758 H 51.500214 Z'],
+
['m_prev', 'M59.8,72l-0.9-.2L21.8,51.3a1.8,1.8,0,0,1,0-3.2L58.9,28.2' +
'a1.8,1.8,0,0,1,2.7,1.6V40.7H77.3a1.8,1.8,0,0,1,1.8,1.8V57.3' +
'a1.8,1.8,0,0,1-1.8,1.8h-7a1.8,1.8,0,1,1,0-3.7h5.2v-11H59.8' +
diff --git a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/icon.service.ts b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/icon.service.ts
index f2497f7..51e4f6b 100644
--- a/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/icon.service.ts
+++ b/web/gui2-fw-lib/projects/gui2-fw-lib/src/lib/svg/icon.service.ts
@@ -55,6 +55,7 @@
['m_oblique', 'm_oblique'],
['m_filters', 'm_filters'],
['m_cycleLabels', 'm_cycleLabels'],
+ ['m_cycleGridDisplay', 'm_cycleGridDisplay'],
['m_prev', 'm_prev'],
['m_next', 'm_next'],
['m_flows', 'm_flows'],
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 bb9e638..b6290b6 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,10 +13,17 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<svg:g xmlns:svg="http://www.w3.org/2000/svg" onos-mapsvg [map]="map"/>
-<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>
- <svg:text *ngFor="let crumb of layoutData.crumbs">{{ crumb.id }} {{ crumb.name }}</svg:text>
-</svg:g>
+<!-- The transform here goes from a 0,0 centred grid of -180 to 180 of
+ longitude to -75 to 75 of latitude
+ It is mapped to a 2000x1000 SVG grid with -500,0 at the top left
+ (The SVG viewbox of ONOS is 1000x1000 - for the geo grid we wanted
+ to keep it the same height 1000 representing +75 latitude down to
+ -75 latitude, but double the width. Why 75? There's no city in the
+ world above 70 - Murmansk)
+ The 6.66 represents 1000/150 and the 5.55 represents 2000/360
+ The reason for the difference is that mercator projection widens
+ countries in the northern and southern extremities, and so
+ the map is squashed horizontally slightly here to compensate
+ (with no squashing the width would be 2400)-->
+<svg:g xmlns:svg="http://www.w3.org/2000/svg" onos-mapsvg [map]="map"
+ transform="translate(500,500), scale(5.5555,6.666666)"/>
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/backgroundsvg/backgroundsvg.component.spec.ts b/web/gui2/src/main/webapp/app/view/topology/layer/backgroundsvg/backgroundsvg.component.spec.ts
index a3eed06..0732018 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/backgroundsvg/backgroundsvg.component.spec.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/backgroundsvg/backgroundsvg.component.spec.ts
@@ -21,6 +21,13 @@
import {HttpClient} from '@angular/common/http';
import {LogService} from 'gui2-fw-lib';
import {MapObject} from '../maputils';
+import {LocMeta} from '../forcesvg/models';
+import {ForceSvgComponent} from '../forcesvg/forcesvg.component';
+import {
+ DeviceNodeSvgComponent,
+ HostNodeSvgComponent, LinkSvgComponent, SubRegionNodeSvgComponent
+} from '../forcesvg/visuals';
+import {DraggableDirective} from '../forcesvg/draggable/draggable.directive';
class MockHttpClient {
get() {
@@ -46,7 +53,13 @@
TestBed.configureTestingModule({
declarations: [
BackgroundSvgComponent,
- MapSvgComponent
+ MapSvgComponent,
+ ForceSvgComponent,
+ DeviceNodeSvgComponent,
+ HostNodeSvgComponent,
+ SubRegionNodeSvgComponent,
+ LinkSvgComponent,
+ DraggableDirective
],
providers: [
{ provide: LogService, useValue: logSpy },
@@ -68,4 +81,33 @@
it('should create', () => {
expect(component).toBeTruthy();
});
+
+ it('should convert latlong to xy', () => {
+ const result = BackgroundSvgComponent.convertGeoToCanvas(<LocMeta>{lat: 52, lng: -8});
+ expect(Math.round(result.x * 100)).toEqual(45556);
+ expect(Math.round(result.y * 100)).toEqual(15333);
+ });
+
+ /**
+ * For some reason including the following causes "ForceSvgComponent should create error
+ * TODO: Investigate
+ */
+
+ // it('should convert xy random extents to latlong', () => {
+ // const result = BackgroundSvgComponent.convertXYtoGeo(455.556, 153.33);
+ // expect(Math.round(result.equivLoc.lng)).toEqual(-8);
+ // expect(Math.round(result.equivLoc.lat)).toEqual(52);
+ // });
+
+ // it('should convert xy min extents to latlong', () => {
+ // const result = BackgroundSvgComponent.convertXYtoGeo(-500, 0);
+ // expect(Math.round(result.equivLoc.lng)).toEqual(-180);
+ // expect(Math.round(result.equivLoc.lat)).toEqual(75);
+ // });
+
+ // it('should convert xy full extents to latlong', () => {
+ // const result = BackgroundSvgComponent.convertXYtoGeo(1500, 1000);
+ // expect(Math.round(result.equivLoc.lng)).toEqual(180);
+ // expect(Math.round(result.equivLoc.lat)).toEqual(-75);
+ // });
});
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 5536784..cc41fb6 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
@@ -15,6 +15,7 @@
*/
import {Component, Input, OnInit} from '@angular/core';
import {MapObject} from '../maputils';
+import {LocMeta, MetaUi} from '../forcesvg/models';
/**
* model of the topo2CurrentLayout attrs from BgZoom below
@@ -68,6 +69,9 @@
regionName: string;
}
+const LONGITUDE_EXTENT = 180;
+const LATITUDE_EXTENT = 75;
+
/**
* ONOS GUI -- Topology Background Layer View.
*/
@@ -81,6 +85,32 @@
layoutData: Layout = <Layout>{};
+ static convertGeoToCanvas(location: LocMeta): MetaUi {
+ const calcX = (LONGITUDE_EXTENT + location.lng) / ( LONGITUDE_EXTENT * 2 ) * 2000 - 500;
+ const calcY = (LATITUDE_EXTENT - location.lat) / ( LATITUDE_EXTENT * 2 ) * 1000;
+ return <MetaUi>{
+ x: calcX,
+ y: calcY,
+ equivLoc: {
+ lat: location.lat,
+ lng: location.lng
+ }
+ };
+ }
+
+ static convertXYtoGeo(x: number, y: number): MetaUi {
+ const calcLong: number = (x + 500) * 2 * LONGITUDE_EXTENT / 2000 - LONGITUDE_EXTENT;
+ const calcLat: number = -(y * 2 * LATITUDE_EXTENT / 1000 - LATITUDE_EXTENT);
+ return <MetaUi>{
+ x: x,
+ y: y,
+ equivLoc: <LocMeta>{
+ lat: calcLat,
+ lng: calcLong
+ }
+ };
+ }
+
constructor() { }
ngOnInit() {
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
index bb6b4d0..94c61db 100644
--- 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
@@ -16,15 +16,18 @@
import { DraggableDirective } from './draggable.directive';
import {inject, TestBed} from '@angular/core/testing';
import {ElementRef} from '@angular/core';
+import {LogService} from 'gui2-fw-lib';
export class MockElementRef extends ElementRef {
nativeElement = {};
}
describe('DraggableDirective', () => {
+ let logServiceSpy: jasmine.SpyObj<LogService>;
let mockWindow: Window;
beforeEach(() => {
+ const logSpy = jasmine.createSpyObj('LogService', ['info', 'debug', 'warn', 'error']);
mockWindow = <any>{
navigator: {
userAgent: 'HeadlessChrome',
@@ -34,10 +37,12 @@
TestBed.configureTestingModule({
providers: [DraggableDirective,
+ { provide: LogService, useValue: logSpy },
{ provide: 'Window', useFactory: (() => mockWindow ) },
{ provide: ElementRef, useValue: mockWindow }
]
});
+ logServiceSpy = TestBed.get(LogService);
});
it('should create an instance', inject([DraggableDirective], (directive: DraggableDirective) => {
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
index 88faa37..ef99728 100644
--- 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
@@ -13,9 +13,17 @@
* 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 {
+ Directive,
+ ElementRef,
+ EventEmitter,
+ Input,
+ OnChanges, Output
+} from '@angular/core';
+import {ForceDirectedGraph, LocMeta, MetaUi, Node} from '../models';
import * as d3 from 'd3';
+import {LogService} from 'gui2-fw-lib';
+import {BackgroundSvgComponent} from '../../backgroundsvg/backgroundsvg.component';
@Directive({
selector: '[onosDraggableNode]'
@@ -23,23 +31,27 @@
export class DraggableDirective implements OnChanges {
@Input() draggableNode: Node;
@Input() draggableInGraph: ForceDirectedGraph;
+ @Output() newLocation = new EventEmitter<MetaUi>();
constructor(
- private _element: ElementRef
+ private _element: ElementRef,
+ private log: LogService
) {
+ this.log.debug('DraggableDirective constructed');
}
ngOnChanges() {
this.applyDraggableBehaviour(
this._element.nativeElement,
this.draggableNode,
- this.draggableInGraph);
+ this.draggableInGraph,
+ this.newLocation);
}
/**
* A method to bind a draggable behaviour to an svg element
*/
- applyDraggableBehaviour(element, node: Node, graph: ForceDirectedGraph) {
+ applyDraggableBehaviour(element, node: Node, graph: ForceDirectedGraph, newLocation: EventEmitter<MetaUi>) {
const d3element = d3.select(element);
function started() {
@@ -50,7 +62,7 @@
graph.simulation.alphaTarget(0.3).restart();
}
- d3.event.on('drag', dragged).on('end', ended);
+ d3.event.on('drag', () => dragged()).on('end', () => ended());
function dragged() {
node.fx = d3.event.x;
@@ -61,9 +73,10 @@
if (!d3.event.active) {
graph.simulation.alphaTarget(0);
}
+ newLocation.emit(BackgroundSvgComponent.convertXYtoGeo(node.fx, node.fy));
- node.fx = null;
- node.fy = null;
+ // node.fx = null;
+ // node.fy = null;
}
}
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 517c3e7..6bbf85b 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
@@ -41,12 +41,15 @@
line 3) Use the onosDraggable directive and pass this device in to
its draggableNode Input parameter and setting the draggableInGraph
Input parameter to 'graph'
- line 4) when the onos-devicenodesvg component emits the selectedEvent
+ line 4) event handler of the draggable directive - causes the new location
+ to be written back to the server
+ line 5) when the onos-devicenodesvg component emits the selectedEvent
call the updateSelected() method of this (forcesvg) component
-->
<svg:g onos-devicenodesvg [device]="device"
*ngFor="let device of regionData.devices[visibleLayerIdx()]"
onosDraggableNode [draggableNode]="device" [draggableInGraph]="graph"
+ (newLocation)="nodeMoved('device', device.id, $event)"
(selectedEvent)="updateSelected($event)"
[labelToggle]="deviceLabelToggle">
<svg:desc>Device nodes</svg:desc>
@@ -62,12 +65,15 @@
line 3) Use the onosDraggable directive and pass this host in to
its draggableNode Input parameter and setting the draggableInGraph
Input parameter to 'graph'
- line 4) when the onos-hostnodesvg component emits the selectedEvent
+ line 4) event handler of the draggable directive - causes the new location
+ to be written back to the server
+ line 5) when the onos-hostnodesvg component emits the selectedEvent
call the updateSelected() method of this (forcesvg) component
-->
<svg:g onos-hostnodesvg [host]="host"
*ngFor="let host of regionData.hosts[visibleLayerIdx()]"
onosDraggableNode [draggableNode]="host" [draggableInGraph]="graph"
+ (newLocation)="nodeMoved('host', device.id, $event)"
(selectedEvent)="updateSelected($event)"
[labelToggle]="hostLabelToggle">
<svg:desc>Host nodes</svg:desc>
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 7a30d75..a834e73 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,7 +16,7 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ForceSvgComponent } from './forcesvg.component';
-import {LogService} from 'gui2-fw-lib';
+import {FnService, LogService} from 'gui2-fw-lib';
import {
DeviceNodeSvgComponent,
HostNodeSvgComponent, LinkSvgComponent,
@@ -25,6 +25,7 @@
import {DraggableDirective} from './draggable/draggable.directive';
import {ActivatedRoute, Params} from '@angular/router';
import {of} from 'rxjs';
+import {MapSvgComponent} from '../mapsvg/mapsvg.component';
class MockActivatedRoute extends ActivatedRoute {
constructor(params: Params) {
@@ -34,6 +35,7 @@
}
describe('ForceSvgComponent', () => {
+ let fs: FnService;
let ar: MockActivatedRoute;
let windowMock: Window;
let logServiceSpy: jasmine.SpyObj<LogService>;
@@ -56,6 +58,8 @@
}
};
+ fs = new FnService(ar, logSpy, windowMock);
+
TestBed.configureTestingModule({
declarations: [
ForceSvgComponent,
@@ -63,10 +67,13 @@
HostNodeSvgComponent,
SubRegionNodeSvgComponent,
LinkSvgComponent,
- DraggableDirective
+ DraggableDirective,
+ MapSvgComponent
],
providers: [
+ { provide: FnService, useValue: fs },
{ provide: LogService, useValue: logSpy },
+ { provide: 'Window', useValue: windowMock },
]
})
.compileComponents();
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 d2a0b18..7b66f36 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
@@ -28,7 +28,7 @@
SimpleChanges,
ViewChildren
} from '@angular/core';
-import {LogService} from 'gui2-fw-lib';
+import {LogService, WebSocketService} from 'gui2-fw-lib';
import {
Device,
ForceDirectedGraph,
@@ -38,6 +38,8 @@
LayerType,
Link,
LinkHighlight,
+ Location, LocMeta,
+ MetaUi,
ModelEventMemo,
ModelEventType,
Region,
@@ -50,6 +52,16 @@
HostNodeSvgComponent,
LinkSvgComponent
} from './visuals';
+import {
+ BackgroundSvgComponent,
+ LocationType
+} from '../backgroundsvg/backgroundsvg.component';
+
+interface UpdateMeta {
+ id: string;
+ class: string;
+ memento: MetaUi;
+}
/**
* ONOS GUI -- Topology Forces Graph Layer View.
@@ -85,7 +97,8 @@
constructor(
protected log: LogService,
- private ref: ChangeDetectorRef
+ private ref: ChangeDetectorRef,
+ protected wss: WebSocketService
) {
this.selectedLink = null;
this.log.debug('ForceSvgComponent constructed');
@@ -172,6 +185,21 @@
this.graph.nodes = this.graph.nodes.concat(subRegions);
}
+ // If a node has a fixed location then assign it to fx and fy so
+ // that it doesn't get affected by forces
+ this.graph.nodes
+ .forEach((n) => {
+ const loc: Location = <Location>n['location'];
+ if (loc && loc.locType === LocationType.GEO) {
+ const position: MetaUi =
+ BackgroundSvgComponent.convertGeoToCanvas(
+ <LocMeta>{lng: loc.longOrX, lat: loc.latOrY});
+ n.fx = position.x;
+ n.fy = position.y;
+ this.log.debug('Found node', n.id, 'with', loc.locType);
+ }
+ });
+
// Associate the endpoints of each link with a real node
this.graph.links = [];
for (const linkIdx of Object.keys(this.regionData.links)) {
@@ -394,5 +422,21 @@
});
}
}
+
+ /**
+ * As nodes are dragged around the graph, their new location should be sent
+ * back to server
+ * @param klass The class of node e.g. 'host' or 'device'
+ * @param id - the ID of the node
+ * @param newLocation - the new Location of the node
+ */
+ nodeMoved(klass: string, id: string, newLocation: MetaUi) {
+ this.wss.sendEvent('updateMeta', <UpdateMeta>{
+ id: id,
+ class: klass,
+ memento: newLocation
+ });
+ this.log.debug(klass, id, 'has been moved to', newLocation);
+ }
}
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/node.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/node.ts
index de70eb4..5f8d8c6 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/node.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/models/node.ts
@@ -79,6 +79,33 @@
}
/**
+ * Toggle state for how the grid should be displayed
+ */
+export enum GridDisplayToggle {
+ GRIDNONE,
+ GRID1000,
+ GRIDGEO,
+ GRIDBOTH
+}
+
+/**
+ * Add the method 'next()' to the GridDisplayToggle enum above
+ */
+export namespace GridDisplayToggle {
+ export function next(current: GridDisplayToggle) {
+ if (current === GridDisplayToggle.GRIDNONE) {
+ return GridDisplayToggle.GRID1000;
+ } else if (current === GridDisplayToggle.GRID1000) {
+ return GridDisplayToggle.GRIDGEO;
+ } else if (current === GridDisplayToggle.GRIDGEO) {
+ return GridDisplayToggle.GRIDBOTH;
+ } else if (current === GridDisplayToggle.GRIDBOTH) {
+ return GridDisplayToggle.GRIDNONE;
+ }
+ }
+}
+
+/**
* model of the topo2CurrentRegion device props from Device below
*/
export interface DeviceProps {
@@ -143,7 +170,7 @@
export class Device extends Node {
id: string;
layer: LayerType;
- location: LocationType;
+ location: Location;
metaUi: MetaUi;
master: string;
online: boolean;
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/gridsvg/gridsvg.component.css b/web/gui2/src/main/webapp/app/view/topology/layer/gridsvg/gridsvg.component.css
new file mode 100644
index 0000000..056a0a0
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/gridsvg/gridsvg.component.css
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2019-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.
+ */
+.gridrect {
+ stroke-width: 1;
+ fill: none;
+}
+
+.gridtext {
+ fill: lightgray;
+ text-anchor: middle;
+ dominant-baseline: middle;
+}
\ No newline at end of file
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/gridsvg/gridsvg.component.html b/web/gui2/src/main/webapp/app/view/topology/layer/gridsvg/gridsvg.component.html
new file mode 100644
index 0000000..c183ac1
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/gridsvg/gridsvg.component.html
@@ -0,0 +1,48 @@
+<!--
+~ Copyright 2019-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 *ngFor="let pt of gridPointsHoriz" xmlns:svg="http://www.w3.org/2000/svg"
+ [attr.transform]="'translate(' + horizCentreOffset + ',' + vertCentreOffset + '), ' +
+ 'scale(' + gridScaleX + ',' + gridScaleY +')'">
+ <svg:desc>Vertical grid lines</svg:desc>
+ <svg:rect id="gridRectVert" class="gridrect"
+ [ngStyle]="{'stroke': gridcolor, 'stroke-width': 1/gridScaleX }"
+ [attr.width]="spacing"
+ [attr.height]="vertUpperLimit-vertLowerLimit"
+ [attr.x]="pt"
+ [attr.y]="vertLowerLimit">
+ </svg:rect>
+ <svg:text id="gridTextVert" class="gridtext"
+ [ngStyle]="{'stroke': gridcolor, 'font-size': 100/gridScaleX+'%', 'stroke-width': 1/gridScaleX }"
+ [attr.x]="pt"
+ [attr.y]="(vertUpperLimit - vertLowerLimit)/2">{{pt}}</svg:text>
+</svg:g>
+
+<svg:g *ngFor="let pt of gridPointsVert" xmlns:svg="http://www.w3.org/2000/svg"
+ [attr.transform]="'translate(' + horizCentreOffset + ',' + vertCentreOffset + '), ' +
+ 'scale(' + gridScaleX + ',' + gridScaleY + ')'">
+ <svg:desc>Horizontal grid lines</svg:desc>
+ <svg:rect id="gridRectHoriz" class="gridrect"
+ [ngStyle]="{'stroke': gridcolor, 'stroke-width': 1/gridScaleY }"
+ [attr.width]="horizUpperLimit-horizLowerLimit"
+ [attr.height]="spacing"
+ [attr.x]="horizLowerLimit"
+ [attr.y]="pt">
+ </svg:rect>
+ <svg:text id="gridTextHoriz" class="gridtext"
+ [ngStyle]="{'stroke': gridcolor, 'font-size': 100/gridScaleY+'%', 'stroke-width': 1/gridScaleY }"
+ [attr.x]="(horizUpperLimit - horizLowerLimit)/2"
+ [attr.y]="invertVertical ? -pt : pt">{{pt}}</svg:text>
+</svg:g>
\ No newline at end of file
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/gridsvg/gridsvg.component.spec.ts b/web/gui2/src/main/webapp/app/view/topology/layer/gridsvg/gridsvg.component.spec.ts
new file mode 100644
index 0000000..48a031a
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/gridsvg/gridsvg.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 { GridsvgComponent } from './gridsvg.component';
+
+describe('GridsvgComponent', () => {
+ let component: GridsvgComponent;
+ let fixture: ComponentFixture<GridsvgComponent>;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ GridsvgComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(GridsvgComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/gridsvg/gridsvg.component.ts b/web/gui2/src/main/webapp/app/view/topology/layer/gridsvg/gridsvg.component.ts
new file mode 100644
index 0000000..e0934be
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/gridsvg/gridsvg.component.ts
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2019-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,
+ OnInit,
+ SimpleChanges
+} from '@angular/core';
+
+/**
+ * How to fit in to the 1000 by 100 SVG viewbox
+ */
+export enum FitOption {
+ FIT1000WIDE = 'fit1000wide',
+ FIT1000HIGH = 'fit1000high',
+ FITNONE = 'fitnone'// 1:1 ratio
+}
+
+const SVG_VIEWBOX_CENTRE = 500; // View box is 0,0,1000,1000
+
+@Component({
+ selector: '[onos-gridsvg]',
+ templateUrl: './gridsvg.component.html',
+ styleUrls: ['./gridsvg.component.css']
+})
+export class GridsvgComponent implements OnInit, OnChanges {
+ @Input() horizLowerLimit: number = 0;
+ @Input() horizUpperLimit: number = 1000;
+ @Input() vertLowerLimit: number = 0;
+ @Input() vertUpperLimit: number = 1000;
+ @Input() spacing: number = 100;
+ @Input() invertVertical: boolean = false;
+ @Input() gridcolor: string = '#e8e7e1'; // If specifying this in a template use [gridcolor]="'#e8e7e1'"
+ @Input() centre: boolean = true;
+ @Input() fit: FitOption = FitOption.FITNONE;
+ @Input() aspectRatio: number = 1.0;
+
+ gridPointsHoriz: number[];
+ gridPointsVert: number[];
+ horizCentreOffset: number = 0;
+ vertCentreOffset: number = 0;
+ gridScaleX: number = 1.0;
+ gridScaleY: number = 1.0;
+
+ public static calculateGridPoints(lwr: number, upper: number, step: number): number[] {
+ const gridPoints = new Array<number>();
+ for (let i = lwr; i < upper; i += step) {
+ gridPoints.push(i);
+ }
+ return gridPoints;
+ }
+
+ public static calcOffset(lwr: number, upper: number): number {
+ return -((upper + lwr) * (upper - lwr) / ((upper - lwr) * 2) - SVG_VIEWBOX_CENTRE);
+ }
+
+ public static calcScale(lwr: number, upper: number): number {
+ return SVG_VIEWBOX_CENTRE * 2 / Math.abs(upper - lwr);
+ }
+
+ constructor() { }
+
+ ngOnInit() {
+ this.gridPointsHoriz = GridsvgComponent.calculateGridPoints(
+ this.horizLowerLimit, this.horizUpperLimit, this.spacing);
+ this.gridPointsVert = GridsvgComponent.calculateGridPoints(
+ this.vertLowerLimit, this.vertUpperLimit, this.spacing);
+ this.horizCentreOffset = GridsvgComponent.calcOffset(this.horizUpperLimit, this.horizLowerLimit);
+ this.vertCentreOffset = GridsvgComponent.calcOffset(this.vertUpperLimit, this.vertLowerLimit);
+ this.gridScaleX = this.whichScale(this.fit, true);
+ this.gridScaleY = this.whichScale(this.fit, false);
+ }
+
+ ngOnChanges(changes: SimpleChanges) {
+ if (changes['horizLowerLimit'] ||
+ changes['horizUpperLimit'] ||
+ changes['horizSpacing']) {
+ this.gridPointsHoriz = GridsvgComponent.calculateGridPoints(
+ this.horizLowerLimit, this.horizUpperLimit, this.spacing);
+ this.horizCentreOffset = GridsvgComponent.calcOffset(this.horizUpperLimit, this.horizLowerLimit);
+ }
+ if (changes['vertLowerLimit'] ||
+ changes['vertUpperLimit'] ||
+ changes['vertSpacing'] ) {
+ this.gridPointsVert = GridsvgComponent.calculateGridPoints(
+ this.vertLowerLimit, this.vertUpperLimit, this.spacing);
+ this.vertCentreOffset = GridsvgComponent.calcOffset(this.vertUpperLimit, this.vertLowerLimit);
+ }
+ }
+
+ whichScale(fit: FitOption, isX: boolean): number {
+ if (fit === FitOption.FIT1000HIGH) {
+ return GridsvgComponent.calcScale(
+ this.vertUpperLimit, this.vertLowerLimit) * (isX ? this.aspectRatio : 1.0);
+ } else if (fit === FitOption.FIT1000WIDE) {
+ return GridsvgComponent.calcScale(
+ this.horizUpperLimit, this.horizLowerLimit) * (isX ? 1.0 : this.aspectRatio);
+ } else {
+ return 1.0;
+ }
+ }
+}
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/mapsvg/mapsvg.component.css b/web/gui2/src/main/webapp/app/view/topology/layer/mapsvg/mapsvg.component.css
index 0dac42a..d35aa7b 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/mapsvg/mapsvg.component.css
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/mapsvg/mapsvg.component.css
@@ -20,7 +20,7 @@
/* --- Topo Map --- */
path.topo-map {
- stroke-width: 2px;
+ stroke-width: 0.05px;
stroke: #f4f4f4;
fill: #e5e5e6;
}
\ No newline at end of file
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 adc0346..cc2a794 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
@@ -15,9 +15,8 @@
-->
<svg:desc xmlns:svg="http://www.w3.org/2000/svg">Map of {{map.id}} in SVG format</svg:desc>
<svg:path class="topo-map"
- *ngFor="let f of mapPathGenerator?.geodata.features"
+ *ngFor="let f of geodata?.features"
xmlns:svg="http://www.w3.org/2000/svg"
[attr.d]="pathGenerator(f)">
- <svg:title>{{ f.id }}</svg:title>
+ <svg:title>{{ f.id }} {{f.properties?.name}}</svg:title>
</svg:path>
-
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/mapsvg/mapsvg.component.ts b/web/gui2/src/main/webapp/app/view/topology/layer/mapsvg/mapsvg.component.ts
index a10cf4c..23dbeb1 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/mapsvg/mapsvg.component.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/mapsvg/mapsvg.component.ts
@@ -46,15 +46,6 @@
}
/**
- * Model of the Path Generator
- */
-interface PathGenerator {
- geodata: FeatureCollection;
- pathgen: (Feature) => string;
- settings: GeneratorSettings;
-}
-
-/**
* Model of the Feature returned prom topojson library
*/
interface Feature {
@@ -83,16 +74,6 @@
transform: TopoDataTransform; // scale and translate
}
-/**
- * Default settings for the path generator for TopoJson
- */
-const DEFAULT_GEN_SETTINGS: GeneratorSettings = <GeneratorSettings>{
- objectTag: 'states',
- projection: d3.geoMercator(),
- logicalSize: 1000,
- mapFillScale: .95,
-};
-
@Component({
selector: '[onos-mapsvg]',
templateUrl: './mapsvg.component.html',
@@ -101,14 +82,33 @@
export class MapSvgComponent implements OnChanges {
@Input() map: MapObject = <MapObject>{id: 'none'};
- cache = new Map<string, TopoData>();
- topodata: TopoData;
- mapPathGenerator: PathGenerator;
+ geodata: FeatureCollection;
+ pathgen: (Feature) => string;
+ // testPath: string;
+ // testFeature = <Feature>{
+ // id: 'test',
+ // type: 'Feature',
+ // geometry: {
+ // coordinates: [
+ // [[-15, 60], [45, 60], [45, 45], [-15, 45], [-15, 60]],
+ // [[-10, 55], [45, 55], [45, 50], [-10, 50], [-10, 55]],
+ // ],
+ // type: 'Polygon'
+ // },
+ // properties: { name: 'Test'}
+ // };
constructor(
private log: LogService,
private httpClient: HttpClient,
) {
+ this.pathgen = d3.geoPath().projection(
+ MapSvgComponent.scale(1, 360, 150));
+
+ // this.log.debug('Feature Test',this.testFeature);
+ // this.testPath = this.pathgen(this.testFeature);
+ // this.log.debug('Feature Path', this.testPath);
+
this.log.debug('MapSvgComponent constructed');
}
@@ -119,24 +119,13 @@
return id + '.topojson';
}
- ngOnChanges(changes: SimpleChanges): void {
- this.log.debug('Change detected', changes);
- if (changes['map']) {
- const map: MapObject = <MapObject>(changes['map'].currentValue);
- if (map.id) {
- if (this.cache.get(map.id)) {
- this.topodata = this.cache.get(map.id);
- } else {
- this.httpClient
- .get(MapSvgComponent.getUrl(map.filePath))
- .subscribe((topoData: TopoData) => {
- this.mapPathGenerator = this.handleTopoJson(map, topoData);
- this.log.debug('Path Generated for', map.id,
- 'from', MapSvgComponent.getUrl(map.filePath));
- });
- }
+ static scale (scaleFactor: number, width: number, height: number) {
+ return d3.geoTransform({
+ point: function(x, y) {
+ this.stream.point( (x - width / 2) * scaleFactor + width / 2,
+ (-y - height / 2) * scaleFactor + height / 2);
}
- }
+ });
}
/**
@@ -144,7 +133,24 @@
* @param feature The county or state within the map
*/
pathGenerator(feature: Feature): string {
- return this.mapPathGenerator.pathgen(feature);
+ return this.pathgen(feature);
+ }
+
+ ngOnChanges(changes: SimpleChanges): void {
+ this.log.debug('Change detected', changes);
+ if (changes['map']) {
+ const map: MapObject = <MapObject>(changes['map'].currentValue);
+ if (map.id) {
+ this.httpClient
+ .get(MapSvgComponent.getUrl(map.filePath))
+ .subscribe((topoData: TopoData) => {
+ // this.mapPathGenerator =
+ this.handleTopoJson(map, topoData);
+ this.log.debug('Path Generated for', map.id,
+ 'from', MapSvgComponent.getUrl(map.filePath));
+ });
+ }
+ }
}
/**
@@ -156,63 +162,15 @@
* @param map The Map chosen in the GUI
* @param topoData The data in the TopoJson file
*/
- handleTopoJson(map: MapObject, topoData: TopoData): PathGenerator {
- this.topodata = topoData;
- this.cache.set(map.id, topoData);
- this.log.debug('Map retrieved', topoData);
+ handleTopoJson(map: MapObject, topoData: TopoData): void {
- const topoObject = topoData.objects[map.id];
- const geoData: FeatureCollection = <FeatureCollection>topojson.feature(topoData, topoObject);
- this.log.debug('Map retrieved', topoData, geoData);
+ let topoObject = topoData.objects[map.id];
+ if (!topoObject) {
+ topoObject = topoData.objects['states'];
+ }
+ this.log.debug('Topo obj', topoObject, 'topodata', topoData);
+ this.geodata = <FeatureCollection>topojson.feature(topoData, topoObject);
+ this.log.debug('Map retrieved', topoData, this.geodata);
- const settings: GeneratorSettings = Object.assign({}, DEFAULT_GEN_SETTINGS);
- const path = d3.geoPath().projection(settings.projection);
- this.rescaleProjection(
- settings.projection,
- settings.mapFillScale,
- settings.logicalSize,
- path,
- geoData);
- this.log.debug('Scale adjusted');
-
- return <PathGenerator>{
- geodata: geoData,
- pathgen: path,
- settings: settings
- };
- }
-
- /**
- * Adjust projection scale and translation to fill the view
- * with the map
- * @param proj
- * @param mfs
- * @param dim
- * @param path
- * @param geoData
- * @param adjustScale
- */
- rescaleProjection(proj: any, mfs: number, dim: number, path: any,
- geoData: FeatureCollection, adjustScale: number = 1.0) {
- // start with unit scale, no translation..
- proj.scale(1).translate([0, 0]);
-
- // figure out dimensions of map data..
- const b = path.bounds(geoData);
- const x1 = b[0][0];
- const y1 = b[0][1];
- const x2 = b[1][0];
- const y2 = b[1][1];
- const dx = x2 - x1;
- const dy = y2 - y1;
- const x = (x1 + x2) / 2;
- const y = (y1 + y2) / 2;
-
- // size map to 95% of minimum dimension to fill space..
- const s = (mfs / Math.min(dx / dim, dy / dim)) * adjustScale;
- const t = [dim / 2 - s * x, dim / 2 - s * y];
-
- // set new scale, translation on the projection..
- proj.scale(s).translate(t);
}
}
diff --git a/web/gui2/src/main/webapp/app/view/topology/panel/toolbar/toolbar.component.html b/web/gui2/src/main/webapp/app/view/topology/panel/toolbar/toolbar.component.html
index fa7fd01..623c425 100644
--- a/web/gui2/src/main/webapp/app/view/topology/panel/toolbar/toolbar.component.html
+++ b/web/gui2/src/main/webapp/app/view/topology/panel/toolbar/toolbar.component.html
@@ -70,5 +70,8 @@
<div class="button" id="toolbar-topo2-toolbar-topo2-quickhelp" (click)="buttonClicked('quickhelp-btn')">
<onos-icon [iconSize]="25" iconId="query" [toolTip]="lionFn('qh_title')" classes="button"></onos-icon>
</div>
+ <div class="button" id="toolbar-topo2-toolbar-topo2-cycleGrid-btn" (click)="buttonClicked('cycleGridDisplay-btn')">
+ <onos-icon [iconSize]="25" iconId="m_cycleGridDisplay" [toolTip]="lionFn('tbtt_cyc_grid_display')" classes="button"></onos-icon>
+ </div>
</div>
</div>
\ No newline at end of file
diff --git a/web/gui2/src/main/webapp/app/view/topology/panel/toolbar/toolbar.component.ts b/web/gui2/src/main/webapp/app/view/topology/panel/toolbar/toolbar.component.ts
index ed9e2ee..0615530 100644
--- a/web/gui2/src/main/webapp/app/view/topology/panel/toolbar/toolbar.component.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/panel/toolbar/toolbar.component.ts
@@ -33,6 +33,7 @@
export const BKGRND_SELECT = 'bkgrnd-sel';
export const CYCLELABELS_BTN = 'cycleLabels-btn';
export const CYCLEHOSTLABEL_BTN = 'cycleHostLabel-btn';
+export const CYCLEGRIDDISPLAY_BTN = 'cycleGridDisplay-btn';
export const RESETZOOM_BTN = 'resetZoom-btn';
export const EQMASTER_BTN = 'eqMaster-btn';
export const CANCEL_TRAFFIC = 'cancel-traffic';
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 b6c79a1..02e39c8 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
@@ -36,6 +36,7 @@
} from './layer/forcesvg/visuals';
import { MapSelectorComponent } from './panel/mapselector/mapselector.component';
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
+import { GridsvgComponent } from './layer/gridsvg/gridsvg.component';
/**
* ONOS GUI -- Topology View Module
@@ -69,7 +70,8 @@
DeviceNodeSvgComponent,
HostNodeSvgComponent,
SubRegionNodeSvgComponent,
- MapSelectorComponent
+ MapSelectorComponent,
+ GridsvgComponent
],
providers: [
TopologyService
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 95d5d9e..b6f511a 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
@@ -55,7 +55,8 @@
whose contents are supplied through the Topology Service, and whose positions
are driven by the d3.force engine
-->
- <svg:svg #svgZoom xmlns:svg="http://www.w3.org/2000/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"
+ preserveAspectRatio="xMaxYMax none">
<svg:desc>The main SVG canvas of the Topology View</svg:desc>
<svg:g *ngIf="force.regionData?.devices[0].length +
force.regionData?.devices[1].length +
@@ -63,6 +64,14 @@
onos-nodeviceconnected />
<svg:g id="topo-zoomlayer" onosZoomableOf [zoomableOf]="svgZoom">
<svg:desc>A logical layer that allows the main SVG canvas to be zoomed and panned</svg:desc>
+ <svg:g #gridFull *ngIf="prefsState.grid == 1 || prefsState.grid == 3" onos-gridsvg>
+ </svg:g>
+ <svg:g #geoGrid *ngIf="prefsState.grid == 2 || prefsState.grid == 3"
+ onos-gridsvg [horizLowerLimit]="-180" [horizUpperLimit]="180"
+ [vertLowerLimit]="-75" [vertUpperLimit]="75" [spacing]="15"
+ [invertVertical]="true" [fit]="'fit1000high'" [aspectRatio]="0.83333"
+ [gridcolor]="'#bfe7fb'">
+ </svg:g>
<svg:g *ngIf="prefsState.bg" onos-backgroundsvg [map]="mapIdState">
<svg:desc>The Background SVG component - contains maps</svg:desc>
</svg:g>
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 0c0a6dd..ed65f5a 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
@@ -49,6 +49,7 @@
import {BackgroundSvgComponent} from '../layer/backgroundsvg/backgroundsvg.component';
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
import {MapSvgComponent} from '../layer/mapsvg/mapsvg.component';
+import {GridsvgComponent} from '../layer/gridsvg/gridsvg.component';
class MockActivatedRoute extends ActivatedRoute {
@@ -119,6 +120,10 @@
this.listeners = this.listeners.filter((obj) => obj !== listener);
}
+ setPrefs(name: string, obj: Object) {
+
+ }
+
}
/**
@@ -185,7 +190,8 @@
SubRegionNodeSvgComponent,
MapSelectorComponent,
BackgroundSvgComponent,
- MapSvgComponent
+ MapSvgComponent,
+ GridsvgComponent
],
providers: [
{ provide: FnService, useValue: fs },
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 750162f..2de3c8b 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
@@ -35,6 +35,7 @@
import {ForceSvgComponent} from '../layer/forcesvg/forcesvg.component';
import {TopologyService} from '../topology.service';
import {
+ GridDisplayToggle,
HostLabelToggle,
LabelToggle,
UiElement
@@ -43,7 +44,7 @@
INSTANCE_TOGGLE, SUMMARY_TOGGLE, DETAILS_TOGGLE,
HOSTS_TOGGLE, OFFLINE_TOGGLE, PORTS_TOGGLE,
BKGRND_TOGGLE, CYCLELABELS_BTN, CYCLEHOSTLABEL_BTN,
- RESETZOOM_BTN, EQMASTER_BTN,
+ CYCLEGRIDDISPLAY_BTN, RESETZOOM_BTN, EQMASTER_BTN,
CANCEL_TRAFFIC, ALL_TRAFFIC, QUICKHELP_BTN, BKGRND_SELECT
} from '../panel/toolbar/toolbar.component';
import {TrafficService} from '../traffic.service';
@@ -57,6 +58,7 @@
const PREF_DETAIL = 'detail';
const PREF_DLBLS = 'dlbls';
const PREF_HLBLS = 'hlbls';
+const PREF_GRID = 'grid';
const PREF_HOSTS = 'hosts';
const PREF_INSTS = 'insts';
const PREF_OFFDEV = 'offdev';
@@ -81,6 +83,7 @@
ovid: string;
summary: number;
toolbar: number;
+ grid: number;
}
/**
@@ -135,6 +138,7 @@
spr: 0,
summary: 1,
toolbar: 0,
+ grid: 0
};
mapIdState: MapObject = <MapObject>{
@@ -144,6 +148,9 @@
mapSelShown: boolean = false;
lionFn; // Function
+ gridShown: boolean = true;
+ geoGridShown: boolean = true;
+
constructor(
protected log: LogService,
protected fs: FnService,
@@ -177,6 +184,7 @@
this.is.loadIconDef('m_map');
this.is.loadIconDef('m_selectMap');
this.is.loadIconDef('m_cycleLabels');
+ this.is.loadIconDef('m_cycleGridDisplay');
this.is.loadIconDef('m_resetZoom');
this.is.loadIconDef('m_eqMaster');
this.is.loadIconDef('m_unknown');
@@ -211,6 +219,15 @@
}
}
+ private static gridDisplayFlashMessage(index: number): string {
+ switch (index) {
+ case 0: return 'fl_grid_display_hide';
+ case 1: return 'fl_grid_display_1000';
+ case 2: return 'fl_grid_display_geo';
+ case 3: return 'fl_grid_display_both';
+ }
+ }
+
/**
* Pass the list of Key Commands to the KeyService, and initialize the Topology
* Service - which communicates with through the WebSocket to the ONOS server
@@ -241,9 +258,6 @@
if (data[TOPO2_PREFS]) {
this.prefsState = data[TOPO2_PREFS];
}
- if (data[TOPO_MAPID_PREFS]) {
- this.mapIdState = data[TOPO_MAPID_PREFS];
- }
this.log.debug('Updated topo2 prefs', this.prefsState, this.mapIdState);
}
@@ -294,6 +308,9 @@
case CYCLEHOSTLABEL_BTN:
this.cycleHostLabels();
break;
+ case CYCLEGRIDDISPLAY_BTN:
+ this.cycleGridDisplay();
+ break;
case RESETZOOM_BTN:
this.resetZoom();
break;
@@ -323,19 +340,20 @@
actionMap() {
return {
A: [() => {this.monitorAllTraffic(); }, 'Monitor all traffic'],
- L: [() => {this.cycleDeviceLabels(); }, 'Cycle device labels'],
B: [(token) => {this.toggleBackground(token); }, 'Toggle background'],
D: [(token) => {this.toggleDetails(token); }, 'Toggle details panel'],
+ E: [() => {this.equalizeMasters(); }, 'Equalize mastership roles'],
+ H: [() => {this.toggleHosts(); }, 'Toggle host visibility'],
I: [(token) => {this.toggleInstancePanel(token); }, 'Toggle ONOS Instance Panel'],
G: [() => {this.mapSelShown = !this.mapSelShown; }, 'Show map selection dialog'],
- O: [() => {this.toggleSummary(); }, 'Toggle the Summary Panel'],
- R: [() => {this.resetZoom(); }, 'Reset pan / zoom'],
- P: [(token) => {this.togglePorts(token); }, 'Toggle Port Highlighting'],
- E: [() => {this.equalizeMasters(); }, 'Equalize mastership roles'],
- X: [() => {this.resetNodeLocation(); }, 'Reset Node Location'],
- U: [() => {this.unpinNode(); }, 'Unpin node (mouse over)'],
- H: [() => {this.toggleHosts(); }, 'Toggle host visibility'],
+ L: [() => {this.cycleDeviceLabels(); }, 'Cycle device labels'],
M: [() => {this.toggleOfflineDevices(); }, 'Toggle offline visibility'],
+ O: [() => {this.toggleSummary(); }, 'Toggle the Summary Panel'],
+ P: [(token) => {this.togglePorts(token); }, 'Toggle Port Highlighting'],
+ Q: [() => {this.cycleGridDisplay(); }, 'Cycle grid display'],
+ R: [() => {this.resetZoom(); }, 'Reset pan / zoom'],
+ U: [() => {this.unpinNode(); }, 'Unpin node (mouse over)'],
+ X: [() => {this.resetNodeLocation(); }, 'Reset Node Location'],
dot: [() => {this.toggleToolbar(); }, 'Toggle Toolbar'],
0: [() => {this.cancelTraffic(); }, 'Cancel traffic monitoring'],
'shift-L': [() => {this.cycleHostLabels(); }, 'Cycle host labels'],
@@ -443,6 +461,14 @@
this.log.debug('Cycling host labels', old, next);
}
+ protected cycleGridDisplay() {
+ const old: GridDisplayToggle = this.prefsState.grid;
+ const next = GridDisplayToggle.next(old);
+ this.flashMsg = this.lionFn(TopologyComponent.gridDisplayFlashMessage(next));
+ this.updatePrefsState(PREF_GRID, next);
+ this.log.debug('Cycling grid display', old, next);
+ }
+
/**
* When the button is clicked on the toolbar or the B key is pressed
* 1) Find the inverse of the current state (held as 1 or 0)