blob: e7444c9057f3740ad28b3a7d55e15a2c1d320348 [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 Hunt99c13842014-11-06 18:23:12 -0800116 space: injectTestEvent, // TODO: remove (testing only)
Simon Hunt1a9eff92014-11-07 11:06:34 -0800117 S: injectStartupEvents, // TODO: remove (testing only)
118 A: testAlert, // TODO: remove (testing only)
119 M: testMe, // TODO: remove (testing only)
Simon Hunt99c13842014-11-06 18:23:12 -0800120
Simon Hunt934c3ce2014-11-05 11:45:07 -0800121 B: toggleBg,
122 G: toggleLayout,
123 L: cycleLabels,
124 P: togglePorts,
125 U: unpin
126 };
Simon Hunt142d0032014-11-04 20:13:09 -0800127
Simon Hunt195cb382014-11-03 17:50:51 -0800128 // state variables
Simon Hunt99c13842014-11-06 18:23:12 -0800129 var network = {
130 nodes: [],
131 links: [],
132 lookup: {}
133 },
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800134 webSock,
Simon Hunt99c13842014-11-06 18:23:12 -0800135 labelIdx = 0,
Simon Hunt195cb382014-11-03 17:50:51 -0800136 selected = {},
137 highlighted = null,
138 hovered = null,
139 viewMode = 'showAll',
140 portLabelsOn = false;
141
Simon Hunt934c3ce2014-11-05 11:45:07 -0800142 // D3 selections
143 var svg,
144 bgImg,
Simon Huntc7ee0662014-11-05 16:44:37 -0800145 topoG,
146 nodeG,
147 linkG,
148 node,
149 link;
Simon Hunt195cb382014-11-03 17:50:51 -0800150
Simon Hunt142d0032014-11-04 20:13:09 -0800151 // ==============================
Simon Hunt934c3ce2014-11-05 11:45:07 -0800152 // For Debugging / Development
Simon Hunt195cb382014-11-03 17:50:51 -0800153
Simon Hunt99c13842014-11-06 18:23:12 -0800154 var eventPrefix = 'json/eventTest_',
Simon Hunt1a9eff92014-11-07 11:06:34 -0800155 eventNumber = 0,
156 alertNumber = 0;
Simon Hunt195cb382014-11-03 17:50:51 -0800157
Simon Hunt99c13842014-11-06 18:23:12 -0800158 function note(label, msg) {
159 console.log('NOTE: ' + label + ': ' + msg);
Simon Hunt195cb382014-11-03 17:50:51 -0800160 }
161
Simon Hunt99c13842014-11-06 18:23:12 -0800162 function debug(what) {
163 return config.debugOn && config.debug[what];
Simon Hunt934c3ce2014-11-05 11:45:07 -0800164 }
165
Simon Hunt99c13842014-11-06 18:23:12 -0800166
Simon Hunt934c3ce2014-11-05 11:45:07 -0800167 // ==============================
168 // Key Callbacks
169
Simon Hunt1a9eff92014-11-07 11:06:34 -0800170 function testAlert(view) {
171 alertNumber++;
172 view.alert("Test me! -- " + alertNumber);
173 }
174
Simon Hunt99c13842014-11-06 18:23:12 -0800175 function testMe(view) {
Simon Hunt99c13842014-11-06 18:23:12 -0800176 }
177
178 function injectTestEvent(view) {
179 eventNumber++;
180 var eventUrl = eventPrefix + eventNumber + '.json';
181
182 console.log('Fetching JSON: ' + eventUrl);
183 d3.json(eventUrl, function(err, data) {
184 if (err) {
185 view.dataLoadError(err, eventUrl);
186 } else {
187 handleServerEvent(data);
188 }
189 });
Simon Hunt934c3ce2014-11-05 11:45:07 -0800190 }
191
Simon Hunt1a9eff92014-11-07 11:06:34 -0800192 function injectStartupEvents(view) {
193 var lastStartupEvent = 32;
194 while (eventNumber < lastStartupEvent) {
195 injectTestEvent(view);
196 }
197 }
198
Simon Hunt934c3ce2014-11-05 11:45:07 -0800199 function toggleBg() {
200 var vis = bgImg.style('visibility');
201 bgImg.style('visibility', (vis === 'hidden') ? 'visible' : 'hidden');
202 }
203
204 function toggleLayout(view) {
205
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) {
235
236 }
237
238 function unpin(view) {
239
240 }
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 ...
251 console.log('showAllLayers()');
Simon Hunt195cb382014-11-03 17:50:51 -0800252 }
253
254 function showPacketLayer() {
Simon Hunt934c3ce2014-11-05 11:45:07 -0800255 showAllLayers();
256 // TODO ...
257 console.log('showPacketLayer()');
Simon Hunt195cb382014-11-03 17:50:51 -0800258 }
259
260 function showOpticalLayer() {
Simon Hunt934c3ce2014-11-05 11:45:07 -0800261 showAllLayers();
262 // TODO ...
263 console.log('showOpticalLayer()');
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 Hunt99c13842014-11-06 18:23:12 -0800282 function establishWebSocket() {
283 // TODO: establish a real web-socket
284 // NOTE, for now, we are using the 'Q' key to artificially inject
285 // "events" from the server.
286 }
Simon Hunt934c3ce2014-11-05 11:45:07 -0800287
Simon Hunt99c13842014-11-06 18:23:12 -0800288 // ==============================
289 // Event handlers for server-pushed events
290
291 var eventDispatch = {
292 addDevice: addDevice,
293 updateDevice: updateDevice,
294 removeDevice: removeDevice,
295 addLink: addLink
296 };
297
298 function addDevice(data) {
299 var device = data.payload,
300 node = createDeviceNode(device);
301 note('addDevice', device.id);
302
303 network.nodes.push(node);
304 network.lookup[node.id] = node;
305 updateNodes();
306 network.force.start();
307 }
308
309 function updateDevice(data) {
310 var device = data.payload;
311 note('updateDevice', device.id);
312
313 }
314
315 function removeDevice(data) {
316 var device = data.payload;
317 note('removeDevice', device.id);
318
319 }
320
321 function addLink(data) {
322 var link = data.payload,
323 lnk = createLink(link);
324
325 if (lnk) {
326 note('addLink', lnk.id);
327
328 network.links.push(lnk);
329 updateLinks();
330 network.force.start();
331 }
332 }
333
334 // ....
335
336 function unknownEvent(data) {
337 // TODO: use dialog, not alert
338 alert('Unknown event type: "' + data.event + '"');
339 }
340
341 function handleServerEvent(data) {
342 var fn = eventDispatch[data.event] || unknownEvent;
343 fn(data);
344 }
345
346 // ==============================
347 // force layout modification functions
348
349 function translate(x, y) {
350 return 'translate(' + x + ',' + y + ')';
351 }
352
353 function createLink(link) {
354 var type = link.type,
355 src = link.src,
356 dst = link.dst,
357 w = link.linkWidth,
358 srcNode = network.lookup[src],
359 dstNode = network.lookup[dst],
360 lnk;
361
362 if (!(srcNode && dstNode)) {
363 alert('nodes not on map');
364 return null;
365 }
366
367 lnk = {
368 id: safeId(src) + '~' + safeId(dst),
369 source: srcNode,
370 target: dstNode,
371 class: 'link',
372 svgClass: type ? 'link ' + type : 'link',
373 x1: srcNode.x,
374 y1: srcNode.y,
375 x2: dstNode.x,
376 y2: dstNode.y,
377 width: w
378 };
379 return lnk;
380 }
381
Simon Hunt1a9eff92014-11-07 11:06:34 -0800382 function linkWidth(w) {
383 // w is number of links between nodes. Scale appropriately.
384 return w * 1.2;
385 }
386
Simon Hunt99c13842014-11-06 18:23:12 -0800387 function updateLinks() {
388 link = linkG.selectAll('.link')
389 .data(network.links, function (d) { return d.id; });
390
391 // operate on existing links, if necessary
392 // link .foo() .bar() ...
393
394 // operate on entering links:
395 var entering = link.enter()
396 .append('line')
397 .attr({
398 id: function (d) { return d.id; },
399 class: function (d) { return d.svgClass; },
400 x1: function (d) { return d.x1; },
401 y1: function (d) { return d.y1; },
402 x2: function (d) { return d.x2; },
403 y2: function (d) { return d.y2; },
Simon Hunt1a9eff92014-11-07 11:06:34 -0800404 stroke: config.topo.linkInColor,
405 'stroke-width': config.topo.linkInWidth
Simon Hunt99c13842014-11-06 18:23:12 -0800406 })
407 .transition().duration(1000)
408 .attr({
Simon Hunt1a9eff92014-11-07 11:06:34 -0800409 'stroke-width': function (d) { return linkWidth(d.width); },
Simon Hunt99c13842014-11-06 18:23:12 -0800410 stroke: '#666' // TODO: remove explicit stroke, rather...
411 });
412
413 // augment links
414 // TODO: add src/dst port labels etc.
415
416 }
417
418 function createDeviceNode(device) {
419 // start with the object as is
420 var node = device,
421 type = device.type;
422
423 // Augment as needed...
424 node.class = 'device';
425 node.svgClass = type ? 'node device ' + type : 'node device';
426 positionNode(node);
427
428 // cache label array length
429 network.deviceLabelCount = device.labels.length;
430
431 return node;
432 }
433
434 function positionNode(node) {
435 var meta = node.metaUi,
436 x = 0,
437 y = 0;
438
439 if (meta) {
440 x = meta.x;
441 y = meta.y;
442 }
443 if (x && y) {
444 node.fixed = true;
445 }
446 node.x = x || network.view.width() / 2;
447 node.y = y || network.view.height() / 2;
448 }
449
450
451 function iconUrl(d) {
452 return 'img/' + d.type + '.png';
453 }
454
455 // returns the newly computed bounding box of the rectangle
456 function adjustRectToFitText(n) {
457 var text = n.select('text'),
458 box = text.node().getBBox(),
459 lab = config.labels;
460
461 text.attr('text-anchor', 'middle')
462 .attr('y', '-0.8em')
463 .attr('x', lab.imgPad/2);
464
465 // translate the bbox so that it is centered on [x,y]
466 box.x = -box.width / 2;
467 box.y = -box.height / 2;
468
469 // add padding
470 box.x -= (lab.padLR + lab.imgPad/2);
471 box.width += lab.padLR * 2 + lab.imgPad;
472 box.y -= lab.padTB;
473 box.height += lab.padTB * 2;
474
475 return box;
476 }
477
Simon Hunt1a9eff92014-11-07 11:06:34 -0800478 function mkSvgClass(d) {
479 return d.fixed ? d.svgClass + ' fixed' : d.svgClass;
480 }
481
Simon Hunt99c13842014-11-06 18:23:12 -0800482 function updateNodes() {
483 node = nodeG.selectAll('.node')
484 .data(network.nodes, function (d) { return d.id; });
485
486 // operate on existing nodes, if necessary
487 //node .foo() .bar() ...
488
489 // operate on entering nodes:
490 var entering = node.enter()
491 .append('g')
492 .attr({
493 id: function (d) { return safeId(d.id); },
Simon Hunt1a9eff92014-11-07 11:06:34 -0800494 class: mkSvgClass,
Simon Hunt99c13842014-11-06 18:23:12 -0800495 transform: function (d) { return translate(d.x, d.y); },
496 opacity: 0
497 })
Simon Hunt1a9eff92014-11-07 11:06:34 -0800498 .call(network.drag)
Simon Hunt99c13842014-11-06 18:23:12 -0800499 //.on('mouseover', function (d) {})
500 //.on('mouseover', function (d) {})
501 .transition()
502 .attr('opacity', 1);
503
504 // augment device nodes...
505 entering.filter('.device').each(function (d) {
506 var node = d3.select(this),
507 icon = iconUrl(d),
508 idx = (labelIdx < d.labels.length) ? labelIdx : 0,
509 box;
510
511 node.append('rect')
512 .attr({
513 'rx': 5,
514 'ry': 5
515 });
516
517 node.append('text')
518 .text(d.labels[idx])
519 .attr('dy', '1.1em');
520
521 box = adjustRectToFitText(node);
522
523 node.select('rect')
524 .attr(box);
525
526 if (icon) {
527 var cfg = config.icons;
528 node.append('svg:image')
529 .attr({
530 x: box.x + config.icons.xoff,
531 y: box.y + config.icons.yoff,
532 width: cfg.w,
533 height: cfg.h,
534 'xlink:href': icon
535 });
536 }
537
538 // debug function to show the modelled x,y coordinates of nodes...
539 if (debug('showNodeXY')) {
540 node.select('rect').attr('fill-opacity', 0.5);
541 node.append('circle')
542 .attr({
543 class: 'debug',
544 cx: 0,
545 cy: 0,
546 r: '3px'
547 });
Simon Huntc7ee0662014-11-05 16:44:37 -0800548 }
549 });
Simon Hunt934c3ce2014-11-05 11:45:07 -0800550
Simon Huntc7ee0662014-11-05 16:44:37 -0800551
Simon Hunt99c13842014-11-06 18:23:12 -0800552 // operate on both existing and new nodes, if necessary
553 //node .foo() .bar() ...
Simon Huntc7ee0662014-11-05 16:44:37 -0800554
Simon Hunt99c13842014-11-06 18:23:12 -0800555 // operate on exiting nodes:
556 // TODO: figure out how to remove the node 'g' AND its children
557 node.exit()
558 .transition()
559 .duration(750)
560 .attr({
561 opacity: 0,
562 cx: 0,
563 cy: 0,
564 r: 0
565 })
566 .remove();
Simon Huntc7ee0662014-11-05 16:44:37 -0800567 }
568
569
570 function tick() {
571 node.attr({
Simon Hunt99c13842014-11-06 18:23:12 -0800572 transform: function (d) { return translate(d.x, d.y); }
Simon Huntc7ee0662014-11-05 16:44:37 -0800573 });
574
575 link.attr({
576 x1: function (d) { return d.source.x; },
577 y1: function (d) { return d.source.y; },
578 x2: function (d) { return d.target.x; },
579 y2: function (d) { return d.target.y; }
580 });
581 }
Simon Hunt934c3ce2014-11-05 11:45:07 -0800582
583 // ==============================
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800584 // Web-Socket for live data
585
586 function webSockUrl() {
587 return document.location.toString()
588 .replace(/\#.*/, '')
589 .replace('http://', 'ws://')
590 .replace('https://', 'wss://')
591 .replace('index2.html', config.webSockUrl);
592 }
593
594 webSock = {
595 ws : null,
596
597 connect : function() {
598 webSock.ws = new WebSocket(webSockUrl());
599
600 webSock.ws.onopen = function() {
601 webSock._send("Hi there!");
602 };
603
604 webSock.ws.onmessage = function(m) {
605 if (m.data) {
606 console.log(m.data);
607 }
608 };
609
610 webSock.ws.onclose = function(m) {
611 webSock.ws = null;
612 };
613 },
614
615 send : function(text) {
616 if (text != null && text.length > 0) {
617 webSock._send(text);
618 }
619 },
620
621 _send : function(message) {
622 if (webSock.ws) {
623 webSock.ws.send(message);
624 }
625 }
626
627 };
628
629 // ==============================
Simon Hunt142d0032014-11-04 20:13:09 -0800630 // View life-cycle callbacks
Simon Hunt195cb382014-11-03 17:50:51 -0800631
Simon Hunt142d0032014-11-04 20:13:09 -0800632 function preload(view, ctx) {
633 var w = view.width(),
634 h = view.height(),
635 idBg = view.uid('bg'),
Simon Huntc7ee0662014-11-05 16:44:37 -0800636 showBg = config.options.showBackground ? 'visible' : 'hidden',
637 fcfg = config.force,
638 fpad = fcfg.pad,
639 forceDim = [w - 2*fpad, h - 2*fpad];
Simon Hunt195cb382014-11-03 17:50:51 -0800640
Simon Hunt142d0032014-11-04 20:13:09 -0800641 // NOTE: view.$div is a D3 selection of the view's div
642 svg = view.$div.append('svg');
Simon Hunt934c3ce2014-11-05 11:45:07 -0800643 setSize(svg, view);
644
Simon Hunt1a9eff92014-11-07 11:06:34 -0800645 // add blue glow filter to svg layer
646 d3u.appendGlow(svg);
647
Simon Hunt142d0032014-11-04 20:13:09 -0800648 // load the background image
649 bgImg = svg.append('svg:image')
Simon Hunt195cb382014-11-03 17:50:51 -0800650 .attr({
Simon Hunt142d0032014-11-04 20:13:09 -0800651 id: idBg,
652 width: w,
653 height: h,
Simon Hunt195cb382014-11-03 17:50:51 -0800654 'xlink:href': config.backgroundUrl
655 })
Simon Hunt142d0032014-11-04 20:13:09 -0800656 .style({
657 visibility: showBg
Simon Hunt195cb382014-11-03 17:50:51 -0800658 });
Simon Huntc7ee0662014-11-05 16:44:37 -0800659
660 // group for the topology
661 topoG = svg.append('g')
662 .attr('transform', fcfg.translate());
663
664 // subgroups for links and nodes
665 linkG = topoG.append('g').attr('id', 'links');
666 nodeG = topoG.append('g').attr('id', 'nodes');
667
668 // selection of nodes and links
669 link = linkG.selectAll('.link');
670 node = nodeG.selectAll('.node');
671
Simon Hunt99c13842014-11-06 18:23:12 -0800672 function ldist(d) {
673 return fcfg.linkDistance[d.class] || 150;
674 }
675 function lstrg(d) {
676 return fcfg.linkStrength[d.class] || 1;
677 }
678 function lchrg(d) {
679 return fcfg.charge[d.class] || -200;
680 }
681
Simon Hunt1a9eff92014-11-07 11:06:34 -0800682 function selectCb(d, self) {
683 // TODO: selectObject(d, self);
684 }
685
686 function atDragEnd(d, self) {
687 // once we've finished moving, pin the node in position,
688 // if it is a device (not a host)
689 if (d.class === 'device') {
690 d.fixed = true;
691 d3.select(self).classed('fixed', true)
692 // TODO: send new [x,y] back to server, via websocket.
693 }
694 }
695
Simon Huntc7ee0662014-11-05 16:44:37 -0800696 // set up the force layout
697 network.force = d3.layout.force()
698 .size(forceDim)
699 .nodes(network.nodes)
700 .links(network.links)
Simon Hunt99c13842014-11-06 18:23:12 -0800701 .charge(lchrg)
702 .linkDistance(ldist)
703 .linkStrength(lstrg)
Simon Huntc7ee0662014-11-05 16:44:37 -0800704 .on('tick', tick);
Simon Hunt195cb382014-11-03 17:50:51 -0800705
Simon Hunt1a9eff92014-11-07 11:06:34 -0800706 network.drag = d3u.createDragBehavior(network.force, selectCb, atDragEnd);
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800707 webSock.connect();
Simon Hunt1a9eff92014-11-07 11:06:34 -0800708 }
Simon Hunt195cb382014-11-03 17:50:51 -0800709
Simon Hunt142d0032014-11-04 20:13:09 -0800710 function load(view, ctx) {
Simon Hunt99c13842014-11-06 18:23:12 -0800711 // cache the view token, so network topo functions can access it
712 network.view = view;
713
714 // set our radio buttons and key bindings
Simon Hunt934c3ce2014-11-05 11:45:07 -0800715 view.setRadio(btnSet);
716 view.setKeys(keyDispatch);
Simon Hunt195cb382014-11-03 17:50:51 -0800717
Simon Hunt99c13842014-11-06 18:23:12 -0800718 establishWebSocket();
Simon Hunt195cb382014-11-03 17:50:51 -0800719 }
720
Simon Hunt142d0032014-11-04 20:13:09 -0800721 function resize(view, ctx) {
Simon Hunt934c3ce2014-11-05 11:45:07 -0800722 setSize(svg, view);
723 setSize(bgImg, view);
Simon Hunt99c13842014-11-06 18:23:12 -0800724
725 // TODO: hook to recompute layout, perhaps? work with zoom/pan code
726 // adjust force layout size
Simon Hunt142d0032014-11-04 20:13:09 -0800727 }
728
729
730 // ==============================
731 // View registration
Simon Hunt195cb382014-11-03 17:50:51 -0800732
Simon Hunt25248912014-11-04 11:25:48 -0800733 onos.ui.addView('topo', {
Simon Hunt142d0032014-11-04 20:13:09 -0800734 preload: preload,
735 load: load,
736 resize: resize
Simon Hunt195cb382014-11-03 17:50:51 -0800737 });
738
Simon Hunt195cb382014-11-03 17:50:51 -0800739}(ONOS));