blob: 01e4acdeff5f791391ebd9da4242f6148aff3170 [file] [log] [blame]
Simon Hunt0b05d4a2014-10-21 21:50:15 -07001/*
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07002 * Copyright 2014 Open Networking Laboratory
Thomas Vachuska781d18b2014-10-27 10:31:25 -07003 *
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07004 * 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
Thomas Vachuska781d18b2014-10-27 10:31:25 -07007 *
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07008 * 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.
Thomas Vachuska781d18b2014-10-27 10:31:25 -070015 */
16
17/*
Simon Hunt0b05d4a2014-10-21 21:50:15 -070018 ONOS network topology viewer - PoC version 1.0
19
20 @author Simon Hunt
21 */
22
23(function (onos) {
24 'use strict';
25
Simon Huntd35961b2014-10-28 08:49:48 -070026 // reference to the framework api
Simon Hunt0b05d4a2014-10-21 21:50:15 -070027 var api = onos.api;
28
Simon Huntd35961b2014-10-28 08:49:48 -070029 // configuration data
Simon Hunt0b05d4a2014-10-21 21:50:15 -070030 var config = {
Simon Hunt0df1b1d2014-11-04 22:58:29 -080031 useLiveData: false,
Simon Hunt73171372014-10-30 09:25:36 -070032 debugOn: false,
33 debug: {
34 showNodeXY: false,
35 showKeyHandler: true
36 },
37 options: {
38 layering: true,
Simon Huntee66e612014-10-30 14:56:31 -070039 collisionPrevention: true,
40 loadBackground: true
Simon Hunt73171372014-10-30 09:25:36 -070041 },
Simon Huntee66e612014-10-30 14:56:31 -070042 backgroundUrl: 'img/us-map.png',
Simon Huntb4d9d4c2014-10-30 11:27:23 -070043 data: {
44 live: {
45 jsonUrl: 'rs/topology/graph',
46 detailPrefix: 'rs/topology/graph/',
47 detailSuffix: ''
48 },
49 fake: {
50 jsonUrl: 'json/network2.json',
51 detailPrefix: 'json/',
52 detailSuffix: '.json'
53 }
54 },
Simon Hunt73171372014-10-30 09:25:36 -070055 iconUrl: {
56 device: 'img/device.png',
57 host: 'img/host.png',
58 pkt: 'img/pkt.png',
59 opt: 'img/opt.png'
60 },
61 mastHeight: 36,
62 force: {
63 note: 'node.class or link.class is used to differentiate',
64 linkDistance: {
65 infra: 200,
66 host: 40
Simon Huntd35961b2014-10-28 08:49:48 -070067 },
Simon Hunt73171372014-10-30 09:25:36 -070068 linkStrength: {
69 infra: 1.0,
70 host: 1.0
Simon Hunt2c9e0c22014-10-23 15:12:58 -070071 },
Simon Hunt73171372014-10-30 09:25:36 -070072 charge: {
73 device: -800,
74 host: -1000
Simon Hunt68ae6652014-10-22 13:58:07 -070075 },
Simon Hunt73171372014-10-30 09:25:36 -070076 ticksWithoutCollisions: 50,
77 marginLR: 20,
78 marginTB: 20,
79 translate: function() {
80 return 'translate(' +
81 config.force.marginLR + ',' +
82 config.force.marginTB + ')';
83 }
84 },
85 labels: {
86 imgPad: 16,
87 padLR: 8,
88 padTB: 6,
89 marginLR: 3,
Simon Hunt69a8d212014-10-30 17:57:35 -070090 marginTB: 2,
91 port: {
Simon Hunt3c29c142014-10-30 21:31:44 -070092 gap: 3,
93 width: 18,
94 height: 14
Simon Hunt69a8d212014-10-30 17:57:35 -070095 }
Simon Hunt73171372014-10-30 09:25:36 -070096 },
97 icons: {
98 w: 32,
99 h: 32,
100 xoff: -12,
101 yoff: -8
102 },
103 constraints: {
104 ypos: {
105 host: 0.05,
106 switch: 0.3,
107 roadm: 0.7
108 }
109 },
110 hostLinkWidth: 1.0,
111 hostRadius: 7,
112 mouseOutTimerDelayMs: 120
113 };
Simon Huntd35961b2014-10-28 08:49:48 -0700114
115 // state variables
116 var view = {},
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700117 network = {},
118 selected = {},
Simon Hunt5cd0e8f2014-10-27 16:18:40 -0700119 highlighted = null,
Simon Hunt9a16c822014-10-28 16:09:19 -0700120 hovered = null,
Simon Hunt69a8d212014-10-30 17:57:35 -0700121 viewMode = 'showAll',
122 portLabelsOn = false;
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700123
124
Simon Huntd35961b2014-10-28 08:49:48 -0700125 function debug(what) {
126 return config.debugOn && config.debug[what];
127 }
128
Simon Huntb4d9d4c2014-10-30 11:27:23 -0700129 function urlData() {
130 return config.data[config.useLiveData ? 'live' : 'fake'];
131 }
132
133 function networkJsonUrl() {
134 return urlData().jsonUrl;
135 }
136
Simon Hunt69a8d212014-10-30 17:57:35 -0700137 function safeId(id) {
138 return id.replace(/[^a-z0-9]/gi, '_');
139 }
140
Simon Huntb4d9d4c2014-10-30 11:27:23 -0700141 function detailJsonUrl(id) {
142 var u = urlData(),
Simon Hunt69a8d212014-10-30 17:57:35 -0700143 encId = config.useLiveData ? encodeURIComponent(id) : safeId(id);
Simon Huntb4d9d4c2014-10-30 11:27:23 -0700144 return u.detailPrefix + encId + u.detailSuffix;
145 }
146
147
Simon Huntd35961b2014-10-28 08:49:48 -0700148 // load the topology view of the network
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700149 function loadNetworkView() {
150 // Hey, here I am, calling something on the ONOS api:
151 api.printTime();
152
153 resize();
154
Simon Huntd35961b2014-10-28 08:49:48 -0700155 // go get our network data from the server...
Simon Huntb4d9d4c2014-10-30 11:27:23 -0700156 var url = networkJsonUrl();
157 d3.json(url , function (err, data) {
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700158 if (err) {
159 alert('Oops! Error reading JSON...\n\n' +
Simon Huntb4d9d4c2014-10-30 11:27:23 -0700160 'URL: ' + url + '\n\n' +
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700161 'Error: ' + err.message);
162 return;
163 }
Simon Huntd35961b2014-10-28 08:49:48 -0700164// console.log("here is the JSON data...");
165// console.log(data);
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700166
167 network.data = data;
168 drawNetwork();
169 });
170
Simon Huntd35961b2014-10-28 08:49:48 -0700171 // while we wait for the data, set up the handlers...
172 setUpClickHandler();
173 setUpRadioButtonHandler();
174 setUpKeyHandler();
175 $(window).on('resize', resize);
176 }
177
178 function setUpClickHandler() {
179 // click handler for "selectable" objects
180 $(document).on('click', '.select-object', function () {
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700181 // when any object of class "select-object" is clicked...
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700182 var obj = network.lookup[$(this).data('id')];
183 if (obj) {
184 selectObject(obj);
185 }
186 // stop propagation of event (I think) ...
187 return false;
188 });
Simon Huntd35961b2014-10-28 08:49:48 -0700189 }
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700190
Simon Huntd35961b2014-10-28 08:49:48 -0700191 function setUpRadioButtonHandler() {
192 d3.selectAll('#displayModes .radio').on('click', function () {
193 var id = d3.select(this).attr('id');
Simon Hunt5cd0e8f2014-10-27 16:18:40 -0700194 if (id !== viewMode) {
195 radioButton('displayModes', id);
196 viewMode = id;
Simon Huntf967d512014-10-28 20:34:29 -0700197 doRadioAction(id);
198 }
199 });
200 }
201
202 function doRadioAction(id) {
203 showAllLayers();
204 if (id === 'showPkt') {
205 showPacketLayer();
206 } else if (id === 'showOpt') {
207 showOpticalLayer();
208 }
209 }
210
211 function showAllLayers() {
212 network.node.classed('inactive', false);
213 network.link.classed('inactive', false);
Simon Huntac525752014-10-31 08:38:21 -0700214 d3.selectAll('svg .port').classed('inactive', false)
215 d3.selectAll('svg .portText').classed('inactive', false)
Simon Huntf967d512014-10-28 20:34:29 -0700216 }
217
218 function showPacketLayer() {
219 network.node.each(function(d) {
220 // deactivate nodes that are not hosts or switches
221 if (d.class === 'device' && d.type !== 'switch') {
222 d3.select(this).classed('inactive', true);
223 }
224 });
225
226 network.link.each(function(lnk) {
227 // deactivate infrastructure links that have opt's as endpoints
228 if (lnk.source.type === 'roadm' || lnk.target.type === 'roadm') {
229 d3.select(this).classed('inactive', true);
230 }
231 });
Simon Huntac525752014-10-31 08:38:21 -0700232
233 // deactivate non-packet ports
234 d3.selectAll('svg .optPort').classed('inactive', true)
Simon Huntf967d512014-10-28 20:34:29 -0700235 }
236
237 function showOpticalLayer() {
238 network.node.each(function(d) {
239 // deactivate nodes that are not optical devices
240 if (d.type !== 'roadm') {
241 d3.select(this).classed('inactive', true);
242 }
243 });
244
245 network.link.each(function(lnk) {
246 // deactivate infrastructure links that have opt's as endpoints
247 if (lnk.source.type !== 'roadm' || lnk.target.type !== 'roadm') {
248 d3.select(this).classed('inactive', true);
Simon Hunt5cd0e8f2014-10-27 16:18:40 -0700249 }
250 });
Simon Huntac525752014-10-31 08:38:21 -0700251
252 // deactivate non-packet ports
253 d3.selectAll('svg .pktPort').classed('inactive', true)
Simon Hunt5cd0e8f2014-10-27 16:18:40 -0700254 }
255
Simon Huntd35961b2014-10-28 08:49:48 -0700256 function setUpKeyHandler() {
257 d3.select('body')
258 .on('keydown', function () {
259 processKeyEvent();
260 if (debug('showKeyHandler')) {
261 network.svg.append('text')
262 .attr('x', 5)
263 .attr('y', 15)
264 .style('font-size', '20pt')
265 .text('keyCode: ' + d3.event.keyCode +
266 ' applied to : ' + contextLabel())
267 .transition().duration(2000)
268 .style('font-size', '2pt')
269 .style('fill-opacity', 0.01)
270 .remove();
271 }
272 });
273 }
274
Simon Hunt9a16c822014-10-28 16:09:19 -0700275 function contextLabel() {
276 return hovered === null ? "(nothing)" : hovered.id;
277 }
278
Simon Hunt5cd0e8f2014-10-27 16:18:40 -0700279 function radioButton(group, id) {
280 d3.selectAll("#" + group + " .radio").classed("active", false);
281 d3.select("#" + group + " #" + id).classed("active", true);
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700282 }
283
Simon Huntd35961b2014-10-28 08:49:48 -0700284 function processKeyEvent() {
285 var code = d3.event.keyCode;
286 switch (code) {
Simon Huntee66e612014-10-30 14:56:31 -0700287 case 66: // B
288 toggleBackground();
289 break;
Thomas Vachuska1de66012014-10-30 03:03:30 -0700290 case 71: // G
291 cycleLayout();
292 break;
Simon Huntd35961b2014-10-28 08:49:48 -0700293 case 76: // L
294 cycleLabels();
295 break;
296 case 80: // P
297 togglePorts();
Simon Hunt9a16c822014-10-28 16:09:19 -0700298 break;
299 case 85: // U
300 unpin();
301 break;
Simon Huntd35961b2014-10-28 08:49:48 -0700302 }
303
304 }
305
Simon Huntee66e612014-10-30 14:56:31 -0700306 function toggleBackground() {
307 var bg = d3.select('#bg'),
308 vis = bg.style('visibility'),
309 newvis = (vis === 'hidden') ? 'visible' : 'hidden';
310 bg.style('visibility', newvis);
311 }
312
Thomas Vachuska1de66012014-10-30 03:03:30 -0700313 function cycleLayout() {
314 config.options.layering = !config.options.layering;
315 network.force.resume();
316 }
317
Simon Huntd35961b2014-10-28 08:49:48 -0700318 function cycleLabels() {
Simon Hunt9a16c822014-10-28 16:09:19 -0700319 console.log('Cycle Labels - context = ' + contextLabel());
Simon Huntd35961b2014-10-28 08:49:48 -0700320 }
321
322 function togglePorts() {
Simon Hunt69a8d212014-10-30 17:57:35 -0700323 portLabelsOn = !portLabelsOn;
324 var portVis = portLabelsOn ? 'visible' : 'hidden';
325 d3.selectAll('.port').style('visibility', portVis);
Simon Hunt3c29c142014-10-30 21:31:44 -0700326 d3.selectAll('.portText').style('visibility', portVis);
Simon Hunt9a16c822014-10-28 16:09:19 -0700327 }
328
329 function unpin() {
330 if (hovered) {
331 hovered.fixed = false;
Simon Huntf967d512014-10-28 20:34:29 -0700332 findNodeFromData(hovered).classed('fixed', false);
Simon Hunt9a16c822014-10-28 16:09:19 -0700333 network.force.resume();
334 }
335 console.log('Unpin - context = ' + contextLabel());
Simon Huntd35961b2014-10-28 08:49:48 -0700336 }
337
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700338
339 // ========================================================
340
341 function drawNetwork() {
342 $('#view').empty();
343
344 prepareNodesAndLinks();
345 createLayout();
346 console.log("\n\nHere is the augmented network object...");
Simon Hunt9a16c822014-10-28 16:09:19 -0700347 console.log(network);
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700348 }
349
350 function prepareNodesAndLinks() {
351 network.lookup = {};
352 network.nodes = [];
353 network.links = [];
354
355 var nw = network.forceWidth,
356 nh = network.forceHeight;
357
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700358 function yPosConstraintForNode(n) {
359 return config.constraints.ypos[n.type || 'host'];
360 }
361
362 // Note that both 'devices' and 'hosts' get mapped into the nodes array
363
364 // first, the devices...
365 network.data.devices.forEach(function(n) {
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700366 var ypc = yPosConstraintForNode(n),
Simon Hunt3ab76a82014-10-22 13:07:32 -0700367 ix = Math.random() * 0.6 * nw + 0.2 * nw,
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700368 iy = ypc * nh,
369 node = {
Thomas Vachuska5f998492014-10-31 00:46:11 -0700370 id: n.id,
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700371 labels: n.labels,
372 class: 'device',
373 icon: 'device',
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700374 type: n.type,
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700375 x: ix,
376 y: iy,
377 constraint: {
378 weight: 0.7,
379 y: iy
380 }
381 };
382 network.lookup[n.id] = node;
383 network.nodes.push(node);
384 });
385
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700386 // then, the hosts...
387 network.data.hosts.forEach(function(n) {
388 var ypc = yPosConstraintForNode(n),
389 ix = Math.random() * 0.6 * nw + 0.2 * nw,
390 iy = ypc * nh,
391 node = {
Thomas Vachuska5f998492014-10-31 00:46:11 -0700392 id: n.id,
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700393 labels: n.labels,
394 class: 'host',
395 icon: 'host',
396 type: n.type,
397 x: ix,
398 y: iy,
399 constraint: {
400 weight: 0.7,
401 y: iy
402 }
403 };
404 network.lookup[n.id] = node;
405 network.nodes.push(node);
406 });
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700407
408
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700409 // now, process the explicit links...
Simon Hunt6f376a32014-10-28 12:38:30 -0700410 network.data.links.forEach(function(lnk) {
411 var src = network.lookup[lnk.src],
412 dst = network.lookup[lnk.dst],
Simon Hunt69a8d212014-10-30 17:57:35 -0700413 id = src.id + "-" + dst.id;
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700414
415 var link = {
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700416 class: 'infra',
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700417 id: id,
Simon Hunt6f376a32014-10-28 12:38:30 -0700418 type: lnk.type,
419 width: lnk.linkWidth,
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700420 source: src,
Simon Hunt69a8d212014-10-30 17:57:35 -0700421 srcPort: lnk.srcPort,
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700422 target: dst,
Simon Hunt69a8d212014-10-30 17:57:35 -0700423 tgtPort: lnk.dstPort,
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700424 strength: config.force.linkStrength.infra
425 };
426 network.links.push(link);
427 });
428
429 // finally, infer host links...
430 network.data.hosts.forEach(function(n) {
431 var src = network.lookup[n.id],
432 dst = network.lookup[n.cp.device],
Simon Hunt69a8d212014-10-30 17:57:35 -0700433 id = src.id + "-" + dst.id;
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700434
435 var link = {
436 class: 'host',
437 id: id,
438 type: 'hostLink',
439 width: config.hostLinkWidth,
440 source: src,
441 target: dst,
442 strength: config.force.linkStrength.host
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700443 };
444 network.links.push(link);
445 });
446 }
447
448 function createLayout() {
449
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700450 var cfg = config.force;
451
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700452 network.force = d3.layout.force()
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700453 .size([network.forceWidth, network.forceHeight])
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700454 .nodes(network.nodes)
455 .links(network.links)
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700456 .linkStrength(function(d) { return cfg.linkStrength[d.class]; })
457 .linkDistance(function(d) { return cfg.linkDistance[d.class]; })
458 .charge(function(d) { return cfg.charge[d.class]; })
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700459 .on('tick', tick);
460
461 network.svg = d3.select('#view').append('svg')
462 .attr('width', view.width)
463 .attr('height', view.height)
464 .append('g')
Simon Huntae968a62014-10-22 14:54:41 -0700465 .attr('transform', config.force.translate());
Simon Hunt3ab76a82014-10-22 13:07:32 -0700466// .attr('id', 'zoomable')
Simon Hunt3ab76a82014-10-22 13:07:32 -0700467// .call(d3.behavior.zoom().on("zoom", zoomRedraw));
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700468
Thomas Vachuska8cd66a52014-10-30 11:53:07 -0700469 network.svg.append('svg:image')
470 .attr({
471 id: 'bg',
472 width: view.width,
473 height: view.height,
Simon Huntee66e612014-10-30 14:56:31 -0700474 'xlink:href': config.backgroundUrl
475 })
476 .style('visibility',
477 config.options.loadBackground ? 'visible' : 'hidden');
Thomas Vachuska8cd66a52014-10-30 11:53:07 -0700478
Simon Hunt3ab76a82014-10-22 13:07:32 -0700479// function zoomRedraw() {
480// d3.select("#zoomable").attr("transform",
481// "translate(" + d3.event.translate + ")"
482// + " scale(" + d3.event.scale + ")");
483// }
484
Simon Hunt3ab76a82014-10-22 13:07:32 -0700485 // TODO: move glow/blur stuff to util script
486 var glow = network.svg.append('filter')
487 .attr('x', '-50%')
488 .attr('y', '-50%')
489 .attr('width', '200%')
490 .attr('height', '200%')
491 .attr('id', 'blue-glow');
492
493 glow.append('feColorMatrix')
494 .attr('type', 'matrix')
495 .attr('values', '0 0 0 0 0 ' +
496 '0 0 0 0 0 ' +
497 '0 0 0 0 .7 ' +
498 '0 0 0 1 0 ');
499
500 glow.append('feGaussianBlur')
501 .attr('stdDeviation', 3)
502 .attr('result', 'coloredBlur');
503
504 glow.append('feMerge').selectAll('feMergeNode')
505 .data(['coloredBlur', 'SourceGraphic'])
506 .enter().append('feMergeNode')
507 .attr('in', String);
508
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700509 // TODO: legend (and auto adjust on scroll)
Simon Hunt3ab76a82014-10-22 13:07:32 -0700510// $('#view').on('scroll', function() {
511//
512// });
513
514
Simon Hunt6f376a32014-10-28 12:38:30 -0700515 // TODO: move drag behavior into separate method.
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700516 // == define node drag behavior...
Simon Hunt3ab76a82014-10-22 13:07:32 -0700517 network.draggedThreshold = d3.scale.linear()
518 .domain([0, 0.1])
519 .range([5, 20])
520 .clamp(true);
521
522 function dragged(d) {
523 var threshold = network.draggedThreshold(network.force.alpha()),
524 dx = d.oldX - d.px,
525 dy = d.oldY - d.py;
526 if (Math.abs(dx) >= threshold || Math.abs(dy) >= threshold) {
527 d.dragged = true;
528 }
529 return d.dragged;
530 }
531
532 network.drag = d3.behavior.drag()
533 .origin(function(d) { return d; })
534 .on('dragstart', function(d) {
535 d.oldX = d.x;
536 d.oldY = d.y;
537 d.dragged = false;
538 d.fixed |= 2;
539 })
540 .on('drag', function(d) {
541 d.px = d3.event.x;
542 d.py = d3.event.y;
543 if (dragged(d)) {
544 if (!network.force.alpha()) {
545 network.force.alpha(.025);
546 }
547 }
548 })
549 .on('dragend', function(d) {
550 if (!dragged(d)) {
551 selectObject(d, this);
552 }
553 d.fixed &= ~6;
Simon Hunt9a16c822014-10-28 16:09:19 -0700554
555 // once we've finished moving, pin the node in position,
Simon Huntf967d512014-10-28 20:34:29 -0700556 // if it is a device (not a host)
Simon Hunt9a16c822014-10-28 16:09:19 -0700557 if (d.class === 'device') {
558 d.fixed = true;
Simon Huntf967d512014-10-28 20:34:29 -0700559 d3.select(this).classed('fixed', true)
Simon Hunt9a16c822014-10-28 16:09:19 -0700560 }
Simon Hunt3ab76a82014-10-22 13:07:32 -0700561 });
562
563 $('#view').on('click', function(e) {
564 if (!$(e.target).closest('.node').length) {
565 deselectObject();
566 }
567 });
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700568
Simon Hunt69a8d212014-10-30 17:57:35 -0700569 // ...............................................................
570
571 // add links to the display
572 network.link = network.svg.append('g').attr('id', 'links')
573 .selectAll('.link')
574 .data(network.force.links(), function(d) {return d.id})
575 .enter().append('line')
576 .attr('class', function(d) {return 'link ' + d.class});
577
578 network.linkSrcPort = network.svg.append('g')
579 .attr({
580 id: 'srcPorts',
581 class: 'portLayer'
582 });
583 network.linkTgtPort = network.svg.append('g')
584 .attr({
585 id: 'tgtPorts',
586 class: 'portLayer'
587 });
588
Simon Hunt3c29c142014-10-30 21:31:44 -0700589 var portVis = portLabelsOn ? 'visible' : 'hidden',
590 pw = config.labels.port.width,
591 ph = config.labels.port.height;
Simon Hunt69a8d212014-10-30 17:57:35 -0700592
Simon Huntac525752014-10-31 08:38:21 -0700593 network.link.filter('.infra').each(function(d) {
594 var srcType = d.source.type === 'roadm' ? 'optPort' : 'pktPort',
595 tgtType = d.target.type === 'roadm' ? 'optPort' : 'pktPort';
596
597 if (d.source.type)
598
Simon Hunt69a8d212014-10-30 17:57:35 -0700599 network.linkSrcPort.append('rect').attr({
Thomas Vachuska5f998492014-10-31 00:46:11 -0700600 id: 'srcPort-' + safeId(d.id),
Simon Huntac525752014-10-31 08:38:21 -0700601 class: 'port ' + srcType,
Simon Hunt3c29c142014-10-30 21:31:44 -0700602 width: pw,
603 height: ph,
Thomas Vachuska5f998492014-10-31 00:46:11 -0700604 rx: 4,
605 ry: 4
606 }).style('visibility', portVis);
Simon Hunt69a8d212014-10-30 17:57:35 -0700607
608 network.linkTgtPort.append('rect').attr({
Thomas Vachuska5f998492014-10-31 00:46:11 -0700609 id: 'tgtPort-' + safeId(d.id),
Simon Huntac525752014-10-31 08:38:21 -0700610 class: 'port ' + tgtType,
Simon Hunt3c29c142014-10-30 21:31:44 -0700611 width: pw,
612 height: ph,
Thomas Vachuska5f998492014-10-31 00:46:11 -0700613 rx: 4,
614 ry: 4
615 }).style('visibility', portVis);
Simon Hunt69a8d212014-10-30 17:57:35 -0700616
Simon Hunt3c29c142014-10-30 21:31:44 -0700617 network.linkSrcPort.append('text').attr({
Thomas Vachuska5f998492014-10-31 00:46:11 -0700618 id: 'srcText-' + safeId(d.id),
Simon Huntac525752014-10-31 08:38:21 -0700619 class: 'portText ' + srcType
Simon Hunt3c29c142014-10-30 21:31:44 -0700620 }).text(d.srcPort)
621 .style('visibility', portVis);
622
623 network.linkTgtPort.append('text').attr({
Thomas Vachuska5f998492014-10-31 00:46:11 -0700624 id: 'tgtText-' + safeId(d.id),
Simon Huntac525752014-10-31 08:38:21 -0700625 class: 'portText ' + tgtType
Simon Hunt3c29c142014-10-30 21:31:44 -0700626 }).text(d.tgtPort)
627 .style('visibility', portVis);
Simon Hunt69a8d212014-10-30 17:57:35 -0700628 });
629
630 // ...............................................................
Simon Hunt1c5f8b62014-10-22 14:43:01 -0700631
Simon Hunt5cd0e8f2014-10-27 16:18:40 -0700632 // add nodes to the display
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700633 network.node = network.svg.selectAll('.node')
634 .data(network.force.nodes(), function(d) {return d.id})
635 .enter().append('g')
Simon Hunt3ab76a82014-10-22 13:07:32 -0700636 .attr('class', function(d) {
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700637 var cls = 'node ' + d.class;
638 if (d.type) {
639 cls += ' ' + d.type;
640 }
641 return cls;
Simon Hunt3ab76a82014-10-22 13:07:32 -0700642 })
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700643 .attr('transform', function(d) {
644 return translate(d.x, d.y);
645 })
Simon Hunt3ab76a82014-10-22 13:07:32 -0700646 .call(network.drag)
647 .on('mouseover', function(d) {
Simon Hunt6f376a32014-10-28 12:38:30 -0700648 // TODO: show tooltip
Simon Hunt9a16c822014-10-28 16:09:19 -0700649 if (network.mouseoutTimeout) {
650 clearTimeout(network.mouseoutTimeout);
651 network.mouseoutTimeout = null;
Simon Hunt3ab76a82014-10-22 13:07:32 -0700652 }
Simon Hunt9a16c822014-10-28 16:09:19 -0700653 hoverObject(d);
Simon Hunt3ab76a82014-10-22 13:07:32 -0700654 })
655 .on('mouseout', function(d) {
Simon Hunt6f376a32014-10-28 12:38:30 -0700656 // TODO: hide tooltip
Simon Hunt9a16c822014-10-28 16:09:19 -0700657 if (network.mouseoutTimeout) {
658 clearTimeout(network.mouseoutTimeout);
659 network.mouseoutTimeout = null;
Simon Hunt3ab76a82014-10-22 13:07:32 -0700660 }
Simon Hunt9a16c822014-10-28 16:09:19 -0700661 network.mouseoutTimeout = setTimeout(function() {
662 hoverObject(null);
663 }, config.mouseOutTimerDelayMs);
Simon Hunt3ab76a82014-10-22 13:07:32 -0700664 });
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700665
Simon Hunt6f376a32014-10-28 12:38:30 -0700666
667 // deal with device nodes first
668 network.nodeRect = network.node.filter('.device')
669 .append('rect')
Simon Hunt5cd0e8f2014-10-27 16:18:40 -0700670 .attr({
671 rx: 5,
672 ry: 5,
673 width: 100,
674 height: 12
675 });
676 // note that width/height are adjusted to fit the label text
Simon Hunt6f376a32014-10-28 12:38:30 -0700677 // then padded, and space made for the icon.
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700678
Simon Hunt6f376a32014-10-28 12:38:30 -0700679 network.node.filter('.device').each(function(d) {
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700680 var node = d3.select(this),
Simon Hunt5cd0e8f2014-10-27 16:18:40 -0700681 icon = iconUrl(d);
682
683 node.append('text')
684 // TODO: add label cycle behavior
685 .text(d.id)
686 .attr('dy', '1.1em');
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700687
688 if (icon) {
689 var cfg = config.icons;
690 node.append('svg:image')
Simon Hunt5cd0e8f2014-10-27 16:18:40 -0700691 .attr({
692 width: cfg.w,
693 height: cfg.h,
694 'xlink:href': icon
695 });
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700696 // note, icon relative positioning (x,y) is done after we have
697 // adjusted the bounds of the rectangle...
698 }
Simon Hunt68ae6652014-10-22 13:58:07 -0700699
Simon Huntd35961b2014-10-28 08:49:48 -0700700 // debug function to show the modelled x,y coordinates of nodes...
701 if (debug('showNodeXY')) {
702 node.select('rect').attr('fill-opacity', 0.5);
Simon Hunt5cd0e8f2014-10-27 16:18:40 -0700703 node.append('circle')
704 .attr({
705 class: 'debug',
706 cx: 0,
707 cy: 0,
708 r: '3px'
709 });
710 }
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700711 });
712
Simon Hunt6f376a32014-10-28 12:38:30 -0700713 // now process host nodes
714 network.nodeCircle = network.node.filter('.host')
715 .append('circle')
716 .attr({
717 r: config.hostRadius
718 });
Simon Hunt5cd0e8f2014-10-27 16:18:40 -0700719
Simon Hunt6f376a32014-10-28 12:38:30 -0700720 network.node.filter('.host').each(function(d) {
721 var node = d3.select(this),
722 icon = iconUrl(d);
Simon Hunt5cd0e8f2014-10-27 16:18:40 -0700723
Simon Hunt6f376a32014-10-28 12:38:30 -0700724 // debug function to show the modelled x,y coordinates of nodes...
725 if (debug('showNodeXY')) {
726 node.select('circle').attr('fill-opacity', 0.5);
727 node.append('circle')
728 .attr({
729 class: 'debug',
730 cx: 0,
731 cy: 0,
732 r: '3px'
733 });
734 }
735 });
Simon Hunt5cd0e8f2014-10-27 16:18:40 -0700736
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700737 // this function is scheduled to happen soon after the given thread ends
738 setTimeout(function() {
Simon Hunt69a8d212014-10-30 17:57:35 -0700739 var lab = config.labels,
740 portGap = lab.port.gap,
741 midW = portGap + lab.port.width/ 2,
742 midH = portGap + lab.port.height / 2;
743
Simon Hunt6f376a32014-10-28 12:38:30 -0700744 // post process the device nodes, to pad their size to fit the
745 // label text and attach the icon to the right location.
746 network.node.filter('.device').each(function(d) {
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700747 // for every node, recompute size, padding, etc. so text fits
748 var node = d3.select(this),
Simon Hunt5cd0e8f2014-10-27 16:18:40 -0700749 text = node.select('text'),
Simon Hunt69a8d212014-10-30 17:57:35 -0700750 box = adjustRectToFitText(node);
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700751
Simon Hunt5cd0e8f2014-10-27 16:18:40 -0700752 // now make the computed adjustment
Simon Hunt1c5f8b62014-10-22 14:43:01 -0700753 node.select('rect')
Simon Hunt5cd0e8f2014-10-27 16:18:40 -0700754 .attr(box);
Simon Hunt1c5f8b62014-10-22 14:43:01 -0700755
756 node.select('image')
Simon Hunt5cd0e8f2014-10-27 16:18:40 -0700757 .attr('x', box.x + config.icons.xoff)
758 .attr('y', box.y + config.icons.yoff);
Simon Hunt1c219892014-10-22 16:32:39 -0700759
Simon Hunt69a8d212014-10-30 17:57:35 -0700760 var bounds = boundsFromBox(box),
761 portBounds = {
762 x1: bounds.x1 - midW,
763 x2: bounds.x2 + midW,
764 y1: bounds.y1 - midH,
765 y2: bounds.y2 + midH
766 };
Simon Hunt5cd0e8f2014-10-27 16:18:40 -0700767
768 // todo: clean up extent and edge work..
Simon Hunt1c219892014-10-22 16:32:39 -0700769 d.extent = {
770 left: bounds.x1 - lab.marginLR,
771 right: bounds.x2 + lab.marginLR,
772 top: bounds.y1 - lab.marginTB,
773 bottom: bounds.y2 + lab.marginTB
774 };
775
776 d.edge = {
777 left : new geo.LineSegment(bounds.x1, bounds.y1, bounds.x1, bounds.y2),
778 right : new geo.LineSegment(bounds.x2, bounds.y1, bounds.x2, bounds.y2),
779 top : new geo.LineSegment(bounds.x1, bounds.y1, bounds.x2, bounds.y1),
780 bottom : new geo.LineSegment(bounds.x1, bounds.y2, bounds.x2, bounds.y2)
781 };
782
Simon Hunt69a8d212014-10-30 17:57:35 -0700783 d.portEdge = {
784 left : new geo.LineSegment(
785 portBounds.x1, portBounds.y1, portBounds.x1, portBounds.y2
786 ),
787 right : new geo.LineSegment(
788 portBounds.x2, portBounds.y1, portBounds.x2, portBounds.y2
789 ),
790 top : new geo.LineSegment(
791 portBounds.x1, portBounds.y1, portBounds.x2, portBounds.y1
792 ),
793 bottom : new geo.LineSegment(
794 portBounds.x1, portBounds.y2, portBounds.x2, portBounds.y2
795 )
796 };
797
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700798 });
799
800 network.numTicks = 0;
801 network.preventCollisions = false;
802 network.force.start();
Simon Hunt1c219892014-10-22 16:32:39 -0700803 for (var i = 0; i < config.force.ticksWithoutCollisions; i++) {
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700804 network.force.tick();
805 }
806 network.preventCollisions = true;
807 $('#view').css('visibility', 'visible');
808 });
809
Simon Hunt6f376a32014-10-28 12:38:30 -0700810
811 // returns the newly computed bounding box of the rectangle
812 function adjustRectToFitText(n) {
813 var text = n.select('text'),
814 box = text.node().getBBox(),
815 lab = config.labels;
816
Simon Hunt9a16c822014-10-28 16:09:19 -0700817 // not sure why n.data() returns an array of 1 element...
818 var data = n.data()[0];
819
Simon Hunt6f376a32014-10-28 12:38:30 -0700820 text.attr('text-anchor', 'middle')
821 .attr('y', '-0.8em')
822 .attr('x', lab.imgPad/2)
823 ;
824
Simon Hunt6f376a32014-10-28 12:38:30 -0700825 // translate the bbox so that it is centered on [x,y]
826 box.x = -box.width / 2;
827 box.y = -box.height / 2;
828
829 // add padding
830 box.x -= (lab.padLR + lab.imgPad/2);
831 box.width += lab.padLR * 2 + lab.imgPad;
832 box.y -= lab.padTB;
833 box.height += lab.padTB * 2;
834
835 return box;
836 }
837
838 function boundsFromBox(box) {
839 return {
840 x1: box.x,
841 y1: box.y,
842 x2: box.x + box.width,
843 y2: box.y + box.height
844 };
845 }
846
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700847 }
848
Simon Hunt68ae6652014-10-22 13:58:07 -0700849 function iconUrl(d) {
Thomas Vachuska1de66012014-10-30 03:03:30 -0700850 return 'img/' + d.type + '.png';
851// return config.iconUrl[d.icon];
Simon Hunt68ae6652014-10-22 13:58:07 -0700852 }
853
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700854 function translate(x, y) {
855 return 'translate(' + x + ',' + y + ')';
856 }
857
Simon Hunt6f376a32014-10-28 12:38:30 -0700858 // prevents collisions amongst device nodes
Simon Hunt1c219892014-10-22 16:32:39 -0700859 function preventCollisions() {
Simon Hunt6f376a32014-10-28 12:38:30 -0700860 var quadtree = d3.geom.quadtree(network.nodes),
861 hrad = config.hostRadius;
Simon Hunt1c219892014-10-22 16:32:39 -0700862
863 network.nodes.forEach(function(n) {
Simon Hunt6f376a32014-10-28 12:38:30 -0700864 var nx1, nx2, ny1, ny2;
865
866 if (n.class === 'device') {
867 nx1 = n.x + n.extent.left;
868 nx2 = n.x + n.extent.right;
869 ny1 = n.y + n.extent.top;
Simon Hunt1c219892014-10-22 16:32:39 -0700870 ny2 = n.y + n.extent.bottom;
871
Simon Hunt6f376a32014-10-28 12:38:30 -0700872 } else {
873 nx1 = n.x - hrad;
874 nx2 = n.x + hrad;
875 ny1 = n.y - hrad;
876 ny2 = n.y + hrad;
877 }
878
Simon Hunt1c219892014-10-22 16:32:39 -0700879 quadtree.visit(function(quad, x1, y1, x2, y2) {
880 if (quad.point && quad.point !== n) {
Simon Hunt6f376a32014-10-28 12:38:30 -0700881 // check if the rectangles/circles intersect
Simon Hunt1c219892014-10-22 16:32:39 -0700882 var p = quad.point,
Simon Hunt6f376a32014-10-28 12:38:30 -0700883 px1, px2, py1, py2, ix;
884
885 if (p.class === 'device') {
886 px1 = p.x + p.extent.left;
887 px2 = p.x + p.extent.right;
888 py1 = p.y + p.extent.top;
889 py2 = p.y + p.extent.bottom;
890
891 } else {
892 px1 = p.x - hrad;
893 px2 = p.x + hrad;
894 py1 = p.y - hrad;
895 py2 = p.y + hrad;
896 }
897
898 ix = (px1 <= nx2 && nx1 <= px2 && py1 <= ny2 && ny1 <= py2);
899
Simon Hunt1c219892014-10-22 16:32:39 -0700900 if (ix) {
901 var xa1 = nx2 - px1, // shift n left , p right
902 xa2 = px2 - nx1, // shift n right, p left
903 ya1 = ny2 - py1, // shift n up , p down
904 ya2 = py2 - ny1, // shift n down , p up
905 adj = Math.min(xa1, xa2, ya1, ya2);
906
907 if (adj == xa1) {
908 n.x -= adj / 2;
909 p.x += adj / 2;
910 } else if (adj == xa2) {
911 n.x += adj / 2;
912 p.x -= adj / 2;
913 } else if (adj == ya1) {
914 n.y -= adj / 2;
915 p.y += adj / 2;
916 } else if (adj == ya2) {
917 n.y += adj / 2;
918 p.y -= adj / 2;
919 }
920 }
921 return ix;
922 }
923 });
924
925 });
926 }
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700927
928 function tick(e) {
929 network.numTicks++;
930
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700931 if (config.options.layering) {
Simon Hunt68ae6652014-10-22 13:58:07 -0700932 // adjust the y-coord of each node, based on y-pos constraints
933 network.nodes.forEach(function (n) {
934 var z = e.alpha * n.constraint.weight;
935 if (!isNaN(n.constraint.y)) {
936 n.y = (n.constraint.y * z + n.y * (1 - z));
937 }
938 });
939 }
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700940
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700941 if (config.options.collisionPrevention && network.preventCollisions) {
Simon Hunt1c219892014-10-22 16:32:39 -0700942 preventCollisions();
943 }
944
Simon Hunt69a8d212014-10-30 17:57:35 -0700945 var portHalfW = config.labels.port.width / 2,
946 portHalfH = config.labels.port.height / 2;
947
Simon Huntd35961b2014-10-28 08:49:48 -0700948 // clip visualization of links at bounds of nodes...
949 network.link.each(function(d) {
Simon Hunt69a8d212014-10-30 17:57:35 -0700950 var xs = d.source.x,
951 ys = d.source.y,
952 xt = d.target.x,
953 yt = d.target.y,
954 line = new geo.LineSegment(xs, ys, xt, yt),
955 e, ix,
956 exs, eys, ext, eyt,
957 pxs, pys, pxt, pyt;
Simon Hunt1c219892014-10-22 16:32:39 -0700958
Simon Hunt69a8d212014-10-30 17:57:35 -0700959 if (d.class === 'host') {
960 // no adjustment for source end of link, since hosts are dots
961 exs = xs;
962 eys = ys;
963
964 } else {
Simon Huntd35961b2014-10-28 08:49:48 -0700965 for (e in d.source.edge) {
966 ix = line.intersect(d.source.edge[e].offset(xs, ys));
Simon Hunt1c219892014-10-22 16:32:39 -0700967 if (ix.in1 && ix.in2) {
Simon Hunt69a8d212014-10-30 17:57:35 -0700968 exs = ix.x;
969 eys = ix.y;
970
971 // also pick off the port label intersection
972 ix = line.intersect(d.source.portEdge[e].offset(xs, ys));
973 pxs = ix.x;
974 pys = ix.y;
Simon Huntd35961b2014-10-28 08:49:48 -0700975 break;
976 }
977 }
Simon Hunt69a8d212014-10-30 17:57:35 -0700978 }
Simon Huntd35961b2014-10-28 08:49:48 -0700979
Simon Hunt69a8d212014-10-30 17:57:35 -0700980 for (e in d.target.edge) {
981 ix = line.intersect(d.target.edge[e].offset(xt, yt));
982 if (ix.in1 && ix.in2) {
983 ext = ix.x;
984 eyt = ix.y;
985
986 // also pick off the port label intersection
987 ix = line.intersect(d.target.portEdge[e].offset(xt, yt));
988 pxt = ix.x;
989 pyt = ix.y;
990 break;
Simon Hunt1c219892014-10-22 16:32:39 -0700991 }
Simon Hunt69a8d212014-10-30 17:57:35 -0700992 }
Simon Hunt1c219892014-10-22 16:32:39 -0700993
Simon Hunt69a8d212014-10-30 17:57:35 -0700994 // adjust the endpoints of the link's line to match rectangles
Thomas Vachuska5f998492014-10-31 00:46:11 -0700995 var sid = safeId(d.id);
Simon Hunt69a8d212014-10-30 17:57:35 -0700996 d3.select(this)
997 .attr('x1', exs)
998 .attr('y1', eys)
999 .attr('x2', ext)
1000 .attr('y2', eyt);
1001
Thomas Vachuska5f998492014-10-31 00:46:11 -07001002 d3.select('#srcPort-' + sid)
Simon Hunt69a8d212014-10-30 17:57:35 -07001003 .attr('x', pxs - portHalfW)
1004 .attr('y', pys - portHalfH);
1005
Thomas Vachuska5f998492014-10-31 00:46:11 -07001006 d3.select('#tgtPort-' + sid)
Simon Hunt69a8d212014-10-30 17:57:35 -07001007 .attr('x', pxt - portHalfW)
1008 .attr('y', pyt - portHalfH);
1009
Simon Hunt3c29c142014-10-30 21:31:44 -07001010 // TODO: fit label rect to size of port number.
Thomas Vachuska5f998492014-10-31 00:46:11 -07001011 d3.select('#srcText-' + sid)
Simon Hunt3c29c142014-10-30 21:31:44 -07001012 .attr('x', pxs - 5)
1013 .attr('y', pys + 3);
1014
Thomas Vachuska5f998492014-10-31 00:46:11 -07001015 d3.select('#tgtText-' + sid)
Simon Hunt3c29c142014-10-30 21:31:44 -07001016 .attr('x', pxt - 5)
1017 .attr('y', pyt + 3);
1018
Simon Hunt69a8d212014-10-30 17:57:35 -07001019 });
Simon Hunt0b05d4a2014-10-21 21:50:15 -07001020
Simon Huntd35961b2014-10-28 08:49:48 -07001021 // position each node by translating the node (group) by x,y
Simon Hunt0b05d4a2014-10-21 21:50:15 -07001022 network.node
1023 .attr('transform', function(d) {
1024 return translate(d.x, d.y);
1025 });
1026
1027 }
1028
1029 // $('#docs-close').on('click', function() {
1030 // deselectObject();
1031 // return false;
1032 // });
1033
1034 // $(document).on('click', '.select-object', function() {
1035 // var obj = graph.data[$(this).data('name')];
1036 // if (obj) {
1037 // selectObject(obj);
1038 // }
1039 // return false;
1040 // });
1041
Simon Hunt6f376a32014-10-28 12:38:30 -07001042 function findNodeFromData(d) {
1043 var el = null;
1044 network.node.filter('.' + d.class).each(function(n) {
1045 if (n.id === d.id) {
1046 el = d3.select(this);
1047 }
1048 });
1049 return el;
1050 }
1051
Simon Hunt0b05d4a2014-10-21 21:50:15 -07001052 function selectObject(obj, el) {
1053 var node;
1054 if (el) {
1055 node = d3.select(el);
1056 } else {
1057 network.node.each(function(d) {
1058 if (d == obj) {
1059 node = d3.select(el = this);
1060 }
1061 });
1062 }
1063 if (!node) return;
1064
1065 if (node.classed('selected')) {
1066 deselectObject();
Simon Huntc586e212014-10-28 21:24:08 -07001067 flyinPane(null);
Simon Hunt0b05d4a2014-10-21 21:50:15 -07001068 return;
1069 }
1070 deselectObject(false);
1071
1072 selected = {
1073 obj : obj,
1074 el : el
1075 };
1076
Simon Hunt0b05d4a2014-10-21 21:50:15 -07001077 node.classed('selected', true);
Simon Huntc586e212014-10-28 21:24:08 -07001078 flyinPane(obj);
Simon Hunt0b05d4a2014-10-21 21:50:15 -07001079 }
1080
1081 function deselectObject(doResize) {
1082 // Review: logic of 'resize(...)' function.
1083 if (doResize || typeof doResize == 'undefined') {
1084 resize(false);
1085 }
Simon Hunt9a16c822014-10-28 16:09:19 -07001086
Simon Hunt0b05d4a2014-10-21 21:50:15 -07001087 // deselect all nodes in the network...
1088 network.node.classed('selected', false);
1089 selected = {};
Simon Huntc586e212014-10-28 21:24:08 -07001090 flyinPane(null);
1091 }
1092
1093 function flyinPane(obj) {
1094 var pane = d3.select('#flyout'),
Simon Huntcc267562014-10-29 10:22:17 -07001095 url;
Simon Huntc586e212014-10-28 21:24:08 -07001096
1097 if (obj) {
Simon Huntcc267562014-10-29 10:22:17 -07001098 // go get details of the selected object from the server...
Simon Huntb4d9d4c2014-10-30 11:27:23 -07001099 url = detailJsonUrl(obj.id);
Simon Huntcc267562014-10-29 10:22:17 -07001100 d3.json(url, function (err, data) {
1101 if (err) {
1102 alert('Oops! Error reading JSON...\n\n' +
1103 'URL: ' + url + '\n\n' +
1104 'Error: ' + err.message);
1105 return;
1106 }
1107// console.log("JSON data... " + url);
1108// console.log(data);
1109
1110 displayDetails(data, pane);
1111 });
1112
1113 } else {
1114 // hide pane
1115 pane.transition().duration(750)
1116 .style('right', '-320px')
1117 .style('opacity', 0.0);
1118 }
1119 }
1120
1121 function displayDetails(data, pane) {
1122 $('#flyout').empty();
1123
Thomas Vachuska1de66012014-10-30 03:03:30 -07001124 var title = pane.append("h2"),
1125 table = pane.append("table"),
Simon Huntcc267562014-10-29 10:22:17 -07001126 tbody = table.append("tbody");
1127
Thomas Vachuska1de66012014-10-30 03:03:30 -07001128 $('<img src="img/' + data.type + '.png">').appendTo(title);
1129 $('<span>').attr('class', 'icon').text(data.id).appendTo(title);
1130
1131
Simon Huntcc267562014-10-29 10:22:17 -07001132 // TODO: consider using d3 data bind to TR/TD
1133
1134 data.propOrder.forEach(function(p) {
Thomas Vachuska1de66012014-10-30 03:03:30 -07001135 if (p === '-') {
1136 addSep(tbody);
1137 } else {
1138 addProp(tbody, p, data.props[p]);
1139 }
Simon Huntcc267562014-10-29 10:22:17 -07001140 });
1141
Thomas Vachuska1de66012014-10-30 03:03:30 -07001142 function addSep(tbody) {
1143 var tr = tbody.append('tr');
1144 $('<hr>').appendTo(tr.append('td').attr('colspan', 2));
1145 }
1146
Simon Huntcc267562014-10-29 10:22:17 -07001147 function addProp(tbody, label, value) {
1148 var tr = tbody.append('tr');
1149
1150 tr.append('td')
1151 .attr('class', 'label')
1152 .text(label + ' :');
1153
1154 tr.append('td')
1155 .attr('class', 'value')
1156 .text(value);
Simon Huntc586e212014-10-28 21:24:08 -07001157 }
1158
Simon Huntcc267562014-10-29 10:22:17 -07001159 // show pane
Simon Huntc586e212014-10-28 21:24:08 -07001160 pane.transition().duration(750)
Simon Huntcc267562014-10-29 10:22:17 -07001161 .style('right', '20px')
1162 .style('opacity', 1.0);
Simon Hunt0b05d4a2014-10-21 21:50:15 -07001163 }
1164
1165 function highlightObject(obj) {
1166 if (obj) {
1167 if (obj != highlighted) {
1168 // TODO set or clear "inactive" class on nodes, based on criteria
1169 network.node.classed('inactive', function(d) {
1170 // return (obj !== d &&
1171 // d.relation(obj.id));
1172 return (obj !== d);
1173 });
1174 // TODO: same with links
1175 network.link.classed('inactive', function(d) {
1176 return (obj !== d.source && obj !== d.target);
1177 });
1178 }
1179 highlighted = obj;
1180 } else {
1181 if (highlighted) {
1182 // clear the inactive flag (no longer suppressed visually)
1183 network.node.classed('inactive', false);
1184 network.link.classed('inactive', false);
1185 }
1186 highlighted = null;
1187
1188 }
1189 }
1190
Simon Hunt9a16c822014-10-28 16:09:19 -07001191 function hoverObject(obj) {
1192 if (obj) {
1193 hovered = obj;
1194 } else {
1195 if (hovered) {
1196 hovered = null;
1197 }
1198 }
1199 }
1200
1201
Simon Huntc586e212014-10-28 21:24:08 -07001202 function resize() {
Simon Hunt0b05d4a2014-10-21 21:50:15 -07001203 view.height = window.innerHeight - config.mastHeight;
1204 view.width = window.innerWidth;
1205 $('#view')
1206 .css('height', view.height + 'px')
1207 .css('width', view.width + 'px');
1208
1209 network.forceWidth = view.width - config.force.marginLR;
1210 network.forceHeight = view.height - config.force.marginTB;
1211 }
1212
1213 // ======================================================================
1214 // register with the UI framework
1215
1216 api.addView('network', {
1217 load: loadNetworkView
1218 });
1219
1220
1221}(ONOS));
1222