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
Showing
22 changed files
with
1535 additions
and
127 deletions
... | @@ -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 | }]); | ... | ... |
File mode changed
File mode changed
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 | ... | ... |
web/gui/src/test/_karma/package.json
0 → 100644
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 | +} |
-
Please register or login to post a comment