GUI -- Added handling of hosts and links. (Still WIP).
Change-Id: I0ad3b16d47b264b6812f732f220230a2ae92de02
Showing
10 changed files
with
872 additions
and
101 deletions
... | @@ -20,7 +20,7 @@ | ... | @@ -20,7 +20,7 @@ |
20 | (function () { | 20 | (function () { |
21 | 'use strict'; | 21 | 'use strict'; |
22 | 22 | ||
23 | - var $log, fs, gs; | 23 | + var $log, fs, gs, sus; |
24 | 24 | ||
25 | var vboxSize = 50, | 25 | var vboxSize = 50, |
26 | cornerSize = vboxSize / 10, | 26 | cornerSize = vboxSize / 10, |
... | @@ -144,8 +144,21 @@ | ... | @@ -144,8 +144,21 @@ |
144 | return g; | 144 | return g; |
145 | } | 145 | } |
146 | 146 | ||
147 | - function addHostIcon(elem, glyphId) { | 147 | + function addHostIcon(elem, radius, glyphId) { |
148 | - // TODO: | 148 | + var dim = radius * 1.5, |
149 | + xlate = -dim / 2, | ||
150 | + g = elem.append('g') | ||
151 | + .attr('class', 'svgIcon hostIcon'); | ||
152 | + | ||
153 | + g.append('circle').attr('r', radius); | ||
154 | + | ||
155 | + g.append('use').attr({ | ||
156 | + 'xlink:href': '#' + glyphId, | ||
157 | + width: dim, | ||
158 | + height: dim, | ||
159 | + transform: sus.translate(xlate,xlate) | ||
160 | + }); | ||
161 | + return g; | ||
149 | } | 162 | } |
150 | 163 | ||
151 | 164 | ||
... | @@ -154,10 +167,13 @@ | ... | @@ -154,10 +167,13 @@ |
154 | 167 | ||
155 | angular.module('onosSvg') | 168 | angular.module('onosSvg') |
156 | .factory('IconService', ['$log', 'FnService', 'GlyphService', | 169 | .factory('IconService', ['$log', 'FnService', 'GlyphService', |
157 | - function (_$log_, _fs_, _gs_) { | 170 | + 'SvgUtilService', |
171 | + | ||
172 | + function (_$log_, _fs_, _gs_, _sus_) { | ||
158 | $log = _$log_; | 173 | $log = _$log_; |
159 | fs = _fs_; | 174 | fs = _fs_; |
160 | gs = _gs_; | 175 | gs = _gs_; |
176 | + sus = _sus_; | ||
161 | 177 | ||
162 | return { | 178 | return { |
163 | loadIcon: loadIcon, | 179 | loadIcon: loadIcon, | ... | ... |
... | @@ -325,19 +325,121 @@ | ... | @@ -325,19 +325,121 @@ |
325 | /* Host Nodes */ | 325 | /* Host Nodes */ |
326 | 326 | ||
327 | #ov-topo svg .node.host { | 327 | #ov-topo svg .node.host { |
328 | - stroke: #000; | ||
329 | } | 328 | } |
330 | 329 | ||
331 | #ov-topo svg .node.host text { | 330 | #ov-topo svg .node.host text { |
332 | - fill: #846; | ||
333 | stroke: none; | 331 | stroke: none; |
334 | font: 9pt sans-serif; | 332 | font: 9pt sans-serif; |
335 | } | 333 | } |
334 | +.light #ov-topo svg .node.host text { | ||
335 | + fill: #846; | ||
336 | +} | ||
337 | +.dark #ov-topo svg .node.host text { | ||
338 | + fill: #BB809D; | ||
339 | +} | ||
336 | 340 | ||
337 | -svg .node.host circle { | 341 | +.light svg .node.host circle { |
338 | stroke: #000; | 342 | stroke: #000; |
339 | fill: #edb; | 343 | fill: #edb; |
340 | } | 344 | } |
345 | +.dark svg .node.host circle { | ||
346 | + stroke: #eee; | ||
347 | + fill: #B2A180; | ||
348 | +} | ||
349 | + | ||
350 | +.light svg .node.host .svgIcon { | ||
351 | + fill: #444; | ||
352 | +} | ||
353 | +.dark svg .node.host .svgIcon { | ||
354 | + fill: #222; | ||
355 | +} | ||
356 | + | ||
357 | +/* --- Topo Links --- */ | ||
358 | + | ||
359 | +#ov-topo svg .link { | ||
360 | + opacity: .9; | ||
361 | +} | ||
362 | + | ||
363 | +#ov-topo svg .link.inactive { | ||
364 | + opacity: .5; | ||
365 | + stroke-dasharray: 8 4; | ||
366 | +} | ||
367 | + | ||
368 | +#ov-topo svg .link.secondary { | ||
369 | + stroke-width: 3px; | ||
370 | +} | ||
371 | +.light #ov-topo svg .link.secondary { | ||
372 | + stroke: rgba(0,153,51,0.5); | ||
373 | +} | ||
374 | +.dark #ov-topo svg .link.secondary { | ||
375 | + stroke: rgba(121,231,158,0.5); | ||
376 | +} | ||
377 | + | ||
378 | +#ov-topo svg .link.primary { | ||
379 | + stroke-width: 4px; | ||
380 | +} | ||
381 | +.light #ov-topo svg .link.primary { | ||
382 | + stroke: #ffA300; | ||
383 | +} | ||
384 | +.dark #ov-topo svg .link.primary { | ||
385 | + stroke: #D58E0F; | ||
386 | +} | ||
387 | + | ||
388 | +.light #ov-topo svg .link.animated { | ||
389 | + stroke: #ffA300; | ||
390 | +} | ||
391 | +.dark #ov-topo svg .link.animated { | ||
392 | + stroke: #D58E0F; | ||
393 | +} | ||
394 | + | ||
395 | +#ov-topo svg .link.secondary.optical { | ||
396 | + stroke-width: 4px; | ||
397 | +} | ||
398 | +.light #ov-topo svg .link.secondary.optical { | ||
399 | + stroke: rgba(128,64,255,0.5); | ||
400 | +} | ||
401 | +.dark #ov-topo svg .link.secondary.optical { | ||
402 | + stroke: rgba(164,139,215,0.5); | ||
403 | +} | ||
341 | 404 | ||
405 | +#ov-topo svg .link.primary.optical { | ||
406 | + stroke-width: 6px; | ||
407 | +} | ||
408 | +.light #ov-topo svg .link.primary.optical { | ||
409 | + stroke: #74f; | ||
410 | +} | ||
411 | +.dark #ov-topo svg .link.primary.optical { | ||
412 | + stroke: #7352CD; | ||
413 | +} | ||
414 | + | ||
415 | +#ov-topo svg .link.animated.optical { | ||
416 | + stroke-width: 10px; | ||
417 | +} | ||
418 | +.light #ov-topo svg .link.animated.optical { | ||
419 | + stroke: #74f; | ||
420 | +} | ||
421 | +.dark #ov-topo svg .link.animated.optical { | ||
422 | + stroke: #7352CD; | ||
423 | +} | ||
342 | 424 | ||
425 | +#ov-topo svg .linkLabel rect { | ||
426 | + stroke: none; | ||
427 | +} | ||
428 | +.light #ov-topo svg .linkLabel rect { | ||
429 | + fill: #eee; | ||
430 | +} | ||
431 | +.dark #ov-topo svg .linkLabel rect { | ||
432 | + fill: #eee; | ||
433 | +} | ||
343 | 434 | ||
435 | +#ov-topo svg .linkLabel text { | ||
436 | + text-anchor: middle; | ||
437 | + stroke-width: 0.1; | ||
438 | + font-size: 9pt; | ||
439 | +} | ||
440 | +.light #ov-topo svg .linkLabel text { | ||
441 | + stroke: #777; | ||
442 | +} | ||
443 | +.dark #ov-topo svg .linkLabel text { | ||
444 | + stroke: #777; | ||
445 | +} | ... | ... |
... | @@ -198,12 +198,6 @@ | ... | @@ -198,12 +198,6 @@ |
198 | return ms.loadMapInto(mapG, '*continental_us'); | 198 | return ms.loadMapInto(mapG, '*continental_us'); |
199 | } | 199 | } |
200 | 200 | ||
201 | - // --- Force Layout -------------------------------------------------- | ||
202 | - | ||
203 | - function setUpForce(xlink) { | ||
204 | - forceG = zoomLayer.append('g').attr('id', 'topo-force'); | ||
205 | - tfs.initForce(forceG, xlink, svg.attr('width'), svg.attr('height')); | ||
206 | - } | ||
207 | 201 | ||
208 | // --- Controller Definition ----------------------------------------- | 202 | // --- Controller Definition ----------------------------------------- |
209 | 203 | ||
... | @@ -219,8 +213,12 @@ | ... | @@ -219,8 +213,12 @@ |
219 | function ($scope, _$log_, $loc, $timeout, _fs_, mast, | 213 | function ($scope, _$log_, $loc, $timeout, _fs_, mast, |
220 | _ks_, _zs_, _gs_, _ms_, _sus_, tes, _tfs_, tps, _tis_) { | 214 | _ks_, _zs_, _gs_, _ms_, _sus_, tes, _tfs_, tps, _tis_) { |
221 | var self = this, | 215 | var self = this, |
222 | - xlink = { | 216 | + projection, |
223 | - showNoDevs: showNoDevs | 217 | + uplink = { |
218 | + // provides function calls back into this space | ||
219 | + showNoDevs: showNoDevs, | ||
220 | + projection: function () { return projection; }, | ||
221 | + sendEvent: tes.sendEvent | ||
224 | }; | 222 | }; |
225 | 223 | ||
226 | $log = _$log_; | 224 | $log = _$log_; |
... | @@ -255,9 +253,15 @@ | ... | @@ -255,9 +253,15 @@ |
255 | setUpDefs(); | 253 | setUpDefs(); |
256 | setUpZoom(); | 254 | setUpZoom(); |
257 | setUpNoDevs(); | 255 | setUpNoDevs(); |
258 | - xlink.projectionPromise = setUpMap(); | 256 | + setUpMap().then( |
259 | - setUpForce(xlink); | 257 | + function (proj) { |
260 | - | 258 | + projection = proj; |
259 | + $log.debug('** We installed the projection: ', proj); | ||
260 | + } | ||
261 | + ); | ||
262 | + | ||
263 | + forceG = zoomLayer.append('g').attr('id', 'topo-force'); | ||
264 | + tfs.initForce(forceG, uplink, svg.attr('width'), svg.attr('height')); | ||
261 | tis.initInst(); | 265 | tis.initInst(); |
262 | tps.initPanels(); | 266 | tps.initPanels(); |
263 | tes.openSock(); | 267 | tes.openSock(); | ... | ... |
... | @@ -34,9 +34,16 @@ | ... | @@ -34,9 +34,16 @@ |
34 | updateInstance: updateInstance, | 34 | updateInstance: updateInstance, |
35 | removeInstance: removeInstance, | 35 | removeInstance: removeInstance, |
36 | addDevice: addDevice, | 36 | addDevice: addDevice, |
37 | - updateDevice: updateDevice | 37 | + updateDevice: updateDevice, |
38 | - // TODO: implement remaining handlers.. | 38 | + removeDevice: removeDevice, |
39 | + addHost: addHost, | ||
40 | + updateHost: updateHost, | ||
41 | + removeHost: removeHost, | ||
42 | + addLink: addLink, | ||
43 | + updateLink: updateLink, | ||
44 | + removeLink: removeLink | ||
39 | 45 | ||
46 | + // TODO: implement remaining handlers.. | ||
40 | }; | 47 | }; |
41 | 48 | ||
42 | function unknownEvent(ev) { | 49 | function unknownEvent(ev) { |
... | @@ -45,6 +52,9 @@ | ... | @@ -45,6 +52,9 @@ |
45 | 52 | ||
46 | // === Event Handlers === | 53 | // === Event Handlers === |
47 | 54 | ||
55 | + // NOTE: --- once these are done, we will collapse them into | ||
56 | + // a more compact data structure... but for now, write in full.. | ||
57 | + | ||
48 | function showSummary(ev) { | 58 | function showSummary(ev) { |
49 | $log.debug(' **** Show Summary **** ', ev.payload); | 59 | $log.debug(' **** Show Summary **** ', ev.payload); |
50 | tps.showSummary(ev.payload); | 60 | tps.showSummary(ev.payload); |
... | @@ -75,6 +85,42 @@ | ... | @@ -75,6 +85,42 @@ |
75 | tfs.updateDevice(ev.payload); | 85 | tfs.updateDevice(ev.payload); |
76 | } | 86 | } |
77 | 87 | ||
88 | + function removeDevice(ev) { | ||
89 | + $log.debug(' **** Remove Device **** ', ev.payload); | ||
90 | + tfs.removeDevice(ev.payload); | ||
91 | + } | ||
92 | + | ||
93 | + function addHost(ev) { | ||
94 | + $log.debug(' **** Add Host **** ', ev.payload); | ||
95 | + tfs.addHost(ev.payload); | ||
96 | + } | ||
97 | + | ||
98 | + function updateHost(ev) { | ||
99 | + $log.debug(' **** Update Host **** ', ev.payload); | ||
100 | + tfs.updateHost(ev.payload); | ||
101 | + } | ||
102 | + | ||
103 | + function removeHost(ev) { | ||
104 | + $log.debug(' **** Remove Host **** ', ev.payload); | ||
105 | + tfs.removeHost(ev.payload); | ||
106 | + } | ||
107 | + | ||
108 | + function addLink(ev) { | ||
109 | + $log.debug(' **** Add Link **** ', ev.payload); | ||
110 | + tfs.addLink(ev.payload); | ||
111 | + } | ||
112 | + | ||
113 | + function updateLink(ev) { | ||
114 | + $log.debug(' **** Update Link **** ', ev.payload); | ||
115 | + tfs.updateLink(ev.payload); | ||
116 | + } | ||
117 | + | ||
118 | + function removeLink(ev) { | ||
119 | + $log.debug(' **** Remove Link **** ', ev.payload); | ||
120 | + tfs.removeLink(ev.payload); | ||
121 | + } | ||
122 | + | ||
123 | + | ||
78 | // ========================== | 124 | // ========================== |
79 | 125 | ||
80 | var dispatcher = { | 126 | var dispatcher = { |
... | @@ -122,14 +168,9 @@ | ... | @@ -122,14 +168,9 @@ |
122 | tis = _tis_; | 168 | tis = _tis_; |
123 | tfs = _tfs_; | 169 | tfs = _tfs_; |
124 | 170 | ||
125 | - function bindDispatcher(TopoDomElementsPassedHere) { | ||
126 | - // TODO: store refs to topo DOM elements... | ||
127 | - | ||
128 | - return dispatcher; | ||
129 | - } | ||
130 | - | ||
131 | // TODO: handle "guiSuccessor" functionality (replace host) | 171 | // TODO: handle "guiSuccessor" functionality (replace host) |
132 | // TODO: implement retry on close functionality | 172 | // TODO: implement retry on close functionality |
173 | + | ||
133 | function openSock() { | 174 | function openSock() { |
134 | wsock = wss.createWebSocket('topology', { | 175 | wsock = wss.createWebSocket('topology', { |
135 | onOpen: onWsOpen, | 176 | onOpen: onWsOpen, |
... | @@ -151,9 +192,9 @@ | ... | @@ -151,9 +192,9 @@ |
151 | } | 192 | } |
152 | 193 | ||
153 | return { | 194 | return { |
154 | - bindDispatcher: bindDispatcher, | ||
155 | openSock: openSock, | 195 | openSock: openSock, |
156 | - closeSock: closeSock | 196 | + closeSock: closeSock, |
197 | + sendEvent: dispatcher.sendEvent | ||
157 | }; | 198 | }; |
158 | }]); | 199 | }]); |
159 | }()); | 200 | }()); | ... | ... |
... | @@ -23,7 +23,9 @@ | ... | @@ -23,7 +23,9 @@ |
23 | 'use strict'; | 23 | 'use strict'; |
24 | 24 | ||
25 | // injected refs | 25 | // injected refs |
26 | - var $log, sus, is, ts, tis, xlink; | 26 | + var $log, fs, sus, is, ts, tis, uplink; |
27 | + | ||
28 | + var icfg; | ||
27 | 29 | ||
28 | // configuration | 30 | // configuration |
29 | var labelConfig = { | 31 | var labelConfig = { |
... | @@ -44,6 +46,21 @@ | ... | @@ -44,6 +46,21 @@ |
44 | yoff: -18 | 46 | yoff: -18 |
45 | }; | 47 | }; |
46 | 48 | ||
49 | + var linkConfig = { | ||
50 | + light: { | ||
51 | + baseColor: '#666', | ||
52 | + inColor: '#66f', | ||
53 | + outColor: '#f00', | ||
54 | + }, | ||
55 | + dark: { | ||
56 | + baseColor: '#666', | ||
57 | + inColor: '#66f', | ||
58 | + outColor: '#f00', | ||
59 | + }, | ||
60 | + inWidth: 12, | ||
61 | + outWidth: 10 | ||
62 | + }; | ||
63 | + | ||
47 | // internal state | 64 | // internal state |
48 | var settings, // merged default settings and options | 65 | var settings, // merged default settings and options |
49 | force, // force layout object | 66 | force, // force layout object |
... | @@ -54,9 +71,11 @@ | ... | @@ -54,9 +71,11 @@ |
54 | lookup: {}, | 71 | lookup: {}, |
55 | revLinkToKey: {} | 72 | revLinkToKey: {} |
56 | }, | 73 | }, |
57 | - projection, // background map projection | 74 | + lu = network.lookup, // shorthand |
58 | deviceLabelIndex = 0, // for device label cycling | 75 | deviceLabelIndex = 0, // for device label cycling |
59 | - hostLabelIndex = 0; // for host label cycling | 76 | + hostLabelIndex = 0, // for host label cycling |
77 | + showHosts = 1, // whether hosts are displayed | ||
78 | + width, height; | ||
60 | 79 | ||
61 | // SVG elements; | 80 | // SVG elements; |
62 | var linkG, linkLabelG, nodeG; | 81 | var linkG, linkLabelG, nodeG; |
... | @@ -99,18 +118,18 @@ | ... | @@ -99,18 +118,18 @@ |
99 | var id = data.id, | 118 | var id = data.id, |
100 | d; | 119 | d; |
101 | 120 | ||
102 | - xlink.showNoDevs(false); | 121 | + uplink.showNoDevs(false); |
103 | 122 | ||
104 | // although this is an add device event, if we already have the | 123 | // although this is an add device event, if we already have the |
105 | // device, treat it as an update instead.. | 124 | // device, treat it as an update instead.. |
106 | - if (network.lookup[id]) { | 125 | + if (lu[id]) { |
107 | updateDevice(data); | 126 | updateDevice(data); |
108 | return; | 127 | return; |
109 | } | 128 | } |
110 | 129 | ||
111 | d = createDeviceNode(data); | 130 | d = createDeviceNode(data); |
112 | network.nodes.push(d); | 131 | network.nodes.push(d); |
113 | - network.lookup[id] = d; | 132 | + lu[id] = d; |
114 | 133 | ||
115 | $log.debug("Created new device.. ", d.id, d.x, d.y); | 134 | $log.debug("Created new device.. ", d.id, d.x, d.y); |
116 | 135 | ||
... | @@ -120,7 +139,7 @@ | ... | @@ -120,7 +139,7 @@ |
120 | 139 | ||
121 | function updateDevice(data) { | 140 | function updateDevice(data) { |
122 | var id = data.id, | 141 | var id = data.id, |
123 | - d = network.lookup[id], | 142 | + d = lu[id], |
124 | wasOnline; | 143 | wasOnline; |
125 | 144 | ||
126 | if (d) { | 145 | if (d) { |
... | @@ -141,26 +160,379 @@ | ... | @@ -141,26 +160,379 @@ |
141 | } | 160 | } |
142 | } | 161 | } |
143 | 162 | ||
163 | + function removeDevice(data) { | ||
164 | + var id = data.id, | ||
165 | + d = lu[id]; | ||
166 | + if (d) { | ||
167 | + removeDeviceElement(d); | ||
168 | + } else { | ||
169 | + // TODO: decide whether we want to capture logic errors | ||
170 | + //logicError('removeDevice lookup fail. ID = "' + id + '"'); | ||
171 | + } | ||
172 | + } | ||
173 | + | ||
174 | + function addHost(data) { | ||
175 | + var id = data.id, | ||
176 | + d, lnk; | ||
177 | + | ||
178 | + // although this is an add host event, if we already have the | ||
179 | + // host, treat it as an update instead.. | ||
180 | + if (lu[id]) { | ||
181 | + updateHost(data); | ||
182 | + return; | ||
183 | + } | ||
184 | + | ||
185 | + d = createHostNode(data); | ||
186 | + network.nodes.push(d); | ||
187 | + lu[id] = d; | ||
188 | + | ||
189 | + $log.debug("Created new host.. ", d.id, d.x, d.y); | ||
190 | + | ||
191 | + updateNodes(); | ||
192 | + | ||
193 | + lnk = createHostLink(data); | ||
194 | + if (lnk) { | ||
195 | + | ||
196 | + $log.debug("Created new host-link.. ", lnk.key); | ||
197 | + | ||
198 | + d.linkData = lnk; // cache ref on its host | ||
199 | + network.links.push(lnk); | ||
200 | + lu[d.ingress] = lnk; | ||
201 | + lu[d.egress] = lnk; | ||
202 | + updateLinks(); | ||
203 | + } | ||
204 | + | ||
205 | + fStart(); | ||
206 | + } | ||
207 | + | ||
208 | + function updateHost(data) { | ||
209 | + var id = data.id, | ||
210 | + d = lu[id]; | ||
211 | + if (d) { | ||
212 | + angular.extend(d, data); | ||
213 | + if (positionNode(d, true)) { | ||
214 | + sendUpdateMeta(d, true); | ||
215 | + } | ||
216 | + updateNodes(); | ||
217 | + } else { | ||
218 | + // TODO: decide whether we want to capture logic errors | ||
219 | + //logicError('updateHost lookup fail. ID = "' + id + '"'); | ||
220 | + } | ||
221 | + } | ||
222 | + | ||
223 | + function removeHost(data) { | ||
224 | + var id = data.id, | ||
225 | + d = lu[id]; | ||
226 | + if (d) { | ||
227 | + removeHostElement(d, true); | ||
228 | + } else { | ||
229 | + // may have already removed host, if attached to removed device | ||
230 | + //console.warn('removeHost lookup fail. ID = "' + id + '"'); | ||
231 | + } | ||
232 | + } | ||
233 | + | ||
234 | + function addLink(data) { | ||
235 | + var result = findLink(data, 'add'), | ||
236 | + bad = result.badLogic, | ||
237 | + d = result.ldata; | ||
238 | + | ||
239 | + if (bad) { | ||
240 | + //logicError(bad + ': ' + link.id); | ||
241 | + return; | ||
242 | + } | ||
243 | + | ||
244 | + if (d) { | ||
245 | + // we already have a backing store link for src/dst nodes | ||
246 | + addLinkUpdate(d, data); | ||
247 | + return; | ||
248 | + } | ||
249 | + | ||
250 | + // no backing store link yet | ||
251 | + d = createLink(data); | ||
252 | + if (d) { | ||
253 | + network.links.push(d); | ||
254 | + lu[d.key] = d; | ||
255 | + updateLinks(); | ||
256 | + fStart(); | ||
257 | + } | ||
258 | + } | ||
259 | + | ||
260 | + function updateLink(data) { | ||
261 | + var result = findLink(data, 'update'), | ||
262 | + bad = result.badLogic; | ||
263 | + if (bad) { | ||
264 | + //logicError(bad + ': ' + link.id); | ||
265 | + return; | ||
266 | + } | ||
267 | + result.updateWith(link); | ||
268 | + } | ||
269 | + | ||
270 | + function removeLink(data) { | ||
271 | + var result = findLink(data, 'remove'), | ||
272 | + bad = result.badLogic; | ||
273 | + if (bad) { | ||
274 | + // may have already removed link, if attached to removed device | ||
275 | + //console.warn(bad + ': ' + link.id); | ||
276 | + return; | ||
277 | + } | ||
278 | + result.removeRawLink(); | ||
279 | + } | ||
280 | + | ||
281 | + // ======================== | ||
282 | + | ||
283 | + function addLinkUpdate(ldata, link) { | ||
284 | + // add link event, but we already have the reverse link installed | ||
285 | + ldata.fromTarget = link; | ||
286 | + network.revLinkToKey[link.id] = ldata.key; | ||
287 | + restyleLinkElement(ldata); | ||
288 | + } | ||
289 | + | ||
290 | + function createLink(link) { | ||
291 | + var lnk = linkEndPoints(link.src, link.dst); | ||
292 | + | ||
293 | + if (!lnk) { | ||
294 | + return null; | ||
295 | + } | ||
296 | + | ||
297 | + angular.extend(lnk, { | ||
298 | + key: link.id, | ||
299 | + class: 'link', | ||
300 | + fromSource: link, | ||
301 | + | ||
302 | + // functions to aggregate dual link state | ||
303 | + type: function () { | ||
304 | + var s = lnk.fromSource, | ||
305 | + t = lnk.fromTarget; | ||
306 | + return (s && s.type) || (t && t.type) || defaultLinkType; | ||
307 | + }, | ||
308 | + online: function () { | ||
309 | + var s = lnk.fromSource, | ||
310 | + t = lnk.fromTarget, | ||
311 | + both = lnk.source.online && lnk.target.online; | ||
312 | + return both && ((s && s.online) || (t && t.online)); | ||
313 | + }, | ||
314 | + linkWidth: function () { | ||
315 | + var s = lnk.fromSource, | ||
316 | + t = lnk.fromTarget, | ||
317 | + ws = (s && s.linkWidth) || 0, | ||
318 | + wt = (t && t.linkWidth) || 0; | ||
319 | + return Math.max(ws, wt); | ||
320 | + } | ||
321 | + }); | ||
322 | + return lnk; | ||
323 | + } | ||
324 | + | ||
325 | + | ||
326 | + function makeNodeKey(d, what) { | ||
327 | + var port = what + 'Port'; | ||
328 | + return d[what] + '/' + d[port]; | ||
329 | + } | ||
330 | + | ||
331 | + function makeLinkKey(d, flipped) { | ||
332 | + var one = flipped ? makeNodeKey(d, 'dst') : makeNodeKey(d, 'src'), | ||
333 | + two = flipped ? makeNodeKey(d, 'src') : makeNodeKey(d, 'dst'); | ||
334 | + return one + '-' + two; | ||
335 | + } | ||
336 | + | ||
337 | + var widthRatio = 1.4, | ||
338 | + linkScale = d3.scale.linear() | ||
339 | + .domain([1, 12]) | ||
340 | + .range([widthRatio, 12 * widthRatio]) | ||
341 | + .clamp(true); | ||
342 | + | ||
343 | + var allLinkTypes = 'direct indirect optical tunnel', | ||
344 | + defaultLinkType = 'direct'; | ||
345 | + | ||
346 | + function restyleLinkElement(ldata) { | ||
347 | + // this fn's job is to look at raw links and decide what svg classes | ||
348 | + // need to be applied to the line element in the DOM | ||
349 | + var th = ts.theme(), | ||
350 | + el = ldata.el, | ||
351 | + type = ldata.type(), | ||
352 | + lw = ldata.linkWidth(), | ||
353 | + online = ldata.online(); | ||
354 | + | ||
355 | + el.classed('link', true); | ||
356 | + el.classed('inactive', !online); | ||
357 | + el.classed(allLinkTypes, false); | ||
358 | + if (type) { | ||
359 | + el.classed(type, true); | ||
360 | + } | ||
361 | + el.transition() | ||
362 | + .duration(1000) | ||
363 | + .attr('stroke-width', linkScale(lw)) | ||
364 | + .attr('stroke', linkConfig[th].baseColor); | ||
365 | + } | ||
366 | + | ||
367 | + function findLink(linkData, op) { | ||
368 | + var key = makeLinkKey(linkData), | ||
369 | + keyrev = makeLinkKey(linkData, 1), | ||
370 | + link = lu[key], | ||
371 | + linkRev = lu[keyrev], | ||
372 | + result = {}, | ||
373 | + ldata = link || linkRev, | ||
374 | + rawLink; | ||
375 | + | ||
376 | + if (op === 'add') { | ||
377 | + if (link) { | ||
378 | + // trying to add a link that we already know about | ||
379 | + result.ldata = link; | ||
380 | + result.badLogic = 'addLink: link already added'; | ||
381 | + | ||
382 | + } else if (linkRev) { | ||
383 | + // we found the reverse of the link to be added | ||
384 | + result.ldata = linkRev; | ||
385 | + if (linkRev.fromTarget) { | ||
386 | + result.badLogic = 'addLink: link already added'; | ||
387 | + } | ||
388 | + } | ||
389 | + } else if (op === 'update') { | ||
390 | + if (!ldata) { | ||
391 | + result.badLogic = 'updateLink: link not found'; | ||
392 | + } else { | ||
393 | + rawLink = link ? ldata.fromSource : ldata.fromTarget; | ||
394 | + result.updateWith = function (data) { | ||
395 | + angular.extend(rawLink, data); | ||
396 | + restyleLinkElement(ldata); | ||
397 | + } | ||
398 | + } | ||
399 | + } else if (op === 'remove') { | ||
400 | + if (!ldata) { | ||
401 | + result.badLogic = 'removeLink: link not found'; | ||
402 | + } else { | ||
403 | + rawLink = link ? ldata.fromSource : ldata.fromTarget; | ||
404 | + | ||
405 | + if (!rawLink) { | ||
406 | + result.badLogic = 'removeLink: link not found'; | ||
407 | + | ||
408 | + } else { | ||
409 | + result.removeRawLink = function () { | ||
410 | + if (link) { | ||
411 | + // remove fromSource | ||
412 | + ldata.fromSource = null; | ||
413 | + if (ldata.fromTarget) { | ||
414 | + // promote target into source position | ||
415 | + ldata.fromSource = ldata.fromTarget; | ||
416 | + ldata.fromTarget = null; | ||
417 | + ldata.key = keyrev; | ||
418 | + delete network.lookup[key]; | ||
419 | + network.lookup[keyrev] = ldata; | ||
420 | + delete network.revLinkToKey[keyrev]; | ||
421 | + } | ||
422 | + } else { | ||
423 | + // remove fromTarget | ||
424 | + ldata.fromTarget = null; | ||
425 | + delete network.revLinkToKey[keyrev]; | ||
426 | + } | ||
427 | + if (ldata.fromSource) { | ||
428 | + restyleLinkElement(ldata); | ||
429 | + } else { | ||
430 | + removeLinkElement(ldata); | ||
431 | + } | ||
432 | + } | ||
433 | + } | ||
434 | + } | ||
435 | + } | ||
436 | + return result; | ||
437 | + } | ||
438 | + | ||
439 | + | ||
440 | + function findAttachedHosts(devId) { | ||
441 | + var hosts = []; | ||
442 | + network.nodes.forEach(function (d) { | ||
443 | + if (d.class === 'host' && d.cp.device === devId) { | ||
444 | + hosts.push(d); | ||
445 | + } | ||
446 | + }); | ||
447 | + return hosts; | ||
448 | + } | ||
449 | + | ||
450 | + function findAttachedLinks(devId) { | ||
451 | + var links = []; | ||
452 | + network.links.forEach(function (d) { | ||
453 | + if (d.source.id === devId || d.target.id === devId) { | ||
454 | + links.push(d); | ||
455 | + } | ||
456 | + }); | ||
457 | + return links; | ||
458 | + } | ||
459 | + | ||
460 | + function removeLinkElement(d) { | ||
461 | + var idx = fs.find(d.key, network.links, 'key'), | ||
462 | + removed; | ||
463 | + if (idx >=0) { | ||
464 | + // remove from links array | ||
465 | + removed = network.links.splice(idx, 1); | ||
466 | + // remove from lookup cache | ||
467 | + delete lu[removed[0].key]; | ||
468 | + updateLinks(); | ||
469 | + fResume(); | ||
470 | + } | ||
471 | + } | ||
472 | + | ||
473 | + function removeHostElement(d, upd) { | ||
474 | + // first, remove associated hostLink... | ||
475 | + removeLinkElement(d.linkData); | ||
476 | + | ||
477 | + // remove hostLink bindings | ||
478 | + delete lu[d.ingress]; | ||
479 | + delete lu[d.egress]; | ||
480 | + | ||
481 | + // remove from lookup cache | ||
482 | + delete lu[d.id]; | ||
483 | + // remove from nodes array | ||
484 | + var idx = fs.find(d.id, network.nodes); | ||
485 | + network.nodes.splice(idx, 1); | ||
486 | + | ||
487 | + // remove from SVG | ||
488 | + // NOTE: upd is false if we were called from removeDeviceElement() | ||
489 | + if (upd) { | ||
490 | + updateNodes(); | ||
491 | + fResume(); | ||
492 | + } | ||
493 | + } | ||
494 | + | ||
495 | + function removeDeviceElement(d) { | ||
496 | + var id = d.id; | ||
497 | + // first, remove associated hosts and links.. | ||
498 | + findAttachedHosts(id).forEach(removeHostElement); | ||
499 | + findAttachedLinks(id).forEach(removeLinkElement); | ||
500 | + | ||
501 | + // remove from lookup cache | ||
502 | + delete lu[id]; | ||
503 | + // remove from nodes array | ||
504 | + var idx = fs.find(id, network.nodes); | ||
505 | + network.nodes.splice(idx, 1); | ||
506 | + | ||
507 | + if (!network.nodes.length) { | ||
508 | + xlink.showNoDevs(true); | ||
509 | + } | ||
510 | + | ||
511 | + // remove from SVG | ||
512 | + updateNodes(); | ||
513 | + fResume(); | ||
514 | + } | ||
515 | + | ||
516 | + | ||
144 | function sendUpdateMeta(d, store) { | 517 | function sendUpdateMeta(d, store) { |
145 | var metaUi = {}, | 518 | var metaUi = {}, |
146 | ll; | 519 | ll; |
147 | 520 | ||
148 | - // TODO: fix this code to send event to server... | 521 | + if (store) { |
149 | - //if (store) { | 522 | + ll = lngLatFromCoord([d.x, d.y]); |
150 | - // ll = geoMapProj.invert([d.x, d.y]); | 523 | + metaUi = { |
151 | - // metaUi = { | 524 | + x: d.x, |
152 | - // x: d.x, | 525 | + y: d.y, |
153 | - // y: d.y, | 526 | + lng: ll[0], |
154 | - // lng: ll[0], | 527 | + lat: ll[1] |
155 | - // lat: ll[1] | 528 | + }; |
156 | - // }; | 529 | + } |
157 | - //} | 530 | + d.metaUi = metaUi; |
158 | - //d.metaUi = metaUi; | 531 | + uplink.sendEvent('updateMeta', { |
159 | - //sendMessage('updateMeta', { | 532 | + id: d.id, |
160 | - // id: d.id, | 533 | + 'class': d.class, |
161 | - // 'class': d.class, | 534 | + memento: metaUi |
162 | - // memento: metaUi | 535 | + }); |
163 | - //}); | ||
164 | } | 536 | } |
165 | 537 | ||
166 | 538 | ||
... | @@ -178,9 +550,13 @@ | ... | @@ -178,9 +550,13 @@ |
178 | // === Devices and hosts - helper functions | 550 | // === Devices and hosts - helper functions |
179 | 551 | ||
180 | function coordFromLngLat(loc) { | 552 | function coordFromLngLat(loc) { |
181 | - // Our hope is that the projection is installed before we start | 553 | + var p = uplink.projection(); |
182 | - // handling incoming nodes. But if not, we'll just return the origin. | 554 | + return p ? p([loc.lng, loc.lat]) : [0, 0]; |
183 | - return projection ? projection([loc.lng, loc.lat]) : [0, 0]; | 555 | + } |
556 | + | ||
557 | + function lngLatFromCoord(coord) { | ||
558 | + var p = uplink.projection(); | ||
559 | + return p ? p.invert([coord.x, coord.y]) : [0, 0]; | ||
184 | } | 560 | } |
185 | 561 | ||
186 | function positionNode(node, forUpdate) { | 562 | function positionNode(node, forUpdate) { |
... | @@ -230,8 +606,8 @@ | ... | @@ -230,8 +606,8 @@ |
230 | 606 | ||
231 | function rand() { | 607 | function rand() { |
232 | return { | 608 | return { |
233 | - x: randDim(network.view.width()), | 609 | + x: randDim(width), |
234 | - y: randDim(network.view.height()) | 610 | + y: randDim(height) |
235 | }; | 611 | }; |
236 | } | 612 | } |
237 | 613 | ||
... | @@ -246,7 +622,7 @@ | ... | @@ -246,7 +622,7 @@ |
246 | } | 622 | } |
247 | 623 | ||
248 | function getDevice(cp) { | 624 | function getDevice(cp) { |
249 | - var d = network.lookup[cp.device]; | 625 | + var d = lu[cp.device]; |
250 | return d || rand(); | 626 | return d || rand(); |
251 | } | 627 | } |
252 | 628 | ||
... | @@ -267,9 +643,83 @@ | ... | @@ -267,9 +643,83 @@ |
267 | return node; | 643 | return node; |
268 | } | 644 | } |
269 | 645 | ||
646 | + function createHostNode(host) { | ||
647 | + var node = host; | ||
648 | + | ||
649 | + // Augment as needed... | ||
650 | + node.class = 'host'; | ||
651 | + if (!node.type) { | ||
652 | + node.type = 'endstation'; | ||
653 | + } | ||
654 | + node.svgClass = 'node host ' + node.type; | ||
655 | + positionNode(node); | ||
656 | + return node; | ||
657 | + } | ||
658 | + | ||
659 | + function createHostLink(host) { | ||
660 | + var src = host.id, | ||
661 | + dst = host.cp.device, | ||
662 | + id = host.ingress, | ||
663 | + lnk = linkEndPoints(src, dst); | ||
664 | + | ||
665 | + if (!lnk) { | ||
666 | + return null; | ||
667 | + } | ||
668 | + | ||
669 | + // Synthesize link ... | ||
670 | + angular.extend(lnk, { | ||
671 | + key: id, | ||
672 | + class: 'link', | ||
673 | + | ||
674 | + type: function () { return 'hostLink'; }, | ||
675 | + online: function () { | ||
676 | + // hostlink target is edge switch | ||
677 | + return lnk.target.online; | ||
678 | + }, | ||
679 | + linkWidth: function () { return 1; } | ||
680 | + }); | ||
681 | + return lnk; | ||
682 | + } | ||
683 | + | ||
684 | + function linkEndPoints(srcId, dstId) { | ||
685 | + var srcNode = lu[srcId], | ||
686 | + dstNode = lu[dstId], | ||
687 | + sMiss = !srcNode ? missMsg('src', srcId) : '', | ||
688 | + dMiss = !dstNode ? missMsg('dst', dstId) : ''; | ||
689 | + | ||
690 | + if (sMiss || dMiss) { | ||
691 | + $log.error('Node(s) not on map for link:\n' + sMiss + dMiss); | ||
692 | + //logicError('Node(s) not on map for link:\n' + sMiss + dMiss); | ||
693 | + return null; | ||
694 | + } | ||
695 | + return { | ||
696 | + source: srcNode, | ||
697 | + target: dstNode, | ||
698 | + x1: srcNode.x, | ||
699 | + y1: srcNode.y, | ||
700 | + x2: dstNode.x, | ||
701 | + y2: dstNode.y | ||
702 | + }; | ||
703 | + } | ||
704 | + | ||
705 | + function missMsg(what, id) { | ||
706 | + return '\n[' + what + '] "' + id + '" missing '; | ||
707 | + } | ||
708 | + | ||
270 | // ========================== | 709 | // ========================== |
271 | // === Devices and hosts - D3 rendering | 710 | // === Devices and hosts - D3 rendering |
272 | 711 | ||
712 | + function nodeMouseOver(m) { | ||
713 | + // TODO | ||
714 | + $log.debug("TODO nodeMouseOver()...", m); | ||
715 | + } | ||
716 | + | ||
717 | + function nodeMouseOut(m) { | ||
718 | + // TODO | ||
719 | + $log.debug("TODO nodeMouseOut()...", m); | ||
720 | + } | ||
721 | + | ||
722 | + | ||
273 | // Returns the newly computed bounding box of the rectangle | 723 | // Returns the newly computed bounding box of the rectangle |
274 | function adjustRectToFitText(n) { | 724 | function adjustRectToFitText(n) { |
275 | var text = n.select('text'), | 725 | var text = n.select('text'), |
... | @@ -323,7 +773,7 @@ | ... | @@ -323,7 +773,7 @@ |
323 | var label = trimLabel(deviceLabel(d)), | 773 | var label = trimLabel(deviceLabel(d)), |
324 | noLabel = !label, | 774 | noLabel = !label, |
325 | node = d.el, | 775 | node = d.el, |
326 | - dim = is.iconConfig().device.dim, | 776 | + dim = icfg.device.dim, |
327 | devCfg = deviceIconConfig, | 777 | devCfg = deviceIconConfig, |
328 | box, dx, dy; | 778 | box, dx, dy; |
329 | 779 | ||
... | @@ -357,16 +807,6 @@ | ... | @@ -357,16 +807,6 @@ |
357 | d.el.select('text').text(label); | 807 | d.el.select('text').text(label); |
358 | } | 808 | } |
359 | 809 | ||
360 | - function nodeMouseOver(m) { | ||
361 | - // TODO | ||
362 | - $log.debug("TODO nodeMouseOver()...", m); | ||
363 | - } | ||
364 | - | ||
365 | - function nodeMouseOut(m) { | ||
366 | - // TODO | ||
367 | - $log.debug("TODO nodeMouseOut()...", m); | ||
368 | - } | ||
369 | - | ||
370 | function updateDeviceColors(d) { | 810 | function updateDeviceColors(d) { |
371 | if (d) { | 811 | if (d) { |
372 | setDeviceColor(d); | 812 | setDeviceColor(d); |
... | @@ -445,13 +885,14 @@ | ... | @@ -445,13 +885,14 @@ |
445 | return sus.cat7().getColor(id, !online, ts.theme()); | 885 | return sus.cat7().getColor(id, !online, ts.theme()); |
446 | } | 886 | } |
447 | 887 | ||
448 | - //============ | 888 | + // ========================== |
449 | 889 | ||
450 | function updateNodes() { | 890 | function updateNodes() { |
891 | + // select all the nodes in the layout: | ||
451 | node = nodeG.selectAll('.node') | 892 | node = nodeG.selectAll('.node') |
452 | .data(network.nodes, function (d) { return d.id; }); | 893 | .data(network.nodes, function (d) { return d.id; }); |
453 | 894 | ||
454 | - // operate on existing nodes... | 895 | + // operate on existing nodes: |
455 | node.filter('.device').each(deviceExisting); | 896 | node.filter('.device').each(deviceExisting); |
456 | node.filter('.host').each(hostExisting); | 897 | node.filter('.host').each(hostExisting); |
457 | 898 | ||
... | @@ -470,7 +911,7 @@ | ... | @@ -470,7 +911,7 @@ |
470 | .transition() | 911 | .transition() |
471 | .attr('opacity', 1); | 912 | .attr('opacity', 1); |
472 | 913 | ||
473 | - // augment nodes... | 914 | + // augment entering nodes: |
474 | entering.filter('.device').each(deviceEnter); | 915 | entering.filter('.device').each(deviceEnter); |
475 | entering.filter('.host').each(hostEnter); | 916 | entering.filter('.host').each(hostEnter); |
476 | 917 | ||
... | @@ -486,7 +927,7 @@ | ... | @@ -486,7 +927,7 @@ |
486 | .style('opacity', 0) | 927 | .style('opacity', 0) |
487 | .remove(); | 928 | .remove(); |
488 | 929 | ||
489 | - // node specific.... | 930 | + // exiting node specifics: |
490 | exiting.filter('.host').each(hostExit); | 931 | exiting.filter('.host').each(hostExit); |
491 | exiting.filter('.device').each(deviceExit); | 932 | exiting.filter('.device').each(deviceExit); |
492 | 933 | ||
... | @@ -539,25 +980,20 @@ | ... | @@ -539,25 +980,20 @@ |
539 | } | 980 | } |
540 | 981 | ||
541 | function hostEnter(d) { | 982 | function hostEnter(d) { |
542 | - var node = d3.select(this); | 983 | + var node = d3.select(this), |
543 | - | 984 | + gid = d.type || 'unknown', |
544 | - //cfg = config.icons.host, | 985 | + rad = icfg.host.radius, |
545 | - //r = cfg.radius[d.type] || cfg.defaultRadius, | 986 | + r = d.type ? rad.withGlyph : rad.noGlyph, |
546 | - //textDy = r + 10, | 987 | + textDy = r + 10; |
547 | - //TODO: iid = iconGlyphUrl(d), | ||
548 | - // _dummy; | ||
549 | 988 | ||
550 | d.el = node; | 989 | d.el = node; |
990 | + sus.makeVisible(node, showHosts); | ||
551 | 991 | ||
552 | - //TODO: showHostVis(node); | 992 | + is.addHostIcon(node, r, gid); |
553 | 993 | ||
554 | - node.append('circle').attr('r', r); | ||
555 | - //if (iid) { | ||
556 | - //TODO: addHostIcon(node, r, iid); | ||
557 | - //} | ||
558 | node.append('text') | 994 | node.append('text') |
559 | .text(hostLabel) | 995 | .text(hostLabel) |
560 | - //.attr('dy', textDy) | 996 | + .attr('dy', textDy) |
561 | .attr('text-anchor', 'middle'); | 997 | .attr('text-anchor', 'middle'); |
562 | } | 998 | } |
563 | 999 | ||
... | @@ -598,6 +1034,160 @@ | ... | @@ -598,6 +1034,160 @@ |
598 | .style('opacity', 0.5); | 1034 | .style('opacity', 0.5); |
599 | } | 1035 | } |
600 | 1036 | ||
1037 | + // ========================== | ||
1038 | + | ||
1039 | + function updateLinks() { | ||
1040 | + var th = ts.theme(); | ||
1041 | + | ||
1042 | + link = linkG.selectAll('.link') | ||
1043 | + .data(network.links, function (d) { return d.key; }); | ||
1044 | + | ||
1045 | + // operate on existing links: | ||
1046 | + //link.each(linkExisting); | ||
1047 | + | ||
1048 | + // operate on entering links: | ||
1049 | + var entering = link.enter() | ||
1050 | + .append('line') | ||
1051 | + .attr({ | ||
1052 | + x1: function (d) { return d.x1; }, | ||
1053 | + y1: function (d) { return d.y1; }, | ||
1054 | + x2: function (d) { return d.x2; }, | ||
1055 | + y2: function (d) { return d.y2; }, | ||
1056 | + stroke: linkConfig[th].inColor, | ||
1057 | + 'stroke-width': linkConfig.inWidth | ||
1058 | + }); | ||
1059 | + | ||
1060 | + // augment links | ||
1061 | + entering.each(linkEntering); | ||
1062 | + | ||
1063 | + // operate on both existing and new links: | ||
1064 | + //link.each(...) | ||
1065 | + | ||
1066 | + // apply or remove labels | ||
1067 | + var labelData = getLabelData(); | ||
1068 | + applyLinkLabels(labelData); | ||
1069 | + | ||
1070 | + // operate on exiting links: | ||
1071 | + link.exit() | ||
1072 | + .attr('stroke-dasharray', '3 3') | ||
1073 | + .style('opacity', 0.5) | ||
1074 | + .transition() | ||
1075 | + .duration(1500) | ||
1076 | + .attr({ | ||
1077 | + 'stroke-dasharray': '3 12', | ||
1078 | + stroke: linkConfig[th].outColor, | ||
1079 | + 'stroke-width': linkConfig.outWidth | ||
1080 | + }) | ||
1081 | + .style('opacity', 0.0) | ||
1082 | + .remove(); | ||
1083 | + | ||
1084 | + // NOTE: invoke a single tick to force the labels to position | ||
1085 | + // onto their links. | ||
1086 | + tick(); | ||
1087 | + // FIXME: this is a bug when in oblique view | ||
1088 | + // It causes the nodes to jump into "overhead" view positions, even | ||
1089 | + // though the oblique planes are still showing... | ||
1090 | + } | ||
1091 | + | ||
1092 | + // ========================== | ||
1093 | + // updateLinks - subfunctions | ||
1094 | + | ||
1095 | + function getLabelData() { | ||
1096 | + // create the backing data for showing labels.. | ||
1097 | + var data = []; | ||
1098 | + link.each(function (d) { | ||
1099 | + if (d.label) { | ||
1100 | + data.push({ | ||
1101 | + id: 'lab-' + d.key, | ||
1102 | + key: d.key, | ||
1103 | + label: d.label, | ||
1104 | + ldata: d | ||
1105 | + }); | ||
1106 | + } | ||
1107 | + }); | ||
1108 | + return data; | ||
1109 | + } | ||
1110 | + | ||
1111 | + //function linkExisting(d) { } | ||
1112 | + | ||
1113 | + function linkEntering(d) { | ||
1114 | + var link = d3.select(this); | ||
1115 | + d.el = link; | ||
1116 | + restyleLinkElement(d); | ||
1117 | + if (d.type() === 'hostLink') { | ||
1118 | + sus.makeVisible(link, showHosts); | ||
1119 | + } | ||
1120 | + } | ||
1121 | + | ||
1122 | + //function linkExiting(d) { } | ||
1123 | + | ||
1124 | + var linkLabelOffset = '0.3em'; | ||
1125 | + | ||
1126 | + function applyLinkLabels(data) { | ||
1127 | + var entering; | ||
1128 | + | ||
1129 | + linkLabel = linkLabelG.selectAll('.linkLabel') | ||
1130 | + .data(data, function (d) { return d.id; }); | ||
1131 | + | ||
1132 | + // for elements already existing, we need to update the text | ||
1133 | + // and adjust the rectangle size to fit | ||
1134 | + linkLabel.each(function (d) { | ||
1135 | + var el = d3.select(this), | ||
1136 | + rect = el.select('rect'), | ||
1137 | + text = el.select('text'); | ||
1138 | + text.text(d.label); | ||
1139 | + rect.attr(rectAroundText(el)); | ||
1140 | + }); | ||
1141 | + | ||
1142 | + entering = linkLabel.enter().append('g') | ||
1143 | + .classed('linkLabel', true) | ||
1144 | + .attr('id', function (d) { return d.id; }); | ||
1145 | + | ||
1146 | + entering.each(function (d) { | ||
1147 | + var el = d3.select(this), | ||
1148 | + rect, | ||
1149 | + text, | ||
1150 | + parms = { | ||
1151 | + x1: d.ldata.x1, | ||
1152 | + y1: d.ldata.y1, | ||
1153 | + x2: d.ldata.x2, | ||
1154 | + y2: d.ldata.y2 | ||
1155 | + }; | ||
1156 | + | ||
1157 | + d.el = el; | ||
1158 | + rect = el.append('rect'); | ||
1159 | + text = el.append('text').text(d.label); | ||
1160 | + rect.attr(rectAroundText(el)); | ||
1161 | + text.attr('dy', linkLabelOffset); | ||
1162 | + | ||
1163 | + el.attr('transform', transformLabel(parms)); | ||
1164 | + }); | ||
1165 | + | ||
1166 | + // Remove any labels that are no longer required. | ||
1167 | + linkLabel.exit().remove(); | ||
1168 | + } | ||
1169 | + | ||
1170 | + function rectAroundText(el) { | ||
1171 | + var text = el.select('text'), | ||
1172 | + box = text.node().getBBox(); | ||
1173 | + | ||
1174 | + // translate the bbox so that it is centered on [x,y] | ||
1175 | + box.x = -box.width / 2; | ||
1176 | + box.y = -box.height / 2; | ||
1177 | + | ||
1178 | + // add padding | ||
1179 | + box.x -= 1; | ||
1180 | + box.width += 2; | ||
1181 | + return box; | ||
1182 | + } | ||
1183 | + | ||
1184 | + function transformLabel(p) { | ||
1185 | + var dx = p.x2 - p.x1, | ||
1186 | + dy = p.y2 - p.y1, | ||
1187 | + xMid = dx/2 + p.x1, | ||
1188 | + yMid = dy/2 + p.y1; | ||
1189 | + return sus.translate(xMid, yMid); | ||
1190 | + } | ||
601 | 1191 | ||
602 | // ========================== | 1192 | // ========================== |
603 | // force layout tick function | 1193 | // force layout tick function |
... | @@ -620,34 +1210,31 @@ | ... | @@ -620,34 +1210,31 @@ |
620 | 1210 | ||
621 | angular.module('ovTopo') | 1211 | angular.module('ovTopo') |
622 | .factory('TopoForceService', | 1212 | .factory('TopoForceService', |
623 | - ['$log', 'SvgUtilService', 'IconService', 'ThemeService', | 1213 | + ['$log', 'FnService', 'SvgUtilService', 'IconService', 'ThemeService', |
624 | 'TopoInstService', | 1214 | 'TopoInstService', |
625 | 1215 | ||
626 | - function (_$log_, _sus_, _is_, _ts_, _tis_) { | 1216 | + function (_$log_, _fs_, _sus_, _is_, _ts_, _tis_) { |
627 | $log = _$log_; | 1217 | $log = _$log_; |
1218 | + fs = _fs_; | ||
628 | sus = _sus_; | 1219 | sus = _sus_; |
629 | is = _is_; | 1220 | is = _is_; |
630 | ts = _ts_; | 1221 | ts = _ts_; |
631 | tis = _tis_; | 1222 | tis = _tis_; |
632 | 1223 | ||
1224 | + icfg = is.iconConfig(); | ||
1225 | + | ||
633 | // forceG is the SVG group to display the force layout in | 1226 | // forceG is the SVG group to display the force layout in |
634 | // xlink is the cross-link api from the main topo source file | 1227 | // xlink is the cross-link api from the main topo source file |
635 | // w, h are the initial dimensions of the SVG | 1228 | // w, h are the initial dimensions of the SVG |
636 | // opts are, well, optional :) | 1229 | // opts are, well, optional :) |
637 | - function initForce(forceG, _xlink_, w, h, opts) { | 1230 | + function initForce(forceG, _uplink_, w, h, opts) { |
638 | $log.debug('initForce().. WxH = ' + w + 'x' + h); | 1231 | $log.debug('initForce().. WxH = ' + w + 'x' + h); |
639 | - xlink = _xlink_; | 1232 | + uplink = _uplink_; |
1233 | + width = w; | ||
1234 | + height = h; | ||
640 | 1235 | ||
641 | settings = angular.extend({}, defaultSettings, opts); | 1236 | settings = angular.extend({}, defaultSettings, opts); |
642 | 1237 | ||
643 | - // when the projection promise is resolved, cache the projection | ||
644 | - xlink.projectionPromise.then( | ||
645 | - function (proj) { | ||
646 | - projection = proj; | ||
647 | - $log.debug('** We installed the projection: ', proj); | ||
648 | - } | ||
649 | - ); | ||
650 | - | ||
651 | linkG = forceG.append('g').attr('id', 'topo-links'); | 1238 | linkG = forceG.append('g').attr('id', 'topo-links'); |
652 | linkLabelG = forceG.append('g').attr('id', 'topo-linkLabels'); | 1239 | linkLabelG = forceG.append('g').attr('id', 'topo-linkLabels'); |
653 | nodeG = forceG.append('g').attr('id', 'topo-nodes'); | 1240 | nodeG = forceG.append('g').attr('id', 'topo-nodes'); |
... | @@ -672,7 +1259,9 @@ | ... | @@ -672,7 +1259,9 @@ |
672 | } | 1259 | } |
673 | 1260 | ||
674 | function resize(dim) { | 1261 | function resize(dim) { |
675 | - force.size([dim.width, dim.height]); | 1262 | + width = dim.width; |
1263 | + height = dim.height; | ||
1264 | + force.size([width, height]); | ||
676 | // Review -- do we need to nudge the layout ? | 1265 | // Review -- do we need to nudge the layout ? |
677 | } | 1266 | } |
678 | 1267 | ||
... | @@ -683,7 +1272,14 @@ | ... | @@ -683,7 +1272,14 @@ |
683 | updateDeviceColors: updateDeviceColors, | 1272 | updateDeviceColors: updateDeviceColors, |
684 | 1273 | ||
685 | addDevice: addDevice, | 1274 | addDevice: addDevice, |
686 | - updateDevice: updateDevice | 1275 | + updateDevice: updateDevice, |
1276 | + removeDevice: removeDevice, | ||
1277 | + addHost: addHost, | ||
1278 | + updateHost: updateHost, | ||
1279 | + removeHost: removeHost, | ||
1280 | + addLink: addLink, | ||
1281 | + updateLink: updateLink, | ||
1282 | + removeLink: removeLink | ||
687 | }; | 1283 | }; |
688 | }]); | 1284 | }]); |
689 | }()); | 1285 | }()); | ... | ... |
... | @@ -323,9 +323,11 @@ | ... | @@ -323,9 +323,11 @@ |
323 | return { | 323 | return { |
324 | initInst: initInst, | 324 | initInst: initInst, |
325 | destroyInst: destroyInst, | 325 | destroyInst: destroyInst, |
326 | + | ||
326 | addInstance: addInstance, | 327 | addInstance: addInstance, |
327 | updateInstance: updateInstance, | 328 | updateInstance: updateInstance, |
328 | removeInstance: removeInstance, | 329 | removeInstance: removeInstance, |
330 | + | ||
329 | isVisible: function () { return oiBox.isVisible(); }, | 331 | isVisible: function () { return oiBox.isVisible(); }, |
330 | show: function () { oiBox.show(); }, | 332 | show: function () { oiBox.show(); }, |
331 | hide: function () { oiBox.hide(); } | 333 | hide: function () { oiBox.hide(); } | ... | ... |
... | @@ -34,7 +34,7 @@ describe('factory: view/topo/topoEvent.js', function() { | ... | @@ -34,7 +34,7 @@ describe('factory: view/topo/topoEvent.js', function() { |
34 | 34 | ||
35 | it('should define api functions', function () { | 35 | it('should define api functions', function () { |
36 | expect(fs.areFunctions(tes, [ | 36 | expect(fs.areFunctions(tes, [ |
37 | - 'bindDispatcher', 'openSock', 'closeSock' | 37 | + 'openSock', 'closeSock', 'sendEvent' |
38 | ])).toBeTruthy(); | 38 | ])).toBeTruthy(); |
39 | }); | 39 | }); |
40 | 40 | ... | ... |
... | @@ -35,7 +35,9 @@ describe('factory: view/topo/topoForce.js', function() { | ... | @@ -35,7 +35,9 @@ describe('factory: view/topo/topoForce.js', function() { |
35 | it('should define api functions', function () { | 35 | it('should define api functions', function () { |
36 | expect(fs.areFunctions(tfs, [ | 36 | expect(fs.areFunctions(tfs, [ |
37 | 'initForce', 'resize', 'updateDeviceColors', | 37 | 'initForce', 'resize', 'updateDeviceColors', |
38 | - 'addDevice', 'updateDevice' | 38 | + 'addDevice', 'updateDevice', 'removeDevice', |
39 | + 'addHost', 'updateHost', 'removeHost', | ||
40 | + 'addLink', 'updateLink', 'removeLink' | ||
39 | ])).toBeTruthy(); | 41 | ])).toBeTruthy(); |
40 | }); | 42 | }); |
41 | 43 | ... | ... |
-
Please register or login to post a comment