blob: b1901baf179393572eca4a42b97d85b09fa54e4c [file] [log] [blame]
Simon Hunt195cb382014-11-03 17:50:51 -08001/*
2 * Copyright 2014 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 Hunt142d0032014-11-04 20:13:09 -080018 ONOS network topology viewer - version 1.1
Simon Hunt195cb382014-11-03 17:50:51 -080019
20 @author Simon Hunt
21 */
22
23(function (onos) {
24 'use strict';
25
Simon Hunt1a9eff92014-11-07 11:06:34 -080026 // shorter names for library APIs
27 var d3u = onos.lib.d3util;
28
Simon Hunt195cb382014-11-03 17:50:51 -080029 // configuration data
30 var config = {
Simon Hunt99c13842014-11-06 18:23:12 -080031 useLiveData: false,
Simon Hunt195cb382014-11-03 17:50:51 -080032 debugOn: false,
33 debug: {
Simon Hunt99c13842014-11-06 18:23:12 -080034 showNodeXY: true,
35 showKeyHandler: false
Simon Hunt195cb382014-11-03 17:50:51 -080036 },
37 options: {
38 layering: true,
39 collisionPrevention: true,
Simon Hunt142d0032014-11-04 20:13:09 -080040 showBackground: true
Simon Hunt195cb382014-11-03 17:50:51 -080041 },
42 backgroundUrl: 'img/us-map.png',
Thomas Vachuska7d638d32014-11-07 10:24:43 -080043 webSockUrl: 'ws/topology',
Simon Hunt195cb382014-11-03 17:50:51 -080044 data: {
45 live: {
46 jsonUrl: 'rs/topology/graph',
47 detailPrefix: 'rs/topology/graph/',
48 detailSuffix: ''
49 },
50 fake: {
51 jsonUrl: 'json/network2.json',
52 detailPrefix: 'json/',
53 detailSuffix: '.json'
54 }
55 },
Simon Hunt99c13842014-11-06 18:23:12 -080056 labels: {
57 imgPad: 16,
58 padLR: 4,
59 padTB: 3,
60 marginLR: 3,
61 marginTB: 2,
62 port: {
63 gap: 3,
64 width: 18,
65 height: 14
66 }
67 },
Simon Hunt1a9eff92014-11-07 11:06:34 -080068 topo: {
69 linkInColor: '#66f',
70 linkInWidth: 14
71 },
Simon Hunt99c13842014-11-06 18:23:12 -080072 icons: {
73 w: 28,
74 h: 28,
75 xoff: -12,
76 yoff: -8
77 },
Simon Hunt195cb382014-11-03 17:50:51 -080078 iconUrl: {
79 device: 'img/device.png',
80 host: 'img/host.png',
81 pkt: 'img/pkt.png',
82 opt: 'img/opt.png'
83 },
Simon Hunt195cb382014-11-03 17:50:51 -080084 force: {
Simon Huntc7ee0662014-11-05 16:44:37 -080085 note: 'node.class or link.class is used to differentiate',
86 linkDistance: {
87 infra: 200,
88 host: 40
89 },
90 linkStrength: {
91 infra: 1.0,
92 host: 1.0
93 },
94 charge: {
95 device: -400,
96 host: -100
97 },
98 pad: 20,
Simon Hunt195cb382014-11-03 17:50:51 -080099 translate: function() {
100 return 'translate(' +
Simon Huntc7ee0662014-11-05 16:44:37 -0800101 config.force.pad + ',' +
102 config.force.pad + ')';
Simon Hunt195cb382014-11-03 17:50:51 -0800103 }
Simon Hunt142d0032014-11-04 20:13:09 -0800104 }
Simon Hunt195cb382014-11-03 17:50:51 -0800105 };
106
Simon Hunt142d0032014-11-04 20:13:09 -0800107 // radio buttons
108 var btnSet = [
Simon Hunt934c3ce2014-11-05 11:45:07 -0800109 { text: 'All Layers', cb: showAllLayers },
110 { text: 'Packet Only', cb: showPacketLayer },
111 { text: 'Optical Only', cb: showOpticalLayer }
112 ];
113
114 // key bindings
115 var keyDispatch = {
Simon Hunt1a9eff92014-11-07 11:06:34 -0800116 M: testMe, // TODO: remove (testing only)
Simon Hunt50128c02014-11-08 13:36:15 -0800117 S: injectStartupEvents, // TODO: remove (testing only)
118 space: injectTestEvent, // TODO: remove (testing only)
Simon Hunt99c13842014-11-06 18:23:12 -0800119
Simon Hunt934c3ce2014-11-05 11:45:07 -0800120 B: toggleBg,
Simon Hunt934c3ce2014-11-05 11:45:07 -0800121 L: cycleLabels,
122 P: togglePorts,
123 U: unpin
124 };
Simon Hunt142d0032014-11-04 20:13:09 -0800125
Simon Hunt195cb382014-11-03 17:50:51 -0800126 // state variables
Simon Hunt99c13842014-11-06 18:23:12 -0800127 var network = {
Simon Hunt50128c02014-11-08 13:36:15 -0800128 view: null, // view token reference
Simon Hunt99c13842014-11-06 18:23:12 -0800129 nodes: [],
130 links: [],
131 lookup: {}
132 },
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800133 webSock,
Simon Hunt99c13842014-11-06 18:23:12 -0800134 labelIdx = 0,
Simon Hunt195cb382014-11-03 17:50:51 -0800135 selected = {},
136 highlighted = null,
137 hovered = null,
138 viewMode = 'showAll',
139 portLabelsOn = false;
140
Simon Hunt934c3ce2014-11-05 11:45:07 -0800141 // D3 selections
142 var svg,
143 bgImg,
Simon Huntc7ee0662014-11-05 16:44:37 -0800144 topoG,
145 nodeG,
146 linkG,
147 node,
148 link;
Simon Hunt195cb382014-11-03 17:50:51 -0800149
Simon Hunt142d0032014-11-04 20:13:09 -0800150 // ==============================
Simon Hunt934c3ce2014-11-05 11:45:07 -0800151 // For Debugging / Development
Simon Hunt195cb382014-11-03 17:50:51 -0800152
Simon Hunt99c13842014-11-06 18:23:12 -0800153 var eventPrefix = 'json/eventTest_',
Simon Hunt1a9eff92014-11-07 11:06:34 -0800154 eventNumber = 0,
155 alertNumber = 0;
Simon Hunt195cb382014-11-03 17:50:51 -0800156
Simon Hunt99c13842014-11-06 18:23:12 -0800157 function note(label, msg) {
158 console.log('NOTE: ' + label + ': ' + msg);
Simon Hunt195cb382014-11-03 17:50:51 -0800159 }
160
Simon Hunt99c13842014-11-06 18:23:12 -0800161 function debug(what) {
162 return config.debugOn && config.debug[what];
Simon Hunt934c3ce2014-11-05 11:45:07 -0800163 }
164
Simon Hunt99c13842014-11-06 18:23:12 -0800165
Simon Hunt934c3ce2014-11-05 11:45:07 -0800166 // ==============================
167 // Key Callbacks
168
Simon Hunt99c13842014-11-06 18:23:12 -0800169 function testMe(view) {
Simon Hunt50128c02014-11-08 13:36:15 -0800170 view.alert('test');
Simon Hunt99c13842014-11-06 18:23:12 -0800171 }
172
173 function injectTestEvent(view) {
Simon Hunt50128c02014-11-08 13:36:15 -0800174 if (config.useLiveData) {
175 view.alert("Sorry, currently using live data..");
176 return;
177 }
178
Simon Hunt99c13842014-11-06 18:23:12 -0800179 eventNumber++;
180 var eventUrl = eventPrefix + eventNumber + '.json';
181
Simon Hunt99c13842014-11-06 18:23:12 -0800182 d3.json(eventUrl, function(err, data) {
183 if (err) {
184 view.dataLoadError(err, eventUrl);
185 } else {
186 handleServerEvent(data);
187 }
188 });
Simon Hunt934c3ce2014-11-05 11:45:07 -0800189 }
190
Simon Hunt1a9eff92014-11-07 11:06:34 -0800191 function injectStartupEvents(view) {
Simon Hunt50128c02014-11-08 13:36:15 -0800192 if (config.useLiveData) {
193 view.alert("Sorry, currently using live data..");
194 return;
195 }
196
Simon Hunt1a9eff92014-11-07 11:06:34 -0800197 var lastStartupEvent = 32;
198 while (eventNumber < lastStartupEvent) {
199 injectTestEvent(view);
200 }
201 }
202
Simon Hunt934c3ce2014-11-05 11:45:07 -0800203 function toggleBg() {
204 var vis = bgImg.style('visibility');
205 bgImg.style('visibility', (vis === 'hidden') ? 'visible' : 'hidden');
206 }
207
Simon Hunt99c13842014-11-06 18:23:12 -0800208 function cycleLabels() {
209 labelIdx = (labelIdx === network.deviceLabelCount - 1) ? 0 : labelIdx + 1;
210 network.nodes.forEach(function (d) {
211 var idx = (labelIdx < d.labels.length) ? labelIdx : 0,
212 node = d3.select('#' + safeId(d.id)),
213 box;
Simon Hunt934c3ce2014-11-05 11:45:07 -0800214
Simon Hunt99c13842014-11-06 18:23:12 -0800215 node.select('text')
216 .text(d.labels[idx])
217 .style('opacity', 0)
218 .transition()
219 .style('opacity', 1);
220
221 box = adjustRectToFitText(node);
222
223 node.select('rect')
224 .transition()
225 .attr(box);
226
227 node.select('image')
228 .transition()
229 .attr('x', box.x + config.icons.xoff)
230 .attr('y', box.y + config.icons.yoff);
231 });
Simon Hunt934c3ce2014-11-05 11:45:07 -0800232 }
233
234 function togglePorts(view) {
Simon Hunt50128c02014-11-08 13:36:15 -0800235 view.alert('togglePorts() callback')
Simon Hunt934c3ce2014-11-05 11:45:07 -0800236 }
237
238 function unpin(view) {
Simon Hunt50128c02014-11-08 13:36:15 -0800239 view.alert('unpin() callback')
Simon Hunt934c3ce2014-11-05 11:45:07 -0800240 }
241
242 // ==============================
243 // Radio Button Callbacks
244
Simon Hunt195cb382014-11-03 17:50:51 -0800245 function showAllLayers() {
Simon Hunt142d0032014-11-04 20:13:09 -0800246// network.node.classed('inactive', false);
247// network.link.classed('inactive', false);
248// d3.selectAll('svg .port').classed('inactive', false);
249// d3.selectAll('svg .portText').classed('inactive', false);
Simon Hunt934c3ce2014-11-05 11:45:07 -0800250 // TODO ...
Simon Hunt50128c02014-11-08 13:36:15 -0800251 network.view.alert('showAllLayers() callback');
Simon Hunt195cb382014-11-03 17:50:51 -0800252 }
253
254 function showPacketLayer() {
Simon Hunt934c3ce2014-11-05 11:45:07 -0800255 showAllLayers();
256 // TODO ...
Simon Hunt50128c02014-11-08 13:36:15 -0800257 network.view.alert('showPacketLayer() callback');
Simon Hunt195cb382014-11-03 17:50:51 -0800258 }
259
260 function showOpticalLayer() {
Simon Hunt934c3ce2014-11-05 11:45:07 -0800261 showAllLayers();
262 // TODO ...
Simon Hunt50128c02014-11-08 13:36:15 -0800263 network.view.alert('showOpticalLayer() callback');
Simon Hunt195cb382014-11-03 17:50:51 -0800264 }
265
Simon Hunt142d0032014-11-04 20:13:09 -0800266 // ==============================
Simon Hunt934c3ce2014-11-05 11:45:07 -0800267 // Private functions
268
Simon Hunt99c13842014-11-06 18:23:12 -0800269 function safeId(s) {
270 return s.replace(/[^a-z0-9]/gi, '-');
271 }
272
Simon Huntc7ee0662014-11-05 16:44:37 -0800273 // set the size of the given element to that of the view (reduced if padded)
274 function setSize(el, view, pad) {
275 var padding = pad ? pad * 2 : 0;
Simon Hunt934c3ce2014-11-05 11:45:07 -0800276 el.attr({
Simon Huntc7ee0662014-11-05 16:44:37 -0800277 width: view.width() - padding,
278 height: view.height() - padding
Simon Hunt934c3ce2014-11-05 11:45:07 -0800279 });
280 }
281
Simon Hunt934c3ce2014-11-05 11:45:07 -0800282
Simon Hunt99c13842014-11-06 18:23:12 -0800283 // ==============================
284 // Event handlers for server-pushed events
285
286 var eventDispatch = {
287 addDevice: addDevice,
288 updateDevice: updateDevice,
289 removeDevice: removeDevice,
290 addLink: addLink
291 };
292
293 function addDevice(data) {
294 var device = data.payload,
295 node = createDeviceNode(device);
296 note('addDevice', device.id);
297
298 network.nodes.push(node);
299 network.lookup[node.id] = node;
300 updateNodes();
301 network.force.start();
302 }
303
304 function updateDevice(data) {
305 var device = data.payload;
306 note('updateDevice', device.id);
307
308 }
309
310 function removeDevice(data) {
311 var device = data.payload;
312 note('removeDevice', device.id);
313
314 }
315
316 function addLink(data) {
317 var link = data.payload,
318 lnk = createLink(link);
319
320 if (lnk) {
321 note('addLink', lnk.id);
322
323 network.links.push(lnk);
324 updateLinks();
325 network.force.start();
326 }
327 }
328
329 // ....
330
331 function unknownEvent(data) {
Simon Hunt50128c02014-11-08 13:36:15 -0800332 network.view.alert('Unknown event type: "' + data.event + '"');
Simon Hunt99c13842014-11-06 18:23:12 -0800333 }
334
335 function handleServerEvent(data) {
336 var fn = eventDispatch[data.event] || unknownEvent;
337 fn(data);
338 }
339
340 // ==============================
341 // force layout modification functions
342
343 function translate(x, y) {
344 return 'translate(' + x + ',' + y + ')';
345 }
346
347 function createLink(link) {
348 var type = link.type,
349 src = link.src,
350 dst = link.dst,
351 w = link.linkWidth,
352 srcNode = network.lookup[src],
353 dstNode = network.lookup[dst],
354 lnk;
355
356 if (!(srcNode && dstNode)) {
Simon Hunt50128c02014-11-08 13:36:15 -0800357 // TODO: send warning message back to server on websocket
358 network.view.alert('nodes not on map for link\n\n' +
359 'src = ' + src + '\ndst = ' + dst);
Simon Hunt99c13842014-11-06 18:23:12 -0800360 return null;
361 }
362
363 lnk = {
364 id: safeId(src) + '~' + safeId(dst),
365 source: srcNode,
366 target: dstNode,
367 class: 'link',
368 svgClass: type ? 'link ' + type : 'link',
369 x1: srcNode.x,
370 y1: srcNode.y,
371 x2: dstNode.x,
372 y2: dstNode.y,
373 width: w
374 };
375 return lnk;
376 }
377
Simon Hunt1a9eff92014-11-07 11:06:34 -0800378 function linkWidth(w) {
379 // w is number of links between nodes. Scale appropriately.
Simon Hunt50128c02014-11-08 13:36:15 -0800380 // TODO: use a d3.scale (linear, log, ... ?)
Simon Hunt1a9eff92014-11-07 11:06:34 -0800381 return w * 1.2;
382 }
383
Simon Hunt99c13842014-11-06 18:23:12 -0800384 function updateLinks() {
385 link = linkG.selectAll('.link')
386 .data(network.links, function (d) { return d.id; });
387
388 // operate on existing links, if necessary
389 // link .foo() .bar() ...
390
391 // operate on entering links:
392 var entering = link.enter()
393 .append('line')
394 .attr({
395 id: function (d) { return d.id; },
396 class: function (d) { return d.svgClass; },
397 x1: function (d) { return d.x1; },
398 y1: function (d) { return d.y1; },
399 x2: function (d) { return d.x2; },
400 y2: function (d) { return d.y2; },
Simon Hunt1a9eff92014-11-07 11:06:34 -0800401 stroke: config.topo.linkInColor,
402 'stroke-width': config.topo.linkInWidth
Simon Hunt99c13842014-11-06 18:23:12 -0800403 })
404 .transition().duration(1000)
405 .attr({
Simon Hunt1a9eff92014-11-07 11:06:34 -0800406 'stroke-width': function (d) { return linkWidth(d.width); },
Simon Hunt99c13842014-11-06 18:23:12 -0800407 stroke: '#666' // TODO: remove explicit stroke, rather...
408 });
409
410 // augment links
411 // TODO: add src/dst port labels etc.
412
413 }
414
415 function createDeviceNode(device) {
416 // start with the object as is
417 var node = device,
418 type = device.type;
419
420 // Augment as needed...
421 node.class = 'device';
422 node.svgClass = type ? 'node device ' + type : 'node device';
423 positionNode(node);
424
425 // cache label array length
426 network.deviceLabelCount = device.labels.length;
427
428 return node;
429 }
430
431 function positionNode(node) {
432 var meta = node.metaUi,
433 x = 0,
434 y = 0;
435
436 if (meta) {
437 x = meta.x;
438 y = meta.y;
439 }
440 if (x && y) {
441 node.fixed = true;
442 }
443 node.x = x || network.view.width() / 2;
444 node.y = y || network.view.height() / 2;
445 }
446
447
448 function iconUrl(d) {
449 return 'img/' + d.type + '.png';
450 }
451
452 // returns the newly computed bounding box of the rectangle
453 function adjustRectToFitText(n) {
454 var text = n.select('text'),
455 box = text.node().getBBox(),
456 lab = config.labels;
457
458 text.attr('text-anchor', 'middle')
459 .attr('y', '-0.8em')
460 .attr('x', lab.imgPad/2);
461
462 // translate the bbox so that it is centered on [x,y]
463 box.x = -box.width / 2;
464 box.y = -box.height / 2;
465
466 // add padding
467 box.x -= (lab.padLR + lab.imgPad/2);
468 box.width += lab.padLR * 2 + lab.imgPad;
469 box.y -= lab.padTB;
470 box.height += lab.padTB * 2;
471
472 return box;
473 }
474
Simon Hunt1a9eff92014-11-07 11:06:34 -0800475 function mkSvgClass(d) {
476 return d.fixed ? d.svgClass + ' fixed' : d.svgClass;
477 }
478
Simon Hunt99c13842014-11-06 18:23:12 -0800479 function updateNodes() {
480 node = nodeG.selectAll('.node')
481 .data(network.nodes, function (d) { return d.id; });
482
483 // operate on existing nodes, if necessary
484 //node .foo() .bar() ...
485
486 // operate on entering nodes:
487 var entering = node.enter()
488 .append('g')
489 .attr({
490 id: function (d) { return safeId(d.id); },
Simon Hunt1a9eff92014-11-07 11:06:34 -0800491 class: mkSvgClass,
Simon Hunt99c13842014-11-06 18:23:12 -0800492 transform: function (d) { return translate(d.x, d.y); },
493 opacity: 0
494 })
Simon Hunt1a9eff92014-11-07 11:06:34 -0800495 .call(network.drag)
Simon Hunt99c13842014-11-06 18:23:12 -0800496 //.on('mouseover', function (d) {})
497 //.on('mouseover', function (d) {})
498 .transition()
499 .attr('opacity', 1);
500
501 // augment device nodes...
502 entering.filter('.device').each(function (d) {
503 var node = d3.select(this),
504 icon = iconUrl(d),
505 idx = (labelIdx < d.labels.length) ? labelIdx : 0,
506 box;
507
508 node.append('rect')
509 .attr({
510 'rx': 5,
511 'ry': 5
512 });
513
514 node.append('text')
515 .text(d.labels[idx])
516 .attr('dy', '1.1em');
517
518 box = adjustRectToFitText(node);
519
520 node.select('rect')
521 .attr(box);
522
523 if (icon) {
524 var cfg = config.icons;
525 node.append('svg:image')
526 .attr({
527 x: box.x + config.icons.xoff,
528 y: box.y + config.icons.yoff,
529 width: cfg.w,
530 height: cfg.h,
531 'xlink:href': icon
532 });
533 }
534
535 // debug function to show the modelled x,y coordinates of nodes...
536 if (debug('showNodeXY')) {
537 node.select('rect').attr('fill-opacity', 0.5);
538 node.append('circle')
539 .attr({
540 class: 'debug',
541 cx: 0,
542 cy: 0,
543 r: '3px'
544 });
Simon Huntc7ee0662014-11-05 16:44:37 -0800545 }
546 });
Simon Hunt934c3ce2014-11-05 11:45:07 -0800547
Simon Huntc7ee0662014-11-05 16:44:37 -0800548
Simon Hunt99c13842014-11-06 18:23:12 -0800549 // operate on both existing and new nodes, if necessary
550 //node .foo() .bar() ...
Simon Huntc7ee0662014-11-05 16:44:37 -0800551
Simon Hunt99c13842014-11-06 18:23:12 -0800552 // operate on exiting nodes:
553 // TODO: figure out how to remove the node 'g' AND its children
554 node.exit()
555 .transition()
556 .duration(750)
557 .attr({
558 opacity: 0,
559 cx: 0,
560 cy: 0,
561 r: 0
562 })
563 .remove();
Simon Huntc7ee0662014-11-05 16:44:37 -0800564 }
565
566
567 function tick() {
568 node.attr({
Simon Hunt99c13842014-11-06 18:23:12 -0800569 transform: function (d) { return translate(d.x, d.y); }
Simon Huntc7ee0662014-11-05 16:44:37 -0800570 });
571
572 link.attr({
573 x1: function (d) { return d.source.x; },
574 y1: function (d) { return d.source.y; },
575 x2: function (d) { return d.target.x; },
576 y2: function (d) { return d.target.y; }
577 });
578 }
Simon Hunt934c3ce2014-11-05 11:45:07 -0800579
580 // ==============================
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800581 // Web-Socket for live data
582
583 function webSockUrl() {
584 return document.location.toString()
585 .replace(/\#.*/, '')
586 .replace('http://', 'ws://')
587 .replace('https://', 'wss://')
588 .replace('index2.html', config.webSockUrl);
589 }
590
591 webSock = {
592 ws : null,
593
594 connect : function() {
595 webSock.ws = new WebSocket(webSockUrl());
596
597 webSock.ws.onopen = function() {
598 webSock._send("Hi there!");
599 };
600
601 webSock.ws.onmessage = function(m) {
602 if (m.data) {
603 console.log(m.data);
Thomas Vachuskad472c6e2014-11-07 19:11:05 -0800604 handleServerEvent(JSON.parse(m.data));
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800605 }
606 };
607
608 webSock.ws.onclose = function(m) {
609 webSock.ws = null;
610 };
611 },
612
613 send : function(text) {
614 if (text != null && text.length > 0) {
615 webSock._send(text);
616 }
617 },
618
619 _send : function(message) {
620 if (webSock.ws) {
621 webSock.ws.send(message);
622 }
623 }
624
625 };
626
627 // ==============================
Simon Hunt142d0032014-11-04 20:13:09 -0800628 // View life-cycle callbacks
Simon Hunt195cb382014-11-03 17:50:51 -0800629
Simon Hunt142d0032014-11-04 20:13:09 -0800630 function preload(view, ctx) {
631 var w = view.width(),
632 h = view.height(),
633 idBg = view.uid('bg'),
Simon Huntc7ee0662014-11-05 16:44:37 -0800634 showBg = config.options.showBackground ? 'visible' : 'hidden',
635 fcfg = config.force,
636 fpad = fcfg.pad,
637 forceDim = [w - 2*fpad, h - 2*fpad];
Simon Hunt195cb382014-11-03 17:50:51 -0800638
Simon Hunt142d0032014-11-04 20:13:09 -0800639 // NOTE: view.$div is a D3 selection of the view's div
640 svg = view.$div.append('svg');
Simon Hunt934c3ce2014-11-05 11:45:07 -0800641 setSize(svg, view);
642
Simon Hunt1a9eff92014-11-07 11:06:34 -0800643 // add blue glow filter to svg layer
644 d3u.appendGlow(svg);
645
Simon Hunt142d0032014-11-04 20:13:09 -0800646 // load the background image
647 bgImg = svg.append('svg:image')
Simon Hunt195cb382014-11-03 17:50:51 -0800648 .attr({
Simon Hunt142d0032014-11-04 20:13:09 -0800649 id: idBg,
650 width: w,
651 height: h,
Simon Hunt195cb382014-11-03 17:50:51 -0800652 'xlink:href': config.backgroundUrl
653 })
Simon Hunt142d0032014-11-04 20:13:09 -0800654 .style({
655 visibility: showBg
Simon Hunt195cb382014-11-03 17:50:51 -0800656 });
Simon Huntc7ee0662014-11-05 16:44:37 -0800657
658 // group for the topology
659 topoG = svg.append('g')
660 .attr('transform', fcfg.translate());
661
662 // subgroups for links and nodes
663 linkG = topoG.append('g').attr('id', 'links');
664 nodeG = topoG.append('g').attr('id', 'nodes');
665
666 // selection of nodes and links
667 link = linkG.selectAll('.link');
668 node = nodeG.selectAll('.node');
669
Simon Hunt99c13842014-11-06 18:23:12 -0800670 function ldist(d) {
671 return fcfg.linkDistance[d.class] || 150;
672 }
673 function lstrg(d) {
674 return fcfg.linkStrength[d.class] || 1;
675 }
676 function lchrg(d) {
677 return fcfg.charge[d.class] || -200;
678 }
679
Simon Hunt1a9eff92014-11-07 11:06:34 -0800680 function selectCb(d, self) {
681 // TODO: selectObject(d, self);
682 }
683
684 function atDragEnd(d, self) {
685 // once we've finished moving, pin the node in position,
686 // if it is a device (not a host)
687 if (d.class === 'device') {
688 d.fixed = true;
689 d3.select(self).classed('fixed', true)
690 // TODO: send new [x,y] back to server, via websocket.
691 }
692 }
693
Simon Huntc7ee0662014-11-05 16:44:37 -0800694 // set up the force layout
695 network.force = d3.layout.force()
696 .size(forceDim)
697 .nodes(network.nodes)
698 .links(network.links)
Simon Hunt99c13842014-11-06 18:23:12 -0800699 .charge(lchrg)
700 .linkDistance(ldist)
701 .linkStrength(lstrg)
Simon Huntc7ee0662014-11-05 16:44:37 -0800702 .on('tick', tick);
Simon Hunt195cb382014-11-03 17:50:51 -0800703
Simon Hunt1a9eff92014-11-07 11:06:34 -0800704 network.drag = d3u.createDragBehavior(network.force, selectCb, atDragEnd);
705 }
Simon Hunt195cb382014-11-03 17:50:51 -0800706
Simon Hunt142d0032014-11-04 20:13:09 -0800707 function load(view, ctx) {
Simon Hunt99c13842014-11-06 18:23:12 -0800708 // cache the view token, so network topo functions can access it
709 network.view = view;
710
711 // set our radio buttons and key bindings
Simon Hunt934c3ce2014-11-05 11:45:07 -0800712 view.setRadio(btnSet);
713 view.setKeys(keyDispatch);
Simon Hunt195cb382014-11-03 17:50:51 -0800714
Simon Hunt50128c02014-11-08 13:36:15 -0800715 if (config.useLiveData) {
716 webSock.connect();
717 }
Simon Hunt195cb382014-11-03 17:50:51 -0800718 }
719
Simon Hunt142d0032014-11-04 20:13:09 -0800720 function resize(view, ctx) {
Simon Hunt934c3ce2014-11-05 11:45:07 -0800721 setSize(svg, view);
722 setSize(bgImg, view);
Simon Hunt99c13842014-11-06 18:23:12 -0800723
724 // TODO: hook to recompute layout, perhaps? work with zoom/pan code
725 // adjust force layout size
Simon Hunt142d0032014-11-04 20:13:09 -0800726 }
727
728
729 // ==============================
730 // View registration
Simon Hunt195cb382014-11-03 17:50:51 -0800731
Simon Hunt25248912014-11-04 11:25:48 -0800732 onos.ui.addView('topo', {
Simon Hunt142d0032014-11-04 20:13:09 -0800733 preload: preload,
734 load: load,
735 resize: resize
Simon Hunt195cb382014-11-03 17:50:51 -0800736 });
737
Simon Hunt195cb382014-11-03 17:50:51 -0800738}(ONOS));