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