GUI2 added in the layout topo overlay
Change-Id: I9960f95ae726a5af9950771ed67bcfc9d172e267
diff --git a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/layer/forcesvg/forcesvg.component.ts b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/layer/forcesvg/forcesvg.component.ts
index 6910353..843c19c 100644
--- a/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/layer/forcesvg/forcesvg.component.ts
+++ b/web/gui2-topo-lib/projects/gui2-topo-lib/src/lib/layer/forcesvg/forcesvg.component.ts
@@ -36,7 +36,7 @@
ZoomUtils
} from 'gui2-fw-lib';
import {
- Device,
+ Device, DeviceProps,
ForceDirectedGraph,
Host,
HostLabelToggle,
@@ -47,6 +47,7 @@
Location,
ModelEventMemo,
ModelEventType,
+ Node,
Region,
RegionLink,
SubRegion,
@@ -56,6 +57,7 @@
import {DeviceNodeSvgComponent} from './visuals/devicenodesvg/devicenodesvg.component';
import { HostNodeSvgComponent} from './visuals/hostnodesvg/hostnodesvg.component';
import { LinkSvgComponent} from './visuals/linksvg/linksvg.component';
+import { Options } from './models/force-directed-graph';
interface UpdateMeta {
id: string;
@@ -63,6 +65,16 @@
memento: MetaUi;
}
+const SVGCANVAS = <Options>{
+ width: 1000,
+ height: 1000
+};
+
+interface ChangeSummary {
+ numChanges: number;
+ locationChanged: boolean;
+}
+
/**
* ONOS GUI -- Topology Forces Graph Layer View.
*
@@ -88,7 +100,6 @@
@Output() linkSelected = new EventEmitter<RegionLink>();
@Output() selectedNodeEvent = new EventEmitter<UiElement>();
public graph: ForceDirectedGraph;
- private _options: { width, height } = { width: 800, height: 600 };
// References to the children of this component - these are created in the
// template view with the *ngFor and we get them by a query here
@@ -131,16 +142,26 @@
* @param existingNode 1st object
* @param updatedNode 2nd object
*/
- private static updateObject(existingNode: Object, updatedNode: Object): number {
- let changed: number = 0;
+ private static updateObject(existingNode: Object, updatedNode: Object): ChangeSummary {
+ const changed = <ChangeSummary>{numChanges: 0, locationChanged: false};
for (const key of Object.keys(updatedNode)) {
const o = updatedNode[key];
- if (key === 'id') {
+ if (['id', 'x', 'y', 'fx', 'fy', 'vx', 'vy', 'index'].some(k => k === key)) {
continue;
} else if (o && typeof o === 'object' && o.constructor === Object) {
- changed += ForceSvgComponent.updateObject(existingNode[key], updatedNode[key]);
+ const subChanged = ForceSvgComponent.updateObject(existingNode[key], updatedNode[key]);
+ changed.numChanges += subChanged.numChanges;
+ changed.locationChanged = subChanged.locationChanged ? true : changed.locationChanged;
+ } else if (existingNode === undefined) {
+ // Copy the whole object
+ existingNode = updatedNode;
+ changed.locationChanged = true;
+ changed.numChanges++;
} else if (existingNode[key] !== updatedNode[key]) {
- changed++;
+ if (['locType', 'latOrY', 'longOrX', 'latitude', 'longitude', 'gridX', 'gridY'].some(k => k === key)) {
+ changed.locationChanged = true;
+ }
+ changed.numChanges++;
existingNode[key] = updatedNode[key];
}
}
@@ -149,8 +170,8 @@
@HostListener('window:resize', ['$event'])
onResize(event) {
- this.graph.initSimulation(this.options);
- this.log.debug('Simulation reinit after resize', event);
+ this.graph.restartSimulation();
+ this.log.debug('Simulation restart after resize', event);
}
/**
@@ -161,7 +182,7 @@
*/
ngOnInit() {
// Receiving an initialized simulated graph from our custom d3 service
- this.graph = new ForceDirectedGraph(this.options, this.log);
+ this.graph = new ForceDirectedGraph(SVGCANVAS, this.log);
/** Binding change detection check on each tick
* This along with an onPush change detection strategy should enforce
@@ -171,9 +192,11 @@
* simulations data binding.
*/
this.graph.ticker.subscribe((simulation) => {
- // this.log.debug("Force simulation has ticked", simulation);
+ // this.log.debug("Force simulation has ticked. Alpha",
+ // Math.round(simulation.alpha() * 1000) / 1000);
this.ref.markForCheck();
});
+
this.log.debug('ForceSvgComponent initialized - waiting for nodes and links');
}
@@ -208,20 +231,7 @@
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 =
- ZoomUtils.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);
- }
- });
+ this.graph.nodes.forEach((n) => this.fixPosition(n));
// Associate the endpoints of each link with a real node
this.graph.links = [];
@@ -240,15 +250,42 @@
}
this.graph.links = this.regionData.links;
-
- this.graph.initSimulation(this.options);
- this.graph.initNodes();
- this.graph.initLinks();
+ if (this.graph.nodes.length > 0) {
+ this.graph.reinitSimulation();
+ }
this.log.debug('ForceSvgComponent input changed',
this.graph.nodes.length, 'nodes,', this.graph.links.length, 'links');
}
+ }
- this.ref.markForCheck();
+ /**
+ * If a node has a fixed location then assign it to fx and fy so
+ * that it doesn't get affected by forces
+ * @param graphNode The node whose location should be processed
+ */
+ private fixPosition(graphNode: Node): void {
+ const loc: Location = <Location>graphNode['location'];
+ const props: DeviceProps = <DeviceProps>graphNode['props'];
+ const metaUi = <MetaUi>graphNode['metaUi'];
+ if (loc && loc.locType === LocationType.GEO) {
+ const position: MetaUi =
+ ZoomUtils.convertGeoToCanvas(
+ <LocMeta>{lng: loc.longOrX, lat: loc.latOrY});
+ graphNode.fx = position.x;
+ graphNode.fy = position.y;
+ this.log.debug('Found node', graphNode.id, 'with', loc.locType);
+ } else if (loc && loc.locType === LocationType.GRID) {
+ graphNode.fx = loc.longOrX;
+ graphNode.fy = loc.latOrY;
+ this.log.debug('Found node', graphNode.id, 'with', loc.locType);
+ } else if (props && props.locType === LocationType.NONE && metaUi) {
+ graphNode.fx = metaUi.x;
+ graphNode.fy = metaUi.y;
+ this.log.debug('Found node', graphNode.id, 'with locType=none and metaUi');
+ } else {
+ graphNode.fx = null;
+ graphNode.fy = null;
+ }
}
/**
@@ -270,13 +307,6 @@
this.linkSelected.emit(link);
}
- get options() {
- return this._options = {
- width: window.innerWidth,
- height: window.innerHeight
- };
- }
-
/**
* Iterate through all hosts and devices to deselect the previously selected
* node. The emit an event to the parent that lets it know the selection has
@@ -326,23 +356,7 @@
switch (type) {
case ModelEventType.DEVICE_ADDED_OR_UPDATED:
if (memo === ModelEventMemo.ADDED) {
- const loc = (<Device>data).location;
- if (loc && loc.locType === LocationType.GEO) {
- const position =
- ZoomUtils.convertGeoToCanvas(<LocMeta>{ lng: loc.longOrX, lat: loc.latOrY});
- (<Device>data).fx = position.x;
- (<Device>data).fy = position.y;
- this.log.debug('Using long', loc.longOrX, 'lat', loc.latOrY, '(', position.x, position.y, ')');
- } else if (loc && loc.locType === LocationType.GRID) {
- (<Device>data).fx = loc.longOrX;
- (<Device>data).fy = loc.latOrY;
- this.log.debug('Using grid', loc.longOrX, loc.latOrY);
- } else {
- (<Device>data).fx = null;
- (<Device>data).fy = null;
- // (<Device>data).x = 500;
- // (<Device>data).y = 500;
- }
+ this.fixPosition(<Device>data);
this.graph.nodes.push(<Device>data);
this.regionData.devices[this.visibleLayerIdx()].push(<Device>data);
this.log.debug('Device added', (<Device>data).id);
@@ -351,8 +365,11 @@
this.regionData.devices[this.visibleLayerIdx()]
.find((d) => d.id === subject);
const changes = ForceSvgComponent.updateObject(oldDevice, <Device>data);
- if (changes > 0) {
+ if (changes.numChanges > 0) {
this.log.debug('Device ', oldDevice.id, memo, ' - ', changes, 'changes');
+ if (changes.locationChanged) {
+ this.fixPosition(oldDevice);
+ }
}
} else {
this.log.warn('Device ', memo, ' - not yet implemented', data);
@@ -360,15 +377,19 @@
break;
case ModelEventType.HOST_ADDED_OR_UPDATED:
if (memo === ModelEventMemo.ADDED) {
- this.regionData.hosts[this.visibleLayerIdx()].push(<Host>data);
+ this.fixPosition(<Host>data);
this.graph.nodes.push(<Host>data);
+ this.regionData.hosts[this.visibleLayerIdx()].push(<Host>data);
this.log.debug('Host added', (<Host>data).id);
} else if (memo === ModelEventMemo.UPDATED) {
const oldHost: Host = this.regionData.hosts[this.visibleLayerIdx()]
.find((h) => h.id === subject);
const changes = ForceSvgComponent.updateObject(oldHost, <Host>data);
- if (changes > 0) {
+ if (changes.numChanges > 0) {
this.log.debug('Host ', oldHost.id, memo, ' - ', changes, 'changes');
+ if (changes.locationChanged) {
+ this.fixPosition(oldHost);
+ }
}
} else {
this.log.warn('Host change', memo, ' - unexpected');
@@ -424,10 +445,8 @@
default:
this.log.error('Unexpected model event', type, 'for', subject);
}
- this.ref.markForCheck();
- this.graph.initSimulation(this.options);
- this.graph.initNodes();
- this.graph.initLinks();
+ this.graph.links = this.regionData.links;
+ this.graph.reinitSimulation();
}
private removeRelatedLinks(subject: string) {
@@ -500,7 +519,7 @@
* @param newLocation - the new Location of the node
*/
nodeMoved(klass: string, id: string, newLocation: MetaUi) {
- this.wss.sendEvent('updateMeta', <UpdateMeta>{
+ this.wss.sendEvent('updateMeta2', <UpdateMeta>{
id: id,
class: klass,
memento: newLocation