blob: 6cf189986dd8cb927bc86c144b24d7d4f3975b25 [file] [log] [blame]
Simon Hunt0b05d4a2014-10-21 21:50:15 -07001/*
2 ONOS network topology viewer - PoC version 1.0
3
4 @author Simon Hunt
5 */
6
7(function (onos) {
8 'use strict';
9
10 var api = onos.api;
11
12 var config = {
Simon Hunt2c9e0c22014-10-23 15:12:58 -070013 options: {
Simon Hunt19cb0982014-10-23 16:44:49 -070014 layering: true,
Simon Hunt2c9e0c22014-10-23 15:12:58 -070015 collisionPrevention: true
16 },
Thomas Vachuska598924e2014-10-23 22:26:07 -070017 XjsonUrl: 'rs/topology/graph',
Simon Hunt0b05d4a2014-10-21 21:50:15 -070018 jsonUrl: 'network.json',
Simon Hunt68ae6652014-10-22 13:58:07 -070019 iconUrl: {
Simon Hunt2c9e0c22014-10-23 15:12:58 -070020 device: 'img/device.png',
21 host: 'img/host.png',
22 pkt: 'img/pkt.png',
23 opt: 'img/opt.png'
Simon Hunt68ae6652014-10-22 13:58:07 -070024 },
Simon Hunt19cb0982014-10-23 16:44:49 -070025 mastHeight: 36,
Simon Hunt0b05d4a2014-10-21 21:50:15 -070026 force: {
Simon Hunt2c9e0c22014-10-23 15:12:58 -070027 note: 'node.class or link.class is used to differentiate',
28 linkDistance: {
29 infra: 240,
30 host: 100
31 },
32 linkStrength: {
33 infra: 1.0,
34 host: 0.4
35 },
36 charge: {
37 device: -800,
38 host: -400
39 },
Simon Hunt0b05d4a2014-10-21 21:50:15 -070040 ticksWithoutCollisions: 50,
41 marginLR: 20,
42 marginTB: 20,
43 translate: function() {
44 return 'translate(' +
45 config.force.marginLR + ',' +
46 config.force.marginTB + ')';
47 }
48 },
49 labels: {
Simon Hunt19cb0982014-10-23 16:44:49 -070050 imgPad: 16,
Simon Hunt1c5f8b62014-10-22 14:43:01 -070051 padLR: 8,
52 padTB: 6,
Simon Hunt0b05d4a2014-10-21 21:50:15 -070053 marginLR: 3,
54 marginTB: 2
55 },
Simon Hunt2c9e0c22014-10-23 15:12:58 -070056 icons: {
57 w: 32,
58 h: 32,
59 xoff: -12,
Simon Hunt19cb0982014-10-23 16:44:49 -070060 yoff: -8
Simon Hunt2c9e0c22014-10-23 15:12:58 -070061 },
Simon Hunt0b05d4a2014-10-21 21:50:15 -070062 constraints: {
63 ypos: {
Simon Hunt2c9e0c22014-10-23 15:12:58 -070064 host: 0.15,
65 switch: 0.3,
66 roadm: 0.7
Simon Hunt0b05d4a2014-10-21 21:50:15 -070067 }
Simon Hunt2c9e0c22014-10-23 15:12:58 -070068 },
69 hostLinkWidth: 1.0,
70 mouseOutTimerDelayMs: 120
Simon Hunt0b05d4a2014-10-21 21:50:15 -070071 },
72 view = {},
73 network = {},
74 selected = {},
75 highlighted = null;
76
77
78 function loadNetworkView() {
79 // Hey, here I am, calling something on the ONOS api:
80 api.printTime();
81
82 resize();
83
84 d3.json(config.jsonUrl, function (err, data) {
85 if (err) {
86 alert('Oops! Error reading JSON...\n\n' +
Simon Huntae968a62014-10-22 14:54:41 -070087 'URL: ' + config.jsonUrl + '\n\n' +
Simon Hunt0b05d4a2014-10-21 21:50:15 -070088 'Error: ' + err.message);
89 return;
90 }
91 console.log("here is the JSON data...");
92 console.log(data);
93
94 network.data = data;
95 drawNetwork();
96 });
97
98 $(document).on('click', '.select-object', function() {
99 // when any object of class "select-object" is clicked...
100 // TODO: get a reference to the object via lookup...
101 var obj = network.lookup[$(this).data('id')];
102 if (obj) {
103 selectObject(obj);
104 }
105 // stop propagation of event (I think) ...
106 return false;
107 });
108
109 $(window).on('resize', resize);
110 }
111
112
113 // ========================================================
114
115 function drawNetwork() {
116 $('#view').empty();
117
118 prepareNodesAndLinks();
119 createLayout();
120 console.log("\n\nHere is the augmented network object...");
121 console.warn(network);
122 }
123
124 function prepareNodesAndLinks() {
125 network.lookup = {};
126 network.nodes = [];
127 network.links = [];
128
129 var nw = network.forceWidth,
130 nh = network.forceHeight;
131
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700132 function yPosConstraintForNode(n) {
133 return config.constraints.ypos[n.type || 'host'];
134 }
135
136 // Note that both 'devices' and 'hosts' get mapped into the nodes array
137
138 // first, the devices...
139 network.data.devices.forEach(function(n) {
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700140 var ypc = yPosConstraintForNode(n),
Simon Hunt3ab76a82014-10-22 13:07:32 -0700141 ix = Math.random() * 0.6 * nw + 0.2 * nw,
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700142 iy = ypc * nh,
143 node = {
144 id: n.id,
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700145 labels: n.labels,
146 class: 'device',
147 icon: 'device',
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700148 type: n.type,
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700149 x: ix,
150 y: iy,
151 constraint: {
152 weight: 0.7,
153 y: iy
154 }
155 };
156 network.lookup[n.id] = node;
157 network.nodes.push(node);
158 });
159
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700160 // then, the hosts...
161 network.data.hosts.forEach(function(n) {
162 var ypc = yPosConstraintForNode(n),
163 ix = Math.random() * 0.6 * nw + 0.2 * nw,
164 iy = ypc * nh,
165 node = {
166 id: n.id,
167 labels: n.labels,
168 class: 'host',
169 icon: 'host',
170 type: n.type,
171 x: ix,
172 y: iy,
173 constraint: {
174 weight: 0.7,
175 y: iy
176 }
177 };
178 network.lookup[n.id] = node;
179 network.nodes.push(node);
180 });
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700181
182
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700183 // now, process the explicit links...
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700184 network.data.links.forEach(function(n) {
185 var src = network.lookup[n.src],
186 dst = network.lookup[n.dst],
187 id = src.id + "~" + dst.id;
188
189 var link = {
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700190 class: 'infra',
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700191 id: id,
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700192 type: n.type,
193 width: n.linkWidth,
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700194 source: src,
195 target: dst,
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700196 strength: config.force.linkStrength.infra
197 };
198 network.links.push(link);
199 });
200
201 // finally, infer host links...
202 network.data.hosts.forEach(function(n) {
203 var src = network.lookup[n.id],
204 dst = network.lookup[n.cp.device],
205 id = src.id + "~" + dst.id;
206
207 var link = {
208 class: 'host',
209 id: id,
210 type: 'hostLink',
211 width: config.hostLinkWidth,
212 source: src,
213 target: dst,
214 strength: config.force.linkStrength.host
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700215 };
216 network.links.push(link);
217 });
218 }
219
220 function createLayout() {
221
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700222 var cfg = config.force;
223
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700224 network.force = d3.layout.force()
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700225 .size([network.forceWidth, network.forceHeight])
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700226 .nodes(network.nodes)
227 .links(network.links)
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700228 .linkStrength(function(d) { return cfg.linkStrength[d.class]; })
229 .linkDistance(function(d) { return cfg.linkDistance[d.class]; })
230 .charge(function(d) { return cfg.charge[d.class]; })
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700231 .on('tick', tick);
232
233 network.svg = d3.select('#view').append('svg')
234 .attr('width', view.width)
235 .attr('height', view.height)
236 .append('g')
Simon Huntae968a62014-10-22 14:54:41 -0700237 .attr('transform', config.force.translate());
Simon Hunt3ab76a82014-10-22 13:07:32 -0700238// .attr('id', 'zoomable')
Simon Hunt3ab76a82014-10-22 13:07:32 -0700239// .call(d3.behavior.zoom().on("zoom", zoomRedraw));
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700240
Simon Hunt3ab76a82014-10-22 13:07:32 -0700241// function zoomRedraw() {
242// d3.select("#zoomable").attr("transform",
243// "translate(" + d3.event.translate + ")"
244// + " scale(" + d3.event.scale + ")");
245// }
246
247 // TODO: svg.append('defs') for markers?
248
249 // TODO: move glow/blur stuff to util script
250 var glow = network.svg.append('filter')
251 .attr('x', '-50%')
252 .attr('y', '-50%')
253 .attr('width', '200%')
254 .attr('height', '200%')
255 .attr('id', 'blue-glow');
256
257 glow.append('feColorMatrix')
258 .attr('type', 'matrix')
259 .attr('values', '0 0 0 0 0 ' +
260 '0 0 0 0 0 ' +
261 '0 0 0 0 .7 ' +
262 '0 0 0 1 0 ');
263
264 glow.append('feGaussianBlur')
265 .attr('stdDeviation', 3)
266 .attr('result', 'coloredBlur');
267
268 glow.append('feMerge').selectAll('feMergeNode')
269 .data(['coloredBlur', 'SourceGraphic'])
270 .enter().append('feMergeNode')
271 .attr('in', String);
272
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700273 // TODO: legend (and auto adjust on scroll)
Simon Hunt3ab76a82014-10-22 13:07:32 -0700274// $('#view').on('scroll', function() {
275//
276// });
277
278
279
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700280
281 network.link = network.svg.append('g').selectAll('.link')
282 .data(network.force.links(), function(d) {return d.id})
283 .enter().append('line')
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700284 .attr('class', function(d) {return 'link ' + d.class});
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700285
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700286
287 // == define node drag behavior...
Simon Hunt3ab76a82014-10-22 13:07:32 -0700288 network.draggedThreshold = d3.scale.linear()
289 .domain([0, 0.1])
290 .range([5, 20])
291 .clamp(true);
292
293 function dragged(d) {
294 var threshold = network.draggedThreshold(network.force.alpha()),
295 dx = d.oldX - d.px,
296 dy = d.oldY - d.py;
297 if (Math.abs(dx) >= threshold || Math.abs(dy) >= threshold) {
298 d.dragged = true;
299 }
300 return d.dragged;
301 }
302
303 network.drag = d3.behavior.drag()
304 .origin(function(d) { return d; })
305 .on('dragstart', function(d) {
306 d.oldX = d.x;
307 d.oldY = d.y;
308 d.dragged = false;
309 d.fixed |= 2;
310 })
311 .on('drag', function(d) {
312 d.px = d3.event.x;
313 d.py = d3.event.y;
314 if (dragged(d)) {
315 if (!network.force.alpha()) {
316 network.force.alpha(.025);
317 }
318 }
319 })
320 .on('dragend', function(d) {
321 if (!dragged(d)) {
322 selectObject(d, this);
323 }
324 d.fixed &= ~6;
325 });
326
327 $('#view').on('click', function(e) {
328 if (!$(e.target).closest('.node').length) {
329 deselectObject();
330 }
331 });
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700332
Simon Hunt1c5f8b62014-10-22 14:43:01 -0700333
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700334 network.node = network.svg.selectAll('.node')
335 .data(network.force.nodes(), function(d) {return d.id})
336 .enter().append('g')
Simon Hunt3ab76a82014-10-22 13:07:32 -0700337 .attr('class', function(d) {
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700338 var cls = 'node ' + d.class;
339 if (d.type) {
340 cls += ' ' + d.type;
341 }
342 return cls;
Simon Hunt3ab76a82014-10-22 13:07:32 -0700343 })
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700344 .attr('transform', function(d) {
345 return translate(d.x, d.y);
346 })
Simon Hunt3ab76a82014-10-22 13:07:32 -0700347 .call(network.drag)
348 .on('mouseover', function(d) {
349 if (!selected.obj) {
350 if (network.mouseoutTimeout) {
351 clearTimeout(network.mouseoutTimeout);
352 network.mouseoutTimeout = null;
353 }
354 highlightObject(d);
355 }
356 })
357 .on('mouseout', function(d) {
358 if (!selected.obj) {
359 if (network.mouseoutTimeout) {
360 clearTimeout(network.mouseoutTimeout);
361 network.mouseoutTimeout = null;
362 }
363 network.mouseoutTimeout = setTimeout(function() {
364 highlightObject(null);
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700365 }, config.mouseOutTimerDelayMs);
Simon Hunt3ab76a82014-10-22 13:07:32 -0700366 }
367 });
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700368
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700369 network.nodeRect = network.node.append('rect')
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700370 .attr('rx', 5)
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700371 .attr('ry', 5);
372 // note that width/height are adjusted to fit the label text
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700373
374 network.node.each(function(d) {
375 var node = d3.select(this),
Simon Hunt68ae6652014-10-22 13:58:07 -0700376 rect = node.select('rect'),
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700377 icon = iconUrl(d),
Simon Hunt68ae6652014-10-22 13:58:07 -0700378 text = node.append('text')
Simon Hunt19cb0982014-10-23 16:44:49 -0700379 // TODO: add label cycle behavior
Simon Hunt68ae6652014-10-22 13:58:07 -0700380 .text(d.id)
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700381 .attr('dy', '1.1em');
382
383 if (icon) {
384 var cfg = config.icons;
385 node.append('svg:image')
386 .attr('width', cfg.w)
387 .attr('height', cfg.h)
388 .attr('xlink:href', icon);
389 // note, icon relative positioning (x,y) is done after we have
390 // adjusted the bounds of the rectangle...
391 }
Simon Hunt68ae6652014-10-22 13:58:07 -0700392
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700393 });
394
395 // this function is scheduled to happen soon after the given thread ends
396 setTimeout(function() {
397 network.node.each(function(d) {
398 // for every node, recompute size, padding, etc. so text fits
399 var node = d3.select(this),
400 text = node.selectAll('text'),
401 bounds = {},
402 first = true;
403
404 // NOTE: probably unnecessary code if we only have one line.
Simon Hunt1c5f8b62014-10-22 14:43:01 -0700405 text.each(function() {
406 var box = this.getBBox();
407 if (first || box.x < bounds.x1) {
408 bounds.x1 = box.x;
409 }
410 if (first || box.y < bounds.y1) {
411 bounds.y1 = box.y;
412 }
413 if (first || box.x + box.width < bounds.x2) {
414 bounds.x2 = box.x + box.width;
415 }
416 if (first || box.y + box.height < bounds.y2) {
417 bounds.y2 = box.y + box.height;
418 }
419 first = false;
420 }).attr('text-anchor', 'middle');
421
422 var lab = config.labels,
423 oldWidth = bounds.x2 - bounds.x1;
424
425 bounds.x1 -= oldWidth / 2;
426 bounds.x2 -= oldWidth / 2;
427
428 bounds.x1 -= (lab.padLR + lab.imgPad);
429 bounds.y1 -= lab.padTB;
430 bounds.x2 += lab.padLR;
431 bounds.y2 += lab.padTB;
432
433 node.select('rect')
434 .attr('x', bounds.x1)
435 .attr('y', bounds.y1)
436 .attr('width', bounds.x2 - bounds.x1)
437 .attr('height', bounds.y2 - bounds.y1);
438
439 node.select('image')
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700440 .attr('x', bounds.x1 + config.icons.xoff)
441 .attr('y', bounds.y1 + config.icons.yoff);
Simon Hunt1c219892014-10-22 16:32:39 -0700442
443 d.extent = {
444 left: bounds.x1 - lab.marginLR,
445 right: bounds.x2 + lab.marginLR,
446 top: bounds.y1 - lab.marginTB,
447 bottom: bounds.y2 + lab.marginTB
448 };
449
450 d.edge = {
451 left : new geo.LineSegment(bounds.x1, bounds.y1, bounds.x1, bounds.y2),
452 right : new geo.LineSegment(bounds.x2, bounds.y1, bounds.x2, bounds.y2),
453 top : new geo.LineSegment(bounds.x1, bounds.y1, bounds.x2, bounds.y1),
454 bottom : new geo.LineSegment(bounds.x1, bounds.y2, bounds.x2, bounds.y2)
455 };
456
Simon Hunt1c5f8b62014-10-22 14:43:01 -0700457 // ====
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700458 });
459
460 network.numTicks = 0;
461 network.preventCollisions = false;
462 network.force.start();
Simon Hunt1c219892014-10-22 16:32:39 -0700463 for (var i = 0; i < config.force.ticksWithoutCollisions; i++) {
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700464 network.force.tick();
465 }
466 network.preventCollisions = true;
467 $('#view').css('visibility', 'visible');
468 });
469
470 }
471
Simon Hunt68ae6652014-10-22 13:58:07 -0700472 function iconUrl(d) {
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700473 return config.iconUrl[d.icon];
Simon Hunt68ae6652014-10-22 13:58:07 -0700474 }
475
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700476 function translate(x, y) {
477 return 'translate(' + x + ',' + y + ')';
478 }
479
Simon Hunt1c219892014-10-22 16:32:39 -0700480 function preventCollisions() {
481 var quadtree = d3.geom.quadtree(network.nodes);
482
483 network.nodes.forEach(function(n) {
484 var nx1 = n.x + n.extent.left,
485 nx2 = n.x + n.extent.right,
486 ny1 = n.y + n.extent.top,
487 ny2 = n.y + n.extent.bottom;
488
489 quadtree.visit(function(quad, x1, y1, x2, y2) {
490 if (quad.point && quad.point !== n) {
491 // check if the rectangles intersect
492 var p = quad.point,
493 px1 = p.x + p.extent.left,
494 px2 = p.x + p.extent.right,
495 py1 = p.y + p.extent.top,
496 py2 = p.y + p.extent.bottom,
497 ix = (px1 <= nx2 && nx1 <= px2 && py1 <= ny2 && ny1 <= py2);
498 if (ix) {
499 var xa1 = nx2 - px1, // shift n left , p right
500 xa2 = px2 - nx1, // shift n right, p left
501 ya1 = ny2 - py1, // shift n up , p down
502 ya2 = py2 - ny1, // shift n down , p up
503 adj = Math.min(xa1, xa2, ya1, ya2);
504
505 if (adj == xa1) {
506 n.x -= adj / 2;
507 p.x += adj / 2;
508 } else if (adj == xa2) {
509 n.x += adj / 2;
510 p.x -= adj / 2;
511 } else if (adj == ya1) {
512 n.y -= adj / 2;
513 p.y += adj / 2;
514 } else if (adj == ya2) {
515 n.y += adj / 2;
516 p.y -= adj / 2;
517 }
518 }
519 return ix;
520 }
521 });
522
523 });
524 }
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700525
526 function tick(e) {
527 network.numTicks++;
528
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700529 if (config.options.layering) {
Simon Hunt68ae6652014-10-22 13:58:07 -0700530 // adjust the y-coord of each node, based on y-pos constraints
531 network.nodes.forEach(function (n) {
532 var z = e.alpha * n.constraint.weight;
533 if (!isNaN(n.constraint.y)) {
534 n.y = (n.constraint.y * z + n.y * (1 - z));
535 }
536 });
537 }
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700538
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700539 if (config.options.collisionPrevention && network.preventCollisions) {
Simon Hunt1c219892014-10-22 16:32:39 -0700540 preventCollisions();
541 }
542
543 // TODO: use intersection technique for source end of link also
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700544 network.link
545 .attr('x1', function(d) {
546 return d.source.x;
547 })
548 .attr('y1', function(d) {
549 return d.source.y;
550 })
Simon Hunt1c219892014-10-22 16:32:39 -0700551 .each(function(d) {
552 var x = d.target.x,
553 y = d.target.y,
554 line = new geo.LineSegment(d.source.x, d.source.y, x, y);
555
556 for (var e in d.target.edge) {
557 var ix = line.intersect(d.target.edge[e].offset(x,y));
558 if (ix.in1 && ix.in2) {
559 x = ix.x;
560 y = ix.y;
561 break;
562 }
563 }
564
565 d3.select(this)
566 .attr('x2', x)
567 .attr('y2', y);
568
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700569 });
570
571 network.node
572 .attr('transform', function(d) {
573 return translate(d.x, d.y);
574 });
575
576 }
577
578 // $('#docs-close').on('click', function() {
579 // deselectObject();
580 // return false;
581 // });
582
583 // $(document).on('click', '.select-object', function() {
584 // var obj = graph.data[$(this).data('name')];
585 // if (obj) {
586 // selectObject(obj);
587 // }
588 // return false;
589 // });
590
591 function selectObject(obj, el) {
592 var node;
593 if (el) {
594 node = d3.select(el);
595 } else {
596 network.node.each(function(d) {
597 if (d == obj) {
598 node = d3.select(el = this);
599 }
600 });
601 }
602 if (!node) return;
603
604 if (node.classed('selected')) {
605 deselectObject();
606 return;
607 }
608 deselectObject(false);
609
610 selected = {
611 obj : obj,
612 el : el
613 };
614
615 highlightObject(obj);
616
617 node.classed('selected', true);
618
619 // TODO animate incoming info pane
620 // resize(true);
621 // TODO: check bounds of selected node and scroll into view if needed
622 }
623
624 function deselectObject(doResize) {
625 // Review: logic of 'resize(...)' function.
626 if (doResize || typeof doResize == 'undefined') {
627 resize(false);
628 }
629 // deselect all nodes in the network...
630 network.node.classed('selected', false);
631 selected = {};
632 highlightObject(null);
633 }
634
635 function highlightObject(obj) {
636 if (obj) {
637 if (obj != highlighted) {
638 // TODO set or clear "inactive" class on nodes, based on criteria
639 network.node.classed('inactive', function(d) {
640 // return (obj !== d &&
641 // d.relation(obj.id));
642 return (obj !== d);
643 });
644 // TODO: same with links
645 network.link.classed('inactive', function(d) {
646 return (obj !== d.source && obj !== d.target);
647 });
648 }
649 highlighted = obj;
650 } else {
651 if (highlighted) {
652 // clear the inactive flag (no longer suppressed visually)
653 network.node.classed('inactive', false);
654 network.link.classed('inactive', false);
655 }
656 highlighted = null;
657
658 }
659 }
660
661 function resize(showDetails) {
662 console.log("resize() called...");
663
664 var $details = $('#details');
665
666 if (typeof showDetails == 'boolean') {
667 var showingDetails = showDetails;
668 // TODO: invoke $details.show() or $details.hide()...
669 // $details[showingDetails ? 'show' : 'hide']();
670 }
671
672 view.height = window.innerHeight - config.mastHeight;
673 view.width = window.innerWidth;
674 $('#view')
675 .css('height', view.height + 'px')
676 .css('width', view.width + 'px');
677
678 network.forceWidth = view.width - config.force.marginLR;
679 network.forceHeight = view.height - config.force.marginTB;
680 }
681
682 // ======================================================================
683 // register with the UI framework
684
685 api.addView('network', {
686 load: loadNetworkView
687 });
688
689
690}(ONOS));
691