Enable link functionality in GUI2 Topology View
Change-Id: I1b88080ecdf8c9b6f8a60af4832a12441186d508
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.css b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.css
index cf965ac..57a2bd3 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.css
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.css
@@ -19,7 +19,7 @@
ONOS GUI -- Topology View (forces device visual) -- CSS file
*/
g.node.device rect {
- fill: #f0f0f0;
+ fill: #f0f0f070;
}
g.node.device text {
fill: #bbb;
@@ -30,7 +30,7 @@
g.node.device.online rect {
- fill: #ffffff;
+ fill: #fafafad0;
}
g.node.device.online text {
fill: #3c3a3a;
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.html b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.html
index e329874..70d4d5b 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.html
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.html
@@ -13,17 +13,75 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<svg:g xmlns:svg="http://www.w3.org/2000/svg" #devSvg
+<svg:defs xmlns:svg="http://www.w3.org/2000/svg">
+ <!-- Template explanation: Define an SVG Filter that in
+ line 1) render the target object in to a bit map and apply a blur to it
+ based on its alpha channel
+ line 2) take that blurred layer and shift it down and to the right by 4
+ line 3) Merge this blurred and shifted layer and overlay it with the
+ original target object
+ -->
+ <svg:filter id="drop-shadow">
+ <svg:feGaussianBlur in="SourceAlpha" stdDeviation="2" result="blur" />
+ <svg:feOffset in="blur" dx="4" dy="4" result="offsetBlur"/>
+ <svg:feMerge >
+ <svg:feMergeNode in="offsetBlur" />
+ <svg:feMergeNode in="SourceGraphic" />
+ </svg:feMerge>
+ </svg:filter>
+ <svg:linearGradient id="diagonal_blue" x1="0%" y1="0%" x2="100%" y2="100%">
+ <svg:stop offset= "0%" style="stop-color: #5b99d2;" />
+ <svg:stop offset= "100%" style="stop-color: #3b79b2;" />
+ </svg:linearGradient>
+</svg:defs>
+<!-- Template explanation: Creates an SVG Group and in
+ line 1) transform it to the position calculated by the d3 force graph engine
+ line 2) Give it various CSS styles depending on attributes
+ line 3) When it is clicked, call the method that toggles the selection and
+ emits an event.
+ Other child objects have their own description
+-->
+<svg:g xmlns:svg="http://www.w3.org/2000/svg"
[attr.transform]="'translate(' + device?.x + ',' + device?.y + '), scale(' + scale + ')'"
[ngClass]="['node', 'device', device.online?'online':'', selected?'selected':'']"
(click)="toggleSelected(device)">
- <svg:rect class="node-container" x="-18" y="-18" width="36" height="36">
- <!--[attr.width]="devText.getComputedTextLength()+36" -->
- <!--[@deviceLabelToggle]="{ value: labelToggle, params: {txtWidth: devSvg.getBBox().width+'px' }}">-->
+ <svg:desc>Device {{device.id}}</svg:desc>
+ <!-- Template explanation: Creates an SVG Rectangle and in
+ line 1) set a css style and shift so that it's centred
+ line 2) set the initial width and height - width changes with label
+ line 3) link to the animation 'deviceLabelToggle', pass in to it a width
+ calculated from the width of the text, and additional padding at the end
+ line 4) Apply the filter defined above to this rectangle (even as its
+ width changes
+ -->
+ <svg:rect
+ class="node-container" x="-18" y="-18"
+ width="36" height="36"
+ [@deviceLabelToggle]="{ value: labelToggle, params: {txtWidth: (36 + labelTextLen() * 1.05)+'px' }}"
+ filter= "url(#drop-shadow)">
</svg:rect>
- <svg:rect x="-16" y="-16" width="32" height="32" [ngStyle]="{'fill': 'rgb(91, 153, 210)'}">
+ <!-- Template explanation: Creates an SVG Rectangle slightly smaller and
+ overlaid on the above. This is the blue box, and its width and height does
+ not change
+ -->
+ <svg:rect x="-16" y="-16" width="32" height="32" style="fill: url(#diagonal_blue)">
</svg:rect>
- <svg:text #devText text-anchor="start" y="0.3em" x="22" [ngStyle]="{'transform': 'scale(' + scale + ')'}">
+ <!-- Template explanation: Creates an SVG Text element and in
+ line 1) make it left aligned and slightly down and to the right of the last rect
+ line 2) set its text length to be the calculated value - see that function
+ line 3) because of kerning the actual text might be shorter or longer than
+ the pre-calculated value - if so change the spacing between the letters
+ (and not the letter width to compensate)
+ line 4) link to the animation deviceLabelToggleTxt, so that the text appears
+ in gently
+ line 5) The text will be one of 3 values - blank, the id or the name
+ -->
+ <svg:text
+ text-anchor="start" y="0.3em" x="22"
+ [attr.textLength]= "labelTextLen()"
+ lengthAdjust= "spacing"
+ [ngStyle]="{'transform': 'scale(' + scale + ')'}"
+ [@deviceLabelToggleTxt]="labelToggle">
{{ labelToggle == 0 ? '': labelToggle == 1 ? device.id:device.props.name }}
</svg:text>
<!-- It's not possible to drive the following xref dynamically -->
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.spec.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.spec.ts
index 0f4feee..32ed2ef 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.spec.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.spec.ts
@@ -21,6 +21,7 @@
import {of} from 'rxjs';
import {ChangeDetectorRef} from '@angular/core';
import {Device} from '../../models';
+import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
class MockActivatedRoute extends ActivatedRoute {
constructor(params: Params) {
@@ -44,6 +45,7 @@
testDevice.online = true;
TestBed.configureTestingModule({
+ imports: [ BrowserAnimationsModule ],
declarations: [ DeviceNodeSvgComponent ],
providers: [
{ provide: LogService, useValue: logSpy },
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.ts
index 036fc08..756769d 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/devicenodesvg/devicenodesvg.component.ts
@@ -14,17 +14,18 @@
* limitations under the License.
*/
import {
+ ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
- ElementRef, EventEmitter,
+ EventEmitter,
Input,
OnChanges, Output,
SimpleChanges,
- ViewChild
} from '@angular/core';
-import {Node, Device, LabelToggle} from '../../models';
+import {Device, LabelToggle, UiElement} from '../../models';
import {LogService} from 'gui2-fw-lib';
import {NodeVisual} from '../nodevisual';
+import {animate, state, style, transition, trigger} from '@angular/animations';
/**
* The Device node in the force graph
@@ -36,28 +37,38 @@
selector: '[onos-devicenodesvg]',
templateUrl: './devicenodesvg.component.html',
styleUrls: ['./devicenodesvg.component.css'],
- // changeDetection: ChangeDetectionStrategy.Default,
- // animations: [
- // trigger('deviceLabelToggle', [
- // state('0', style({ // none
- // width: '36px',
- // })),
- // state('1, 2', // id
- // style({ width: '{{ txtWidth }}'}),
- // { params: {'txtWidth': '36px'}}
- // ), // default
- // transition('0 => *', animate('1000ms ease-in')),
- // transition('* => 0', animate('1000ms ease-out'))
- // ])
- // ]
+ changeDetection: ChangeDetectionStrategy.Default,
+ animations: [
+ trigger('deviceLabelToggle', [
+ state('0', style({ // none
+ width: '36px',
+ })),
+ state('1, 2', // id
+ style({ width: '{{ txtWidth }}'}),
+ { params: {'txtWidth': '36px'}}
+ ), // default
+ transition('0 => 1', animate('250ms ease-in')),
+ transition('1 => 2', animate('250ms ease-in')),
+ transition('* => 0', animate('250ms ease-out'))
+ ]),
+ trigger('deviceLabelToggleTxt', [
+ state('0', style( {
+ opacity: 0,
+ })),
+ state( '1,2', style({
+ opacity: 1.0
+ })),
+ transition('0 => 1', animate('250ms ease-in')),
+ transition('* => 0', animate('250ms ease-out'))
+ ])
+ ]
})
export class DeviceNodeSvgComponent extends NodeVisual implements OnChanges {
@Input() device: Device;
@Input() scale: number = 1.0;
@Input() labelToggle: LabelToggle = LabelToggle.NONE;
- @Output() selectedEvent = new EventEmitter<Node>();
+ @Output() selectedEvent = new EventEmitter<UiElement>();
textWidth: number = 36;
- @ViewChild('idTxt') idTxt: ElementRef;
constructor(
protected log: LogService,
private ref: ChangeDetectorRef
@@ -84,4 +95,31 @@
}
this.ref.markForCheck();
}
+
+ /**
+ * Calculate the text length in advance as well as possible
+ *
+ * The length of SVG text cannot be exactly estimated, because depending on
+ * the letters kerning might mean that it is shorter or longer than expected
+ *
+ * This takes the approach of 8px width per letter of this size, that on average
+ * evens out over words. A word like 'ilj' will be much shorter than 'wm0'
+ * because of kerning
+ *
+ *
+ * In addition in the template, the <svg:text> properties
+ * textLength and lengthAdjust ensure that the text becomes long with extra
+ * wide spacing created as necessary.
+ *
+ * Other approaches like getBBox() of the text
+ */
+ labelTextLen() {
+ if (this.labelToggle === 1) {
+ return this.device.id.length * 8 * this.scale;
+ } else if (this.labelToggle === 2 && this.device && this.device.props.name && this.device.props.name.trim().length > 0) {
+ return this.device.props.name.length * 8 * this.scale;
+ } else {
+ return 0;
+ }
+ }
}
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
index 9cac8b0..728a8ce 100644
--- 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
@@ -13,11 +13,57 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
+<svg:defs xmlns:svg="http://www.w3.org/2000/svg">
+ <!-- Template explanation: Define an SVG Filter that in
+ line 1) render the target object in to a bit map and apply a blur to it
+ based on its alpha channel
+ line 2) take that blurred layer and shift it down and to the right by 4
+ line 3) Merge this blurred and shifted layer and overlay it with the
+ original target object
+ -->
+ <svg:filter id="drop-shadow-host">
+ <svg:feGaussianBlur in="SourceAlpha" stdDeviation="1" result="blur" />
+ <svg:feOffset in="blur" dx="2" dy="2" result="offsetBlur"/>
+ <svg:feMerge >
+ <svg:feMergeNode in="offsetBlur" />
+ <svg:feMergeNode in="SourceGraphic" />
+ </svg:feMerge>
+ </svg:filter>
+ <svg:radialGradient id="three_stops_radial">
+ <svg:stop offset= "0%" style="stop-color: #e3e5d6;" />
+ <svg:stop offset= "70%" style="stop-color: #c3c5b6;" />
+ <svg:stop offset="100%" style="stop-color: #a3a596;" />
+ </svg:radialGradient>
+</svg:defs>
+<!-- Template explanation: Creates an SVG Group and in
+ line 1) transform it to the position calculated by the d3 force graph engine
+ line 2) Give it various CSS styles depending on attributes
+ line 3) When it is clicked, call the method that toggles the selection and
+ emits an event.
+ Other child objects have their own description
+-->
<svg:g xmlns:svg="http://www.w3.org/2000/svg"
[attr.transform]="'translate(' + host?.x + ',' + host?.y + '), scale(' + scale + ')'"
[ngClass]="['node', 'host', 'endstation', 'fixed', selected?'selected':'', 'hovered']"
(click)="toggleSelected(host)">
- <svg:circle r="15"></svg:circle>
+ <svg:desc>Host {{host.id}}</svg:desc>
+ <!-- Template explanation: Creates an SVG Circle and in
+ line 1) Apply the drop shadow defined above to this circle
+ line 2) Apply the radial gradient defined above to the circle
+ -->
+ <svg:circle r="15"
+ filter="url(#drop-shadow-host)"
+ style="fill: url(#three_stops_radial)">
+ </svg:circle>
<svg:use xlink:href="#m_endstation" width="22.5" height="22.5" x="-11.25" y="-11.25" style="transform: scale(1);"></svg:use>
- <svg:text *ngIf="labelToggle != 0" dy="24" text-anchor="middle" style="transform: scale(1);">{{hostName()}}</svg:text>
+ <!-- Template explanation: Creates an SVG Text
+ line 1) if the labelToggle is not 0
+ line 2) shift it below the circle, and have it centred with the circle
+ line 3) apply a scale and call on the hostName(0 method to get the
+ displayed value
+ -->
+ <svg:text
+ *ngIf="labelToggle != 0"
+ dy="30" text-anchor="middle"
+ style="transform: scale(1);">{{hostName()}}</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
index bb791b9..e3efb3f 100644
--- 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
@@ -1,25 +1,69 @@
+/*
+ * 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 { HostNodeSvgComponent } from './hostnodesvg.component';
+import {ActivatedRoute, Params} from '@angular/router';
+import {of} from 'rxjs';
+import {LogService} from 'gui2-fw-lib';
+import {Host} from '../../models';
+import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
+import {ChangeDetectorRef} from '@angular/core';
+
+class MockActivatedRoute extends ActivatedRoute {
+ constructor(params: Params) {
+ super();
+ this.queryParams = of(params);
+ }
+}
describe('HostNodeSvgComponent', () => {
- let component: HostNodeSvgComponent;
- let fixture: ComponentFixture<HostNodeSvgComponent>;
+ let logServiceSpy: jasmine.SpyObj<LogService>;
+ let component: HostNodeSvgComponent;
+ let fixture: ComponentFixture<HostNodeSvgComponent>;
+ let ar: MockActivatedRoute;
+ let testHost: Host;
- beforeEach(async(() => {
- TestBed.configureTestingModule({
- declarations: [ HostNodeSvgComponent ]
- })
- .compileComponents();
- }));
+ beforeEach(async(() => {
+ const logSpy = jasmine.createSpyObj('LogService', ['info', 'debug', 'warn', 'error']);
+ ar = new MockActivatedRoute({ 'debug': 'txrx' });
+ testHost = new Host('host:1');
+ testHost.ips = ['10.205.86.123', '192.168.56.10'];
- beforeEach(() => {
- fixture = TestBed.createComponent(HostNodeSvgComponent);
- component = fixture.componentInstance;
- fixture.detectChanges();
- });
+ TestBed.configureTestingModule({
+ imports: [ BrowserAnimationsModule ],
+ declarations: [ HostNodeSvgComponent ],
+ providers: [
+ { provide: LogService, useValue: logSpy },
+ { provide: ActivatedRoute, useValue: ar },
+ { provide: ChangeDetectorRef, useClass: ChangeDetectorRef }
+ ]
+ })
+ .compileComponents();
+ logServiceSpy = TestBed.get(LogService);
+ }));
- it('should create', () => {
- expect(component).toBeTruthy();
- });
+ beforeEach(() => {
+ fixture = TestBed.createComponent(HostNodeSvgComponent);
+ component = fixture.componentInstance;
+ component.host = testHost;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
});
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
index 0723986..60ab90b 100644
--- 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
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-export * from './linkvisual.component';
+export * from './linksvg/linksvg.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/linksvg/linksvg.component.css b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/linksvg/linksvg.component.css
new file mode 100644
index 0000000..58548c4
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/linksvg/linksvg.component.css
@@ -0,0 +1,149 @@
+/*
+ * 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 svg) -- CSS file
+ */
+/* --- Topo Links --- */
+line {
+ stroke: #888888;
+ stroke-width: 2px;
+}
+
+.link {
+ opacity: .9;
+}
+
+.link.selected {
+ stroke: #009fdb;
+}
+.link.enhanced {
+ stroke: #009fdb;
+ stroke-width: 4px;
+ cursor: pointer;
+}
+
+.link.inactive {
+ opacity: .5;
+ stroke-dasharray: 4 2;
+}
+/* TODO: Review for not-permitted links */
+.link.not-permitted {
+ stroke: rgb(255,0,0);
+ stroke-dasharray: 8 4;
+}
+
+.link.secondary {
+ stroke: rgba(0,153,51,0.5);
+}
+
+.link.secondary.port-traffic-green {
+ stroke: rgb(0,153,51);
+}
+
+.link.secondary.port-traffic-yellow {
+ stroke: rgb(128,145,27);
+}
+
+.link.secondary.port-traffic-orange {
+ stroke: rgb(255, 137, 3);
+}
+
+.link.secondary.port-traffic-red {
+ stroke: rgb(183, 30, 21);
+}
+
+/* Port traffic color visualization for Kbps, Mbps, and Gbps */
+
+.link.secondary.port-traffic-Kbps {
+ stroke: rgb(0,153,51);
+}
+
+.link.secondary.port-traffic-Mbps {
+ stroke: rgb(128,145,27);
+}
+
+.link.secondary.port-traffic-Gbps {
+ stroke: rgb(255, 137, 3);
+}
+
+.link.secondary.port-traffic-Gbps-choked {
+ stroke: rgb(183, 30, 21);
+}
+
+.link.animated {
+ stroke-dasharray: 8 5;
+ animation: ants 5s infinite linear;
+ /* below line could be added via Javascript, based on path, if we cared
+ * enough about the direction of ant-flow
+ */
+ /*animation-direction: reverse;*/
+}
+@keyframes ants {
+ from {
+ stroke-dashoffset: 0;
+ }
+ to {
+ stroke-dashoffset: 400;
+ }
+}
+
+.link.primary {
+ stroke-width: 4px;
+ stroke: #ffA300;
+}
+
+.link.secondary.optical {
+ stroke-width: 4px;
+ stroke: rgba(128,64,255,0.5);
+}
+
+.link.primary.optical {
+ stroke-width: 6px;
+ stroke: #74f;
+}
+
+/* Link Labels */
+.linkLabel rect {
+ stroke: none;
+ fill: #ffffff;
+}
+
+.linkLabel text {
+ fill: #444;
+ text-anchor: middle;
+}
+
+
+/* Port Labels */
+.portLabel rect {
+ stroke: #a3a596;
+ fill: #ffffff;
+}
+
+.portLabel {
+ fill: #444;
+ alignment-baseline: middle;
+ dominant-baseline: middle;
+}
+
+/* Number of Links Labels */
+
+
+#ov-topo2 text.numLinkText {
+ fill: #444;
+}
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/linksvg/linksvg.component.html b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/linksvg/linksvg.component.html
new file mode 100644
index 0000000..b3a5557
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/linksvg/linksvg.component.html
@@ -0,0 +1,97 @@
+<!--
+~ 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:defs xmlns:svg="http://www.w3.org/2000/svg">
+ <svg:filter id="glow">
+ <svg:feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0.9 0 0 0 0 0.9 0 0 0 0 1 0" />
+ <svg:feGaussianBlur stdDeviation="2.5" result="coloredBlur" />
+ <svg:feMerge>
+ <svg:feMergeNode in="coloredBlur" />
+ <svg:feMergeNode in="SourceGraphic"/>
+ </svg:feMerge>
+ </svg:filter>
+</svg:defs>
+<!-- Template explanation: Creates an SVG Line and in
+ line 1) transform end A to the position calculated by the d3 force graph engine
+ line 2) transform end B to the position calculated by the d3 force graph engine
+ line 3) Give it various CSS styles depending on attributes
+ line 4) When it is clicked, call the method that toggles the selection and
+ emits an event.
+ line 5) When the mouse is moved over call on enhance() function. This will
+ flash up the port labels, and display the link in blue for 1 second
+ Other child objects have their own description
+-->
+<svg:line xmlns:svg="http://www.w3.org/2000/svg"
+ [attr.x1]="link.source?.x" [attr.y1]="link.source?.y"
+ [attr.x2]="link.target?.x" [attr.y2]="link.target?.y"
+ [ngClass]="['link', selected?'selected':'', enhanced?'enhanced':'', highlighted]"
+ (click)="toggleSelected(link)"
+ (mouseover)="enhance()"
+ [attr.filter]="highlighted?'url(#glow)':'none'">
+</svg:line>
+<svg:g xmlns:svg="http://www.w3.org/2000/svg" [ngClass]="['linkLabel']">
+ <!-- Template explanation: Creates SVG Text and in
+ line 1) Performs the animation 'linkLabelVisible' whenever the isHighlighted
+ boolean value changes
+ line 2 & 3) Sets the text at half way between the 2 end points of the line
+ Note: we do not use an *ngIf to enable or disable this, because that would
+ cause the fade out of the text to not work
+ -->
+ <svg:text xmlns:svg="http://www.w3.org/2000/svg"
+ [@linkLabelVisible]="isHighlighted"
+ [attr.x]="link.source?.x + (link.target?.x - link.source?.x)/2"
+ [attr.y]="link.source?.y + (link.target?.y - link.source?.y)/2"
+ >{{ label }}</svg:text>
+</svg:g>
+<!-- Template explanation: Creates an SVG Group if
+ line 1) 'enhanced' is active and port text exists
+ line 2) assigns classes to it
+-->
+<svg:g xmlns:svg="http://www.w3.org/2000/svg"
+ *ngIf="enhanced && link.portA"
+ class="portLabel">
+ <!-- Template explanation: Creates an SVG Rectangle and in
+ line 1) transform end A to the position calculated by the d3 force graph engine
+ line 2) assigns classes to it
+ -->
+ <svg:rect
+ [attr.x]="labelPosSrc.x - 2 - textLength(link.portA)/2" [attr.y]="labelPosSrc.y - 8"
+ [attr.width]="4 + textLength(link.portA)" height="16" >
+ </svg:rect>
+ <!-- Template explanation: Creates SVG Text and in
+ line 1) transform it to the position calculated by the method labelPosSrc()
+ line 2) centre aligns it
+ line 3) ensures that the text fills the rectangle by adjusting spacing
+ -->
+ <svg:text
+ [attr.x]="labelPosSrc.x" [attr.y]="labelPosSrc.y + 6"
+ text-anchor="middle"
+ [attr.textLength]= "textLength(link.portA)" lengthAdjust="spacing"
+ >{{ link.portA }}</svg:text>
+</svg:g>
+<!-- A repeat of the above, but for the other end of the line -->
+<svg:g xmlns:svg="http://www.w3.org/2000/svg"
+ *ngIf="enhanced && link.portB"
+ class="portLabel">
+ <svg:rect
+ [attr.x]="labelPosTgt.x - 2 - textLength(link.portB)/2" [attr.y]="labelPosTgt.y - 8"
+ [attr.width]="4 + textLength(link.portB)" height="16">
+ </svg:rect>
+ <svg:text
+ [attr.x]="labelPosTgt.x" [attr.y]="labelPosTgt.y + 6"
+ text-anchor="middle"
+ [attr.textLength]= "textLength(link.portB)" lengthAdjust="spacing"
+ >{{ link.portB }}</svg:text>
+</svg:g>
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/linksvg/linksvg.component.spec.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/linksvg/linksvg.component.spec.ts
new file mode 100644
index 0000000..7626ff5
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/linksvg/linksvg.component.spec.ts
@@ -0,0 +1,76 @@
+/*
+ * 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 { LinkSvgComponent } from './linksvg.component';
+import {LogService} from 'gui2-fw-lib';
+import {ActivatedRoute, Params} from '@angular/router';
+import {of} from 'rxjs';
+import {Device, Link, RegionLink, LinkType} from '../../models';
+import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
+
+class MockActivatedRoute extends ActivatedRoute {
+ constructor(params: Params) {
+ super();
+ this.queryParams = of(params);
+ }
+}
+
+describe('LinkVisualComponent', () => {
+ let logServiceSpy: jasmine.SpyObj<LogService>;
+ let component: LinkSvgComponent;
+ let fixture: ComponentFixture<LinkSvgComponent>;
+ let ar: MockActivatedRoute;
+ let testLink: Link;
+ let testDeviceA: Device;
+ let testDeviceB: Device;
+
+ beforeEach(async(() => {
+ const logSpy = jasmine.createSpyObj('LogService', ['info', 'debug', 'warn', 'error']);
+ ar = new MockActivatedRoute({ 'debug': 'txrx' });
+
+ testDeviceA = new Device('test:A');
+ testDeviceA.online = true;
+
+ testDeviceB = new Device('test:B');
+ testDeviceB.online = true;
+
+ testLink = new RegionLink(LinkType.UiDeviceLink, testDeviceA, testDeviceB);
+ testLink.id = 'test:A/1-test:B/1';
+
+ TestBed.configureTestingModule({
+ imports: [ BrowserAnimationsModule ],
+ declarations: [ LinkSvgComponent ],
+ providers: [
+ { provide: LogService, useValue: logSpy },
+ { provide: ActivatedRoute, useValue: ar },
+ ]
+ })
+ .compileComponents();
+ logServiceSpy = TestBed.get(LogService);
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(LinkSvgComponent);
+ component = fixture.componentInstance;
+ component.link = testLink;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/linksvg/linksvg.component.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/linksvg/linksvg.component.ts
new file mode 100644
index 0000000..8202c67
--- /dev/null
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/linksvg/linksvg.component.ts
@@ -0,0 +1,129 @@
+/*
+ * 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, EventEmitter,
+ Input, OnChanges, Output, SimpleChanges,
+} from '@angular/core';
+import {Link, LinkHighlight, UiElement} from '../../models';
+import {LogService} from 'gui2-fw-lib';
+import {NodeVisual} from '../nodevisual';
+import {animate, state, style, transition, trigger} from '@angular/animations';
+
+interface Point {
+ x: number;
+ y: number;
+}
+
+enum LinkEnd {
+ A,
+ B
+}
+
+@Component({
+ selector: '[onos-linksvg]',
+ templateUrl: './linksvg.component.html',
+ styleUrls: ['./linksvg.component.css'],
+ animations: [
+ trigger('linkLabelVisible', [
+ state('true', style( {
+ opacity: 1.0,
+ })),
+ state( 'false', style({
+ opacity: 0
+ })),
+ transition('false => true', animate('500ms ease-in')),
+ transition('true => false', animate('1000ms ease-out'))
+ ])
+ ]
+})
+export class LinkSvgComponent extends NodeVisual implements OnChanges {
+ @Input() link: Link;
+ @Input() highlighted: string = '';
+ @Input() label: string;
+ isHighlighted: boolean = false;
+ @Output() selectedEvent = new EventEmitter<UiElement>();
+ @Output() enhancedEvent = new EventEmitter<Link>();
+ enhanced: boolean = false;
+ labelPosSrc: Point = {x: 0, y: 0};
+ labelPosTgt: Point = {x: 0, y: 0};
+
+ constructor(
+ protected log: LogService,
+ private ref: ChangeDetectorRef
+ ) {
+ super();
+ }
+
+ ngOnChanges(changes: SimpleChanges) {
+ if (changes['linkHighlight']) {
+ const hl: LinkHighlight = changes['linkHighlight'].currentValue;
+ this.highlighted = hl.css;
+ this.label = hl.label;
+ this.isHighlighted = true;
+ setTimeout(() => {
+ this.isHighlighted = false;
+ this.highlighted = '';
+ this.ref.markForCheck();
+ }, 4990);
+
+ }
+
+ this.ref.markForCheck();
+ }
+
+ enhance() {
+ this.enhancedEvent.emit(this.link);
+ this.enhanced = true;
+ this.repositionLabels();
+ setTimeout(() => {
+ this.enhanced = false;
+ this.ref.markForCheck();
+ }, 1000);
+ }
+
+ /**
+ * We want to place the label for the port about 40 px from the node
+ * If the distance between the nodes is less than 100, then just place the
+ * label 1/3 of the way from the node
+ */
+ repositionLabels(): void {
+ const x1: number = this.link.source.x;
+ const y1: number = this.link.source.y;
+ const x2: number = this.link.target.x;
+ const y2: number = this.link.target.y;
+
+ const dist = Math.sqrt(Math.pow((x2 - x1), 2) + Math.pow((y2 - y1), 2));
+ const offset = dist > 100 ? 40 : dist / 3;
+ this.labelPosSrc = <Point>{
+ x: x1 + (x2 - x1) * offset / dist,
+ y: y1 + (y2 - y1) * offset / dist
+ };
+
+ this.labelPosTgt = <Point>{
+ x: x2 - (x2 - x1) * offset / dist,
+ y: y2 - (y2 - y1) * offset / dist
+ };
+ }
+
+ /**
+ * For the 14pt font we are using, the average width seems to be about 8px
+ * @param text The string we want to calculate a width for
+ */
+ textLength(text: string) {
+ return text.length * 8;
+ }
+}
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
deleted file mode 100644
index 52e5df3..0000000
--- a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/linkvisual.component.css
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * 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
deleted file mode 100644
index 6c4a6bd..0000000
--- a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/linkvisual.component.html
+++ /dev/null
@@ -1,28 +0,0 @@
-<!--
-~ 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
deleted file mode 100644
index c060aab..0000000
--- a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/linkvisual.component.spec.ts
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * 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
deleted file mode 100644
index edde2aa..0000000
--- a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/linkvisual.component.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * 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/nodevisual.ts b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/nodevisual.ts
index 677745d..a41df62 100644
--- a/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/nodevisual.ts
+++ b/web/gui2/src/main/webapp/app/view/topology/layer/forcesvg/visuals/nodevisual.ts
@@ -14,16 +14,19 @@
* limitations under the License.
*/
import {EventEmitter} from '@angular/core';
-import {Node} from '../models';
+import {UiElement} from '../models';
+/**
+ * A base class for the Host and Device components
+ */
export abstract class NodeVisual {
selected: boolean;
- selectedEvent = new EventEmitter<Node>();
+ selectedEvent = new EventEmitter<UiElement>();
- toggleSelected(node: Node) {
+ toggleSelected(uiElement: UiElement) {
this.selected = !this.selected;
if (this.selected) {
- this.selectedEvent.emit(node);
+ this.selectedEvent.emit(uiElement);
} else {
this.selectedEvent.emit();
}