blob: b552a9e2c5ab985a57e7f96aa8875497f9ac3875 [file] [log] [blame]
Steven Burrows57e24e92016-08-04 18:38:24 +01001/*
2 * Copyright 2016-present Open Networking Laboratory
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17/*
18 ONOS GUI -- Topology Links Module.
19 Module that holds the links for a region
20 */
21
22(function () {
23 'use strict';
24
Steven Burrows86af4352016-11-16 18:19:12 -060025 var $log, Collection, Model, ts, sus, t2zs, t2vs, t2lps, fn;
Steven Burrows9edc7e02016-08-29 11:52:07 +010026
27 var linkLabelOffset = '0.35em';
Steven Burrows57e24e92016-08-04 18:38:24 +010028
Steven Burrowsec1f45c2016-08-08 16:14:41 +010029 var widthRatio = 1.4,
30 linkScale = d3.scale.linear()
31 .domain([1, 12])
32 .range([widthRatio, 12 * widthRatio])
33 .clamp(true),
Steven Burrows583f4be2016-11-04 14:06:50 +010034 allLinkTypes = 'direct optical tunnel UiDeviceLink',
35 allLinkSubTypes = 'not-permitted',
Steven Burrowsa3fca812016-10-14 15:11:04 -050036 labelDim = 30;
Steven Burrowsec1f45c2016-08-08 16:14:41 +010037
38 // configuration
39 var linkConfig = {
40 light: {
41 baseColor: '#939598',
42 inColor: '#66f',
43 outColor: '#f00'
44 },
45 dark: {
46 // TODO : theme
47 baseColor: '#939598',
48 inColor: '#66f',
49 outColor: '#f00'
50 },
51 inWidth: 12,
52 outWidth: 10
53 };
54
Steven Burrowsec1f45c2016-08-08 16:14:41 +010055 function createLink() {
56
57 var linkPoints = this.linkEndPoints(this.get('epA'), this.get('epB'));
Steven Burrowsec1f45c2016-08-08 16:14:41 +010058
59 var attrs = angular.extend({}, linkPoints, {
60 key: this.get('id'),
61 class: 'link',
Steven Burrowsec1f45c2016-08-08 16:14:41 +010062 srcPort: this.get('srcPort'),
63 tgtPort: this.get('dstPort'),
64 position: {
65 x1: 0,
66 y1: 0,
67 x2: 0,
68 y2: 0
69 }
70 // functions to aggregate dual link state
Steven Burrows9edc7e02016-08-29 11:52:07 +010071 // extra: link.extra
Steven Burrowsec1f45c2016-08-08 16:14:41 +010072 });
73
74 this.set(attrs);
75 }
76
Steven Burrows9edc7e02016-08-29 11:52:07 +010077 function rectAroundText(el) {
78 var text = el.select('text'),
79 box = text.node().getBBox();
80
81 // translate the bbox so that it is centered on [x,y]
82 box.x = -box.width / 2;
83 box.y = -box.height / 2;
84
85 // add padding
86 box.x -= 4;
87 box.width += 8;
88 return box;
89 }
90
Steven Burrowsdfa52b02016-09-02 13:50:43 +010091 function isLinkOnline(node) {
92 return (node.get('nodeType') === 'region') ? true : node.get('online');
93 }
94
Steven Burrowsec1f45c2016-08-08 16:14:41 +010095 function linkEndPoints(srcId, dstId) {
96
Steven Burrowsa3fca812016-10-14 15:11:04 -050097 var allNodes = this.region.nodes();
Steven Burrows8f45ce22016-10-27 20:04:14 -050098 var sourceNode = this.region.findNodeById(this, srcId);
99 var targetNode = this.region.findNodeById(this, dstId);
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100100
Steven Burrows6deb4ce2016-08-26 16:06:23 +0100101 if (!sourceNode || !targetNode) {
Steven Burrows8f45ce22016-10-27 20:04:14 -0500102 $log.error('Node(s) not on map for link:' + srcId + '~' + dstId);
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100103 // logicError('Node(s) not on map for link:\n' + sMiss + dMiss);
Steven Burrows6deb4ce2016-08-26 16:06:23 +0100104 return null;
105 }
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100106
Steven Burrowsa3fca812016-10-14 15:11:04 -0500107 this.source = allNodes.indexOf(sourceNode);
108 this.target = allNodes.indexOf(targetNode);
109 this.sourceNode = sourceNode;
110 this.targetNode = targetNode;
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100111
112 return {
113 source: sourceNode,
114 target: targetNode
115 };
116 }
117
118 function createLinkCollection(data, _region) {
119
120 var LinkModel = Model.extend({
121 region: _region,
122 createLink: createLink,
123 linkEndPoints: linkEndPoints,
124 type: function () {
125 return this.get('type');
126 },
Steven Burrows86af4352016-11-16 18:19:12 -0600127 svgClassName: function () {
128 return fn.classNames('link',
129 this.nodeType,
130 this.get('type'),
131 {
132 enhanced: this.get('enhanced'),
133 selected: this.get('selected')
134 }
135 );
136 },
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100137 expected: function () {
Steven Burrows9edc7e02016-08-29 11:52:07 +0100138 // TODO: original code is: (s && s.expected) && (t && t.expected);
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100139 return true;
140 },
141 online: function () {
Steven Burrows9edc7e02016-08-29 11:52:07 +0100142
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100143 var source = this.get('source'),
144 target = this.get('target'),
145 sourceOnline = isLinkOnline(source),
146 targetOnline = isLinkOnline(target);
147
148 return (sourceOnline) && (targetOnline);
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100149 },
Steven Burrows86af4352016-11-16 18:19:12 -0600150 onChange: function () {
151 // Update class names when the model changes
152 if (this.el) {
153 this.el.attr('class', this.svgClassName());
154 }
155 },
Steven Burrows9edc7e02016-08-29 11:52:07 +0100156 enhance: function () {
157 var data = [],
158 point;
159
160 angular.forEach(this.collection.models, function (link) {
161 link.unenhance();
162 });
163
Steven Burrows86af4352016-11-16 18:19:12 -0600164 this.set('enhanced', true);
Steven Burrows9edc7e02016-08-29 11:52:07 +0100165
Steven Burrows1c5c8612016-10-05 13:45:13 -0500166 if (showPort()) {
167 point = this.locatePortLabel();
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100168 angular.extend(point, {
Steven Burrows1c5c8612016-10-05 13:45:13 -0500169 id: 'topo-port-tgt',
170 num: this.get('portB')
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100171 });
172 data.push(point);
Steven Burrows1c5c8612016-10-05 13:45:13 -0500173
174 if (this.get('portA')) {
175 point = this.locatePortLabel(1);
176 angular.extend(point, {
177 id: 'topo-port-src',
178 num: this.get('portA')
179 });
180 data.push(point);
181 }
182
183 var entering = d3.select('#topo-portLabels')
184 .selectAll('.portLabel')
Steven Burrowsa3fca812016-10-14 15:11:04 -0500185 .data(data)
186 .enter().append('g')
Steven Burrows1c5c8612016-10-05 13:45:13 -0500187 .classed('portLabel', true)
Steven Burrowsa3fca812016-10-14 15:11:04 -0500188 .attr('id', function (d) { return d.id; })
Steven Burrows1c5c8612016-10-05 13:45:13 -0500189
190 entering.each(function (d) {
191 var el = d3.select(this),
192 rect = el.append('rect'),
193 text = el.append('text').text(d.num);
194
195 var rectSize = rectAroundText(el);
196
197 rect.attr(rectSize)
198 .attr('rx', 2)
199 .attr('ry', 2);
200
201 text.attr('dy', linkLabelOffset)
202 .attr('text-anchor', 'middle');
203
204 el.attr('transform', sus.translate(d.x, d.y));
Steven Burrowsa3fca812016-10-14 15:11:04 -0500205
Steven Burrows1c5c8612016-10-05 13:45:13 -0500206 });
Steven Burrowsa3fca812016-10-14 15:11:04 -0500207
208 this.setScale();
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100209 }
Steven Burrows9edc7e02016-08-29 11:52:07 +0100210 },
211 unenhance: function () {
Steven Burrows86af4352016-11-16 18:19:12 -0600212 this.set('enhanced', false);
Steven Burrows9edc7e02016-08-29 11:52:07 +0100213 d3.select('#topo-portLabels').selectAll('.portLabel').remove();
214 },
Steven Burrows86af4352016-11-16 18:19:12 -0600215 select: function () {
216 var ev = d3.event;
217
218 // TODO: if single selection clear selected devices, hosts, sub-regions
219 var s = Boolean(this.get('selected'));
220 // Clear all selected Items
221 _.each(this.collection.models, function (m) {
222 m.set('selected', false);
223 });
224
225 this.set('selected', !s);
226
227 var selected = this.collection.filter(function (m) {
228 return m.get('selected');
229 });
230
231 return selected;
232 },
233 showDetails: function () {
234 var selected = this.select(d3.event);
235
236 if (selected) {
237 t2lps.displayLink(this);
238 } else {
239 t2lps.hide();
240 }
241 },
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100242 locatePortLabel: function (src) {
Steven Burrowsa3fca812016-10-14 15:11:04 -0500243
244 var offset = 32 / (labelDim * t2zs.scale()),
245 sourceX = this.get('position').x1,
246 sourceY = this.get('position').y1,
247 targetX = this.get('position').x2,
248 targetY = this.get('position').y2,
249 nearX = src ? sourceX : targetX,
250 nearY = src ? sourceY : targetY,
251 farX = src ? targetX : sourceX,
252 farY = src ? targetY : sourceY;
Steven Burrows9edc7e02016-08-29 11:52:07 +0100253
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100254 function dist(x, y) {
255 return Math.sqrt(x * x + y * y);
256 }
Steven Burrows9edc7e02016-08-29 11:52:07 +0100257
258 var dx = farX - nearX,
259 dy = farY - nearY,
Steven Burrowsa3fca812016-10-14 15:11:04 -0500260 k = (32 * offset) / dist(dx, dy);
Steven Burrows9edc7e02016-08-29 11:52:07 +0100261
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100262 return { x: k * dx + nearX, y: k * dy + nearY };
Steven Burrows9edc7e02016-08-29 11:52:07 +0100263 },
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100264 restyleLinkElement: function (immediate) {
265 // this fn's job is to look at raw links and decide what svg classes
266 // need to be applied to the line element in the DOM
267 var th = ts.theme(),
268 el = this.el,
269 type = this.get('type'),
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100270 online = this.online(),
Steven Burrows583f4be2016-11-04 14:06:50 +0100271 modeCls = this.expected() ? '-inactive' : 'not-permitted',
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100272 delay = immediate ? 0 : 1000;
273
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100274 // NOTE: understand why el is sometimes undefined on addLink events...
275 // Investigated:
276 // el is undefined when it's a reverse link that is being added.
277 // updateLinks (which sets ldata.el) isn't called before this is called.
278 // Calling _updateLinks in addLinkUpdate fixes it, but there might be
279 // a more efficient way to fix it.
280 if (el && !el.empty()) {
281 el.classed('link', true);
282 el.classed(allLinkSubTypes, false);
283 el.classed(modeCls, !online);
284 el.classed(allLinkTypes, false);
285 if (type) {
286 el.classed(type, true);
287 }
288 el.transition()
289 .duration(delay)
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100290 .attr('stroke', linkConfig[th].baseColor);
Steven Burrowsa3fca812016-10-14 15:11:04 -0500291
292 this.setScale();
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100293 }
294 },
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100295 onEnter: function (el) {
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100296 var link = d3.select(el);
Steven Burrows9edc7e02016-08-29 11:52:07 +0100297
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100298 this.el = link;
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100299 this.restyleLinkElement();
300
Steven Burrowsa3fca812016-10-14 15:11:04 -0500301 // TODO: Needs improving - originally this was calculated
302 // from mouse position.
303 this.el.on('mouseover', this.enhance.bind(this));
304 this.el.on('mouseout', this.unenhance.bind(this));
Steven Burrows86af4352016-11-16 18:19:12 -0600305 this.el.on('click', this.showDetails.bind(this));
Steven Burrowsa3fca812016-10-14 15:11:04 -0500306
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100307 if (this.get('type') === 'hostLink') {
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100308 // sus.visible(link, api.showHosts());
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100309 }
Steven Burrows0616e802016-10-06 21:45:07 -0500310 },
311 setScale: function () {
Steven Burrowsa3fca812016-10-14 15:11:04 -0500312 var width = linkScale(widthRatio) / t2zs.scale();
Steven Burrows0616e802016-10-06 21:45:07 -0500313 this.el.style('stroke-width', width + 'px');
Steven Burrowsa3fca812016-10-14 15:11:04 -0500314
315 var labelScale = labelDim / (labelDim * t2zs.scale());
316
317 d3.select('#topo-portLabels')
318 .selectAll('.portLabel')
319 .selectAll('*')
320 .style('transform', 'scale(' + labelScale + ')');
321
Steven Burrows1c5c8612016-10-05 13:45:13 -0500322 },
323 update: function () {
Steven Burrows86af4352016-11-16 18:19:12 -0600324 if (this.get('enhanced')) {
Steven Burrows1c5c8612016-10-05 13:45:13 -0500325 this.enhance();
326 }
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100327 }
328 });
Steven Burrows57e24e92016-08-04 18:38:24 +0100329
330 var LinkCollection = Collection.extend({
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100331 model: LinkModel
Steven Burrows57e24e92016-08-04 18:38:24 +0100332 });
333
334 return new LinkCollection(data);
335 }
336
Steven Burrows1c5c8612016-10-05 13:45:13 -0500337 function showPort() {
338 return t2vs.getPortHighlighting();
339 }
340
Steven Burrows57e24e92016-08-04 18:38:24 +0100341 angular.module('ovTopo2')
342 .factory('Topo2LinkService',
Steven Burrowsdfa52b02016-09-02 13:50:43 +0100343 ['$log', 'Topo2Collection', 'Topo2Model',
Steven Burrows0616e802016-10-06 21:45:07 -0500344 'ThemeService', 'SvgUtilService', 'Topo2ZoomService',
Steven Burrows86af4352016-11-16 18:19:12 -0600345 'Topo2ViewService', 'Topo2LinkPanelService', 'FnService',
Steven Burrows1c5c8612016-10-05 13:45:13 -0500346 function (_$log_, _Collection_, _Model_, _ts_, _sus_,
Steven Burrows86af4352016-11-16 18:19:12 -0600347 _t2zs_, _t2vs_, _t2lps_, _fn_) {
Steven Burrows57e24e92016-08-04 18:38:24 +0100348
Steven Burrows6deb4ce2016-08-26 16:06:23 +0100349 $log = _$log_;
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100350 ts = _ts_;
Steven Burrows9edc7e02016-08-29 11:52:07 +0100351 sus = _sus_;
Steven Burrows0616e802016-10-06 21:45:07 -0500352 t2zs = _t2zs_;
Steven Burrows1c5c8612016-10-05 13:45:13 -0500353 t2vs = _t2vs_;
Steven Burrows57e24e92016-08-04 18:38:24 +0100354 Collection = _Collection_;
Steven Burrowsec1f45c2016-08-08 16:14:41 +0100355 Model = _Model_;
Steven Burrows86af4352016-11-16 18:19:12 -0600356 t2lps = _t2lps_;
357 fn = _fn_;
Steven Burrows57e24e92016-08-04 18:38:24 +0100358
359 return {
360 createLinkCollection: createLinkCollection
361 };
362 }
363 ]);
364
365})();