Steven Burrows
Committed by Gerrit Code Review

Updated fn-spec to include classNames

Removed Classnames file and added code to fn.js
Fixed typo dimentions to dimensions
Moved Device/Link logic from Topo2D3 into the model
Model now calls onChange when any property is changed via the set Method

WIP - Added d3 force layout for devices and lines

Change-Id: I4d1afd3cd4cecf2f719e27f4be5d1e874bd9e342
...@@ -386,6 +386,34 @@ ...@@ -386,6 +386,34 @@
386 } 386 }
387 387
388 388
389 + var hasOwn = {}.hasOwnProperty;
390 +
391 + function classNames () {
392 + var classes = [];
393 +
394 + for (var i = 0; i < arguments.length; i++) {
395 + var arg = arguments[i];
396 + if (!arg) continue;
397 +
398 + var argType = typeof arg;
399 +
400 + if (argType === 'string' || argType === 'number') {
401 + classes.push(arg);
402 + } else if (Array.isArray(arg)) {
403 + classes.push(classNames.apply(null, arg));
404 + } else if (argType === 'object') {
405 + for (var key in arg) {
406 + if (hasOwn.call(arg, key) && arg[key]) {
407 + classes.push(key);
408 + }
409 + }
410 + }
411 + }
412 +
413 + return classes.join(' ');
414 + }
415 +
416 +
389 angular.module('onosUtil') 417 angular.module('onosUtil')
390 .factory('FnService', 418 .factory('FnService',
391 ['$window', '$location', '$log', function (_$window_, $loc, _$log_) { 419 ['$window', '$location', '$log', function (_$window_, $loc, _$log_) {
...@@ -423,7 +451,8 @@ ...@@ -423,7 +451,8 @@
423 parseBitRate: parseBitRate, 451 parseBitRate: parseBitRate,
424 addToTrie: addToTrie, 452 addToTrie: addToTrie,
425 removeFromTrie: removeFromTrie, 453 removeFromTrie: removeFromTrie,
426 - trieLookup: trieLookup 454 + trieLookup: trieLookup,
455 + classNames: classNames
427 }; 456 };
428 }]); 457 }]);
429 458
......
...@@ -320,6 +320,7 @@ ...@@ -320,6 +320,7 @@
320 // updateLinks - subfunctions 320 // updateLinks - subfunctions
321 321
322 function linkEntering(d) { 322 function linkEntering(d) {
323 +
323 var link = d3.select(this); 324 var link = d3.select(this);
324 d.el = link; 325 d.el = link;
325 api.restyleLinkElement(d); 326 api.restyleLinkElement(d);
......
...@@ -103,6 +103,7 @@ ...@@ -103,6 +103,7 @@
103 // === EVENT HANDLERS 103 // === EVENT HANDLERS
104 104
105 function addDevice(data) { 105 function addDevice(data) {
106 + console.log(data);
106 var id = data.id, 107 var id = data.id,
107 d; 108 d;
108 109
...@@ -1044,7 +1045,7 @@ ...@@ -1044,7 +1045,7 @@
1044 updateLinks(); 1045 updateLinks();
1045 updateNodes(); 1046 updateNodes();
1046 } 1047 }
1047 - 1048 +
1048 angular.module('ovTopo') 1049 angular.module('ovTopo')
1049 .factory('TopoForceService', 1050 .factory('TopoForceService',
1050 ['$log', '$timeout', 'FnService', 'SvgUtilService', 1051 ['$log', '$timeout', 'FnService', 'SvgUtilService',
......
...@@ -14,7 +14,6 @@ ...@@ -14,7 +14,6 @@
14 * limitations under the License. 14 * limitations under the License.
15 */ 15 */
16 16
17 -
18 /* 17 /*
19 ONOS GUI -- Topology View (theme) -- CSS file 18 ONOS GUI -- Topology View (theme) -- CSS file
20 */ 19 */
...@@ -22,8 +21,7 @@ ...@@ -22,8 +21,7 @@
22 /* --- Base SVG Layer --- */ 21 /* --- Base SVG Layer --- */
23 22
24 #ov-topo2 svg { 23 #ov-topo2 svg {
25 - /*background-color: #f4f4f4;*/ 24 + background-color: #f4f4f4;
26 - background-color: goldenrod; /* just for testing */
27 } 25 }
28 26
29 /* --- "No Devices" Layer --- */ 27 /* --- "No Devices" Layer --- */
...@@ -32,15 +30,355 @@ ...@@ -32,15 +30,355 @@
32 fill: #db7773; 30 fill: #db7773;
33 } 31 }
34 32
35 -#ov-topo2 svg #topo2-noDevsLayer text { 33 +#ov-topo2 svg #topo-noDevsLayer text {
36 fill: #7e9aa8; 34 fill: #7e9aa8;
37 } 35 }
38 36
39 /* --- Topo Map --- */ 37 /* --- Topo Map --- */
40 38
41 -#ov-topo2 svg #topo2-map { 39 +#ov-topo2 svg #topo-map {
42 stroke-width: 2px; 40 stroke-width: 2px;
43 stroke: #f4f4f4; 41 stroke: #f4f4f4;
44 fill: #e5e5e6; 42 fill: #e5e5e6;
45 } 43 }
46 44
45 +/* --- general topo-panel styling --- */
46 +
47 +.topo-p svg {
48 + background: #c0242b;
49 +}
50 +
51 +.topo-p svg .glyph {
52 + fill: #ffffff;
53 +}
54 +
55 +.topo-p hr {
56 + background-color: #cccccc;
57 +}
58 +
59 +#topo-p-detail svg {
60 + background: none;
61 +}
62 +
63 +#topo-p-detail .header svg .glyph {
64 + fill: #c0242b;
65 +}
66 +
67 +
68 +/* --- Topo Instance Panel --- */
69 +
70 +#topo-p-instance svg rect {
71 + stroke-width: 0;
72 + fill: #fbfbfb;
73 +}
74 +
75 +/* body of an instance */
76 +#topo-p-instance .online svg rect {
77 + opacity: 1;
78 + fill: #fbfbfb;
79 +}
80 +
81 +#topo-p-instance svg .glyph {
82 + fill: #fff;
83 +}
84 +#topo-p-instance .online svg .glyph {
85 + fill: #fff;
86 +}
87 +
88 +
89 +/* offline */
90 +#topo-p-instance svg .badgeIcon {
91 + opacity: 0.4;
92 + fill: #939598;
93 +}
94 +
95 +/* online */
96 +#topo-p-instance .online svg .badgeIcon {
97 + opacity: 1.0;
98 + fill: #939598;
99 +}
100 +#topo-p-instance .online svg .badgeIcon.bird {
101 + fill: #ffffff;
102 +}
103 +
104 +#topo-p-instance svg .readyBadge {
105 + visibility: hidden;
106 +}
107 +#topo-p-instance .ready svg .readyBadge {
108 + visibility: visible;
109 +}
110 +
111 +#topo-p-instance svg text {
112 + text-anchor: left;
113 + opacity: 0.5;
114 + fill: #3c3a3a;
115 +}
116 +
117 +#topo-p-instance .online svg text {
118 + opacity: 1.0;
119 + fill: #3c3a3a;
120 +}
121 +
122 +#topo-p-instance .onosInst.mastership {
123 + opacity: 0.3;
124 +}
125 +#topo-p-instance .onosInst.mastership.affinity {
126 + opacity: 1.0;
127 +}
128 +#topo-p-instance .onosInst.mastership.affinity svg rect {
129 + filter: url(#blue-glow);
130 +}
131 +
132 +.firefox #topo-p-instance .onosInst.mastership.affinity svg rect {
133 + filter: url("data:image/svg+xml;utf8, <svg xmlns = \'http://www.w3.org/2000/svg\'><filter x=\"-50%\" y=\"-50%\" width=\"200%\" height=\"200%\" id=\"blue-glow\"><feColorMatrix type=\"matrix\" values=\"0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.7 0 0 0 1 0 \"></feColorMatrix><feGaussianBlur stdDeviation=\"3\" result=\"coloredBlur\"></feGaussianBlur><feMerge><feMergeNode in=\"coloredBlur\"></feMergeNode><feMergeNode in=\"SourceGraphic\"></feMergeNode></feMerge></filter></svg>#blue-glow");
134 +}
135 +
136 +/* --- Topo Nodes --- */
137 +
138 +#ov-topo2 svg .suppressed {
139 + opacity: 0.5 !important;
140 +}
141 +
142 +#ov-topo2 svg .suppressedmax {
143 + opacity: 0.2 !important;
144 +}
145 +
146 +/* Device Nodes */
147 +
148 +/* note: device without the 'online' class is offline */
149 +#ov-topo2 svg .node.device rect {
150 + /* TODO: theme */
151 + fill: #f0f0f0;
152 +}
153 +#ov-topo2 svg .node.device text {
154 + /*TODO: theme*/
155 + fill: #bbb;
156 +}
157 +#ov-topo2 svg .node.device use {
158 + /*TODO: theme*/
159 + fill: #777;
160 +}
161 +
162 +
163 +#ov-topo2 svg .node.device.online rect {
164 + fill: #ffffff;
165 +}
166 +#ov-topo2 svg .node.device.online text {
167 + fill: #3c3a3a;
168 +}
169 +#ov-topo2 svg .node.device.online use {
170 + /* NOTE: this gets overridden programatically */
171 + fill: #454545;
172 +}
173 +
174 +
175 +#ov-topo2 svg .node.device.selected rect {
176 + stroke-width: 2.0;
177 + stroke: #009fdb;
178 +}
179 +
180 +/* Badges */
181 +/* (... works for bothand dark themes...) */
182 +#ov-topo2 svg .node .badge circle {
183 + stroke: #aaa;
184 +}
185 +
186 +#ov-topo2 svg .node .badge.badgeInfo circle {
187 + fill: #99d;
188 +}
189 +
190 +#ov-topo2 svg .node .badge.badgeWarn circle {
191 + fill: #da2;
192 +}
193 +
194 +#ov-topo2 svg .node .badge.badgeError circle {
195 + fill: #e44;
196 +}
197 +
198 +#ov-topo2 svg .node .badge use {
199 + fill: white !important;
200 +}
201 +
202 +#ov-topo2 svg .node .badge.badgeInfo use {
203 + fill: #448;
204 +}
205 +
206 +#ov-topo2 svg .node .badge text {
207 + fill: white !important;
208 +}
209 +
210 +#ov-topo2 svg .node .badge.badgeInfo text {
211 + fill: #448;
212 +}
213 +
214 +/* Host Nodes */
215 +
216 +#ov-topo2 svg .node.host {
217 +}
218 +
219 +#ov-topo2 svg .node.host text {
220 + stroke: none;
221 + font: 9pt sans-serif;
222 + fill: #846;
223 +}
224 +
225 +#ov-topo2 svg .node.host circle {
226 + stroke: #a3a596;
227 + fill: #e0dfd6;
228 +}
229 +#ov-topo2 svg .node.host.selected .hostIcon > circle {
230 + stroke-width: 2.0;
231 + stroke: #009fdb;
232 +}
233 +
234 +#ov-topo2 svg .node.host use {
235 + fill: #3c3a3a;
236 +}
237 +
238 +/* --- Topo Links --- */
239 +
240 +#ov-topo2 svg .link {
241 + opacity: .9;
242 +}
243 +
244 +#ov-topo2 svg .link.selected,
245 +#ov-topo2 svg .link.enhanced {
246 + stroke-width: 3.5;
247 + stroke: #009fdb;
248 +}
249 +
250 +#ov-topo2 svg .link.inactive {
251 + opacity: .5;
252 + stroke-dasharray: 8 4;
253 +}
254 +/* TODO: Review for not-permitted links */
255 +#ov-topo2 svg .link.not-permitted {
256 + stroke: rgb(255,0,0);
257 + stroke-width: 5.0;
258 + stroke-dasharray: 8 4;
259 +}
260 +
261 +#ov-topo2 svg .link.secondary {
262 + stroke-width: 3px;
263 + stroke: rgba(0,153,51,0.5);
264 +}
265 +
266 +/* Port traffic color visualization for Kbps, Mbps, and Gbps */
267 +
268 +#ov-topo2 svg .link.secondary.port-traffic-Kbps {
269 + stroke: rgb(0,153,51);
270 + stroke-width: 5.0;
271 +}
272 +
273 +#ov-topo2 svg .link.secondary.port-traffic-Mbps {
274 + stroke: rgb(128,145,27);
275 + stroke-width: 6.5;
276 +}
277 +
278 +#ov-topo2 svg .link.secondary.port-traffic-Gbps {
279 + stroke: rgb(255, 137, 3);
280 + stroke-width: 8.0;
281 +}
282 +
283 +#ov-topo2 svg .link.secondary.port-traffic-Gbps-choked {
284 + stroke: rgb(183, 30, 21);
285 + stroke-width: 8.0;
286 +}
287 +
288 +
289 +
290 +#ov-topo2 svg .link.animated {
291 + stroke-dasharray: 8 5;
292 + animation: ants 5s infinite linear;
293 + /* below line could be added via Javascript, based on path, if we cared
294 + * enough about the direction of ant-flow
295 + */
296 + /*animation-direction: reverse;*/
297 +}
298 +@keyframes ants {
299 + from {
300 + stroke-dashoffset: 0;
301 + }
302 + to {
303 + stroke-dashoffset: 400;
304 + }
305 +}
306 +
307 +#ov-topo2 svg .link.primary {
308 + stroke-width: 4px;
309 + stroke: #ffA300;
310 +}
311 +
312 +#ov-topo2 svg .link.secondary.optical {
313 + stroke-width: 4px;
314 + stroke: rgba(128,64,255,0.5);
315 +}
316 +
317 +#ov-topo2 svg .link.primary.optical {
318 + stroke-width: 6px;
319 + stroke: #74f;
320 +}
321 +
322 +/* Link Labels */
323 +#ov-topo2 svg .linkLabel rect {
324 + stroke: none;
325 + fill: #ffffff;
326 +}
327 +
328 +#ov-topo2 svg .linkLabel text {
329 + fill: #444;
330 +}
331 +
332 +/* Port Labels */
333 +
334 +#ov-topo2 svg .portLabel rect {
335 + stroke: #a3a596;
336 + fill: #ffffff;
337 +}
338 +
339 +#ov-topo2 svg .portLabel text {
340 + fill: #444;
341 +}
342 +
343 +/* Number of Links Labels */
344 +
345 +
346 +#ov-topo2 text.numLinkText {
347 + fill: #444;
348 +}
349 +
350 +/* ------------------------------------------------- */
351 +/* Sprite Layer */
352 +
353 +#ov-topo2 svg #topo-sprites .gold1 use {
354 + stroke: #fda;
355 + fill: none;
356 +}
357 +#ov-topo2 svg #topo-sprites .gold1 text {
358 + fill: #eda;
359 +}
360 +
361 +#ov-topo2 svg #topo-sprites .blue1 use {
362 + stroke: #bbd;
363 + fill: none;
364 +}
365 +#ov-topo2 svg #topo-sprites .blue1 text {
366 + fill: #cce;
367 +}
368 +
369 +#ov-topo2 svg #topo-sprites .gray1 use {
370 + stroke: #ccc;
371 + fill: none;
372 +}
373 +#ov-topo2 svg #topo-sprites .gray1 text {
374 + fill: #ddd;
375 +}
376 +
377 +/* fills */
378 +#ov-topo2 svg #topo-sprites use.fill-gray2 {
379 + fill: #eee;
380 +}
381 +
382 +#ov-topo2 svg #topo-sprites use.fill-blue2 {
383 + fill: #bce;
384 +}
......
1 <!-- Topology View partial HTML --> 1 <!-- Topology View partial HTML -->
2 <div id="ov-topo2"> 2 <div id="ov-topo2">
3 - <div id="topo2tmp"> 3 +
4 + <!-- <div id="topo2tmp">
4 <div class="parentRegion"> 5 <div class="parentRegion">
5 Parent Region: <span> - </span> 6 Parent Region: <span> - </span>
6 </div> 7 </div>
...@@ -27,7 +28,7 @@ ...@@ -27,7 +28,7 @@
27 <h4>Peers</h4> 28 <h4>Peers</h4>
28 <div></div> 29 <div></div>
29 </div> 30 </div>
30 - </div> 31 + </div> -->
31 32
32 <!-- Below here is good; Above here is temporary, for debugging --> 33 <!-- Below here is good; Above here is temporary, for debugging -->
33 34
......
...@@ -48,6 +48,7 @@ ...@@ -48,6 +48,7 @@
48 // callback invoked when the SVG view has been resized.. 48 // callback invoked when the SVG view has been resized..
49 function svgResized(s) { 49 function svgResized(s) {
50 $log.debug('topo2 view resized', s); 50 $log.debug('topo2 view resized', s);
51 + t2fs.newDim([s.width, s.height]);
51 } 52 }
52 53
53 function setUpKeys(overlayKeys) { 54 function setUpKeys(overlayKeys) {
...@@ -68,7 +69,7 @@ ...@@ -68,7 +69,7 @@
68 ps.setPrefs('topo_zoom', {tx:tr[0], ty:tr[1], sc:sc}); 69 ps.setPrefs('topo_zoom', {tx:tr[0], ty:tr[1], sc:sc});
69 70
70 // keep the map lines constant width while zooming 71 // keep the map lines constant width while zooming
71 - mapG.style('stroke-width', (2.0 / sc) + 'px'); 72 +// mapG.style('stroke-width', (2.0 / sc) + 'px');
72 } 73 }
73 74
74 function setUpZoom() { 75 function setUpZoom() {
......
...@@ -55,8 +55,6 @@ ...@@ -55,8 +55,6 @@
55 _this._byId[d.id] = model; 55 _this._byId[d.id] = model;
56 }); 56 });
57 } 57 }
58 -
59 -// this.sort();
60 }, 58 },
61 get: function (id) { 59 get: function (id) {
62 if (!id) { 60 if (!id) {
...@@ -77,7 +75,10 @@ ...@@ -77,7 +75,10 @@
77 _reset: function () { 75 _reset: function () {
78 this._byId = []; 76 this._byId = [];
79 this.models = []; 77 this.models = [];
80 - } 78 + },
79 + toJSON: function(options) {
80 + return this.models.map(function(model) { return model.toJSON(options); });
81 + },
81 }; 82 };
82 83
83 Collection.extend = function (protoProps, staticProps) { 84 Collection.extend = function (protoProps, staticProps) {
......
1 +/*
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 Layout Module.
19 +Module that contains the d3.force.layout logic
20 +*/
21 +
22 +(function () {
23 + 'use strict';
24 +
25 + var sus, is, ts;
26 +
27 + // internal state
28 + var deviceLabelIndex = 0,
29 + hostLabelIndex = 0;
30 +
31 + // configuration
32 + var devIconDim = 36,
33 + labelPad = 4,
34 + hostRadius = 14,
35 + badgeConfig = {
36 + radius: 12,
37 + yoff: 5,
38 + gdelta: 10
39 + },
40 + halfDevIcon = devIconDim / 2,
41 + devBadgeOff = { dx: -halfDevIcon, dy: -halfDevIcon },
42 + hostBadgeOff = { dx: -hostRadius, dy: -hostRadius },
43 + status = {
44 + i: 'badgeInfo',
45 + w: 'badgeWarn',
46 + e: 'badgeError'
47 + };
48 +
49 + // note: these are the device icon colors without affinity (no master)
50 + var dColTheme = {
51 + light: {
52 + online: '#444444',
53 + offline: '#cccccc'
54 + },
55 + dark: {
56 + // TODO: theme
57 + online: '#444444',
58 + offline: '#cccccc'
59 + }
60 + };
61 +
62 + function init() {}
63 +
64 + function renderBadge(node, bdg, boff) {
65 + var bsel,
66 + bcr = badgeConfig.radius,
67 + bcgd = badgeConfig.gdelta;
68 +
69 + node.select('g.badge').remove();
70 +
71 + bsel = node.append('g')
72 + .classed('badge', true)
73 + .classed(badgeStatus(bdg), true)
74 + .attr('transform', sus.translate(boff.dx, boff.dy));
75 +
76 + bsel.append('circle')
77 + .attr('r', bcr);
78 +
79 + if (bdg.txt) {
80 + bsel.append('text')
81 + .attr('dy', badgeConfig.yoff)
82 + .attr('text-anchor', 'middle')
83 + .text(bdg.txt);
84 + } else if (bdg.gid) {
85 + bsel.append('use')
86 + .attr({
87 + width: bcgd * 2,
88 + height: bcgd * 2,
89 + transform: sus.translate(-bcgd, -bcgd),
90 + 'xlink:href': '#' + bdg.gid
91 + });
92 + }
93 + }
94 +
95 + // TODO: Move to Device Model when working on the Exit Devices
96 + function updateDeviceRendering(d) {
97 + var node = d.el,
98 + bdg = d.badge,
99 + label = trimLabel(deviceLabel(d)),
100 + labelWidth;
101 +
102 + node.select('text').text(label);
103 + labelWidth = label ? computeLabelWidth(node) : 0;
104 +
105 + node.select('rect')
106 + .transition()
107 + .attr(iconBox(devIconDim, labelWidth));
108 +
109 + if (bdg) {
110 + renderBadge(node, bdg, devBadgeOff);
111 + }
112 + }
113 +
114 + function deviceEnter(device) {
115 + device.onEnter(this, device);
116 + }
117 +
118 + function hostLabel(d) {
119 + return d.get('id');
120 +
121 + // var idx = (hostLabelIndex < d.get('labels').length) ? hostLabelIndex : 0;
122 + // return d.labels[idx];
123 + }
124 +
125 + function hostEnter(d) {
126 + var node = d3.select(this),
127 + gid = d.get('type') || 'unknown',
128 + textDy = hostRadius + 10;
129 +
130 + d.el = node;
131 + // sus.visible(node, api.showHosts());
132 +
133 + is.addHostIcon(node, hostRadius, gid);
134 +
135 + node.append('text')
136 + .text(hostLabel)
137 + .attr('dy', textDy)
138 + .attr('text-anchor', 'middle');
139 + }
140 +
141 + function linkEntering(link) {
142 + link.onEnter(this);
143 + }
144 +
145 + angular.module('ovTopo2')
146 + .factory('Topo2D3Service',
147 + ['SvgUtilService', 'IconService', 'ThemeService',
148 +
149 + function (_sus_, _is_, _ts_) {
150 + sus = _sus_;
151 + is = _is_;
152 + ts = _ts_;
153 +
154 + return {
155 + init: init,
156 + deviceEnter: deviceEnter,
157 + hostEnter: hostEnter,
158 + linkEntering: linkEntering
159 + }
160 + }
161 + ]
162 +);
163 +})();
...@@ -22,16 +22,37 @@ ...@@ -22,16 +22,37 @@
22 (function () { 22 (function () {
23 'use strict'; 23 'use strict';
24 24
25 - var Collection, Model; 25 + var Collection, Model, is, sus, ts, t2vs;
26 +
27 + var remappedDeviceTypes = {
28 + virtual: 'cord'
29 + };
30 +
31 + // configuration
32 + var devIconDim = 36,
33 + labelPad = 10,
34 + hostRadius = 14,
35 + badgeConfig = {
36 + radius: 12,
37 + yoff: 5,
38 + gdelta: 10
39 + },
40 + halfDevIcon = devIconDim / 2,
41 + devBadgeOff = { dx: -halfDevIcon, dy: -halfDevIcon },
42 + hostBadgeOff = { dx: -hostRadius, dy: -hostRadius },
43 + status = {
44 + i: 'badgeInfo',
45 + w: 'badgeWarn',
46 + e: 'badgeError'
47 + },
48 + deviceLabelIndex = 0;
26 49
27 function createDeviceCollection(data, region) { 50 function createDeviceCollection(data, region) {
28 51
29 var DeviceCollection = Collection.extend({ 52 var DeviceCollection = Collection.extend({
30 model: Model, 53 model: Model,
31 - get: function () {},
32 comparator: function(a, b) { 54 comparator: function(a, b) {
33 - 55 + var order = region.get('layerOrder');
34 - var order = region.layerOrder;
35 return order.indexOf(a.get('layer')) - order.indexOf(b.get('layer')); 56 return order.indexOf(a.get('layer')) - order.indexOf(b.get('layer'));
36 } 57 }
37 }); 58 });
...@@ -49,14 +70,106 @@ ...@@ -49,14 +70,106 @@
49 return deviceCollection; 70 return deviceCollection;
50 } 71 }
51 72
73 + function mapDeviceTypeToGlyph(type) {
74 + return remappedDeviceTypes[type] || type || 'unknown';
75 + }
76 +
77 + function deviceLabel(d) {
78 + //TODO: Device Json is missing labels array
79 + return "";
80 + var labels = this.get('labels'),
81 + idx = (deviceLabelIndex < labels.length) ? deviceLabelIndex : 0;
82 + return labels[idx];
83 + }
84 +
85 + function trimLabel(label) {
86 + return (label && label.trim()) || '';
87 + }
88 +
89 + function computeLabelWidth() {
90 + var text = this.select('text'),
91 + box = text.node().getBBox();
92 + return box.width + labelPad * 2;
93 + }
94 +
95 + function iconBox(dim, labelWidth) {
96 + return {
97 + x: -dim / 2,
98 + y: -dim / 2,
99 + width: dim + labelWidth,
100 + height: dim
101 + }
102 + }
103 +
104 + function deviceGlyphColor(d) {
105 +
106 + var o = this.node.online,
107 + id = "127.0.0.1", // TODO: This should be from node.master
108 + otag = o ? 'online' : 'offline';
109 + return o ? sus.cat7().getColor(id, 0, ts.theme())
110 + : dColTheme[ts.theme()][otag];
111 + }
112 +
113 + function setDeviceColor() {
114 + this.el.select('use')
115 + .style('fill', this.deviceGlyphColor());
116 + }
117 +
52 angular.module('ovTopo2') 118 angular.module('ovTopo2')
53 .factory('Topo2DeviceService', 119 .factory('Topo2DeviceService',
54 - ['Topo2Collection', 'Topo2Model', 120 + ['Topo2Collection', 'Topo2NodeModel', 'IconService', 'SvgUtilService',
121 + 'ThemeService', 'Topo2ViewService',
55 122
56 - function (_Collection_, _Model_) { 123 + function (_Collection_, _NodeModel_, _is_, _sus_, _ts_, classnames, _t2vs_) {
57 124
125 + t2vs = _t2vs_;
126 + is = _is_;
127 + sus = _sus_;
128 + ts = _ts_;
58 Collection = _Collection_; 129 Collection = _Collection_;
59 - Model = _Model_.extend({}); 130 +
131 + Model = _NodeModel_.extend({
132 + initialize: function () {
133 + this.set('weight', 0);
134 + this.constructor.__super__.initialize.apply(this, arguments);
135 + },
136 + nodeType: 'device',
137 + deviceLabel: deviceLabel,
138 + deviceGlyphColor: deviceGlyphColor,
139 + mapDeviceTypeToGlyph: mapDeviceTypeToGlyph,
140 + trimLabel: trimLabel,
141 + setDeviceColor: setDeviceColor,
142 + onEnter: function (el) {
143 +
144 + var node = d3.select(el),
145 + glyphId = mapDeviceTypeToGlyph(this.get('type')),
146 + label = trimLabel(this.deviceLabel()),
147 + rect, text, glyph, labelWidth;
148 +
149 + this.el = node;
150 +
151 + rect = node.append('rect');
152 +
153 + text = node.append('text').text(label)
154 + .attr('text-anchor', 'left')
155 + .attr('y', '0.3em')
156 + .attr('x', halfDevIcon + labelPad);
157 +
158 + glyph = is.addDeviceIcon(node, glyphId, devIconDim);
159 +
160 + labelWidth = label ? computeLabelWidth(node) : 0;
161 +
162 + rect.attr(iconBox(devIconDim, labelWidth));
163 + glyph.attr(iconBox(devIconDim, 0));
164 +
165 + node.attr('transform', sus.translate(-halfDevIcon, -halfDevIcon));
166 + this.render();
167 + },
168 + onExit: function () {},
169 + render: function () {
170 + this.setDeviceColor();
171 + }
172 + });
60 173
61 return { 174 return {
62 createDeviceCollection: createDeviceCollection 175 createDeviceCollection: createDeviceCollection
......
...@@ -60,62 +60,17 @@ ...@@ -60,62 +60,17 @@
60 linkLabel, 60 linkLabel,
61 node; 61 node;
62 62
63 - var $log, wss, t2is, t2rs; 63 + var $log, wss, t2is, t2rs, t2ls, t2vs;
64 + var svg, forceG, uplink, dim, opts;
64 65
65 // ========================== Helper Functions 66 // ========================== Helper Functions
66 67
67 - function init(_svg_, forceG, _uplink_, _dim_, opts) { 68 + function init(_svg_, _forceG_, _uplink_, _dim_, _opts_) {
68 - 69 + svg = _svg_;
69 - $log.debug('Initialize topo force layout'); 70 + forceG = _forceG_;
70 - 71 + uplink = _uplink_;
71 - nodeG = forceG.append('g').attr('id', 'topo-nodes'); 72 + dim = _dim_;
72 - node = nodeG.selectAll('.node'); 73 + opts = _opts_
73 -
74 - linkG = forceG.append('g').attr('id', 'topo-links');
75 - linkLabelG = forceG.append('g').attr('id', 'topo-linkLabels');
76 - numLinkLblsG = forceG.append('g').attr('id', 'topo-numLinkLabels');
77 - nodeG = forceG.append('g').attr('id', 'topo-nodes');
78 - portLabelG = forceG.append('g').attr('id', 'topo-portLabels');
79 -
80 - link = linkG.selectAll('.link');
81 - linkLabel = linkLabelG.selectAll('.linkLabel');
82 - node = nodeG.selectAll('.node');
83 -
84 - var width = 640,
85 - height = 480;
86 -
87 - var nodes = [
88 - { x: width/3, y: height/2 },
89 - { x: 2*width/3, y: height/2 }
90 - ];
91 -
92 - var links = [
93 - { source: 0, target: 1 }
94 - ];
95 -
96 - var svg = d3.select('body').append('svg')
97 - .attr('width', width)
98 - .attr('height', height);
99 -
100 - var force = d3.layout.force()
101 - .size([width, height])
102 - .nodes(nodes)
103 - .links(links);
104 -
105 - force.linkDistance(width/2);
106 -
107 -
108 - var link = svg.selectAll('.link')
109 - .data(links)
110 - .enter().append('line')
111 - .attr('class', 'link');
112 -
113 - var node = svg.selectAll('.node')
114 - .data(nodes)
115 - .enter().append('circle')
116 - .attr('class', 'node');
117 -
118 - force.start();
119 } 74 }
120 75
121 function destroy() { 76 function destroy() {
...@@ -206,6 +161,9 @@ ...@@ -206,6 +161,9 @@
206 $log.debug('>> topo2CurrentRegion event:', data); 161 $log.debug('>> topo2CurrentRegion event:', data);
207 doTmpCurrentRegion(data); 162 doTmpCurrentRegion(data);
208 t2rs.addRegion(data); 163 t2rs.addRegion(data);
164 + t2ls.init(svg, forceG, uplink, dim, opts);
165 + t2ls.update();
166 + t2ls.start();
209 } 167 }
210 168
211 function topo2PeerRegions(data) { 169 function topo2PeerRegions(data) {
...@@ -257,20 +215,37 @@ ...@@ -257,20 +215,37 @@
257 // link.classed(cls, b); 215 // link.classed(cls, b);
258 } 216 }
259 217
218 + function newDim(_dim_) {
219 + dim = _dim_;
220 + t2vs.newDim(dim);
221 + // force.size(dim);
222 + // tms.newDim(dim);
223 + t2ls.setDimensions();
224 + }
225 +
226 + function getDim() {
227 + return dim;
228 + }
229 +
260 // ========================== Main Service Definition 230 // ========================== Main Service Definition
261 231
262 angular.module('ovTopo2') 232 angular.module('ovTopo2')
263 .factory('Topo2ForceService', 233 .factory('Topo2ForceService',
264 ['$log', 'WebSocketService', 'Topo2InstanceService', 'Topo2RegionService', 234 ['$log', 'WebSocketService', 'Topo2InstanceService', 'Topo2RegionService',
265 - function (_$log_, _wss_, _t2is_, _t2rs_) { 235 + 'Topo2LayoutService', 'Topo2ViewService',
236 + function (_$log_, _wss_, _t2is_, _t2rs_, _t2ls_, _t2vs_) {
237 +
266 $log = _$log_; 238 $log = _$log_;
267 wss = _wss_; 239 wss = _wss_;
268 t2is = _t2is_; 240 t2is = _t2is_;
269 t2rs = _t2rs_; 241 t2rs = _t2rs_;
242 + t2ls = _t2ls_;
243 + t2vs = _t2vs_;
270 244
271 return { 245 return {
272 246
273 init: init, 247 init: init,
248 + newDim: newDim,
274 249
275 destroy: destroy, 250 destroy: destroy,
276 topo2AllInstances: allInstances, 251 topo2AllInstances: allInstances,
......
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
22 (function () { 22 (function () {
23 'use strict'; 23 'use strict';
24 24
25 - var Collection, Model; 25 + var Collection, Model, t2vs;
26 26
27 function createHostCollection(data, region) { 27 function createHostCollection(data, region) {
28 28
...@@ -42,17 +42,21 @@ ...@@ -42,17 +42,21 @@
42 42
43 angular.module('ovTopo2') 43 angular.module('ovTopo2')
44 .factory('Topo2HostService', 44 .factory('Topo2HostService',
45 - ['Topo2Collection', 'Topo2Model', 45 + [
46 + 'Topo2Collection', 'Topo2NodeModel', 'Topo2ViewService',
47 + function (_Collection_, _NodeModel_, classnames, _t2vs_) {
46 48
47 - function (_Collection_, _Model_) { 49 + t2vs = _t2vs_;
50 + Collection = _Collection_;
48 51
49 - Collection = _Collection_; 52 + Model = _NodeModel_.extend({
50 - Model = _Model_.extend(); 53 + nodeType: 'host'
54 + });
51 55
52 - return { 56 + return {
53 - createHostCollection: createHostCollection 57 + createHostCollection: createHostCollection
54 - }; 58 + };
55 - } 59 + }
56 - ]); 60 + ]);
57 61
58 })(); 62 })();
......
1 +/*
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 Layout Module.
19 + Module that contains the d3.force.layout logic
20 + */
21 +
22 +(function () {
23 + 'use strict';
24 +
25 + var $log, sus, t2rs, t2d3, t2vs;
26 +
27 + var linkG, linkLabelG, numLinkLabelsG, nodeG, portLabelG;
28 + var link, linkLabel, node;
29 +
30 + var nodes, links;
31 +
32 + var force;
33 +
34 + // default settings for force layout
35 + var defaultSettings = {
36 + gravity: 0.4,
37 + friction: 0.7,
38 + charge: {
39 + // note: key is node.class
40 + device: -8000,
41 + host: -5000,
42 + _def_: -12000
43 + },
44 + linkDistance: {
45 + // note: key is link.type
46 + direct: 100,
47 + optical: 120,
48 + hostLink: 3,
49 + _def_: 50
50 + },
51 + linkStrength: {
52 + // note: key is link.type
53 + // range: {0.0 ... 1.0}
54 + //direct: 1.0,
55 + //optical: 1.0,
56 + //hostLink: 1.0,
57 + _def_: 1.0
58 + }
59 + };
60 +
61 + // configuration
62 + var linkConfig = {
63 + light: {
64 + baseColor: '#939598',
65 + inColor: '#66f',
66 + outColor: '#f00'
67 + },
68 + dark: {
69 + // TODO : theme
70 + baseColor: '#939598',
71 + inColor: '#66f',
72 + outColor: '#f00'
73 + },
74 + inWidth: 12,
75 + outWidth: 10
76 + };
77 +
78 + // internal state
79 + var settings, // merged default settings and options
80 + force, // force layout object
81 + drag, // drag behavior handler
82 + network = {
83 + nodes: [],
84 + links: [],
85 + linksByDevice: {},
86 + lookup: {},
87 + revLinkToKey: {}
88 + },
89 + lu, // shorthand for lookup
90 + rlk, // shorthand for revLinktoKey
91 + showHosts = false, // whether hosts are displayed
92 + showOffline = true, // whether offline devices are displayed
93 + nodeLock = false, // whether nodes can be dragged or not (locked)
94 + fTimer, // timer for delayed force layout
95 + fNodesTimer, // timer for delayed nodes update
96 + fLinksTimer, // timer for delayed links update
97 + dim, // the dimensions of the force layout [w,h]
98 + linkNums = []; // array of link number labels
99 +
100 + var tickStuff = {
101 + nodeAttr: {
102 + transform: function (d) {
103 + var dx = isNaN(d.x) ? 0 : d.x,
104 + dy = isNaN(d.y) ? 0 : d.y;
105 + return sus.translate(dx, dy);
106 + }
107 + },
108 + linkAttr: {
109 + x1: function (d) { return d.get('position').x1; },
110 + y1: function (d) { return d.get('position').y1; },
111 + x2: function (d) { return d.get('position').x2; },
112 + y2: function (d) { return d.get('position').y2; }
113 + },
114 + linkLabelAttr: {
115 + transform: function (d) {
116 + var lnk = tms.findLinkById(d.get('key'));
117 + if (lnk) {
118 + return t2d3.transformLabel(lnk.get('position'));
119 + }
120 + }
121 + }
122 + };
123 +
124 + function init(_svg_, forceG, _uplink_, _dim_, opts) {
125 +
126 + $log.debug("Initialising Topology Layout");
127 +
128 + settings = angular.extend({}, defaultSettings, opts);
129 +
130 + linkG = forceG.append('g').attr('id', 'topo-links');
131 + linkLabelG = forceG.append('g').attr('id', 'topo-linkLabels');
132 + numLinkLabelsG = forceG.append('g').attr('id', 'topo-numLinkLabels');
133 + nodeG = forceG.append('g').attr('id', 'topo-nodes');
134 + portLabelG = forceG.append('g').attr('id', 'topo-portLabels');
135 +
136 + link = linkG.selectAll('.link');
137 + linkLabel = linkLabelG.selectAll('.linkLabel');
138 + node = nodeG.selectAll('.node');
139 +
140 + force = d3.layout.force()
141 + .size(t2vs.getDimensions())
142 + .nodes(t2rs.regionNodes())
143 + .links(t2rs.regionLinks())
144 + .gravity(settings.gravity)
145 + .friction(settings.friction)
146 + .charge(settings.charge._def_)
147 + .linkDistance(settings.linkDistance._def_)
148 + .linkStrength(settings.linkStrength._def_)
149 + .on('tick', tick);
150 + }
151 +
152 + function tick() {
153 + // guard against null (which can happen when our view pages out)...
154 + if (node && node.size()) {
155 + node.attr(tickStuff.nodeAttr);
156 + }
157 + if (link && link.size()) {
158 + link.call(calcPosition)
159 + .attr(tickStuff.linkAttr);
160 + // t2d3.applyNumLinkLabels(linkNums, numLinkLabelsG);
161 + }
162 + if (linkLabel && linkLabel.size()) {
163 + linkLabel.attr(tickStuff.linkLabelAttr);
164 + }
165 + }
166 +
167 + function update() {
168 + _updateNodes();
169 + _updateLinks();
170 + }
171 +
172 + function _updateNodes() {
173 +
174 + var regionNodes = t2rs.regionNodes();
175 +
176 + // select all the nodes in the layout:
177 + node = nodeG.selectAll('.node')
178 + .data(regionNodes, function (d) { return d.get('id'); });
179 +
180 + var entering = node.enter()
181 + .append('g')
182 + .attr({
183 + id: function (d) { return sus.safeId(d.get('id')); },
184 + class: function (d) { return d.svgClassName() },
185 + transform: function (d) {
186 + // Need to guard against NaN here ??
187 + return sus.translate(d.node.x, d.node.y);
188 + },
189 + opacity: 0
190 + })
191 + // .on('mouseover', tss.nodeMouseOver)
192 + // .on('mouseout', tss.nodeMouseOut)
193 + .transition()
194 + .attr('opacity', 1);
195 +
196 + entering.filter('.device').each(t2d3.deviceEnter);
197 + entering.filter('.host').each(t2d3.hostEnter);
198 +
199 + // operate on both existing and new nodes:
200 + // node.filter('.device').each(function (device) {
201 + // t2d3.updateDeviceColors(device);
202 + // });
203 + }
204 +
205 + function _updateLinks() {
206 +
207 + // var th = ts.theme();
208 + var regionLinks = t2rs.regionLinks();
209 +
210 + link = linkG.selectAll('.link')
211 + .data(regionLinks, function (d) { return d.get('key'); });
212 +
213 + // operate on existing links:
214 + link.each(function (d) {
215 + // this is supposed to be an existing link, but we have observed
216 + // occasions (where links are deleted and added rapidly?) where
217 + // the DOM element has not been defined. So protect against that...
218 + if (d.el) {
219 + restyleLinkElement(d, true);
220 + }
221 + });
222 +
223 + // operate on entering links:
224 + var entering = link.enter()
225 + .append('line')
226 + .call(calcPosition)
227 + .attr({
228 + x1: function (d) { return d.get('position').x1; },
229 + y1: function (d) { return d.get('position').y1; },
230 + x2: function (d) { return d.get('position').x2; },
231 + y2: function (d) { return d.get('position').y2; },
232 + stroke: linkConfig['light'].inColor,
233 + 'stroke-width': linkConfig.inWidth
234 + });
235 +
236 + entering.each(t2d3.linkEntering);
237 +
238 + // operate on both existing and new links:
239 + //link.each(...)
240 +
241 + // add labels for how many links are in a thick line
242 + // t2d3.applyNumLinkLabels(linkNums, numLinkLabelsG);
243 +
244 + // apply or remove labels
245 + // t2d3.applyLinkLabels();
246 +
247 + // operate on exiting links:
248 + link.exit()
249 + .attr('stroke-dasharray', '3 3')
250 + .attr('stroke', linkConfig['light'].outColor)
251 + .style('opacity', 0.5)
252 + .transition()
253 + .duration(1500)
254 + .attr({
255 + 'stroke-dasharray': '3 12',
256 + 'stroke-width': linkConfig.outWidth
257 + })
258 + .style('opacity', 0.0)
259 + .remove();
260 + }
261 +
262 + function calcPosition() {
263 + var lines = this,
264 + linkSrcId,
265 + linkNums = [];
266 +
267 + lines.each(function (d) {
268 + if (d.get('type') === 'hostLink') {
269 + d.set('position', getDefaultPos(d));
270 + }
271 + });
272 +
273 + function normalizeLinkSrc(link) {
274 + // ensure source device is consistent across set of links
275 + // temporary measure until link modeling is refactored
276 + if (!linkSrcId) {
277 + linkSrcId = link.source.id;
278 + return false;
279 + }
280 +
281 + return link.source.id !== linkSrcId;
282 + }
283 +
284 + lines.each(function (d) {
285 + d.set('position', getDefaultPos(d));
286 + });
287 + }
288 +
289 + function getDefaultPos(link) {
290 +
291 + return {
292 + x1: link.get('source').x,
293 + y1: link.get('source').y,
294 + x2: link.get('target').x,
295 + y2: link.get('target').y
296 + };
297 + }
298 +
299 + function setDimensions() {
300 + if (force) {
301 + force.size(t2vs.getDimensions());
302 + }
303 + }
304 +
305 +
306 + function start() {
307 + force.start();
308 + }
309 +
310 + angular.module('ovTopo2')
311 + .factory('Topo2LayoutService',
312 + [
313 + '$log', 'SvgUtilService', 'Topo2RegionService',
314 + 'Topo2D3Service', 'Topo2ViewService',
315 +
316 + function (_$log_, _sus_, _t2rs_, _t2d3_, _t2vs_) {
317 +
318 + $log = _$log_;
319 + t2rs = _t2rs_;
320 + t2d3 = _t2d3_;
321 + t2vs = _t2vs_;
322 + sus = _sus_;
323 +
324 + return {
325 + init: init,
326 + update: update,
327 + start: start,
328 +
329 + setDimensions: setDimensions
330 + }
331 + }
332 + ]
333 + );
334 +})();
...@@ -22,12 +22,162 @@ ...@@ -22,12 +22,162 @@
22 (function () { 22 (function () {
23 'use strict'; 23 'use strict';
24 24
25 - var Collection, Model; 25 + var Collection, Model, region, ts;
26 26
27 - function createLinkCollection(data, region) { 27 + var widthRatio = 1.4,
28 + linkScale = d3.scale.linear()
29 + .domain([1, 12])
30 + .range([widthRatio, 12 * widthRatio])
31 + .clamp(true),
32 + allLinkTypes = 'direct indirect optical tunnel UiDeviceLink',
33 + allLinkSubTypes = 'inactive not-permitted';
34 +
35 + // configuration
36 + var linkConfig = {
37 + light: {
38 + baseColor: '#939598',
39 + inColor: '#66f',
40 + outColor: '#f00'
41 + },
42 + dark: {
43 + // TODO : theme
44 + baseColor: '#939598',
45 + inColor: '#66f',
46 + outColor: '#f00'
47 + },
48 + inWidth: 12,
49 + outWidth: 10
50 + };
51 +
52 + var defaultLinkType = 'direct',
53 + nearDist = 15;
54 +
55 + function createLink() {
56 +
57 + var linkPoints = this.linkEndPoints(this.get('epA'), this.get('epB'));
58 + console.log(this);
59 +
60 + var attrs = angular.extend({}, linkPoints, {
61 + key: this.get('id'),
62 + class: 'link',
63 + weight: 1,
64 + srcPort: this.get('srcPort'),
65 + tgtPort: this.get('dstPort'),
66 + position: {
67 + x1: 0,
68 + y1: 0,
69 + x2: 0,
70 + y2: 0
71 + }
72 + // functions to aggregate dual link state
73 +// extra: link.extra
74 + });
75 +
76 + this.set(attrs);
77 + }
78 +
79 + function linkEndPoints(srcId, dstId) {
80 +
81 + var sourceNode = this.region.get('devices').get(srcId.substring(0, srcId.length -2));
82 + var targetNode = this.region.get('devices').get(dstId.substring(0, dstId.length -2));
83 +
84 +// var srcNode = lu[srcId],
85 +// dstNode = lu[dstId],
86 +// sMiss = !srcNode ? missMsg('src', srcId) : '',
87 +// dMiss = !dstNode ? missMsg('dst', dstId) : '';
88 +//
89 +// if (sMiss || dMiss) {
90 +// $log.error('Node(s) not on map for link:' + sMiss + dMiss);
91 +// //logicError('Node(s) not on map for link:\n' + sMiss + dMiss);
92 +// return null;
93 +// }
94 +
95 + this.source = sourceNode.toJSON();
96 + this.target = targetNode.toJSON();
97 +
98 + return {
99 + source: sourceNode,
100 + target: targetNode
101 + };
102 + }
103 +
104 + function createLinkCollection(data, _region) {
105 +
106 + var LinkModel = Model.extend({
107 + region: _region,
108 + createLink: createLink,
109 + linkEndPoints: linkEndPoints,
110 + type: function () {
111 + return this.get('type');
112 + },
113 + expected: function () {
114 + //TODO: original code is: (s && s.expected) && (t && t.expected);
115 + return true;
116 + },
117 + online: function () {
118 + return true;
119 + return both && (s && s.online) && (t && t.online);
120 + },
121 + linkWidth: function () {
122 + var s = this.get('fromSource'),
123 + t = this.get('fromTarget'),
124 + ws = (s && s.linkWidth) || 0,
125 + wt = (t && t.linkWidth) || 0;
126 +
127 + // console.log(s);
128 + // TODO: Current json is missing linkWidth
129 + return 1.2;
130 + return this.get('position').multiLink ? 5 : Math.max(ws, wt);
131 + },
132 +
133 + restyleLinkElement: function (immediate) {
134 + // this fn's job is to look at raw links and decide what svg classes
135 + // need to be applied to the line element in the DOM
136 + var th = ts.theme(),
137 + el = this.el,
138 + type = this.get('type'),
139 + lw = this.linkWidth(),
140 + online = this.online(),
141 + modeCls = this.expected() ? 'inactive' : 'not-permitted',
142 + delay = immediate ? 0 : 1000;
143 +
144 + console.log(type);
145 +
146 + // NOTE: understand why el is sometimes undefined on addLink events...
147 + // Investigated:
148 + // el is undefined when it's a reverse link that is being added.
149 + // updateLinks (which sets ldata.el) isn't called before this is called.
150 + // Calling _updateLinks in addLinkUpdate fixes it, but there might be
151 + // a more efficient way to fix it.
152 + if (el && !el.empty()) {
153 + el.classed('link', true);
154 + el.classed(allLinkSubTypes, false);
155 + el.classed(modeCls, !online);
156 + el.classed(allLinkTypes, false);
157 + if (type) {
158 + el.classed(type, true);
159 + }
160 + el.transition()
161 + .duration(delay)
162 + .attr('stroke-width', linkScale(lw))
163 + .attr('stroke', linkConfig[th].baseColor);
164 + }
165 + },
166 +
167 + onEnter: function (el) {
168 + var link = d3.select(el);
169 + this.el = link;
170 +
171 + this.restyleLinkElement();
172 +
173 + if (this.get('type') === 'hostLink') {
174 + sus.visible(link, api.showHosts());
175 + }
176 + }
177 + });
28 178
29 var LinkCollection = Collection.extend({ 179 var LinkCollection = Collection.extend({
30 - model: Model 180 + model: LinkModel,
31 }); 181 });
32 182
33 return new LinkCollection(data); 183 return new LinkCollection(data);
...@@ -35,12 +185,13 @@ ...@@ -35,12 +185,13 @@
35 185
36 angular.module('ovTopo2') 186 angular.module('ovTopo2')
37 .factory('Topo2LinkService', 187 .factory('Topo2LinkService',
38 - ['Topo2Collection', 'Topo2Model', 188 + ['Topo2Collection', 'Topo2Model', 'ThemeService',
39 189
40 - function (_Collection_, _Model_) { 190 + function (_Collection_, _Model_, _ts_) {
41 191
192 + ts = _ts_;
42 Collection = _Collection_; 193 Collection = _Collection_;
43 - Model = _Model_.extend({}); 194 + Model = _Model_;
44 195
45 return { 196 return {
46 createLinkCollection: createLinkCollection 197 createLinkCollection: createLinkCollection
......
1 /* 1 /*
2 - * Copyright 2016-present Open Networking Laboratory 2 +* Copyright 2016-present Open Networking Laboratory
3 - * 3 +*
4 - * Licensed under the Apache License, Version 2.0 (the "License"); 4 +* Licensed under the Apache License, Version 2.0 (the "License");
5 - * you may not use this file except in compliance with 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 6 +* You may obtain a copy of the License at
7 - * 7 +*
8 - * http://www.apache.org/licenses/LICENSE-2.0 8 +* http://www.apache.org/licenses/LICENSE-2.0
9 - * 9 +*
10 - * Unless required by applicable law or agreed to in writing, software 10 +* Unless required by applicable law or agreed to in writing, software
11 - * distributed under the License is distributed on an "AS IS" BASIS, 11 +* distributed under the License is distributed on an "AS IS" BASIS,
12 - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 - * See the License for the specific language governing permissions and 13 +* See the License for the specific language governing permissions and
14 - * limitations under the License. 14 +* limitations under the License.
15 - */ 15 +*/
16 16
17 /* 17 /*
18 - ONOS GUI -- Topology Force Module. 18 +ONOS GUI -- Topology Force Module.
19 - Visualization of the topology in an SVG layer, using a D3 Force Layout. 19 +Visualization of the topology in an SVG layer, using a D3 Force Layout.
20 - */ 20 +*/
21 21
22 (function () { 22 (function () {
23 'use strict'; 23 'use strict';
...@@ -28,17 +28,86 @@ ...@@ -28,17 +28,86 @@
28 this.attributes = {}; 28 this.attributes = {};
29 29
30 attrs = angular.extend({}, attrs); 30 attrs = angular.extend({}, attrs);
31 - this.set(attrs); 31 + this.set(attrs, { silent: true });
32 + this.initialize.apply(this, arguments);
32 } 33 }
33 34
34 Model.prototype = { 35 Model.prototype = {
35 36
37 + initialize: function () {},
38 +
39 + onChange: function (property, value, options) {},
40 +
36 get: function (attr) { 41 get: function (attr) {
37 return this.attributes[attr]; 42 return this.attributes[attr];
38 }, 43 },
39 44
40 - set: function(data) { 45 + set: function(key, val, options) {
41 - angular.extend(this.attributes, data); 46 +
47 + if (!key) {
48 + return this;
49 + }
50 +
51 + var attributes;
52 + if (typeof key === 'object') {
53 + attributes = key;
54 + options = val;
55 + } else {
56 + (attributes = {})[key] = val;
57 + }
58 +
59 + options || (options = {});
60 +
61 + var unset = options.unset,
62 + silent = options.silent,
63 + changes = [],
64 + changing = this._changing;
65 +
66 + this._changing = true;
67 +
68 + if (!changing) {
69 +
70 + // NOTE: angular.copy causes issues in chrome
71 + this._previousAttributes = Object.create(Object.getPrototypeOf(this.attributes));
72 + this.changed = {};
73 + }
74 +
75 + var current = this.attributes,
76 + changed = this.changed,
77 + previous = this._previousAttributes;
78 +
79 + angular.forEach(attributes, function (attribute, index) {
80 +
81 + val = attribute;
82 +
83 + if (!angular.equals(current[index], val)) {
84 + changes.push(index);
85 + }
86 +
87 + if (!angular.equals(previous[index], val)) {
88 + changed[index] = val;
89 + } else {
90 + delete changed[index];
91 + }
92 +
93 + unset ? delete current[index] : current[index] = val;
94 + });
95 +
96 + // Trigger all relevant attribute changes.
97 + if (!silent) {
98 + if (changes.length) {
99 + this._pending = options;
100 + }
101 + for (var i = 0; i < changes.length; i++) {
102 + this.onChange(changes[i], this, current[changes[i]], options);
103 + }
104 + }
105 +
106 + this._changing = false;
107 + return this;
108 + },
109 + toJSON: function(options) {
110 + return angular.copy(this.attributes)
42 }, 111 },
43 }; 112 };
44 113
...@@ -67,11 +136,11 @@ ...@@ -67,11 +136,11 @@
67 }; 136 };
68 137
69 angular.module('ovTopo2') 138 angular.module('ovTopo2')
70 - .factory('Topo2Model', 139 + .factory('Topo2Model',
71 - [ 140 + [
72 - function () { 141 + function () {
73 - return Model; 142 + return Model;
74 - } 143 + }
75 - ]); 144 + ]);
76 145
77 })(); 146 })();
......
1 +/*
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 Layout Module.
19 + Module that contains the d3.force.layout logic
20 + */
21 +
22 +(function () {
23 + 'use strict';
24 +
25 + var randomService;
26 + var fn;
27 +
28 + //internal state;
29 + var defaultLinkType = 'direct',
30 + nearDist = 15;
31 +
32 + function positionNode(node, forUpdate) {
33 +
34 + var meta = node.metaUi,
35 + x = meta && meta.x,
36 + y = meta && meta.y,
37 + dim = [800, 600],
38 + xy;
39 +
40 + // if the device contains explicit LONG/LAT data, use that to position
41 + if (setLongLat(node)) {
42 + //indicate we want to update cached meta data...
43 + return true;
44 + }
45 +
46 + // else if we have [x,y] cached in meta data, use that...
47 + if (x !== undefined && y !== undefined) {
48 + node.fixed = true;
49 + node.px = node.x = x;
50 + node.py = node.y = y;
51 + return;
52 + }
53 +
54 + // if this is a node update (not a node add).. skip randomizer
55 + if (forUpdate) {
56 + return;
57 + }
58 +
59 + // Note: Placing incoming unpinned nodes at exactly the same point
60 + // (center of the view) causes them to explode outwards when
61 + // the force layout kicks in. So, we spread them out a bit
62 + // initially, to provide a more serene layout convergence.
63 + // Additionally, if the node is a host, we place it near
64 + // the device it is connected to.
65 +
66 + function rand() {
67 + return {
68 + x: randomService.randDim(dim[0]),
69 + y: randomService.randDim(dim[1])
70 + };
71 + }
72 +
73 + function near(node) {
74 + return {
75 + x: node.x + nearDist + randomService.spread(nearDist),
76 + y: node.y + nearDist + randomService.spread(nearDist)
77 + };
78 + }
79 +
80 + function getDevice(cp) {
81 + // console.log(cp);
82 + // var d = lu[cp.device];
83 + // return d || rand();
84 + return rand();
85 + }
86 +
87 + xy = (node.class === 'host') ? near(getDevice(node.cp)) : rand();
88 + angular.extend(node, xy);
89 + }
90 +
91 + function setLongLat(node) {
92 + var loc = node.location,
93 + coord;
94 +
95 + if (loc && loc.type === 'lnglat') {
96 + coord = [0, 0];
97 + node.fixed = true;
98 + node.px = node.x = coord[0];
99 + node.py = node.y = coord[1];
100 + return true;
101 + }
102 + }
103 +
104 + angular.module('ovTopo2')
105 + .factory('Topo2NodeModel',
106 + ['Topo2Model', 'FnService', 'RandomService',
107 + function (Model, _fn_, _RandomService_) {
108 +
109 + randomService = _RandomService_;
110 + fn = _fn_;
111 +
112 + return Model.extend({
113 + initialize: function () {
114 + this.node = this.createNode();
115 + },
116 + svgClassName: function () {
117 + return fn.classNames('node', this.nodeType, this.get('type'), {
118 + online: this.get('online')
119 + });
120 + },
121 + createNode: function () {
122 +
123 + var node = angular.extend({}, this.attributes);
124 +
125 + // Augment as needed...
126 + node.class = this.nodeType;
127 + node.svgClass = this.svgClassName();
128 + positionNode(node);
129 + return node;
130 + }
131 + });
132 + }]
133 + );
134 +})();
...@@ -24,12 +24,13 @@ ...@@ -24,12 +24,13 @@
24 24
25 var $log, 25 var $log,
26 wss, 26 wss,
27 + Model,
27 t2sr, 28 t2sr,
28 t2ds, 29 t2ds,
29 t2hs, 30 t2hs,
30 t2ls; 31 t2ls;
31 32
32 - var regions; 33 + var region;
33 34
34 function init() { 35 function init() {
35 regions = {}; 36 regions = {};
...@@ -37,25 +38,46 @@ ...@@ -37,25 +38,46 @@
37 38
38 function addRegion(data) { 39 function addRegion(data) {
39 40
40 - var region = { 41 + region = new Model({
41 - subregions: t2sr.createSubRegionCollection(data.subregions), 42 + id: data.id,
42 - devices: t2ds.createDeviceCollection(data.devices, data), 43 + layerOrder: data.layerOrder
43 - hosts: t2hs.createHostCollection(data.hosts), 44 + });
44 - links: t2ls.createLinkCollection(data.links), 45 +
45 - }; 46 + region.set({
47 + subregions: t2sr.createSubRegionCollection(data.subregions, region),
48 + devices: t2ds.createDeviceCollection(data.devices, region),
49 + hosts: t2hs.createHostCollection(data.hosts, region),
50 + links: t2ls.createLinkCollection(data.links, region),
51 + });
52 +
53 + region.set('test', 2);
54 +
55 + angular.forEach(region.get('links').models, function (link) {
56 + link.createLink();
57 + });
46 58
47 $log.debug('Region: ', region); 59 $log.debug('Region: ', region);
48 } 60 }
49 61
62 + function regionNodes() {
63 + return [].concat(region.get('devices').models, region.get('hosts').models);
64 + }
65 +
66 +
67 + function regionLinks() {
68 + return region.get('links').models;
69 + }
70 +
50 angular.module('ovTopo2') 71 angular.module('ovTopo2')
51 .factory('Topo2RegionService', 72 .factory('Topo2RegionService',
52 - ['$log', 'WebSocketService', 'Topo2SubRegionService', 'Topo2DeviceService', 73 + ['$log', 'WebSocketService', 'Topo2Model', 'Topo2SubRegionService', 'Topo2DeviceService',
53 'Topo2HostService', 'Topo2LinkService', 74 'Topo2HostService', 'Topo2LinkService',
54 75
55 - function (_$log_, _wss_, _t2sr_, _t2ds_, _t2hs_, _t2ls_) { 76 + function (_$log_, _wss_, _Model_, _t2sr_, _t2ds_, _t2hs_, _t2ls_) {
56 77
57 $log = _$log_; 78 $log = _$log_;
58 wss = _wss_; 79 wss = _wss_;
80 + Model = _Model_
59 t2sr = _t2sr_; 81 t2sr = _t2sr_;
60 t2ds = _t2ds_; 82 t2ds = _t2ds_;
61 t2hs = _t2hs_; 83 t2hs = _t2hs_;
...@@ -65,6 +87,9 @@ ...@@ -65,6 +87,9 @@
65 init: init, 87 init: init,
66 88
67 addRegion: addRegion, 89 addRegion: addRegion,
90 + regionNodes: regionNodes,
91 + regionLinks: regionLinks,
92 +
68 getSubRegions: t2sr.getSubRegions 93 getSubRegions: t2sr.getSubRegions
69 }; 94 };
70 }]); 95 }]);
......
1 +/*
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 Layout Module.
19 + Module that contains the d3.force.layout logic
20 + */
21 +
22 +(function () {
23 + 'use strict';
24 +
25 + var dimensions;
26 +
27 + function newDim(_dimensions) {
28 + dimensions = _dimensions;
29 + }
30 +
31 + function getDimensions() {
32 + return dimensions;
33 + }
34 +
35 + angular.module('ovTopo2')
36 + .factory('Topo2ViewService',
37 + [
38 + function () {
39 + return {
40 + newDim: newDim,
41 + getDimensions: getDimensions
42 + }
43 + }
44 + ]
45 + );
46 +})();
...@@ -128,15 +128,21 @@ ...@@ -128,15 +128,21 @@
128 <!-- Under development for Region support. --> 128 <!-- Under development for Region support. -->
129 <script src="app/view/topo2/topo2.js"></script> 129 <script src="app/view/topo2/topo2.js"></script>
130 <script src="app/view/topo2/topo2Collection.js"></script> 130 <script src="app/view/topo2/topo2Collection.js"></script>
131 + <script src="app/view/topo2/topo2D3.js"></script>
131 <script src="app/view/topo2/topo2Device.js"></script> 132 <script src="app/view/topo2/topo2Device.js"></script>
132 - <script src="app/view/topo2/topo2Model.js"></script>
133 <script src="app/view/topo2/topo2Event.js"></script> 133 <script src="app/view/topo2/topo2Event.js"></script>
134 <script src="app/view/topo2/topo2Force.js"></script> 134 <script src="app/view/topo2/topo2Force.js"></script>
135 <script src="app/view/topo2/topo2Host.js"></script> 135 <script src="app/view/topo2/topo2Host.js"></script>
136 <script src="app/view/topo2/topo2Instance.js"></script> 136 <script src="app/view/topo2/topo2Instance.js"></script>
137 + <script src="app/view/topo2/topo2Layout.js"></script>
137 <script src="app/view/topo2/topo2Link.js"></script> 138 <script src="app/view/topo2/topo2Link.js"></script>
139 + <script src="app/view/topo2/topo2Model.js"></script>
140 + <script src="app/view/topo2/topo2NodeModel.js"></script>
138 <script src="app/view/topo2/topo2Region.js"></script> 141 <script src="app/view/topo2/topo2Region.js"></script>
142 + <script src="app/view/topo2/topo2Select.js"></script>
139 <script src="app/view/topo2/topo2SubRegion.js"></script> 143 <script src="app/view/topo2/topo2SubRegion.js"></script>
144 + <script src="app/view/topo2/topo2Theme.js"></script>
145 + <script src="app/view/topo2/topo2View.js"></script>
140 <link rel="stylesheet" href="app/view/topo2/topo2.css"> 146 <link rel="stylesheet" href="app/view/topo2/topo2.css">
141 <link rel="stylesheet" href="app/view/topo2/topo2-theme.css"> 147 <link rel="stylesheet" href="app/view/topo2/topo2-theme.css">
142 148
......
...@@ -216,7 +216,8 @@ describe('factory: fw/util/fn.js', function() { ...@@ -216,7 +216,8 @@ describe('factory: fw/util/fn.js', function() {
216 'isMobile', 'isChrome', 'isSafari', 'isFirefox', 216 'isMobile', 'isChrome', 'isSafari', 'isFirefox',
217 'debugOn', 'debug', 217 'debugOn', 'debug',
218 'find', 'inArray', 'removeFromArray', 'isEmptyObject', 'sameObjProps', 'containsObj', 'cap', 218 'find', 'inArray', 'removeFromArray', 'isEmptyObject', 'sameObjProps', 'containsObj', 'cap',
219 - 'eecode', 'noPx', 'noPxStyle', 'endsWith', 'parseBitRate', 'addToTrie', 'removeFromTrie', 'trieLookup' 219 + 'eecode', 'noPx', 'noPxStyle', 'endsWith', 'parseBitRate', 'addToTrie', 'removeFromTrie', 'trieLookup',
220 + 'classNames'
220 ])).toBeTruthy(); 221 ])).toBeTruthy();
221 }); 222 });
222 223
......
1 +{
2 + "name": "karma",
3 + "version": "1.0.0",
4 + "description": "",
5 + "main": "mockserver.js",
6 + "dependencies": {
7 + "websocket": "^1.0.23"
8 + },
9 + "devDependencies": {},
10 + "scripts": {
11 + "test": "echo \"Error: no test specified\" && exit 1"
12 + },
13 + "author": "",
14 + "license": "ISC"
15 +}