blob: 9ac72416db3b9318aba67b91f176d1909a8d6222 [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 Huntb4d9d4c2014-10-30 11:27:23 -070031 useLiveData: true,
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);
214 }
215
216 function showPacketLayer() {
217 network.node.each(function(d) {
218 // deactivate nodes that are not hosts or switches
219 if (d.class === 'device' && d.type !== 'switch') {
220 d3.select(this).classed('inactive', true);
221 }
222 });
223
224 network.link.each(function(lnk) {
225 // deactivate infrastructure links that have opt's as endpoints
226 if (lnk.source.type === 'roadm' || lnk.target.type === 'roadm') {
227 d3.select(this).classed('inactive', true);
228 }
229 });
230 }
231
232 function showOpticalLayer() {
233 network.node.each(function(d) {
234 // deactivate nodes that are not optical devices
235 if (d.type !== 'roadm') {
236 d3.select(this).classed('inactive', true);
237 }
238 });
239
240 network.link.each(function(lnk) {
241 // deactivate infrastructure links that have opt's as endpoints
242 if (lnk.source.type !== 'roadm' || lnk.target.type !== 'roadm') {
243 d3.select(this).classed('inactive', true);
Simon Hunt5cd0e8f2014-10-27 16:18:40 -0700244 }
245 });
246 }
247
Simon Huntd35961b2014-10-28 08:49:48 -0700248 function setUpKeyHandler() {
249 d3.select('body')
250 .on('keydown', function () {
251 processKeyEvent();
252 if (debug('showKeyHandler')) {
253 network.svg.append('text')
254 .attr('x', 5)
255 .attr('y', 15)
256 .style('font-size', '20pt')
257 .text('keyCode: ' + d3.event.keyCode +
258 ' applied to : ' + contextLabel())
259 .transition().duration(2000)
260 .style('font-size', '2pt')
261 .style('fill-opacity', 0.01)
262 .remove();
263 }
264 });
265 }
266
Simon Hunt9a16c822014-10-28 16:09:19 -0700267 function contextLabel() {
268 return hovered === null ? "(nothing)" : hovered.id;
269 }
270
Simon Hunt5cd0e8f2014-10-27 16:18:40 -0700271 function radioButton(group, id) {
272 d3.selectAll("#" + group + " .radio").classed("active", false);
273 d3.select("#" + group + " #" + id).classed("active", true);
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700274 }
275
Simon Huntd35961b2014-10-28 08:49:48 -0700276 function processKeyEvent() {
277 var code = d3.event.keyCode;
278 switch (code) {
Simon Huntee66e612014-10-30 14:56:31 -0700279 case 66: // B
280 toggleBackground();
281 break;
Thomas Vachuska1de66012014-10-30 03:03:30 -0700282 case 71: // G
283 cycleLayout();
284 break;
Simon Huntd35961b2014-10-28 08:49:48 -0700285 case 76: // L
286 cycleLabels();
287 break;
288 case 80: // P
289 togglePorts();
Simon Hunt9a16c822014-10-28 16:09:19 -0700290 break;
291 case 85: // U
292 unpin();
293 break;
Simon Huntd35961b2014-10-28 08:49:48 -0700294 }
295
296 }
297
Simon Huntee66e612014-10-30 14:56:31 -0700298 function toggleBackground() {
299 var bg = d3.select('#bg'),
300 vis = bg.style('visibility'),
301 newvis = (vis === 'hidden') ? 'visible' : 'hidden';
302 bg.style('visibility', newvis);
303 }
304
Thomas Vachuska1de66012014-10-30 03:03:30 -0700305 function cycleLayout() {
306 config.options.layering = !config.options.layering;
307 network.force.resume();
308 }
309
Simon Huntd35961b2014-10-28 08:49:48 -0700310 function cycleLabels() {
Simon Hunt9a16c822014-10-28 16:09:19 -0700311 console.log('Cycle Labels - context = ' + contextLabel());
Simon Huntd35961b2014-10-28 08:49:48 -0700312 }
313
314 function togglePorts() {
Simon Hunt69a8d212014-10-30 17:57:35 -0700315 portLabelsOn = !portLabelsOn;
316 var portVis = portLabelsOn ? 'visible' : 'hidden';
317 d3.selectAll('.port').style('visibility', portVis);
Simon Hunt3c29c142014-10-30 21:31:44 -0700318 d3.selectAll('.portText').style('visibility', portVis);
Simon Hunt9a16c822014-10-28 16:09:19 -0700319 }
320
321 function unpin() {
322 if (hovered) {
323 hovered.fixed = false;
Simon Huntf967d512014-10-28 20:34:29 -0700324 findNodeFromData(hovered).classed('fixed', false);
Simon Hunt9a16c822014-10-28 16:09:19 -0700325 network.force.resume();
326 }
327 console.log('Unpin - context = ' + contextLabel());
Simon Huntd35961b2014-10-28 08:49:48 -0700328 }
329
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700330
331 // ========================================================
332
333 function drawNetwork() {
334 $('#view').empty();
335
336 prepareNodesAndLinks();
337 createLayout();
338 console.log("\n\nHere is the augmented network object...");
Simon Hunt9a16c822014-10-28 16:09:19 -0700339 console.log(network);
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700340 }
341
342 function prepareNodesAndLinks() {
343 network.lookup = {};
344 network.nodes = [];
345 network.links = [];
346
347 var nw = network.forceWidth,
348 nh = network.forceHeight;
349
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700350 function yPosConstraintForNode(n) {
351 return config.constraints.ypos[n.type || 'host'];
352 }
353
354 // Note that both 'devices' and 'hosts' get mapped into the nodes array
355
356 // first, the devices...
357 network.data.devices.forEach(function(n) {
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700358 var ypc = yPosConstraintForNode(n),
Simon Hunt3ab76a82014-10-22 13:07:32 -0700359 ix = Math.random() * 0.6 * nw + 0.2 * nw,
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700360 iy = ypc * nh,
361 node = {
Simon Hunt69a8d212014-10-30 17:57:35 -0700362 id: safeId(n.id),
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700363 labels: n.labels,
364 class: 'device',
365 icon: 'device',
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700366 type: n.type,
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700367 x: ix,
368 y: iy,
369 constraint: {
370 weight: 0.7,
371 y: iy
372 }
373 };
374 network.lookup[n.id] = node;
375 network.nodes.push(node);
376 });
377
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700378 // then, the hosts...
379 network.data.hosts.forEach(function(n) {
380 var ypc = yPosConstraintForNode(n),
381 ix = Math.random() * 0.6 * nw + 0.2 * nw,
382 iy = ypc * nh,
383 node = {
Simon Hunt69a8d212014-10-30 17:57:35 -0700384 id: safeId(n.id),
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700385 labels: n.labels,
386 class: 'host',
387 icon: 'host',
388 type: n.type,
389 x: ix,
390 y: iy,
391 constraint: {
392 weight: 0.7,
393 y: iy
394 }
395 };
396 network.lookup[n.id] = node;
397 network.nodes.push(node);
398 });
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700399
400
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700401 // now, process the explicit links...
Simon Hunt6f376a32014-10-28 12:38:30 -0700402 network.data.links.forEach(function(lnk) {
403 var src = network.lookup[lnk.src],
404 dst = network.lookup[lnk.dst],
Simon Hunt69a8d212014-10-30 17:57:35 -0700405 id = src.id + "-" + dst.id;
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700406
407 var link = {
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700408 class: 'infra',
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700409 id: id,
Simon Hunt6f376a32014-10-28 12:38:30 -0700410 type: lnk.type,
411 width: lnk.linkWidth,
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700412 source: src,
Simon Hunt69a8d212014-10-30 17:57:35 -0700413 srcPort: lnk.srcPort,
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700414 target: dst,
Simon Hunt69a8d212014-10-30 17:57:35 -0700415 tgtPort: lnk.dstPort,
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700416 strength: config.force.linkStrength.infra
417 };
418 network.links.push(link);
419 });
420
421 // finally, infer host links...
422 network.data.hosts.forEach(function(n) {
423 var src = network.lookup[n.id],
424 dst = network.lookup[n.cp.device],
Simon Hunt69a8d212014-10-30 17:57:35 -0700425 id = src.id + "-" + dst.id;
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700426
427 var link = {
428 class: 'host',
429 id: id,
430 type: 'hostLink',
431 width: config.hostLinkWidth,
432 source: src,
433 target: dst,
434 strength: config.force.linkStrength.host
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700435 };
436 network.links.push(link);
437 });
438 }
439
440 function createLayout() {
441
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700442 var cfg = config.force;
443
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700444 network.force = d3.layout.force()
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700445 .size([network.forceWidth, network.forceHeight])
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700446 .nodes(network.nodes)
447 .links(network.links)
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700448 .linkStrength(function(d) { return cfg.linkStrength[d.class]; })
449 .linkDistance(function(d) { return cfg.linkDistance[d.class]; })
450 .charge(function(d) { return cfg.charge[d.class]; })
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700451 .on('tick', tick);
452
453 network.svg = d3.select('#view').append('svg')
454 .attr('width', view.width)
455 .attr('height', view.height)
456 .append('g')
Simon Huntae968a62014-10-22 14:54:41 -0700457 .attr('transform', config.force.translate());
Simon Hunt3ab76a82014-10-22 13:07:32 -0700458// .attr('id', 'zoomable')
Simon Hunt3ab76a82014-10-22 13:07:32 -0700459// .call(d3.behavior.zoom().on("zoom", zoomRedraw));
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700460
Thomas Vachuska8cd66a52014-10-30 11:53:07 -0700461 network.svg.append('svg:image')
462 .attr({
463 id: 'bg',
464 width: view.width,
465 height: view.height,
Simon Huntee66e612014-10-30 14:56:31 -0700466 'xlink:href': config.backgroundUrl
467 })
468 .style('visibility',
469 config.options.loadBackground ? 'visible' : 'hidden');
Thomas Vachuska8cd66a52014-10-30 11:53:07 -0700470
Simon Hunt3ab76a82014-10-22 13:07:32 -0700471// function zoomRedraw() {
472// d3.select("#zoomable").attr("transform",
473// "translate(" + d3.event.translate + ")"
474// + " scale(" + d3.event.scale + ")");
475// }
476
Simon Hunt3ab76a82014-10-22 13:07:32 -0700477 // TODO: move glow/blur stuff to util script
478 var glow = network.svg.append('filter')
479 .attr('x', '-50%')
480 .attr('y', '-50%')
481 .attr('width', '200%')
482 .attr('height', '200%')
483 .attr('id', 'blue-glow');
484
485 glow.append('feColorMatrix')
486 .attr('type', 'matrix')
487 .attr('values', '0 0 0 0 0 ' +
488 '0 0 0 0 0 ' +
489 '0 0 0 0 .7 ' +
490 '0 0 0 1 0 ');
491
492 glow.append('feGaussianBlur')
493 .attr('stdDeviation', 3)
494 .attr('result', 'coloredBlur');
495
496 glow.append('feMerge').selectAll('feMergeNode')
497 .data(['coloredBlur', 'SourceGraphic'])
498 .enter().append('feMergeNode')
499 .attr('in', String);
500
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700501 // TODO: legend (and auto adjust on scroll)
Simon Hunt3ab76a82014-10-22 13:07:32 -0700502// $('#view').on('scroll', function() {
503//
504// });
505
506
Simon Hunt6f376a32014-10-28 12:38:30 -0700507 // TODO: move drag behavior into separate method.
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700508 // == define node drag behavior...
Simon Hunt3ab76a82014-10-22 13:07:32 -0700509 network.draggedThreshold = d3.scale.linear()
510 .domain([0, 0.1])
511 .range([5, 20])
512 .clamp(true);
513
514 function dragged(d) {
515 var threshold = network.draggedThreshold(network.force.alpha()),
516 dx = d.oldX - d.px,
517 dy = d.oldY - d.py;
518 if (Math.abs(dx) >= threshold || Math.abs(dy) >= threshold) {
519 d.dragged = true;
520 }
521 return d.dragged;
522 }
523
524 network.drag = d3.behavior.drag()
525 .origin(function(d) { return d; })
526 .on('dragstart', function(d) {
527 d.oldX = d.x;
528 d.oldY = d.y;
529 d.dragged = false;
530 d.fixed |= 2;
531 })
532 .on('drag', function(d) {
533 d.px = d3.event.x;
534 d.py = d3.event.y;
535 if (dragged(d)) {
536 if (!network.force.alpha()) {
537 network.force.alpha(.025);
538 }
539 }
540 })
541 .on('dragend', function(d) {
542 if (!dragged(d)) {
543 selectObject(d, this);
544 }
545 d.fixed &= ~6;
Simon Hunt9a16c822014-10-28 16:09:19 -0700546
547 // once we've finished moving, pin the node in position,
Simon Huntf967d512014-10-28 20:34:29 -0700548 // if it is a device (not a host)
Simon Hunt9a16c822014-10-28 16:09:19 -0700549 if (d.class === 'device') {
550 d.fixed = true;
Simon Huntf967d512014-10-28 20:34:29 -0700551 d3.select(this).classed('fixed', true)
Simon Hunt9a16c822014-10-28 16:09:19 -0700552 }
Simon Hunt3ab76a82014-10-22 13:07:32 -0700553 });
554
555 $('#view').on('click', function(e) {
556 if (!$(e.target).closest('.node').length) {
557 deselectObject();
558 }
559 });
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700560
Simon Hunt69a8d212014-10-30 17:57:35 -0700561 // ...............................................................
562
563 // add links to the display
564 network.link = network.svg.append('g').attr('id', 'links')
565 .selectAll('.link')
566 .data(network.force.links(), function(d) {return d.id})
567 .enter().append('line')
568 .attr('class', function(d) {return 'link ' + d.class});
569
570 network.linkSrcPort = network.svg.append('g')
571 .attr({
572 id: 'srcPorts',
573 class: 'portLayer'
574 });
575 network.linkTgtPort = network.svg.append('g')
576 .attr({
577 id: 'tgtPorts',
578 class: 'portLayer'
579 });
580
Simon Hunt3c29c142014-10-30 21:31:44 -0700581 var portVis = portLabelsOn ? 'visible' : 'hidden',
582 pw = config.labels.port.width,
583 ph = config.labels.port.height;
Simon Hunt69a8d212014-10-30 17:57:35 -0700584
585 network.link.filter('.infra').each(function(d, i) {
586 network.linkSrcPort.append('rect').attr({
587 id: 'srcPort-' + d.id,
588 class: 'port',
Simon Hunt3c29c142014-10-30 21:31:44 -0700589 width: pw,
590 height: ph,
Simon Hunt69a8d212014-10-30 17:57:35 -0700591 x: i * 20,
592 y: 0
593 })
Simon Hunt3c29c142014-10-30 21:31:44 -0700594 .style('visibility', portVis);
Simon Hunt69a8d212014-10-30 17:57:35 -0700595
596 network.linkTgtPort.append('rect').attr({
597 id: 'tgtPort-' + d.id,
598 class: 'port',
Simon Hunt3c29c142014-10-30 21:31:44 -0700599 width: pw,
600 height: ph,
Simon Hunt69a8d212014-10-30 17:57:35 -0700601 x: i * 20,
602 y: 20
603 })
604 .style('visibility', portVis);
605
Simon Hunt3c29c142014-10-30 21:31:44 -0700606 network.linkSrcPort.append('text').attr({
607 id: 'srcText-' + d.id,
608 class: 'portText',
609 x: i * 20,
610 y:0
611 }).text(d.srcPort)
612 .style('visibility', portVis);
613
614 network.linkTgtPort.append('text').attr({
615 id: 'tgtText-' + d.id,
616 class: 'portText',
617 x: i * 20,
618 y:20
619 }).text(d.tgtPort)
620 .style('visibility', portVis);
Simon Hunt69a8d212014-10-30 17:57:35 -0700621 });
622
623 // ...............................................................
Simon Hunt1c5f8b62014-10-22 14:43:01 -0700624
Simon Hunt5cd0e8f2014-10-27 16:18:40 -0700625 // add nodes to the display
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700626 network.node = network.svg.selectAll('.node')
627 .data(network.force.nodes(), function(d) {return d.id})
628 .enter().append('g')
Simon Hunt3ab76a82014-10-22 13:07:32 -0700629 .attr('class', function(d) {
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700630 var cls = 'node ' + d.class;
631 if (d.type) {
632 cls += ' ' + d.type;
633 }
634 return cls;
Simon Hunt3ab76a82014-10-22 13:07:32 -0700635 })
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700636 .attr('transform', function(d) {
637 return translate(d.x, d.y);
638 })
Simon Hunt3ab76a82014-10-22 13:07:32 -0700639 .call(network.drag)
640 .on('mouseover', function(d) {
Simon Hunt6f376a32014-10-28 12:38:30 -0700641 // TODO: show tooltip
Simon Hunt9a16c822014-10-28 16:09:19 -0700642 if (network.mouseoutTimeout) {
643 clearTimeout(network.mouseoutTimeout);
644 network.mouseoutTimeout = null;
Simon Hunt3ab76a82014-10-22 13:07:32 -0700645 }
Simon Hunt9a16c822014-10-28 16:09:19 -0700646 hoverObject(d);
Simon Hunt3ab76a82014-10-22 13:07:32 -0700647 })
648 .on('mouseout', function(d) {
Simon Hunt6f376a32014-10-28 12:38:30 -0700649 // TODO: hide tooltip
Simon Hunt9a16c822014-10-28 16:09:19 -0700650 if (network.mouseoutTimeout) {
651 clearTimeout(network.mouseoutTimeout);
652 network.mouseoutTimeout = null;
Simon Hunt3ab76a82014-10-22 13:07:32 -0700653 }
Simon Hunt9a16c822014-10-28 16:09:19 -0700654 network.mouseoutTimeout = setTimeout(function() {
655 hoverObject(null);
656 }, config.mouseOutTimerDelayMs);
Simon Hunt3ab76a82014-10-22 13:07:32 -0700657 });
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700658
Simon Hunt6f376a32014-10-28 12:38:30 -0700659
660 // deal with device nodes first
661 network.nodeRect = network.node.filter('.device')
662 .append('rect')
Simon Hunt5cd0e8f2014-10-27 16:18:40 -0700663 .attr({
664 rx: 5,
665 ry: 5,
666 width: 100,
667 height: 12
668 });
669 // note that width/height are adjusted to fit the label text
Simon Hunt6f376a32014-10-28 12:38:30 -0700670 // then padded, and space made for the icon.
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700671
Simon Hunt6f376a32014-10-28 12:38:30 -0700672 network.node.filter('.device').each(function(d) {
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700673 var node = d3.select(this),
Simon Hunt5cd0e8f2014-10-27 16:18:40 -0700674 icon = iconUrl(d);
675
676 node.append('text')
677 // TODO: add label cycle behavior
678 .text(d.id)
679 .attr('dy', '1.1em');
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700680
681 if (icon) {
682 var cfg = config.icons;
683 node.append('svg:image')
Simon Hunt5cd0e8f2014-10-27 16:18:40 -0700684 .attr({
685 width: cfg.w,
686 height: cfg.h,
687 'xlink:href': icon
688 });
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700689 // note, icon relative positioning (x,y) is done after we have
690 // adjusted the bounds of the rectangle...
691 }
Simon Hunt68ae6652014-10-22 13:58:07 -0700692
Simon Huntd35961b2014-10-28 08:49:48 -0700693 // debug function to show the modelled x,y coordinates of nodes...
694 if (debug('showNodeXY')) {
695 node.select('rect').attr('fill-opacity', 0.5);
Simon Hunt5cd0e8f2014-10-27 16:18:40 -0700696 node.append('circle')
697 .attr({
698 class: 'debug',
699 cx: 0,
700 cy: 0,
701 r: '3px'
702 });
703 }
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700704 });
705
Simon Hunt6f376a32014-10-28 12:38:30 -0700706 // now process host nodes
707 network.nodeCircle = network.node.filter('.host')
708 .append('circle')
709 .attr({
710 r: config.hostRadius
711 });
Simon Hunt5cd0e8f2014-10-27 16:18:40 -0700712
Simon Hunt6f376a32014-10-28 12:38:30 -0700713 network.node.filter('.host').each(function(d) {
714 var node = d3.select(this),
715 icon = iconUrl(d);
Simon Hunt5cd0e8f2014-10-27 16:18:40 -0700716
Simon Hunt6f376a32014-10-28 12:38:30 -0700717 // debug function to show the modelled x,y coordinates of nodes...
718 if (debug('showNodeXY')) {
719 node.select('circle').attr('fill-opacity', 0.5);
720 node.append('circle')
721 .attr({
722 class: 'debug',
723 cx: 0,
724 cy: 0,
725 r: '3px'
726 });
727 }
728 });
Simon Hunt5cd0e8f2014-10-27 16:18:40 -0700729
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700730 // this function is scheduled to happen soon after the given thread ends
731 setTimeout(function() {
Simon Hunt69a8d212014-10-30 17:57:35 -0700732 var lab = config.labels,
733 portGap = lab.port.gap,
734 midW = portGap + lab.port.width/ 2,
735 midH = portGap + lab.port.height / 2;
736
Simon Hunt6f376a32014-10-28 12:38:30 -0700737 // post process the device nodes, to pad their size to fit the
738 // label text and attach the icon to the right location.
739 network.node.filter('.device').each(function(d) {
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700740 // for every node, recompute size, padding, etc. so text fits
741 var node = d3.select(this),
Simon Hunt5cd0e8f2014-10-27 16:18:40 -0700742 text = node.select('text'),
Simon Hunt69a8d212014-10-30 17:57:35 -0700743 box = adjustRectToFitText(node);
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700744
Simon Hunt5cd0e8f2014-10-27 16:18:40 -0700745 // now make the computed adjustment
Simon Hunt1c5f8b62014-10-22 14:43:01 -0700746 node.select('rect')
Simon Hunt5cd0e8f2014-10-27 16:18:40 -0700747 .attr(box);
Simon Hunt1c5f8b62014-10-22 14:43:01 -0700748
749 node.select('image')
Simon Hunt5cd0e8f2014-10-27 16:18:40 -0700750 .attr('x', box.x + config.icons.xoff)
751 .attr('y', box.y + config.icons.yoff);
Simon Hunt1c219892014-10-22 16:32:39 -0700752
Simon Hunt69a8d212014-10-30 17:57:35 -0700753 var bounds = boundsFromBox(box),
754 portBounds = {
755 x1: bounds.x1 - midW,
756 x2: bounds.x2 + midW,
757 y1: bounds.y1 - midH,
758 y2: bounds.y2 + midH
759 };
Simon Hunt5cd0e8f2014-10-27 16:18:40 -0700760
761 // todo: clean up extent and edge work..
Simon Hunt1c219892014-10-22 16:32:39 -0700762 d.extent = {
763 left: bounds.x1 - lab.marginLR,
764 right: bounds.x2 + lab.marginLR,
765 top: bounds.y1 - lab.marginTB,
766 bottom: bounds.y2 + lab.marginTB
767 };
768
769 d.edge = {
770 left : new geo.LineSegment(bounds.x1, bounds.y1, bounds.x1, bounds.y2),
771 right : new geo.LineSegment(bounds.x2, bounds.y1, bounds.x2, bounds.y2),
772 top : new geo.LineSegment(bounds.x1, bounds.y1, bounds.x2, bounds.y1),
773 bottom : new geo.LineSegment(bounds.x1, bounds.y2, bounds.x2, bounds.y2)
774 };
775
Simon Hunt69a8d212014-10-30 17:57:35 -0700776 d.portEdge = {
777 left : new geo.LineSegment(
778 portBounds.x1, portBounds.y1, portBounds.x1, portBounds.y2
779 ),
780 right : new geo.LineSegment(
781 portBounds.x2, portBounds.y1, portBounds.x2, portBounds.y2
782 ),
783 top : new geo.LineSegment(
784 portBounds.x1, portBounds.y1, portBounds.x2, portBounds.y1
785 ),
786 bottom : new geo.LineSegment(
787 portBounds.x1, portBounds.y2, portBounds.x2, portBounds.y2
788 )
789 };
790
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700791 });
792
793 network.numTicks = 0;
794 network.preventCollisions = false;
795 network.force.start();
Simon Hunt1c219892014-10-22 16:32:39 -0700796 for (var i = 0; i < config.force.ticksWithoutCollisions; i++) {
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700797 network.force.tick();
798 }
799 network.preventCollisions = true;
800 $('#view').css('visibility', 'visible');
801 });
802
Simon Hunt6f376a32014-10-28 12:38:30 -0700803
804 // returns the newly computed bounding box of the rectangle
805 function adjustRectToFitText(n) {
806 var text = n.select('text'),
807 box = text.node().getBBox(),
808 lab = config.labels;
809
Simon Hunt9a16c822014-10-28 16:09:19 -0700810 // not sure why n.data() returns an array of 1 element...
811 var data = n.data()[0];
812
Simon Hunt6f376a32014-10-28 12:38:30 -0700813 text.attr('text-anchor', 'middle')
814 .attr('y', '-0.8em')
815 .attr('x', lab.imgPad/2)
816 ;
817
Simon Hunt6f376a32014-10-28 12:38:30 -0700818 // translate the bbox so that it is centered on [x,y]
819 box.x = -box.width / 2;
820 box.y = -box.height / 2;
821
822 // add padding
823 box.x -= (lab.padLR + lab.imgPad/2);
824 box.width += lab.padLR * 2 + lab.imgPad;
825 box.y -= lab.padTB;
826 box.height += lab.padTB * 2;
827
828 return box;
829 }
830
831 function boundsFromBox(box) {
832 return {
833 x1: box.x,
834 y1: box.y,
835 x2: box.x + box.width,
836 y2: box.y + box.height
837 };
838 }
839
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700840 }
841
Simon Hunt68ae6652014-10-22 13:58:07 -0700842 function iconUrl(d) {
Thomas Vachuska1de66012014-10-30 03:03:30 -0700843 return 'img/' + d.type + '.png';
844// return config.iconUrl[d.icon];
Simon Hunt68ae6652014-10-22 13:58:07 -0700845 }
846
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700847 function translate(x, y) {
848 return 'translate(' + x + ',' + y + ')';
849 }
850
Simon Hunt6f376a32014-10-28 12:38:30 -0700851 // prevents collisions amongst device nodes
Simon Hunt1c219892014-10-22 16:32:39 -0700852 function preventCollisions() {
Simon Hunt6f376a32014-10-28 12:38:30 -0700853 var quadtree = d3.geom.quadtree(network.nodes),
854 hrad = config.hostRadius;
Simon Hunt1c219892014-10-22 16:32:39 -0700855
856 network.nodes.forEach(function(n) {
Simon Hunt6f376a32014-10-28 12:38:30 -0700857 var nx1, nx2, ny1, ny2;
858
859 if (n.class === 'device') {
860 nx1 = n.x + n.extent.left;
861 nx2 = n.x + n.extent.right;
862 ny1 = n.y + n.extent.top;
Simon Hunt1c219892014-10-22 16:32:39 -0700863 ny2 = n.y + n.extent.bottom;
864
Simon Hunt6f376a32014-10-28 12:38:30 -0700865 } else {
866 nx1 = n.x - hrad;
867 nx2 = n.x + hrad;
868 ny1 = n.y - hrad;
869 ny2 = n.y + hrad;
870 }
871
Simon Hunt1c219892014-10-22 16:32:39 -0700872 quadtree.visit(function(quad, x1, y1, x2, y2) {
873 if (quad.point && quad.point !== n) {
Simon Hunt6f376a32014-10-28 12:38:30 -0700874 // check if the rectangles/circles intersect
Simon Hunt1c219892014-10-22 16:32:39 -0700875 var p = quad.point,
Simon Hunt6f376a32014-10-28 12:38:30 -0700876 px1, px2, py1, py2, ix;
877
878 if (p.class === 'device') {
879 px1 = p.x + p.extent.left;
880 px2 = p.x + p.extent.right;
881 py1 = p.y + p.extent.top;
882 py2 = p.y + p.extent.bottom;
883
884 } else {
885 px1 = p.x - hrad;
886 px2 = p.x + hrad;
887 py1 = p.y - hrad;
888 py2 = p.y + hrad;
889 }
890
891 ix = (px1 <= nx2 && nx1 <= px2 && py1 <= ny2 && ny1 <= py2);
892
Simon Hunt1c219892014-10-22 16:32:39 -0700893 if (ix) {
894 var xa1 = nx2 - px1, // shift n left , p right
895 xa2 = px2 - nx1, // shift n right, p left
896 ya1 = ny2 - py1, // shift n up , p down
897 ya2 = py2 - ny1, // shift n down , p up
898 adj = Math.min(xa1, xa2, ya1, ya2);
899
900 if (adj == xa1) {
901 n.x -= adj / 2;
902 p.x += adj / 2;
903 } else if (adj == xa2) {
904 n.x += adj / 2;
905 p.x -= adj / 2;
906 } else if (adj == ya1) {
907 n.y -= adj / 2;
908 p.y += adj / 2;
909 } else if (adj == ya2) {
910 n.y += adj / 2;
911 p.y -= adj / 2;
912 }
913 }
914 return ix;
915 }
916 });
917
918 });
919 }
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700920
921 function tick(e) {
922 network.numTicks++;
923
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700924 if (config.options.layering) {
Simon Hunt68ae6652014-10-22 13:58:07 -0700925 // adjust the y-coord of each node, based on y-pos constraints
926 network.nodes.forEach(function (n) {
927 var z = e.alpha * n.constraint.weight;
928 if (!isNaN(n.constraint.y)) {
929 n.y = (n.constraint.y * z + n.y * (1 - z));
930 }
931 });
932 }
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700933
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700934 if (config.options.collisionPrevention && network.preventCollisions) {
Simon Hunt1c219892014-10-22 16:32:39 -0700935 preventCollisions();
936 }
937
Simon Hunt69a8d212014-10-30 17:57:35 -0700938 var portHalfW = config.labels.port.width / 2,
939 portHalfH = config.labels.port.height / 2;
940
Simon Huntd35961b2014-10-28 08:49:48 -0700941 // clip visualization of links at bounds of nodes...
942 network.link.each(function(d) {
Simon Hunt69a8d212014-10-30 17:57:35 -0700943 var xs = d.source.x,
944 ys = d.source.y,
945 xt = d.target.x,
946 yt = d.target.y,
947 line = new geo.LineSegment(xs, ys, xt, yt),
948 e, ix,
949 exs, eys, ext, eyt,
950 pxs, pys, pxt, pyt;
Simon Hunt1c219892014-10-22 16:32:39 -0700951
Simon Hunt69a8d212014-10-30 17:57:35 -0700952 if (d.class === 'host') {
953 // no adjustment for source end of link, since hosts are dots
954 exs = xs;
955 eys = ys;
956
957 } else {
Simon Huntd35961b2014-10-28 08:49:48 -0700958 for (e in d.source.edge) {
959 ix = line.intersect(d.source.edge[e].offset(xs, ys));
Simon Hunt1c219892014-10-22 16:32:39 -0700960 if (ix.in1 && ix.in2) {
Simon Hunt69a8d212014-10-30 17:57:35 -0700961 exs = ix.x;
962 eys = ix.y;
963
964 // also pick off the port label intersection
965 ix = line.intersect(d.source.portEdge[e].offset(xs, ys));
966 pxs = ix.x;
967 pys = ix.y;
Simon Huntd35961b2014-10-28 08:49:48 -0700968 break;
969 }
970 }
Simon Hunt69a8d212014-10-30 17:57:35 -0700971 }
Simon Huntd35961b2014-10-28 08:49:48 -0700972
Simon Hunt69a8d212014-10-30 17:57:35 -0700973 for (e in d.target.edge) {
974 ix = line.intersect(d.target.edge[e].offset(xt, yt));
975 if (ix.in1 && ix.in2) {
976 ext = ix.x;
977 eyt = ix.y;
978
979 // also pick off the port label intersection
980 ix = line.intersect(d.target.portEdge[e].offset(xt, yt));
981 pxt = ix.x;
982 pyt = ix.y;
983 break;
Simon Hunt1c219892014-10-22 16:32:39 -0700984 }
Simon Hunt69a8d212014-10-30 17:57:35 -0700985 }
Simon Hunt1c219892014-10-22 16:32:39 -0700986
Simon Hunt69a8d212014-10-30 17:57:35 -0700987 // adjust the endpoints of the link's line to match rectangles
988 d3.select(this)
989 .attr('x1', exs)
990 .attr('y1', eys)
991 .attr('x2', ext)
992 .attr('y2', eyt);
993
994 d3.select('#srcPort-' + d.id)
995 .attr('x', pxs - portHalfW)
996 .attr('y', pys - portHalfH);
997
998 d3.select('#tgtPort-' + d.id)
999 .attr('x', pxt - portHalfW)
1000 .attr('y', pyt - portHalfH);
1001
Simon Hunt3c29c142014-10-30 21:31:44 -07001002 // TODO: fit label rect to size of port number.
1003 d3.select('#srcText-' + d.id)
1004 .attr('x', pxs - 5)
1005 .attr('y', pys + 3);
1006
1007 d3.select('#tgtText-' + d.id)
1008 .attr('x', pxt - 5)
1009 .attr('y', pyt + 3);
1010
Simon Hunt69a8d212014-10-30 17:57:35 -07001011 });
Simon Hunt0b05d4a2014-10-21 21:50:15 -07001012
Simon Huntd35961b2014-10-28 08:49:48 -07001013 // position each node by translating the node (group) by x,y
Simon Hunt0b05d4a2014-10-21 21:50:15 -07001014 network.node
1015 .attr('transform', function(d) {
1016 return translate(d.x, d.y);
1017 });
1018
1019 }
1020
1021 // $('#docs-close').on('click', function() {
1022 // deselectObject();
1023 // return false;
1024 // });
1025
1026 // $(document).on('click', '.select-object', function() {
1027 // var obj = graph.data[$(this).data('name')];
1028 // if (obj) {
1029 // selectObject(obj);
1030 // }
1031 // return false;
1032 // });
1033
Simon Hunt6f376a32014-10-28 12:38:30 -07001034 function findNodeFromData(d) {
1035 var el = null;
1036 network.node.filter('.' + d.class).each(function(n) {
1037 if (n.id === d.id) {
1038 el = d3.select(this);
1039 }
1040 });
1041 return el;
1042 }
1043
Simon Hunt0b05d4a2014-10-21 21:50:15 -07001044 function selectObject(obj, el) {
1045 var node;
1046 if (el) {
1047 node = d3.select(el);
1048 } else {
1049 network.node.each(function(d) {
1050 if (d == obj) {
1051 node = d3.select(el = this);
1052 }
1053 });
1054 }
1055 if (!node) return;
1056
1057 if (node.classed('selected')) {
1058 deselectObject();
Simon Huntc586e212014-10-28 21:24:08 -07001059 flyinPane(null);
Simon Hunt0b05d4a2014-10-21 21:50:15 -07001060 return;
1061 }
1062 deselectObject(false);
1063
1064 selected = {
1065 obj : obj,
1066 el : el
1067 };
1068
Simon Hunt0b05d4a2014-10-21 21:50:15 -07001069 node.classed('selected', true);
Simon Huntc586e212014-10-28 21:24:08 -07001070 flyinPane(obj);
Simon Hunt0b05d4a2014-10-21 21:50:15 -07001071 }
1072
1073 function deselectObject(doResize) {
1074 // Review: logic of 'resize(...)' function.
1075 if (doResize || typeof doResize == 'undefined') {
1076 resize(false);
1077 }
Simon Hunt9a16c822014-10-28 16:09:19 -07001078
Simon Hunt0b05d4a2014-10-21 21:50:15 -07001079 // deselect all nodes in the network...
1080 network.node.classed('selected', false);
1081 selected = {};
Simon Huntc586e212014-10-28 21:24:08 -07001082 flyinPane(null);
1083 }
1084
1085 function flyinPane(obj) {
1086 var pane = d3.select('#flyout'),
Simon Huntcc267562014-10-29 10:22:17 -07001087 url;
Simon Huntc586e212014-10-28 21:24:08 -07001088
1089 if (obj) {
Simon Huntcc267562014-10-29 10:22:17 -07001090 // go get details of the selected object from the server...
Simon Huntb4d9d4c2014-10-30 11:27:23 -07001091 url = detailJsonUrl(obj.id);
Simon Huntcc267562014-10-29 10:22:17 -07001092 d3.json(url, function (err, data) {
1093 if (err) {
1094 alert('Oops! Error reading JSON...\n\n' +
1095 'URL: ' + url + '\n\n' +
1096 'Error: ' + err.message);
1097 return;
1098 }
1099// console.log("JSON data... " + url);
1100// console.log(data);
1101
1102 displayDetails(data, pane);
1103 });
1104
1105 } else {
1106 // hide pane
1107 pane.transition().duration(750)
1108 .style('right', '-320px')
1109 .style('opacity', 0.0);
1110 }
1111 }
1112
1113 function displayDetails(data, pane) {
1114 $('#flyout').empty();
1115
Thomas Vachuska1de66012014-10-30 03:03:30 -07001116 var title = pane.append("h2"),
1117 table = pane.append("table"),
Simon Huntcc267562014-10-29 10:22:17 -07001118 tbody = table.append("tbody");
1119
Thomas Vachuska1de66012014-10-30 03:03:30 -07001120 $('<img src="img/' + data.type + '.png">').appendTo(title);
1121 $('<span>').attr('class', 'icon').text(data.id).appendTo(title);
1122
1123
Simon Huntcc267562014-10-29 10:22:17 -07001124 // TODO: consider using d3 data bind to TR/TD
1125
1126 data.propOrder.forEach(function(p) {
Thomas Vachuska1de66012014-10-30 03:03:30 -07001127 if (p === '-') {
1128 addSep(tbody);
1129 } else {
1130 addProp(tbody, p, data.props[p]);
1131 }
Simon Huntcc267562014-10-29 10:22:17 -07001132 });
1133
Thomas Vachuska1de66012014-10-30 03:03:30 -07001134 function addSep(tbody) {
1135 var tr = tbody.append('tr');
1136 $('<hr>').appendTo(tr.append('td').attr('colspan', 2));
1137 }
1138
Simon Huntcc267562014-10-29 10:22:17 -07001139 function addProp(tbody, label, value) {
1140 var tr = tbody.append('tr');
1141
1142 tr.append('td')
1143 .attr('class', 'label')
1144 .text(label + ' :');
1145
1146 tr.append('td')
1147 .attr('class', 'value')
1148 .text(value);
Simon Huntc586e212014-10-28 21:24:08 -07001149 }
1150
Simon Huntcc267562014-10-29 10:22:17 -07001151 // show pane
Simon Huntc586e212014-10-28 21:24:08 -07001152 pane.transition().duration(750)
Simon Huntcc267562014-10-29 10:22:17 -07001153 .style('right', '20px')
1154 .style('opacity', 1.0);
Simon Hunt0b05d4a2014-10-21 21:50:15 -07001155 }
1156
1157 function highlightObject(obj) {
1158 if (obj) {
1159 if (obj != highlighted) {
1160 // TODO set or clear "inactive" class on nodes, based on criteria
1161 network.node.classed('inactive', function(d) {
1162 // return (obj !== d &&
1163 // d.relation(obj.id));
1164 return (obj !== d);
1165 });
1166 // TODO: same with links
1167 network.link.classed('inactive', function(d) {
1168 return (obj !== d.source && obj !== d.target);
1169 });
1170 }
1171 highlighted = obj;
1172 } else {
1173 if (highlighted) {
1174 // clear the inactive flag (no longer suppressed visually)
1175 network.node.classed('inactive', false);
1176 network.link.classed('inactive', false);
1177 }
1178 highlighted = null;
1179
1180 }
1181 }
1182
Simon Hunt9a16c822014-10-28 16:09:19 -07001183 function hoverObject(obj) {
1184 if (obj) {
1185 hovered = obj;
1186 } else {
1187 if (hovered) {
1188 hovered = null;
1189 }
1190 }
1191 }
1192
1193
Simon Huntc586e212014-10-28 21:24:08 -07001194 function resize() {
Simon Hunt0b05d4a2014-10-21 21:50:15 -07001195 view.height = window.innerHeight - config.mastHeight;
1196 view.width = window.innerWidth;
1197 $('#view')
1198 .css('height', view.height + 'px')
1199 .css('width', view.width + 'px');
1200
1201 network.forceWidth = view.width - config.force.marginLR;
1202 network.forceHeight = view.height - config.force.marginTB;
1203 }
1204
1205 // ======================================================================
1206 // register with the UI framework
1207
1208 api.addView('network', {
1209 load: loadNetworkView
1210 });
1211
1212
1213}(ONOS));
1214