Simon Hunt
Committed by Gerrit Code Review

GUI -- TopoView - Refactored a bunch of functions out of topoForce into topoD3.

- finally, topoForce is a respectable sub-1000 LOC :)

Change-Id: I2a9ac2881c9d54663faecf338c512a368f17bc34
1 <script src="app/view/sample/sample.js"></script> 1 <script src="app/view/sample/sample.js"></script>
2 <script src="app/view/topo/topo.js"></script> 2 <script src="app/view/topo/topo.js"></script>
3 +<script src="app/view/topo/topoD3.js"></script>
3 <script src="app/view/topo/topoEvent.js"></script> 4 <script src="app/view/topo/topoEvent.js"></script>
4 <script src="app/view/topo/topoFilter.js"></script> 5 <script src="app/view/topo/topoFilter.js"></script>
5 <script src="app/view/topo/topoForce.js"></script> 6 <script src="app/view/topo/topoForce.js"></script>
......
1 +/*
2 + * Copyright 2015 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 D3 Module.
19 + Functions for manipulating the D3 visualizations of the Topology
20 + */
21 +
22 +(function () {
23 + 'use strict';
24 +
25 + // injected refs
26 + var $log, fs, sus, is, ts;
27 +
28 + // api to topoForce
29 + var api;
30 + /*
31 + node() // get ref to D3 selection of nodes
32 + link() // get ref to D3 selection of links
33 + linkLabel() // get ref to D3 selection of link labels
34 + instVisible() // true if instances panel is visible
35 + posNode() // position node
36 + showHosts() // true if hosts are to be shown
37 + restyleLinkElement() // update link styles based on backing data
38 + updateLinkLabelModel() // update backing data for link labels
39 + */
40 +
41 + // configuration
42 + var devCfg = {
43 + xoff: -20,
44 + yoff: -18
45 + },
46 + labelConfig = {
47 + imgPad: 16,
48 + padLR: 4,
49 + padTB: 3,
50 + marginLR: 3,
51 + marginTB: 2,
52 + port: {
53 + gap: 3,
54 + width: 18,
55 + height: 14
56 + }
57 + },
58 + icfg;
59 +
60 + // internal state
61 + var deviceLabelIndex = 0,
62 + hostLabelIndex = 0;
63 +
64 +
65 + var dCol = {
66 + black: '#000',
67 + paleblue: '#acf',
68 + offwhite: '#ddd',
69 + darkgrey: '#444',
70 + midgrey: '#888',
71 + lightgrey: '#bbb',
72 + orange: '#f90'
73 + };
74 +
75 + // note: these are the device icon colors without affinity
76 + var dColTheme = {
77 + light: {
78 + rfill: dCol.offwhite,
79 + online: {
80 + glyph: dCol.darkgrey,
81 + rect: dCol.paleblue
82 + },
83 + offline: {
84 + glyph: dCol.midgrey,
85 + rect: dCol.lightgrey
86 + }
87 + },
88 + dark: {
89 + rfill: dCol.midgrey,
90 + online: {
91 + glyph: dCol.darkgrey,
92 + rect: dCol.paleblue
93 + },
94 + offline: {
95 + glyph: dCol.midgrey,
96 + rect: dCol.darkgrey
97 + }
98 + }
99 + };
100 +
101 + function devBaseColor(d) {
102 + var o = d.online ? 'online' : 'offline';
103 + return dColTheme[ts.theme()][o];
104 + }
105 +
106 + function setDeviceColor(d) {
107 + var o = d.online,
108 + s = d.el.classed('selected'),
109 + c = devBaseColor(d),
110 + a = instColor(d.master, o),
111 + icon = d.el.select('g.deviceIcon'),
112 + g, r;
113 +
114 + if (s) {
115 + g = c.glyph;
116 + r = dCol.orange;
117 + } else if (api.instVisible()) {
118 + g = o ? a : c.glyph;
119 + r = o ? c.rfill : a;
120 + } else {
121 + g = c.glyph;
122 + r = c.rect;
123 + }
124 +
125 + icon.select('use').style('fill', g);
126 + icon.select('rect').style('fill', r);
127 + }
128 +
129 + function instColor(id, online) {
130 + return sus.cat7().getColor(id, !online, ts.theme());
131 + }
132 +
133 + // ====
134 +
135 + function incDevLabIndex() {
136 + deviceLabelIndex = (deviceLabelIndex+1) % 3;
137 + }
138 +
139 + // Returns the newly computed bounding box of the rectangle
140 + function adjustRectToFitText(n) {
141 + var text = n.select('text'),
142 + box = text.node().getBBox(),
143 + lab = labelConfig;
144 +
145 + text.attr('text-anchor', 'middle')
146 + .attr('y', '-0.8em')
147 + .attr('x', lab.imgPad/2);
148 +
149 + // translate the bbox so that it is centered on [x,y]
150 + box.x = -box.width / 2;
151 + box.y = -box.height / 2;
152 +
153 + // add padding
154 + box.x -= (lab.padLR + lab.imgPad/2);
155 + box.width += lab.padLR * 2 + lab.imgPad;
156 + box.y -= lab.padTB;
157 + box.height += lab.padTB * 2;
158 +
159 + return box;
160 + }
161 +
162 + function hostLabel(d) {
163 + var idx = (hostLabelIndex < d.labels.length) ? hostLabelIndex : 0;
164 + return d.labels[idx];
165 + }
166 + function deviceLabel(d) {
167 + var idx = (deviceLabelIndex < d.labels.length) ? deviceLabelIndex : 0;
168 + return d.labels[idx];
169 + }
170 + function trimLabel(label) {
171 + return (label && label.trim()) || '';
172 + }
173 +
174 + function emptyBox() {
175 + return {
176 + x: -2,
177 + y: -2,
178 + width: 4,
179 + height: 4
180 + };
181 + }
182 +
183 +
184 + function updateDeviceLabel(d) {
185 + var label = trimLabel(deviceLabel(d)),
186 + noLabel = !label,
187 + node = d.el,
188 + dim = icfg.device.dim,
189 + box, dx, dy;
190 +
191 + node.select('text')
192 + .text(label)
193 + .style('opacity', 0)
194 + .transition()
195 + .style('opacity', 1);
196 +
197 + if (noLabel) {
198 + box = emptyBox();
199 + dx = -dim/2;
200 + dy = -dim/2;
201 + } else {
202 + box = adjustRectToFitText(node);
203 + dx = box.x + devCfg.xoff;
204 + dy = box.y + devCfg.yoff;
205 + }
206 +
207 + node.select('rect')
208 + .transition()
209 + .attr(box);
210 +
211 + node.select('g.deviceIcon')
212 + .transition()
213 + .attr('transform', sus.translate(dx, dy));
214 + }
215 +
216 + function updateHostLabel(d) {
217 + var label = trimLabel(hostLabel(d));
218 + d.el.select('text').text(label);
219 + }
220 +
221 + function updateDeviceColors(d) {
222 + if (d) {
223 + setDeviceColor(d);
224 + } else {
225 + api.node().filter('.device').each(function (d) {
226 + setDeviceColor(d);
227 + });
228 + }
229 + }
230 +
231 +
232 + // ==========================
233 + // updateNodes - subfunctions
234 +
235 + function deviceExisting(d) {
236 + var node = d.el;
237 + node.classed('online', d.online);
238 + updateDeviceLabel(d);
239 + api.posNode(d, true);
240 + }
241 +
242 + function hostExisting(d) {
243 + updateHostLabel(d);
244 + api.posNode(d, true);
245 + }
246 +
247 + function deviceEnter(d) {
248 + var node = d3.select(this),
249 + glyphId = d.type || 'unknown',
250 + label = trimLabel(deviceLabel(d)),
251 + //devCfg = deviceIconConfig,
252 + noLabel = !label,
253 + box, dx, dy, icon;
254 +
255 + d.el = node;
256 +
257 + node.append('rect').attr({ rx: 5, ry: 5 });
258 + node.append('text').text(label).attr('dy', '1.1em');
259 + box = adjustRectToFitText(node);
260 + node.select('rect').attr(box);
261 +
262 + icon = is.addDeviceIcon(node, glyphId);
263 +
264 + if (noLabel) {
265 + dx = -icon.dim/2;
266 + dy = -icon.dim/2;
267 + } else {
268 + box = adjustRectToFitText(node);
269 + dx = box.x + devCfg.xoff;
270 + dy = box.y + devCfg.yoff;
271 + }
272 +
273 + icon.attr('transform', sus.translate(dx, dy));
274 + }
275 +
276 + function hostEnter(d) {
277 + var node = d3.select(this),
278 + gid = d.type || 'unknown',
279 + rad = icfg.host.radius,
280 + r = d.type ? rad.withGlyph : rad.noGlyph,
281 + textDy = r + 10;
282 +
283 + d.el = node;
284 + sus.visible(node, api.showHosts());
285 +
286 + is.addHostIcon(node, r, gid);
287 +
288 + node.append('text')
289 + .text(hostLabel)
290 + .attr('dy', textDy)
291 + .attr('text-anchor', 'middle');
292 + }
293 +
294 + function hostExit(d) {
295 + var node = d.el;
296 + node.select('use')
297 + .style('opacity', 0.5)
298 + .transition()
299 + .duration(800)
300 + .style('opacity', 0);
301 +
302 + node.select('text')
303 + .style('opacity', 0.5)
304 + .transition()
305 + .duration(800)
306 + .style('opacity', 0);
307 +
308 + node.select('circle')
309 + .style('stroke-fill', '#555')
310 + .style('fill', '#888')
311 + .style('opacity', 0.5)
312 + .transition()
313 + .duration(1500)
314 + .attr('r', 0);
315 + }
316 +
317 + function deviceExit(d) {
318 + var node = d.el;
319 + node.select('use')
320 + .style('opacity', 0.5)
321 + .transition()
322 + .duration(800)
323 + .style('opacity', 0);
324 +
325 + node.selectAll('rect')
326 + .style('stroke-fill', '#555')
327 + .style('fill', '#888')
328 + .style('opacity', 0.5);
329 + }
330 +
331 +
332 + // ==========================
333 + // updateLinks - subfunctions
334 +
335 + function linkExisting(d) {
336 + // this is supposed to be an existing link, but we have observed
337 + // occasions (where links are deleted and added rapidly?) where
338 + // the DOM element has not been defined. So protection against that...
339 + if (d.el) {
340 + api.restyleLinkElement(d, true);
341 + }
342 + }
343 +
344 + function linkEntering(d) {
345 + var link = d3.select(this);
346 + d.el = link;
347 + api.restyleLinkElement(d);
348 + if (d.type() === 'hostLink') {
349 + sus.visible(link, api.showHosts());
350 + }
351 + }
352 +
353 + var linkLabelOffset = '0.3em';
354 +
355 + function applyLinkLabels() {
356 + var entering;
357 +
358 + api.updateLinkLabelModel();
359 +
360 + // for elements already existing, we need to update the text
361 + // and adjust the rectangle size to fit
362 + api.linkLabel().each(function (d) {
363 + var el = d3.select(this),
364 + rect = el.select('rect'),
365 + text = el.select('text');
366 + text.text(d.label);
367 + rect.attr(rectAroundText(el));
368 + });
369 +
370 + entering = api.linkLabel().enter().append('g')
371 + .classed('linkLabel', true)
372 + .attr('id', function (d) { return d.id; });
373 +
374 + entering.each(function (d) {
375 + var el = d3.select(this),
376 + rect,
377 + text,
378 + parms = {
379 + x1: d.ldata.source.x,
380 + y1: d.ldata.source.y,
381 + x2: d.ldata.target.x,
382 + y2: d.ldata.target.y
383 + };
384 +
385 + if (d.ldata.type() === 'hostLink') {
386 + el.classed('hostLinkLabel', true);
387 + sus.visible(el, api.showHosts());
388 + }
389 +
390 + d.el = el;
391 + rect = el.append('rect');
392 + text = el.append('text').text(d.label);
393 + rect.attr(rectAroundText(el));
394 + text.attr('dy', linkLabelOffset);
395 +
396 + el.attr('transform', transformLabel(parms));
397 + });
398 +
399 + // Remove any labels that are no longer required.
400 + api.linkLabel().exit().remove();
401 + }
402 +
403 + function rectAroundText(el) {
404 + var text = el.select('text'),
405 + box = text.node().getBBox();
406 +
407 + // translate the bbox so that it is centered on [x,y]
408 + box.x = -box.width / 2;
409 + box.y = -box.height / 2;
410 +
411 + // add padding
412 + box.x -= 1;
413 + box.width += 2;
414 + return box;
415 + }
416 +
417 + function transformLabel(p) {
418 + var dx = p.x2 - p.x1,
419 + dy = p.y2 - p.y1,
420 + xMid = dx/2 + p.x1,
421 + yMid = dy/2 + p.y1;
422 + return sus.translate(xMid, yMid);
423 + }
424 +
425 +
426 + // ==========================
427 + // Module definition
428 +
429 + angular.module('ovTopo')
430 + .factory('TopoD3Service',
431 + ['$log', 'FnService', 'SvgUtilService', 'IconService', 'ThemeService',
432 +
433 + function (_$log_, _fs_, _sus_, _is_, _ts_) {
434 + $log = _$log_;
435 + fs = _fs_;
436 + sus = _sus_;
437 + is = _is_;
438 + ts = _ts_;
439 +
440 + icfg = is.iconConfig();
441 +
442 + function initD3(_api_) {
443 + api = _api_;
444 + }
445 +
446 + function destroyD3() { }
447 +
448 + return {
449 + initD3: initD3,
450 + destroyD3: destroyD3,
451 +
452 + incDevLabIndex: incDevLabIndex,
453 + adjustRectToFitText: adjustRectToFitText,
454 + hostLabel: hostLabel,
455 + deviceLabel: deviceLabel,
456 + trimLabel: trimLabel,
457 +
458 + updateDeviceLabel: updateDeviceLabel,
459 + updateHostLabel: updateHostLabel,
460 + updateDeviceColors: updateDeviceColors,
461 +
462 + deviceExisting: deviceExisting,
463 + hostExisting: hostExisting,
464 + deviceEnter: deviceEnter,
465 + hostEnter: hostEnter,
466 + hostExit: hostExit,
467 + deviceExit: deviceExit,
468 +
469 + linkExisting: linkExisting,
470 + linkEntering: linkEntering,
471 + applyLinkLabels: applyLinkLabels,
472 +
473 + transformLabel: transformLabel
474 + };
475 + }]);
476 +}());
...@@ -23,28 +23,10 @@ ...@@ -23,28 +23,10 @@
23 'use strict'; 23 'use strict';
24 24
25 // injected refs 25 // injected refs
26 - var $log, fs, sus, is, ts, flash, tis, tms, tss, tts, tos, fltr, 26 + var $log, fs, sus, is, ts, flash, tis, tms, td3, tss, tts, tos, fltr,
27 icfg, uplink; 27 icfg, uplink;
28 28
29 // configuration 29 // configuration
30 - var labelConfig = {
31 - imgPad: 16,
32 - padLR: 4,
33 - padTB: 3,
34 - marginLR: 3,
35 - marginTB: 2,
36 - port: {
37 - gap: 3,
38 - width: 18,
39 - height: 14
40 - }
41 - };
42 -
43 - var deviceIconConfig = {
44 - xoff: -20,
45 - yoff: -18
46 - };
47 -
48 var linkConfig = { 30 var linkConfig = {
49 light: { 31 light: {
50 baseColor: '#666', 32 baseColor: '#666',
...@@ -72,8 +54,6 @@ ...@@ -72,8 +54,6 @@
72 }, 54 },
73 lu = network.lookup, // shorthand 55 lu = network.lookup, // shorthand
74 rlk = network.revLinkToKey, 56 rlk = network.revLinkToKey,
75 - deviceLabelIndex = 0, // for device label cycling
76 - hostLabelIndex = 0, // for host label cycling
77 showHosts = false, // whether hosts are displayed 57 showHosts = false, // whether hosts are displayed
78 showOffline = true, // whether offline devices are displayed 58 showOffline = true, // whether offline devices are displayed
79 nodeLock = false, // whether nodes can be dragged or not (locked) 59 nodeLock = false, // whether nodes can be dragged or not (locked)
...@@ -152,9 +132,6 @@ ...@@ -152,9 +132,6 @@
152 tms.findAttachedLinks(d.id).forEach(restyleLinkElement); 132 tms.findAttachedLinks(d.id).forEach(restyleLinkElement);
153 updateOfflineVisibility(d); 133 updateOfflineVisibility(d);
154 } 134 }
155 - } else {
156 - // TODO: decide whether we want to capture logic errors
157 - //logicError('updateDevice lookup fail. ID = "' + id + '"');
158 } 135 }
159 } 136 }
160 137
...@@ -163,9 +140,6 @@ ...@@ -163,9 +140,6 @@
163 d = lu[id]; 140 d = lu[id];
164 if (d) { 141 if (d) {
165 removeDeviceElement(d); 142 removeDeviceElement(d);
166 - } else {
167 - // TODO: decide whether we want to capture logic errors
168 - //logicError('removeDevice lookup fail. ID = "' + id + '"');
169 } 143 }
170 } 144 }
171 145
...@@ -206,9 +180,6 @@ ...@@ -206,9 +180,6 @@
206 sendUpdateMeta(d); 180 sendUpdateMeta(d);
207 } 181 }
208 updateNodes(); 182 updateNodes();
209 - } else {
210 - // TODO: decide whether we want to capture logic errors
211 - //logicError('updateHost lookup fail. ID = "' + id + '"');
212 } 183 }
213 } 184 }
214 185
...@@ -217,9 +188,6 @@ ...@@ -217,9 +188,6 @@
217 d = lu[id]; 188 d = lu[id];
218 if (d) { 189 if (d) {
219 removeHostElement(d, true); 190 removeHostElement(d, true);
220 - } else {
221 - // may have already removed host, if attached to removed device
222 - //console.warn('removeHost lookup fail. ID = "' + id + '"');
223 } 191 }
224 } 192 }
225 193
...@@ -260,15 +228,12 @@ ...@@ -260,15 +228,12 @@
260 } 228 }
261 229
262 function removeLink(data) { 230 function removeLink(data) {
263 - var result = tms.findLink(data, 'remove'), 231 + var result = tms.findLink(data, 'remove');
264 - bad = result.badLogic; 232 +
265 - if (bad) { 233 + if (!result.badLogic) {
266 - // may have already removed link, if attached to removed device
267 - //console.warn(bad + ': ' + link.id);
268 - return;
269 - }
270 result.removeRawLink(); 234 result.removeRawLink();
271 } 235 }
236 + }
272 237
273 // ======================== 238 // ========================
274 239
...@@ -417,107 +382,10 @@ ...@@ -417,107 +382,10 @@
417 } 382 }
418 383
419 384
420 - // ==========================
421 - // === Devices and hosts - D3 rendering
422 -
423 -
424 - // Returns the newly computed bounding box of the rectangle
425 - function adjustRectToFitText(n) {
426 - var text = n.select('text'),
427 - box = text.node().getBBox(),
428 - lab = labelConfig;
429 -
430 - text.attr('text-anchor', 'middle')
431 - .attr('y', '-0.8em')
432 - .attr('x', lab.imgPad/2);
433 -
434 - // translate the bbox so that it is centered on [x,y]
435 - box.x = -box.width / 2;
436 - box.y = -box.height / 2;
437 -
438 - // add padding
439 - box.x -= (lab.padLR + lab.imgPad/2);
440 - box.width += lab.padLR * 2 + lab.imgPad;
441 - box.y -= lab.padTB;
442 - box.height += lab.padTB * 2;
443 -
444 - return box;
445 - }
446 -
447 function mkSvgClass(d) { 385 function mkSvgClass(d) {
448 return d.fixed ? d.svgClass + ' fixed' : d.svgClass; 386 return d.fixed ? d.svgClass + ' fixed' : d.svgClass;
449 } 387 }
450 388
451 - function hostLabel(d) {
452 - var idx = (hostLabelIndex < d.labels.length) ? hostLabelIndex : 0;
453 - return d.labels[idx];
454 - }
455 - function deviceLabel(d) {
456 - var idx = (deviceLabelIndex < d.labels.length) ? deviceLabelIndex : 0;
457 - return d.labels[idx];
458 - }
459 - function trimLabel(label) {
460 - return (label && label.trim()) || '';
461 - }
462 -
463 - function emptyBox() {
464 - return {
465 - x: -2,
466 - y: -2,
467 - width: 4,
468 - height: 4
469 - };
470 - }
471 -
472 -
473 - function updateDeviceLabel(d) {
474 - var label = trimLabel(deviceLabel(d)),
475 - noLabel = !label,
476 - node = d.el,
477 - dim = icfg.device.dim,
478 - devCfg = deviceIconConfig,
479 - box, dx, dy;
480 -
481 - node.select('text')
482 - .text(label)
483 - .style('opacity', 0)
484 - .transition()
485 - .style('opacity', 1);
486 -
487 - if (noLabel) {
488 - box = emptyBox();
489 - dx = -dim/2;
490 - dy = -dim/2;
491 - } else {
492 - box = adjustRectToFitText(node);
493 - dx = box.x + devCfg.xoff;
494 - dy = box.y + devCfg.yoff;
495 - }
496 -
497 - node.select('rect')
498 - .transition()
499 - .attr(box);
500 -
501 - node.select('g.deviceIcon')
502 - .transition()
503 - .attr('transform', sus.translate(dx, dy));
504 - }
505 -
506 - function updateHostLabel(d) {
507 - var label = trimLabel(hostLabel(d));
508 - d.el.select('text').text(label);
509 - }
510 -
511 - function updateDeviceColors(d) {
512 - if (d) {
513 - setDeviceColor(d);
514 - } else {
515 - node.filter('.device').each(function (d) {
516 - setDeviceColor(d);
517 - });
518 - }
519 - }
520 -
521 function vis(b) { 389 function vis(b) {
522 return b ? 'visible' : 'hidden'; 390 return b ? 'visible' : 'hidden';
523 } 391 }
...@@ -535,9 +403,9 @@ ...@@ -535,9 +403,9 @@
535 } 403 }
536 404
537 function cycleDeviceLabels() { 405 function cycleDeviceLabels() {
538 - deviceLabelIndex = (deviceLabelIndex+1) % 3; 406 + td3.incDevLabIndex();
539 tms.findDevices().forEach(function (d) { 407 tms.findDevices().forEach(function (d) {
540 - updateDeviceLabel(d); 408 + td3.updateDeviceLabel(d);
541 }); 409 });
542 } 410 }
543 411
...@@ -583,84 +451,14 @@ ...@@ -583,84 +451,14 @@
583 451
584 // ========================================== 452 // ==========================================
585 453
586 - var dCol = {
587 - black: '#000',
588 - paleblue: '#acf',
589 - offwhite: '#ddd',
590 - darkgrey: '#444',
591 - midgrey: '#888',
592 - lightgrey: '#bbb',
593 - orange: '#f90'
594 - };
595 -
596 - // note: these are the device icon colors without affinity
597 - var dColTheme = {
598 - light: {
599 - rfill: dCol.offwhite,
600 - online: {
601 - glyph: dCol.darkgrey,
602 - rect: dCol.paleblue
603 - },
604 - offline: {
605 - glyph: dCol.midgrey,
606 - rect: dCol.lightgrey
607 - }
608 - },
609 - dark: {
610 - rfill: dCol.midgrey,
611 - online: {
612 - glyph: dCol.darkgrey,
613 - rect: dCol.paleblue
614 - },
615 - offline: {
616 - glyph: dCol.midgrey,
617 - rect: dCol.darkgrey
618 - }
619 - }
620 - };
621 -
622 - function devBaseColor(d) {
623 - var o = d.online ? 'online' : 'offline';
624 - return dColTheme[ts.theme()][o];
625 - }
626 -
627 - function setDeviceColor(d) {
628 - var o = d.online,
629 - s = d.el.classed('selected'),
630 - c = devBaseColor(d),
631 - a = instColor(d.master, o),
632 - icon = d.el.select('g.deviceIcon'),
633 - g, r;
634 -
635 - if (s) {
636 - g = c.glyph;
637 - r = dCol.orange;
638 - } else if (tis.isVisible()) {
639 - g = o ? a : c.glyph;
640 - r = o ? c.rfill : a;
641 - } else {
642 - g = c.glyph;
643 - r = c.rect;
644 - }
645 -
646 - icon.select('use').style('fill', g);
647 - icon.select('rect').style('fill', r);
648 - }
649 -
650 - function instColor(id, online) {
651 - return sus.cat7().getColor(id, !online, ts.theme());
652 - }
653 -
654 - // ==========================
655 -
656 function updateNodes() { 454 function updateNodes() {
657 // select all the nodes in the layout: 455 // select all the nodes in the layout:
658 node = nodeG.selectAll('.node') 456 node = nodeG.selectAll('.node')
659 .data(network.nodes, function (d) { return d.id; }); 457 .data(network.nodes, function (d) { return d.id; });
660 458
661 // operate on existing nodes: 459 // operate on existing nodes:
662 - node.filter('.device').each(deviceExisting); 460 + node.filter('.device').each(td3.deviceExisting);
663 - node.filter('.host').each(hostExisting); 461 + node.filter('.host').each(td3.hostExisting);
664 462
665 // operate on entering nodes: 463 // operate on entering nodes:
666 var entering = node.enter() 464 var entering = node.enter()
...@@ -678,11 +476,11 @@ ...@@ -678,11 +476,11 @@
678 .attr('opacity', 1); 476 .attr('opacity', 1);
679 477
680 // augment entering nodes: 478 // augment entering nodes:
681 - entering.filter('.device').each(deviceEnter); 479 + entering.filter('.device').each(td3.deviceEnter);
682 - entering.filter('.host').each(hostEnter); 480 + entering.filter('.host').each(td3.hostEnter);
683 481
684 // operate on both existing and new nodes: 482 // operate on both existing and new nodes:
685 - updateDeviceColors(); 483 + td3.updateDeviceColors();
686 484
687 // operate on exiting nodes: 485 // operate on exiting nodes:
688 // Note that the node is removed after 2 seconds. 486 // Note that the node is removed after 2 seconds.
...@@ -694,113 +492,14 @@ ...@@ -694,113 +492,14 @@
694 .remove(); 492 .remove();
695 493
696 // exiting node specifics: 494 // exiting node specifics:
697 - exiting.filter('.host').each(hostExit); 495 + exiting.filter('.host').each(td3.hostExit);
698 - exiting.filter('.device').each(deviceExit); 496 + exiting.filter('.device').each(td3.deviceExit);
699 497
700 // finally, resume the force layout 498 // finally, resume the force layout
701 fResume(); 499 fResume();
702 } 500 }
703 501
704 // ========================== 502 // ==========================
705 - // updateNodes - subfunctions
706 -
707 - function deviceExisting(d) {
708 - var node = d.el;
709 - node.classed('online', d.online);
710 - updateDeviceLabel(d);
711 - tms.positionNode(d, true);
712 - }
713 -
714 - function hostExisting(d) {
715 - updateHostLabel(d);
716 - tms.positionNode(d, true);
717 - }
718 -
719 - function deviceEnter(d) {
720 - var node = d3.select(this),
721 - glyphId = d.type || 'unknown',
722 - label = trimLabel(deviceLabel(d)),
723 - devCfg = deviceIconConfig,
724 - noLabel = !label,
725 - box, dx, dy, icon;
726 -
727 - d.el = node;
728 -
729 - node.append('rect').attr({ rx: 5, ry: 5 });
730 - node.append('text').text(label).attr('dy', '1.1em');
731 - box = adjustRectToFitText(node);
732 - node.select('rect').attr(box);
733 -
734 - icon = is.addDeviceIcon(node, glyphId);
735 -
736 - if (noLabel) {
737 - dx = -icon.dim/2;
738 - dy = -icon.dim/2;
739 - } else {
740 - box = adjustRectToFitText(node);
741 - dx = box.x + devCfg.xoff;
742 - dy = box.y + devCfg.yoff;
743 - }
744 -
745 - icon.attr('transform', sus.translate(dx, dy));
746 - }
747 -
748 - function hostEnter(d) {
749 - var node = d3.select(this),
750 - gid = d.type || 'unknown',
751 - rad = icfg.host.radius,
752 - r = d.type ? rad.withGlyph : rad.noGlyph,
753 - textDy = r + 10;
754 -
755 - d.el = node;
756 - sus.visible(node, showHosts);
757 -
758 - is.addHostIcon(node, r, gid);
759 -
760 - node.append('text')
761 - .text(hostLabel)
762 - .attr('dy', textDy)
763 - .attr('text-anchor', 'middle');
764 - }
765 -
766 - function hostExit(d) {
767 - var node = d.el;
768 - node.select('use')
769 - .style('opacity', 0.5)
770 - .transition()
771 - .duration(800)
772 - .style('opacity', 0);
773 -
774 - node.select('text')
775 - .style('opacity', 0.5)
776 - .transition()
777 - .duration(800)
778 - .style('opacity', 0);
779 -
780 - node.select('circle')
781 - .style('stroke-fill', '#555')
782 - .style('fill', '#888')
783 - .style('opacity', 0.5)
784 - .transition()
785 - .duration(1500)
786 - .attr('r', 0);
787 - }
788 -
789 - function deviceExit(d) {
790 - var node = d.el;
791 - node.select('use')
792 - .style('opacity', 0.5)
793 - .transition()
794 - .duration(800)
795 - .style('opacity', 0);
796 -
797 - node.selectAll('rect')
798 - .style('stroke-fill', '#555')
799 - .style('fill', '#888')
800 - .style('opacity', 0.5);
801 - }
802 -
803 - // ==========================
804 503
805 function updateLinks() { 504 function updateLinks() {
806 var th = ts.theme(); 505 var th = ts.theme();
...@@ -809,7 +508,7 @@ ...@@ -809,7 +508,7 @@
809 .data(network.links, function (d) { return d.key; }); 508 .data(network.links, function (d) { return d.key; });
810 509
811 // operate on existing links: 510 // operate on existing links:
812 - link.each(linkExisting); 511 + link.each(td3.linkExisting);
813 512
814 // operate on entering links: 513 // operate on entering links:
815 var entering = link.enter() 514 var entering = link.enter()
...@@ -824,14 +523,13 @@ ...@@ -824,14 +523,13 @@
824 }); 523 });
825 524
826 // augment links 525 // augment links
827 - entering.each(linkEntering); 526 + entering.each(td3.linkEntering);
828 527
829 // operate on both existing and new links: 528 // operate on both existing and new links:
830 //link.each(...) 529 //link.each(...)
831 530
832 // apply or remove labels 531 // apply or remove labels
833 - var labelData = getLabelData(); 532 + td3.applyLinkLabels();
834 - applyLinkLabels(labelData);
835 533
836 // operate on exiting links: 534 // operate on exiting links:
837 link.exit() 535 link.exit()
...@@ -848,117 +546,6 @@ ...@@ -848,117 +546,6 @@
848 .remove(); 546 .remove();
849 } 547 }
850 548
851 - // ==========================
852 - // updateLinks - subfunctions
853 -
854 - function getLabelData() {
855 - // create the backing data for showing labels..
856 - var data = [];
857 - link.each(function (d) {
858 - if (d.label) {
859 - data.push({
860 - id: 'lab-' + d.key,
861 - key: d.key,
862 - label: d.label,
863 - ldata: d
864 - });
865 - }
866 - });
867 - return data;
868 - }
869 -
870 - function linkExisting(d) {
871 - // this is supposed to be an existing link, but we have observed
872 - // occasions (where links are deleted and added rapidly?) where
873 - // the DOM element has not been defined. So protection against that...
874 - if (d.el) {
875 - restyleLinkElement(d, true);
876 - }
877 - }
878 -
879 - function linkEntering(d) {
880 - var link = d3.select(this);
881 - d.el = link;
882 - restyleLinkElement(d);
883 - if (d.type() === 'hostLink') {
884 - sus.visible(link, showHosts);
885 - }
886 - }
887 -
888 - //function linkExiting(d) { }
889 -
890 - var linkLabelOffset = '0.3em';
891 -
892 - function applyLinkLabels(data) {
893 - var entering;
894 -
895 - linkLabel = linkLabelG.selectAll('.linkLabel')
896 - .data(data, function (d) { return d.id; });
897 -
898 - // for elements already existing, we need to update the text
899 - // and adjust the rectangle size to fit
900 - linkLabel.each(function (d) {
901 - var el = d3.select(this),
902 - rect = el.select('rect'),
903 - text = el.select('text');
904 - text.text(d.label);
905 - rect.attr(rectAroundText(el));
906 - });
907 -
908 - entering = linkLabel.enter().append('g')
909 - .classed('linkLabel', true)
910 - .attr('id', function (d) { return d.id; });
911 -
912 - entering.each(function (d) {
913 - var el = d3.select(this),
914 - rect,
915 - text,
916 - parms = {
917 - x1: d.ldata.source.x,
918 - y1: d.ldata.source.y,
919 - x2: d.ldata.target.x,
920 - y2: d.ldata.target.y
921 - };
922 -
923 - if (d.ldata.type() === 'hostLink') {
924 - el.classed('hostLinkLabel', true);
925 - sus.visible(el, showHosts);
926 - }
927 -
928 - d.el = el;
929 - rect = el.append('rect');
930 - text = el.append('text').text(d.label);
931 - rect.attr(rectAroundText(el));
932 - text.attr('dy', linkLabelOffset);
933 -
934 - el.attr('transform', transformLabel(parms));
935 - });
936 -
937 - // Remove any labels that are no longer required.
938 - linkLabel.exit().remove();
939 - }
940 -
941 - function rectAroundText(el) {
942 - var text = el.select('text'),
943 - box = text.node().getBBox();
944 -
945 - // translate the bbox so that it is centered on [x,y]
946 - box.x = -box.width / 2;
947 - box.y = -box.height / 2;
948 -
949 - // add padding
950 - box.x -= 1;
951 - box.width += 2;
952 - return box;
953 - }
954 -
955 - function transformLabel(p) {
956 - var dx = p.x2 - p.x1,
957 - dy = p.y2 - p.y1,
958 - xMid = dx/2 + p.x1,
959 - yMid = dy/2 + p.y1;
960 - return sus.translate(xMid, yMid);
961 - }
962 549
963 // ========================== 550 // ==========================
964 // force layout tick function 551 // force layout tick function
...@@ -989,7 +576,7 @@ ...@@ -989,7 +576,7 @@
989 transform: function (d) { 576 transform: function (d) {
990 var lnk = tms.findLinkById(d.key); 577 var lnk = tms.findLinkById(d.key);
991 if (lnk) { 578 if (lnk) {
992 - return transformLabel({ 579 + return td3.transformLabel({
993 x1: lnk.source.x, 580 x1: lnk.source.x,
994 y1: lnk.source.y, 581 y1: lnk.source.y,
995 x2: lnk.target.x, 582 x2: lnk.target.x,
...@@ -1049,6 +636,24 @@ ...@@ -1049,6 +636,24 @@
1049 }); 636 });
1050 } 637 }
1051 638
639 + function updateLinkLabelModel() {
640 + // create the backing data for showing labels..
641 + var data = [];
642 + link.each(function (d) {
643 + if (d.label) {
644 + data.push({
645 + id: 'lab-' + d.key,
646 + key: d.key,
647 + label: d.label,
648 + ldata: d
649 + });
650 + }
651 + });
652 +
653 + linkLabel = linkLabelG.selectAll('.linkLabel')
654 + .data(data, function (d) { return d.id; });
655 + }
656 +
1052 // ========================== 657 // ==========================
1053 // Module definition 658 // Module definition
1054 659
...@@ -1061,11 +666,24 @@ ...@@ -1061,11 +666,24 @@
1061 }; 666 };
1062 } 667 }
1063 668
669 + function mkD3Api(uplink) {
670 + return {
671 + node: function () { return node; },
672 + link: function () { return link; },
673 + linkLabel: function () { return linkLabel; },
674 + instVisible: function () { return tis.isVisible(); },
675 + posNode: tms.positionNode,
676 + showHosts: function () { return showHosts; },
677 + restyleLinkElement: restyleLinkElement,
678 + updateLinkLabelModel: updateLinkLabelModel
679 + }
680 + }
681 +
1064 function mkSelectApi(uplink) { 682 function mkSelectApi(uplink) {
1065 return { 683 return {
1066 node: function () { return node; }, 684 node: function () { return node; },
1067 zoomingOrPanning: zoomingOrPanning, 685 zoomingOrPanning: zoomingOrPanning,
1068 - updateDeviceColors: updateDeviceColors, 686 + updateDeviceColors: td3.updateDeviceColors,
1069 sendEvent: uplink.sendEvent 687 sendEvent: uplink.sendEvent
1070 }; 688 };
1071 } 689 }
...@@ -1114,11 +732,11 @@ ...@@ -1114,11 +732,11 @@
1114 .factory('TopoForceService', 732 .factory('TopoForceService',
1115 ['$log', 'FnService', 'SvgUtilService', 'IconService', 'ThemeService', 733 ['$log', 'FnService', 'SvgUtilService', 'IconService', 'ThemeService',
1116 'FlashService', 'TopoInstService', 'TopoModelService', 734 'FlashService', 'TopoInstService', 'TopoModelService',
1117 - 'TopoSelectService', 'TopoTrafficService', 735 + 'TopoD3Service', 'TopoSelectService', 'TopoTrafficService',
1118 'TopoObliqueService', 'TopoFilterService', 736 'TopoObliqueService', 'TopoFilterService',
1119 737
1120 function (_$log_, _fs_, _sus_, _is_, _ts_, _flash_, 738 function (_$log_, _fs_, _sus_, _is_, _ts_, _flash_,
1121 - _tis_, _tms_, _tss_, _tts_, _tos_, _fltr_) { 739 + _tis_, _tms_, _td3_, _tss_, _tts_, _tos_, _fltr_) {
1122 $log = _$log_; 740 $log = _$log_;
1123 fs = _fs_; 741 fs = _fs_;
1124 sus = _sus_; 742 sus = _sus_;
...@@ -1127,6 +745,7 @@ ...@@ -1127,6 +745,7 @@
1127 flash = _flash_; 745 flash = _flash_;
1128 tis = _tis_; 746 tis = _tis_;
1129 tms = _tms_; 747 tms = _tms_;
748 + td3 = _td3_;
1130 tss = _tss_; 749 tss = _tss_;
1131 tts = _tts_; 750 tts = _tts_;
1132 tos = _tos_; 751 tos = _tos_;
...@@ -1150,6 +769,7 @@ ...@@ -1150,6 +769,7 @@
1150 $log.debug('initForce().. dim = ' + dim); 769 $log.debug('initForce().. dim = ' + dim);
1151 770
1152 tms.initModel(mkModelApi(uplink), dim); 771 tms.initModel(mkModelApi(uplink), dim);
772 + td3.initD3(mkD3Api(uplink));
1153 tss.initSelect(mkSelectApi(uplink)); 773 tss.initSelect(mkSelectApi(uplink));
1154 tts.initTraffic(mkTrafficApi(uplink)); 774 tts.initTraffic(mkTrafficApi(uplink));
1155 tos.initOblique(mkObliqueApi(uplink, fltr)); 775 tos.initOblique(mkObliqueApi(uplink, fltr));
...@@ -1192,6 +812,7 @@ ...@@ -1192,6 +812,7 @@
1192 tos.destroyOblique(); 812 tos.destroyOblique();
1193 tts.destroyTraffic(); 813 tts.destroyTraffic();
1194 tss.destroySelect(); 814 tss.destroySelect();
815 + td3.destroyD3();
1195 tms.destroyModel(); 816 tms.destroyModel();
1196 ts.removeListener(themeListener); 817 ts.removeListener(themeListener);
1197 themeListener = null; 818 themeListener = null;
...@@ -1202,7 +823,7 @@ ...@@ -1202,7 +823,7 @@
1202 newDim: newDim, 823 newDim: newDim,
1203 destroyForce: destroyForce, 824 destroyForce: destroyForce,
1204 825
1205 - updateDeviceColors: updateDeviceColors, 826 + updateDeviceColors: td3.updateDeviceColors,
1206 toggleHosts: toggleHosts, 827 toggleHosts: toggleHosts,
1207 toggleOffline: toggleOffline, 828 toggleOffline: toggleOffline,
1208 cycleDeviceLabels: cycleDeviceLabels, 829 cycleDeviceLabels: cycleDeviceLabels,
......
...@@ -86,6 +86,7 @@ ...@@ -86,6 +86,7 @@
86 <!-- {INJECTED-JAVASCRIPT-START} --> 86 <!-- {INJECTED-JAVASCRIPT-START} -->
87 <script src="app/view/sample/sample.js"></script> 87 <script src="app/view/sample/sample.js"></script>
88 <script src="app/view/topo/topo.js"></script> 88 <script src="app/view/topo/topo.js"></script>
89 + <script src="app/view/topo/topoD3.js"></script>
89 <script src="app/view/topo/topoEvent.js"></script> 90 <script src="app/view/topo/topoEvent.js"></script>
90 <script src="app/view/topo/topoFilter.js"></script> 91 <script src="app/view/topo/topoFilter.js"></script>
91 <script src="app/view/topo/topoForce.js"></script> 92 <script src="app/view/topo/topoForce.js"></script>
...@@ -104,7 +105,6 @@ ...@@ -104,7 +105,6 @@
104 <link rel="stylesheet" href="app/view/sample/sample.css"> 105 <link rel="stylesheet" href="app/view/sample/sample.css">
105 <link rel="stylesheet" href="app/view/topo/topo.css"> 106 <link rel="stylesheet" href="app/view/topo/topo.css">
106 <link rel="stylesheet" href="app/view/device/device.css"> 107 <link rel="stylesheet" href="app/view/device/device.css">
107 - <!-- TODO: inject style-sheet refs server-side -->
108 <!-- {INJECTED-STYLESHEETS-END} --> 108 <!-- {INJECTED-STYLESHEETS-END} -->
109 109
110 </head> 110 </head>
......