blob: bddfdaad20e0525c6efcf204511c89ba4065fa7b [file] [log] [blame]
Simon Hunt737c89f2015-01-28 12:23:19 -08001/*
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/*
Simon Hunt3a6eec02015-02-09 21:16:43 -080018 ONOS GUI -- Topology Force Module.
19 Visualization of the topology in an SVG layer, using a D3 Force Layout.
Simon Hunt737c89f2015-01-28 12:23:19 -080020 */
21
22(function () {
23 'use strict';
24
25 // injected refs
Simon Hunt96f88c62015-02-19 17:57:25 -080026 var $log, fs, sus, is, ts, flash, tis, tms, tss, tts, tos, fltr,
Simon Hunteb0fa052015-02-17 19:20:28 -080027 icfg, uplink;
Simon Huntac4c6f72015-02-03 19:50:53 -080028
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 };
Simon Hunt737c89f2015-01-28 12:23:19 -080047
Simon Hunt1894d792015-02-04 17:09:20 -080048 var linkConfig = {
49 light: {
50 baseColor: '#666',
51 inColor: '#66f',
Simon Hunt3a6eec02015-02-09 21:16:43 -080052 outColor: '#f00'
Simon Hunt1894d792015-02-04 17:09:20 -080053 },
54 dark: {
Simon Hunt5724fb42015-02-05 16:59:40 -080055 baseColor: '#aaa',
Simon Hunt1894d792015-02-04 17:09:20 -080056 inColor: '#66f',
Simon Hunt5724fb42015-02-05 16:59:40 -080057 outColor: '#f66'
Simon Hunt1894d792015-02-04 17:09:20 -080058 },
59 inWidth: 12,
60 outWidth: 10
61 };
62
Simon Hunt737c89f2015-01-28 12:23:19 -080063 // internal state
Simon Huntac4c6f72015-02-03 19:50:53 -080064 var settings, // merged default settings and options
Simon Hunt737c89f2015-01-28 12:23:19 -080065 force, // force layout object
66 drag, // drag behavior handler
67 network = {
68 nodes: [],
69 links: [],
70 lookup: {},
71 revLinkToKey: {}
Simon Huntac4c6f72015-02-03 19:50:53 -080072 },
Simon Hunt1894d792015-02-04 17:09:20 -080073 lu = network.lookup, // shorthand
Simon Huntdc6adea2015-02-09 22:29:36 -080074 rlk = network.revLinkToKey,
Simon Huntac4c6f72015-02-03 19:50:53 -080075 deviceLabelIndex = 0, // for device label cycling
Simon Hunt1894d792015-02-04 17:09:20 -080076 hostLabelIndex = 0, // for host label cycling
Simon Hunta142dd22015-02-12 22:07:51 -080077 showHosts = false, // whether hosts are displayed
Simon Hunt5724fb42015-02-05 16:59:40 -080078 showOffline = true, // whether offline devices are displayed
Simon Hunt445e8152015-02-06 13:00:12 -080079 nodeLock = false, // whether nodes can be dragged or not (locked)
Simon Hunt08f841d02015-02-10 14:39:20 -080080 dim; // the dimensions of the force layout [w,h]
Simon Hunt737c89f2015-01-28 12:23:19 -080081
82 // SVG elements;
83 var linkG, linkLabelG, nodeG;
84
85 // D3 selections;
86 var link, linkLabel, node;
87
88 // default settings for force layout
89 var defaultSettings = {
90 gravity: 0.4,
91 friction: 0.7,
92 charge: {
93 // note: key is node.class
94 device: -8000,
95 host: -5000,
96 _def_: -12000
97 },
98 linkDistance: {
99 // note: key is link.type
100 direct: 100,
101 optical: 120,
102 hostLink: 3,
103 _def_: 50
104 },
105 linkStrength: {
106 // note: key is link.type
107 // range: {0.0 ... 1.0}
108 //direct: 1.0,
109 //optical: 1.0,
110 //hostLink: 1.0,
111 _def_: 1.0
112 }
113 };
114
115
Simon Huntac4c6f72015-02-03 19:50:53 -0800116 // ==========================
117 // === EVENT HANDLERS
118
119 function addDevice(data) {
120 var id = data.id,
121 d;
122
Simon Hunt1894d792015-02-04 17:09:20 -0800123 uplink.showNoDevs(false);
Simon Huntac4c6f72015-02-03 19:50:53 -0800124
125 // although this is an add device event, if we already have the
126 // device, treat it as an update instead..
Simon Hunt1894d792015-02-04 17:09:20 -0800127 if (lu[id]) {
Simon Huntac4c6f72015-02-03 19:50:53 -0800128 updateDevice(data);
129 return;
130 }
131
Simon Hunt3a6eec02015-02-09 21:16:43 -0800132 d = tms.createDeviceNode(data);
Simon Huntac4c6f72015-02-03 19:50:53 -0800133 network.nodes.push(d);
Simon Hunt1894d792015-02-04 17:09:20 -0800134 lu[id] = d;
Simon Huntac4c6f72015-02-03 19:50:53 -0800135 updateNodes();
136 fStart();
137 }
138
139 function updateDevice(data) {
140 var id = data.id,
Simon Hunt1894d792015-02-04 17:09:20 -0800141 d = lu[id],
Simon Huntac4c6f72015-02-03 19:50:53 -0800142 wasOnline;
143
144 if (d) {
145 wasOnline = d.online;
146 angular.extend(d, data);
Simon Hunt3a6eec02015-02-09 21:16:43 -0800147 if (tms.positionNode(d, true)) {
Simon Hunt445e8152015-02-06 13:00:12 -0800148 sendUpdateMeta(d);
Simon Huntac4c6f72015-02-03 19:50:53 -0800149 }
150 updateNodes();
151 if (wasOnline !== d.online) {
Simon Huntdc6adea2015-02-09 22:29:36 -0800152 tms.findAttachedLinks(d.id).forEach(restyleLinkElement);
Simon Hunt5724fb42015-02-05 16:59:40 -0800153 updateOfflineVisibility(d);
Simon Huntac4c6f72015-02-03 19:50:53 -0800154 }
155 } else {
156 // TODO: decide whether we want to capture logic errors
157 //logicError('updateDevice lookup fail. ID = "' + id + '"');
158 }
159 }
160
Simon Hunt1894d792015-02-04 17:09:20 -0800161 function removeDevice(data) {
162 var id = data.id,
163 d = lu[id];
164 if (d) {
165 removeDeviceElement(d);
166 } else {
167 // TODO: decide whether we want to capture logic errors
168 //logicError('removeDevice lookup fail. ID = "' + id + '"');
169 }
170 }
171
172 function addHost(data) {
173 var id = data.id,
174 d, lnk;
175
176 // although this is an add host event, if we already have the
177 // host, treat it as an update instead..
178 if (lu[id]) {
179 updateHost(data);
180 return;
181 }
182
Simon Hunt3a6eec02015-02-09 21:16:43 -0800183 d = tms.createHostNode(data);
Simon Hunt1894d792015-02-04 17:09:20 -0800184 network.nodes.push(d);
185 lu[id] = d;
Simon Hunt1894d792015-02-04 17:09:20 -0800186 updateNodes();
187
Simon Hunt3a6eec02015-02-09 21:16:43 -0800188 lnk = tms.createHostLink(data);
Simon Hunt1894d792015-02-04 17:09:20 -0800189 if (lnk) {
Simon Hunt1894d792015-02-04 17:09:20 -0800190 d.linkData = lnk; // cache ref on its host
191 network.links.push(lnk);
192 lu[d.ingress] = lnk;
193 lu[d.egress] = lnk;
194 updateLinks();
195 }
196
197 fStart();
198 }
199
200 function updateHost(data) {
201 var id = data.id,
202 d = lu[id];
203 if (d) {
204 angular.extend(d, data);
Simon Hunt3a6eec02015-02-09 21:16:43 -0800205 if (tms.positionNode(d, true)) {
Simon Hunt445e8152015-02-06 13:00:12 -0800206 sendUpdateMeta(d);
Simon Hunt1894d792015-02-04 17:09:20 -0800207 }
208 updateNodes();
209 } else {
210 // TODO: decide whether we want to capture logic errors
211 //logicError('updateHost lookup fail. ID = "' + id + '"');
212 }
213 }
214
215 function removeHost(data) {
216 var id = data.id,
217 d = lu[id];
218 if (d) {
219 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 }
224 }
225
226 function addLink(data) {
Simon Huntdc6adea2015-02-09 22:29:36 -0800227 var result = tms.findLink(data, 'add'),
Simon Hunt1894d792015-02-04 17:09:20 -0800228 bad = result.badLogic,
229 d = result.ldata;
230
231 if (bad) {
232 //logicError(bad + ': ' + link.id);
233 return;
234 }
235
236 if (d) {
237 // we already have a backing store link for src/dst nodes
238 addLinkUpdate(d, data);
239 return;
240 }
241
242 // no backing store link yet
Simon Hunt3a6eec02015-02-09 21:16:43 -0800243 d = tms.createLink(data);
Simon Hunt1894d792015-02-04 17:09:20 -0800244 if (d) {
245 network.links.push(d);
246 lu[d.key] = d;
247 updateLinks();
248 fStart();
249 }
250 }
251
252 function updateLink(data) {
Simon Huntdc6adea2015-02-09 22:29:36 -0800253 var result = tms.findLink(data, 'update'),
Simon Hunt1894d792015-02-04 17:09:20 -0800254 bad = result.badLogic;
255 if (bad) {
256 //logicError(bad + ': ' + link.id);
257 return;
258 }
259 result.updateWith(link);
260 }
261
262 function removeLink(data) {
Simon Huntdc6adea2015-02-09 22:29:36 -0800263 var result = tms.findLink(data, 'remove'),
Simon Hunt1894d792015-02-04 17:09:20 -0800264 bad = result.badLogic;
265 if (bad) {
266 // may have already removed link, if attached to removed device
267 //console.warn(bad + ': ' + link.id);
268 return;
269 }
270 result.removeRawLink();
271 }
272
273 // ========================
274
275 function addLinkUpdate(ldata, link) {
276 // add link event, but we already have the reverse link installed
277 ldata.fromTarget = link;
Simon Huntdc6adea2015-02-09 22:29:36 -0800278 rlk[link.id] = ldata.key;
Simon Hunt1894d792015-02-04 17:09:20 -0800279 restyleLinkElement(ldata);
280 }
281
Simon Hunt1894d792015-02-04 17:09:20 -0800282
283 var widthRatio = 1.4,
284 linkScale = d3.scale.linear()
285 .domain([1, 12])
286 .range([widthRatio, 12 * widthRatio])
Simon Hunt5724fb42015-02-05 16:59:40 -0800287 .clamp(true),
Simon Hunt3a6eec02015-02-09 21:16:43 -0800288 allLinkTypes = 'direct indirect optical tunnel';
Simon Hunt1894d792015-02-04 17:09:20 -0800289
Simon Hunta142dd22015-02-12 22:07:51 -0800290 function restyleLinkElement(ldata, immediate) {
Simon Hunt1894d792015-02-04 17:09:20 -0800291 // this fn's job is to look at raw links and decide what svg classes
292 // need to be applied to the line element in the DOM
293 var th = ts.theme(),
294 el = ldata.el,
295 type = ldata.type(),
296 lw = ldata.linkWidth(),
Simon Hunta142dd22015-02-12 22:07:51 -0800297 online = ldata.online(),
298 delay = immediate ? 0 : 1000;
Simon Hunt1894d792015-02-04 17:09:20 -0800299
300 el.classed('link', true);
301 el.classed('inactive', !online);
302 el.classed(allLinkTypes, false);
303 if (type) {
304 el.classed(type, true);
305 }
306 el.transition()
Simon Hunta142dd22015-02-12 22:07:51 -0800307 .duration(delay)
Simon Hunt1894d792015-02-04 17:09:20 -0800308 .attr('stroke-width', linkScale(lw))
309 .attr('stroke', linkConfig[th].baseColor);
310 }
311
Simon Hunt1894d792015-02-04 17:09:20 -0800312 function removeLinkElement(d) {
313 var idx = fs.find(d.key, network.links, 'key'),
314 removed;
315 if (idx >=0) {
316 // remove from links array
317 removed = network.links.splice(idx, 1);
318 // remove from lookup cache
319 delete lu[removed[0].key];
320 updateLinks();
321 fResume();
322 }
323 }
324
325 function removeHostElement(d, upd) {
326 // first, remove associated hostLink...
327 removeLinkElement(d.linkData);
328
329 // remove hostLink bindings
330 delete lu[d.ingress];
331 delete lu[d.egress];
332
333 // remove from lookup cache
334 delete lu[d.id];
335 // remove from nodes array
336 var idx = fs.find(d.id, network.nodes);
337 network.nodes.splice(idx, 1);
338
339 // remove from SVG
340 // NOTE: upd is false if we were called from removeDeviceElement()
341 if (upd) {
342 updateNodes();
343 fResume();
344 }
345 }
346
347 function removeDeviceElement(d) {
348 var id = d.id;
349 // first, remove associated hosts and links..
Simon Huntdc6adea2015-02-09 22:29:36 -0800350 tms.findAttachedHosts(id).forEach(removeHostElement);
351 tms.findAttachedLinks(id).forEach(removeLinkElement);
Simon Hunt1894d792015-02-04 17:09:20 -0800352
353 // remove from lookup cache
354 delete lu[id];
355 // remove from nodes array
356 var idx = fs.find(id, network.nodes);
357 network.nodes.splice(idx, 1);
358
359 if (!network.nodes.length) {
Simon Huntdc6adea2015-02-09 22:29:36 -0800360 uplink.showNoDevs(true);
Simon Hunt1894d792015-02-04 17:09:20 -0800361 }
362
363 // remove from SVG
364 updateNodes();
365 fResume();
366 }
367
Simon Hunt5724fb42015-02-05 16:59:40 -0800368 function updateHostVisibility() {
Simon Hunt18bf9822015-02-12 17:35:45 -0800369 sus.visible(nodeG.selectAll('.host'), showHosts);
370 sus.visible(linkG.selectAll('.hostLink'), showHosts);
Simon Hunt8eb4d3a2015-02-23 18:23:29 -0800371 sus.visible(linkLabelG.selectAll('.hostLinkLabel'), showHosts);
Simon Hunt5724fb42015-02-05 16:59:40 -0800372 }
373
374 function updateOfflineVisibility(dev) {
375 function updDev(d, show) {
Simon Hunt8eb4d3a2015-02-23 18:23:29 -0800376 var b;
Simon Hunt18bf9822015-02-12 17:35:45 -0800377 sus.visible(d.el, show);
Simon Hunt5724fb42015-02-05 16:59:40 -0800378
Simon Huntdc6adea2015-02-09 22:29:36 -0800379 tms.findAttachedLinks(d.id).forEach(function (link) {
Simon Hunt5724fb42015-02-05 16:59:40 -0800380 b = show && ((link.type() !== 'hostLink') || showHosts);
Simon Hunt18bf9822015-02-12 17:35:45 -0800381 sus.visible(link.el, b);
Simon Hunt5724fb42015-02-05 16:59:40 -0800382 });
Simon Huntdc6adea2015-02-09 22:29:36 -0800383 tms.findAttachedHosts(d.id).forEach(function (host) {
Simon Hunt5724fb42015-02-05 16:59:40 -0800384 b = show && showHosts;
Simon Hunt18bf9822015-02-12 17:35:45 -0800385 sus.visible(host.el, b);
Simon Hunt5724fb42015-02-05 16:59:40 -0800386 });
387 }
388
389 if (dev) {
390 // updating a specific device that just toggled off/on-line
391 updDev(dev, dev.online || showOffline);
392 } else {
393 // updating all offline devices
Simon Huntdc6adea2015-02-09 22:29:36 -0800394 tms.findDevices(true).forEach(function (d) {
Simon Hunt5724fb42015-02-05 16:59:40 -0800395 updDev(d, showOffline);
396 });
397 }
398 }
399
Simon Hunt1894d792015-02-04 17:09:20 -0800400
Simon Hunt445e8152015-02-06 13:00:12 -0800401 function sendUpdateMeta(d, clearPos) {
Simon Huntac4c6f72015-02-03 19:50:53 -0800402 var metaUi = {},
403 ll;
404
Simon Hunt445e8152015-02-06 13:00:12 -0800405 // if we are not clearing the position data (unpinning),
406 // attach the x, y, longitude, latitude...
407 if (!clearPos) {
Simon Hunt3a6eec02015-02-09 21:16:43 -0800408 ll = tms.lngLatFromCoord([d.x, d.y]);
Simon Huntdc6adea2015-02-09 22:29:36 -0800409 metaUi = {x: d.x, y: d.y, lng: ll[0], lat: ll[1]};
Simon Hunt1894d792015-02-04 17:09:20 -0800410 }
411 d.metaUi = metaUi;
412 uplink.sendEvent('updateMeta', {
413 id: d.id,
414 'class': d.class,
415 memento: metaUi
416 });
Simon Huntac4c6f72015-02-03 19:50:53 -0800417 }
418
Simon Hunt1894d792015-02-04 17:09:20 -0800419
Simon Huntac4c6f72015-02-03 19:50:53 -0800420 // ==========================
421 // === Devices and hosts - D3 rendering
422
Simon Hunt1894d792015-02-04 17:09:20 -0800423
Simon Huntac4c6f72015-02-03 19:50:53 -0800424 // 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) {
448 return d.fixed ? d.svgClass + ' fixed' : d.svgClass;
449 }
450
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,
Simon Hunt1894d792015-02-04 17:09:20 -0800477 dim = icfg.device.dim,
Simon Huntac4c6f72015-02-03 19:50:53 -0800478 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
Simon Huntac4c6f72015-02-03 19:50:53 -0800511 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
Simon Hunt5724fb42015-02-05 16:59:40 -0800521 function vis(b) {
522 return b ? 'visible' : 'hidden';
523 }
524
525 function toggleHosts() {
526 showHosts = !showHosts;
527 updateHostVisibility();
528 flash.flash('Hosts ' + vis(showHosts));
529 }
530
531 function toggleOffline() {
532 showOffline = !showOffline;
533 updateOfflineVisibility();
534 flash.flash('Offline devices ' + vis(showOffline));
535 }
536
537 function cycleDeviceLabels() {
Simon Hunt1c367112015-02-05 18:02:46 -0800538 deviceLabelIndex = (deviceLabelIndex+1) % 3;
Simon Huntdc6adea2015-02-09 22:29:36 -0800539 tms.findDevices().forEach(function (d) {
Simon Hunt1c367112015-02-05 18:02:46 -0800540 updateDeviceLabel(d);
541 });
Simon Hunt5724fb42015-02-05 16:59:40 -0800542 }
543
Simon Hunt445e8152015-02-06 13:00:12 -0800544 function unpin() {
Simon Hunt08f841d02015-02-10 14:39:20 -0800545 var hov = tss.hovered();
546 if (hov) {
547 sendUpdateMeta(hov, true);
548 hov.fixed = false;
549 hov.el.classed('fixed', false);
Simon Hunt445e8152015-02-06 13:00:12 -0800550 fResume();
551 }
552 }
553
Simon Hunta142dd22015-02-12 22:07:51 -0800554 function showMastership(masterId) {
555 if (!masterId) {
556 restoreLayerState();
557 } else {
558 showMastershipFor(masterId);
559 }
560 }
561
562 function restoreLayerState() {
563 // NOTE: this level of indirection required, for when we have
564 // the layer filter functionality re-implemented
565 suppressLayers(false);
566 }
567
568 function showMastershipFor(id) {
569 suppressLayers(true);
570 node.each(function (n) {
571 if (n.master === id) {
572 n.el.classed('suppressed', false);
573 }
574 });
575 }
576
577 function suppressLayers(b) {
578 node.classed('suppressed', b);
579 link.classed('suppressed', b);
580// d3.selectAll('svg .port').classed('inactive', b);
581// d3.selectAll('svg .portText').classed('inactive', b);
582 }
Simon Hunt445e8152015-02-06 13:00:12 -0800583
Simon Hunt5724fb42015-02-05 16:59:40 -0800584 // ==========================================
585
Simon Huntac4c6f72015-02-03 19:50:53 -0800586 var dCol = {
587 black: '#000',
588 paleblue: '#acf',
589 offwhite: '#ddd',
Simon Hunt78c10bf2015-02-03 21:18:20 -0800590 darkgrey: '#444',
Simon Huntac4c6f72015-02-03 19:50:53 -0800591 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: {
Simon Hunt78c10bf2015-02-03 21:18:20 -0800599 rfill: dCol.offwhite,
Simon Huntac4c6f72015-02-03 19:50:53 -0800600 online: {
Simon Hunt78c10bf2015-02-03 21:18:20 -0800601 glyph: dCol.darkgrey,
Simon Huntac4c6f72015-02-03 19:50:53 -0800602 rect: dCol.paleblue
603 },
604 offline: {
605 glyph: dCol.midgrey,
606 rect: dCol.lightgrey
607 }
608 },
Simon Huntac4c6f72015-02-03 19:50:53 -0800609 dark: {
Simon Hunt78c10bf2015-02-03 21:18:20 -0800610 rfill: dCol.midgrey,
Simon Huntac4c6f72015-02-03 19:50:53 -0800611 online: {
Simon Hunt78c10bf2015-02-03 21:18:20 -0800612 glyph: dCol.darkgrey,
Simon Huntac4c6f72015-02-03 19:50:53 -0800613 rect: dCol.paleblue
614 },
615 offline: {
616 glyph: dCol.midgrey,
Simon Hunt78c10bf2015-02-03 21:18:20 -0800617 rect: dCol.darkgrey
Simon Huntac4c6f72015-02-03 19:50:53 -0800618 }
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),
Simon Hunt51056592015-02-03 21:48:07 -0800632 icon = d.el.select('g.deviceIcon'),
633 g, r;
Simon Huntac4c6f72015-02-03 19:50:53 -0800634
635 if (s) {
636 g = c.glyph;
637 r = dCol.orange;
638 } else if (tis.isVisible()) {
639 g = o ? a : c.glyph;
Simon Hunt78c10bf2015-02-03 21:18:20 -0800640 r = o ? c.rfill : a;
Simon Huntac4c6f72015-02-03 19:50:53 -0800641 } else {
642 g = c.glyph;
643 r = c.rect;
644 }
645
Simon Hunt51056592015-02-03 21:48:07 -0800646 icon.select('use').style('fill', g);
647 icon.select('rect').style('fill', r);
Simon Huntac4c6f72015-02-03 19:50:53 -0800648 }
649
650 function instColor(id, online) {
651 return sus.cat7().getColor(id, !online, ts.theme());
652 }
653
Simon Hunt1894d792015-02-04 17:09:20 -0800654 // ==========================
Simon Huntac4c6f72015-02-03 19:50:53 -0800655
656 function updateNodes() {
Simon Hunt1894d792015-02-04 17:09:20 -0800657 // select all the nodes in the layout:
Simon Huntac4c6f72015-02-03 19:50:53 -0800658 node = nodeG.selectAll('.node')
659 .data(network.nodes, function (d) { return d.id; });
660
Simon Hunt1894d792015-02-04 17:09:20 -0800661 // operate on existing nodes:
Simon Hunt51056592015-02-03 21:48:07 -0800662 node.filter('.device').each(deviceExisting);
663 node.filter('.host').each(hostExisting);
Simon Huntac4c6f72015-02-03 19:50:53 -0800664
665 // operate on entering nodes:
666 var entering = node.enter()
667 .append('g')
668 .attr({
669 id: function (d) { return sus.safeId(d.id); },
670 class: mkSvgClass,
671 transform: function (d) { return sus.translate(d.x, d.y); },
672 opacity: 0
673 })
674 .call(drag)
Simon Hunt08f841d02015-02-10 14:39:20 -0800675 .on('mouseover', tss.nodeMouseOver)
676 .on('mouseout', tss.nodeMouseOut)
Simon Huntac4c6f72015-02-03 19:50:53 -0800677 .transition()
678 .attr('opacity', 1);
679
Simon Hunt1894d792015-02-04 17:09:20 -0800680 // augment entering nodes:
Simon Hunt51056592015-02-03 21:48:07 -0800681 entering.filter('.device').each(deviceEnter);
682 entering.filter('.host').each(hostEnter);
Simon Huntac4c6f72015-02-03 19:50:53 -0800683
Simon Hunt51056592015-02-03 21:48:07 -0800684 // operate on both existing and new nodes:
Simon Huntac4c6f72015-02-03 19:50:53 -0800685 updateDeviceColors();
686
687 // operate on exiting nodes:
688 // Note that the node is removed after 2 seconds.
689 // Sub element animations should be shorter than 2 seconds.
690 var exiting = node.exit()
691 .transition()
692 .duration(2000)
693 .style('opacity', 0)
694 .remove();
695
Simon Hunt1894d792015-02-04 17:09:20 -0800696 // exiting node specifics:
Simon Hunt51056592015-02-03 21:48:07 -0800697 exiting.filter('.host').each(hostExit);
698 exiting.filter('.device').each(deviceExit);
Simon Huntac4c6f72015-02-03 19:50:53 -0800699
Simon Hunt51056592015-02-03 21:48:07 -0800700 // finally, resume the force layout
Simon Huntac4c6f72015-02-03 19:50:53 -0800701 fResume();
702 }
703
Simon Hunt51056592015-02-03 21:48:07 -0800704 // ==========================
705 // updateNodes - subfunctions
706
707 function deviceExisting(d) {
708 var node = d.el;
709 node.classed('online', d.online);
710 updateDeviceLabel(d);
Simon Hunt3a6eec02015-02-09 21:16:43 -0800711 tms.positionNode(d, true);
Simon Hunt51056592015-02-03 21:48:07 -0800712 }
713
714 function hostExisting(d) {
715 updateHostLabel(d);
Simon Hunt3a6eec02015-02-09 21:16:43 -0800716 tms.positionNode(d, true);
Simon Hunt51056592015-02-03 21:48:07 -0800717 }
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) {
Simon Hunt1894d792015-02-04 17:09:20 -0800749 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;
Simon Hunt51056592015-02-03 21:48:07 -0800754
755 d.el = node;
Simon Hunt18bf9822015-02-12 17:35:45 -0800756 sus.visible(node, showHosts);
Simon Hunt51056592015-02-03 21:48:07 -0800757
Simon Hunt1894d792015-02-04 17:09:20 -0800758 is.addHostIcon(node, r, gid);
Simon Hunt51056592015-02-03 21:48:07 -0800759
Simon Hunt51056592015-02-03 21:48:07 -0800760 node.append('text')
761 .text(hostLabel)
Simon Hunt1894d792015-02-04 17:09:20 -0800762 .attr('dy', textDy)
Simon Hunt51056592015-02-03 21:48:07 -0800763 .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
Simon Hunt1894d792015-02-04 17:09:20 -0800803 // ==========================
804
805 function updateLinks() {
806 var th = ts.theme();
807
808 link = linkG.selectAll('.link')
809 .data(network.links, function (d) { return d.key; });
810
811 // operate on existing links:
Simon Hunta142dd22015-02-12 22:07:51 -0800812 link.each(linkExisting);
Simon Hunt1894d792015-02-04 17:09:20 -0800813
814 // operate on entering links:
815 var entering = link.enter()
816 .append('line')
817 .attr({
818 x1: function (d) { return d.x1; },
819 y1: function (d) { return d.y1; },
820 x2: function (d) { return d.x2; },
821 y2: function (d) { return d.y2; },
822 stroke: linkConfig[th].inColor,
823 'stroke-width': linkConfig.inWidth
824 });
825
826 // augment links
827 entering.each(linkEntering);
828
829 // operate on both existing and new links:
830 //link.each(...)
831
832 // apply or remove labels
833 var labelData = getLabelData();
834 applyLinkLabels(labelData);
835
836 // operate on exiting links:
837 link.exit()
838 .attr('stroke-dasharray', '3 3')
Simon Hunt5724fb42015-02-05 16:59:40 -0800839 .attr('stroke', linkConfig[th].outColor)
Simon Hunt1894d792015-02-04 17:09:20 -0800840 .style('opacity', 0.5)
841 .transition()
842 .duration(1500)
843 .attr({
844 'stroke-dasharray': '3 12',
Simon Hunt1894d792015-02-04 17:09:20 -0800845 'stroke-width': linkConfig.outWidth
846 })
847 .style('opacity', 0.0)
848 .remove();
849
850 // NOTE: invoke a single tick to force the labels to position
851 // onto their links.
852 tick();
Simon Hunt5724fb42015-02-05 16:59:40 -0800853 // TODO: this causes undesirable behavior when in oblique view
Simon Hunt1894d792015-02-04 17:09:20 -0800854 // It causes the nodes to jump into "overhead" view positions, even
855 // though the oblique planes are still showing...
856 }
857
858 // ==========================
859 // updateLinks - subfunctions
860
861 function getLabelData() {
862 // create the backing data for showing labels..
863 var data = [];
864 link.each(function (d) {
865 if (d.label) {
866 data.push({
867 id: 'lab-' + d.key,
868 key: d.key,
869 label: d.label,
870 ldata: d
871 });
872 }
873 });
874 return data;
875 }
876
Simon Hunta142dd22015-02-12 22:07:51 -0800877 function linkExisting(d) {
Simon Hunte01de782015-02-19 16:01:02 -0800878 // this is supposed to be an existing link, but we have observed
879 // occasions (where links are deleted and added rapidly?) where
880 // the DOM element has not been defined. So protection against that...
881 if (d.el) {
882 restyleLinkElement(d, true);
883 }
Simon Hunta142dd22015-02-12 22:07:51 -0800884 }
Simon Hunt1894d792015-02-04 17:09:20 -0800885
886 function linkEntering(d) {
887 var link = d3.select(this);
888 d.el = link;
889 restyleLinkElement(d);
890 if (d.type() === 'hostLink') {
Simon Hunt18bf9822015-02-12 17:35:45 -0800891 sus.visible(link, showHosts);
Simon Hunt1894d792015-02-04 17:09:20 -0800892 }
893 }
894
895 //function linkExiting(d) { }
896
897 var linkLabelOffset = '0.3em';
898
899 function applyLinkLabels(data) {
900 var entering;
901
902 linkLabel = linkLabelG.selectAll('.linkLabel')
903 .data(data, function (d) { return d.id; });
904
905 // for elements already existing, we need to update the text
906 // and adjust the rectangle size to fit
907 linkLabel.each(function (d) {
908 var el = d3.select(this),
909 rect = el.select('rect'),
910 text = el.select('text');
911 text.text(d.label);
912 rect.attr(rectAroundText(el));
913 });
914
915 entering = linkLabel.enter().append('g')
916 .classed('linkLabel', true)
917 .attr('id', function (d) { return d.id; });
918
919 entering.each(function (d) {
920 var el = d3.select(this),
921 rect,
922 text,
923 parms = {
924 x1: d.ldata.x1,
925 y1: d.ldata.y1,
926 x2: d.ldata.x2,
927 y2: d.ldata.y2
928 };
929
Simon Hunt8eb4d3a2015-02-23 18:23:29 -0800930 if (d.ldata.type() === 'hostLink') {
931 el.classed('hostLinkLabel', true);
932 sus.visible(el, showHosts);
933 }
934
Simon Hunt1894d792015-02-04 17:09:20 -0800935 d.el = el;
936 rect = el.append('rect');
937 text = el.append('text').text(d.label);
938 rect.attr(rectAroundText(el));
939 text.attr('dy', linkLabelOffset);
940
941 el.attr('transform', transformLabel(parms));
942 });
943
944 // Remove any labels that are no longer required.
945 linkLabel.exit().remove();
946 }
947
948 function rectAroundText(el) {
949 var text = el.select('text'),
950 box = text.node().getBBox();
951
952 // translate the bbox so that it is centered on [x,y]
953 box.x = -box.width / 2;
954 box.y = -box.height / 2;
955
956 // add padding
957 box.x -= 1;
958 box.width += 2;
959 return box;
960 }
961
962 function transformLabel(p) {
963 var dx = p.x2 - p.x1,
964 dy = p.y2 - p.y1,
965 xMid = dx/2 + p.x1,
966 yMid = dy/2 + p.y1;
967 return sus.translate(xMid, yMid);
968 }
Simon Huntac4c6f72015-02-03 19:50:53 -0800969
970 // ==========================
Simon Hunt737c89f2015-01-28 12:23:19 -0800971 // force layout tick function
Simon Hunt737c89f2015-01-28 12:23:19 -0800972
Simon Hunt5724fb42015-02-05 16:59:40 -0800973 function fResume() {
Simon Huntc3c5b672015-02-20 11:32:13 -0800974 if (!tos.isOblique()) {
Simon Hunt5724fb42015-02-05 16:59:40 -0800975 force.resume();
976 }
977 }
978
979 function fStart() {
Simon Huntc3c5b672015-02-20 11:32:13 -0800980 if (!tos.isOblique()) {
Simon Hunt5724fb42015-02-05 16:59:40 -0800981 force.start();
982 }
983 }
984
985 var tickStuff = {
986 nodeAttr: {
987 transform: function (d) { return sus.translate(d.x, d.y); }
988 },
989 linkAttr: {
990 x1: function (d) { return d.source.x; },
991 y1: function (d) { return d.source.y; },
992 x2: function (d) { return d.target.x; },
993 y2: function (d) { return d.target.y; }
994 },
995 linkLabelAttr: {
996 transform: function (d) {
Simon Huntdc6adea2015-02-09 22:29:36 -0800997 var lnk = tms.findLinkById(d.key);
Simon Hunt5724fb42015-02-05 16:59:40 -0800998 if (lnk) {
999 return transformLabel({
1000 x1: lnk.source.x,
1001 y1: lnk.source.y,
1002 x2: lnk.target.x,
1003 y2: lnk.target.y
1004 });
1005 }
1006 }
1007 }
1008 };
1009
1010 function tick() {
1011 node.attr(tickStuff.nodeAttr);
1012 link.attr(tickStuff.linkAttr);
1013 linkLabel.attr(tickStuff.linkLabelAttr);
Simon Hunt737c89f2015-01-28 12:23:19 -08001014 }
1015
1016
Simon Huntac4c6f72015-02-03 19:50:53 -08001017 // ==========================
1018 // === MOUSE GESTURE HANDLERS
1019
Simon Hunt205099e2015-02-07 13:12:01 -08001020 function zoomingOrPanning(ev) {
1021 return ev.metaKey || ev.altKey;
Simon Hunt445e8152015-02-06 13:00:12 -08001022 }
1023
1024 function atDragEnd(d) {
1025 // once we've finished moving, pin the node in position
1026 d.fixed = true;
1027 d3.select(this).classed('fixed', true);
1028 sendUpdateMeta(d);
1029 }
1030
1031 // predicate that indicates when dragging is active
1032 function dragEnabled() {
1033 var ev = d3.event.sourceEvent;
1034 // nodeLock means we aren't allowing nodes to be dragged...
Simon Hunt205099e2015-02-07 13:12:01 -08001035 return !nodeLock && !zoomingOrPanning(ev);
Simon Hunt445e8152015-02-06 13:00:12 -08001036 }
1037
1038 // predicate that indicates when clicking is active
1039 function clickEnabled() {
1040 return true;
1041 }
Simon Hunt737c89f2015-01-28 12:23:19 -08001042
Simon Huntf542d842015-02-11 16:20:33 -08001043 // ==========================
1044 // function entry points for traffic module
1045
1046 var allTrafficClasses = 'primary secondary animated optical';
1047
1048 function clearLinkTrafficStyle() {
1049 link.style('stroke-width', null)
1050 .classed(allTrafficClasses, false);
1051 }
1052
1053 function removeLinkLabels() {
1054 network.links.forEach(function (d) {
1055 d.label = '';
1056 });
1057 }
Simon Hunt737c89f2015-01-28 12:23:19 -08001058
1059 // ==========================
Simon Huntac4c6f72015-02-03 19:50:53 -08001060 // Module definition
Simon Hunt737c89f2015-01-28 12:23:19 -08001061
Simon Huntdc6adea2015-02-09 22:29:36 -08001062 function mkModelApi(uplink) {
1063 return {
1064 projection: uplink.projection,
1065 network: network,
1066 restyleLinkElement: restyleLinkElement,
1067 removeLinkElement: removeLinkElement
1068 };
1069 }
1070
Simon Hunt08f841d02015-02-10 14:39:20 -08001071 function mkSelectApi(uplink) {
1072 return {
1073 node: function () { return node; },
1074 zoomingOrPanning: zoomingOrPanning,
1075 updateDeviceColors: updateDeviceColors,
1076 sendEvent: uplink.sendEvent
1077 };
1078 }
1079
Simon Huntf542d842015-02-11 16:20:33 -08001080 function mkTrafficApi(uplink) {
1081 return {
1082 clearLinkTrafficStyle: clearLinkTrafficStyle,
1083 removeLinkLabels: removeLinkLabels,
1084 updateLinks: updateLinks,
1085 findLinkById: tms.findLinkById,
1086 hovered: tss.hovered,
1087 validateSelectionContext: tss.validateSelectionContext,
1088 selectOrder: tss.selectOrder,
1089 sendEvent: uplink.sendEvent
1090 }
1091 }
1092
Simon Huntc3c5b672015-02-20 11:32:13 -08001093 function mkObliqueApi(uplink, fltr) {
Simon Hunt96f88c62015-02-19 17:57:25 -08001094 return {
Simon Huntc3c5b672015-02-20 11:32:13 -08001095 force: function() { return force; },
1096 zoomLayer: uplink.zoomLayer,
1097 nodeGBBox: function() { return nodeG.node().getBBox(); },
Simon Hunt96f88c62015-02-19 17:57:25 -08001098 node: function () { return node; },
Simon Huntc3c5b672015-02-20 11:32:13 -08001099 link: function () { return link; },
1100 linkLabel: function () { return linkLabel; },
1101 nodes: function () { return network.nodes; },
1102 tickStuff: tickStuff,
1103 nodeLock: function (b) {
1104 var old = nodeLock;
1105 nodeLock = b;
1106 return old;
1107 },
1108 opacifyMap: uplink.opacifyMap,
1109 inLayer: fltr.inLayer
Simon Hunt96f88c62015-02-19 17:57:25 -08001110 };
1111 }
1112
Simon Hunteb0fa052015-02-17 19:20:28 -08001113 function mkFilterApi(uplink) {
1114 return {
1115 node: function () { return node; },
1116 link: function () { return link; }
1117 };
1118 }
1119
Simon Hunt737c89f2015-01-28 12:23:19 -08001120 angular.module('ovTopo')
1121 .factory('TopoForceService',
Simon Hunt1894d792015-02-04 17:09:20 -08001122 ['$log', 'FnService', 'SvgUtilService', 'IconService', 'ThemeService',
Simon Hunt3a6eec02015-02-09 21:16:43 -08001123 'FlashService', 'TopoInstService', 'TopoModelService',
Simon Hunt96f88c62015-02-19 17:57:25 -08001124 'TopoSelectService', 'TopoTrafficService',
1125 'TopoObliqueService', 'TopoFilterService',
Simon Hunt737c89f2015-01-28 12:23:19 -08001126
Simon Huntf542d842015-02-11 16:20:33 -08001127 function (_$log_, _fs_, _sus_, _is_, _ts_, _flash_,
Simon Hunt96f88c62015-02-19 17:57:25 -08001128 _tis_, _tms_, _tss_, _tts_, _tos_, _fltr_) {
Simon Hunt737c89f2015-01-28 12:23:19 -08001129 $log = _$log_;
Simon Hunt1894d792015-02-04 17:09:20 -08001130 fs = _fs_;
Simon Hunt737c89f2015-01-28 12:23:19 -08001131 sus = _sus_;
Simon Huntac4c6f72015-02-03 19:50:53 -08001132 is = _is_;
1133 ts = _ts_;
Simon Hunt5724fb42015-02-05 16:59:40 -08001134 flash = _flash_;
Simon Huntac4c6f72015-02-03 19:50:53 -08001135 tis = _tis_;
Simon Hunt3a6eec02015-02-09 21:16:43 -08001136 tms = _tms_;
Simon Hunt08f841d02015-02-10 14:39:20 -08001137 tss = _tss_;
Simon Huntf542d842015-02-11 16:20:33 -08001138 tts = _tts_;
Simon Hunt96f88c62015-02-19 17:57:25 -08001139 tos = _tos_;
Simon Hunteb0fa052015-02-17 19:20:28 -08001140 fltr = _fltr_;
Simon Hunt737c89f2015-01-28 12:23:19 -08001141
Simon Hunt1894d792015-02-04 17:09:20 -08001142 icfg = is.iconConfig();
1143
Simon Hunta142dd22015-02-12 22:07:51 -08001144 var themeListener = ts.addListener(function () {
1145 updateLinks();
1146 updateNodes();
1147 });
1148
Simon Hunt737c89f2015-01-28 12:23:19 -08001149 // forceG is the SVG group to display the force layout in
Simon Huntdc6adea2015-02-09 22:29:36 -08001150 // uplink is the api from the main topo source file
Simon Hunt3a6eec02015-02-09 21:16:43 -08001151 // dim is the initial dimensions of the SVG as [w,h]
Simon Hunt737c89f2015-01-28 12:23:19 -08001152 // opts are, well, optional :)
Simon Hunt3a6eec02015-02-09 21:16:43 -08001153 function initForce(forceG, _uplink_, _dim_, opts) {
Simon Hunt1894d792015-02-04 17:09:20 -08001154 uplink = _uplink_;
Simon Hunt3a6eec02015-02-09 21:16:43 -08001155 dim = _dim_;
1156
1157 $log.debug('initForce().. dim = ' + dim);
1158
Simon Huntdc6adea2015-02-09 22:29:36 -08001159 tms.initModel(mkModelApi(uplink), dim);
Simon Hunt08f841d02015-02-10 14:39:20 -08001160 tss.initSelect(mkSelectApi(uplink));
Simon Huntf542d842015-02-11 16:20:33 -08001161 tts.initTraffic(mkTrafficApi(uplink));
Simon Huntc3c5b672015-02-20 11:32:13 -08001162 tos.initOblique(mkObliqueApi(uplink, fltr));
Simon Hunteb0fa052015-02-17 19:20:28 -08001163 fltr.initFilter(mkFilterApi(uplink), d3.select('#mast-right'));
Simon Hunta11b4eb2015-01-28 16:20:50 -08001164
Simon Hunt737c89f2015-01-28 12:23:19 -08001165 settings = angular.extend({}, defaultSettings, opts);
1166
1167 linkG = forceG.append('g').attr('id', 'topo-links');
1168 linkLabelG = forceG.append('g').attr('id', 'topo-linkLabels');
1169 nodeG = forceG.append('g').attr('id', 'topo-nodes');
1170
1171 link = linkG.selectAll('.link');
1172 linkLabel = linkLabelG.selectAll('.linkLabel');
1173 node = nodeG.selectAll('.node');
1174
1175 force = d3.layout.force()
Simon Hunt3a6eec02015-02-09 21:16:43 -08001176 .size(dim)
Simon Hunt737c89f2015-01-28 12:23:19 -08001177 .nodes(network.nodes)
1178 .links(network.links)
1179 .gravity(settings.gravity)
1180 .friction(settings.friction)
1181 .charge(settings.charge._def_)
1182 .linkDistance(settings.linkDistance._def_)
1183 .linkStrength(settings.linkStrength._def_)
1184 .on('tick', tick);
1185
1186 drag = sus.createDragBehavior(force,
Simon Hunt08f841d02015-02-10 14:39:20 -08001187 tss.selectObject, atDragEnd, dragEnabled, clickEnabled);
Simon Hunt737c89f2015-01-28 12:23:19 -08001188 }
1189
Simon Hunt3a6eec02015-02-09 21:16:43 -08001190 function newDim(_dim_) {
1191 dim = _dim_;
1192 force.size(dim);
1193 tms.newDim(dim);
Simon Hunt737c89f2015-01-28 12:23:19 -08001194 // Review -- do we need to nudge the layout ?
Simon Hunt737c89f2015-01-28 12:23:19 -08001195 }
1196
Simon Hunt3a6eec02015-02-09 21:16:43 -08001197 function destroyForce() {
Simon Hunteb0fa052015-02-17 19:20:28 -08001198 fltr.destroyFilter();
Simon Hunt96f88c62015-02-19 17:57:25 -08001199 tos.destroyOblique();
Simon Huntf542d842015-02-11 16:20:33 -08001200 tts.destroyTraffic();
1201 tss.destroySelect();
1202 tms.destroyModel();
Simon Hunta142dd22015-02-12 22:07:51 -08001203 ts.removeListener(themeListener);
1204 themeListener = null;
Simon Hunt3a6eec02015-02-09 21:16:43 -08001205 }
1206
Simon Hunt737c89f2015-01-28 12:23:19 -08001207 return {
1208 initForce: initForce,
Simon Hunt3a6eec02015-02-09 21:16:43 -08001209 newDim: newDim,
1210 destroyForce: destroyForce,
Simon Huntac4c6f72015-02-03 19:50:53 -08001211
1212 updateDeviceColors: updateDeviceColors,
Simon Hunt5724fb42015-02-05 16:59:40 -08001213 toggleHosts: toggleHosts,
1214 toggleOffline: toggleOffline,
1215 cycleDeviceLabels: cycleDeviceLabels,
Simon Hunt445e8152015-02-06 13:00:12 -08001216 unpin: unpin,
Simon Hunta142dd22015-02-12 22:07:51 -08001217 showMastership: showMastership,
Simon Huntac4c6f72015-02-03 19:50:53 -08001218
1219 addDevice: addDevice,
Simon Hunt1894d792015-02-04 17:09:20 -08001220 updateDevice: updateDevice,
1221 removeDevice: removeDevice,
1222 addHost: addHost,
1223 updateHost: updateHost,
1224 removeHost: removeHost,
1225 addLink: addLink,
1226 updateLink: updateLink,
1227 removeLink: removeLink
Simon Hunt737c89f2015-01-28 12:23:19 -08001228 };
1229 }]);
1230}());