blob: 32b0f2733c96d014256563bf970be5b60de36865 [file] [log] [blame]
Simon Hunt0b05d4a2014-10-21 21:50:15 -07001/*
Thomas Vachuska781d18b2014-10-27 10:31:25 -07002 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19
20/*
Simon Hunt0b05d4a2014-10-21 21:50:15 -070021 ONOS network topology viewer - PoC version 1.0
22
23 @author Simon Hunt
24 */
25
26(function (onos) {
27 'use strict';
28
29 var api = onos.api;
30
31 var config = {
Simon Hunt2c9e0c22014-10-23 15:12:58 -070032 options: {
Simon Hunt19cb0982014-10-23 16:44:49 -070033 layering: true,
Simon Hunt2c9e0c22014-10-23 15:12:58 -070034 collisionPrevention: true
35 },
Thomas Vachuska598924e2014-10-23 22:26:07 -070036 XjsonUrl: 'rs/topology/graph',
Simon Hunt0b05d4a2014-10-21 21:50:15 -070037 jsonUrl: 'network.json',
Simon Hunt68ae6652014-10-22 13:58:07 -070038 iconUrl: {
Simon Hunt2c9e0c22014-10-23 15:12:58 -070039 device: 'img/device.png',
40 host: 'img/host.png',
41 pkt: 'img/pkt.png',
42 opt: 'img/opt.png'
Simon Hunt68ae6652014-10-22 13:58:07 -070043 },
Simon Hunt19cb0982014-10-23 16:44:49 -070044 mastHeight: 36,
Simon Hunt0b05d4a2014-10-21 21:50:15 -070045 force: {
Simon Hunt2c9e0c22014-10-23 15:12:58 -070046 note: 'node.class or link.class is used to differentiate',
47 linkDistance: {
48 infra: 240,
49 host: 100
50 },
51 linkStrength: {
52 infra: 1.0,
53 host: 0.4
54 },
55 charge: {
56 device: -800,
57 host: -400
58 },
Simon Hunt0b05d4a2014-10-21 21:50:15 -070059 ticksWithoutCollisions: 50,
60 marginLR: 20,
61 marginTB: 20,
62 translate: function() {
63 return 'translate(' +
64 config.force.marginLR + ',' +
65 config.force.marginTB + ')';
66 }
67 },
68 labels: {
Simon Hunt19cb0982014-10-23 16:44:49 -070069 imgPad: 16,
Simon Hunt1c5f8b62014-10-22 14:43:01 -070070 padLR: 8,
71 padTB: 6,
Simon Hunt0b05d4a2014-10-21 21:50:15 -070072 marginLR: 3,
73 marginTB: 2
74 },
Simon Hunt2c9e0c22014-10-23 15:12:58 -070075 icons: {
76 w: 32,
77 h: 32,
78 xoff: -12,
Simon Hunt19cb0982014-10-23 16:44:49 -070079 yoff: -8
Simon Hunt2c9e0c22014-10-23 15:12:58 -070080 },
Simon Hunt0b05d4a2014-10-21 21:50:15 -070081 constraints: {
82 ypos: {
Simon Hunt2c9e0c22014-10-23 15:12:58 -070083 host: 0.15,
84 switch: 0.3,
85 roadm: 0.7
Simon Hunt0b05d4a2014-10-21 21:50:15 -070086 }
Simon Hunt2c9e0c22014-10-23 15:12:58 -070087 },
88 hostLinkWidth: 1.0,
89 mouseOutTimerDelayMs: 120
Simon Hunt0b05d4a2014-10-21 21:50:15 -070090 },
91 view = {},
92 network = {},
93 selected = {},
94 highlighted = null;
95
96
97 function loadNetworkView() {
98 // Hey, here I am, calling something on the ONOS api:
99 api.printTime();
100
101 resize();
102
103 d3.json(config.jsonUrl, function (err, data) {
104 if (err) {
105 alert('Oops! Error reading JSON...\n\n' +
Simon Huntae968a62014-10-22 14:54:41 -0700106 'URL: ' + config.jsonUrl + '\n\n' +
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700107 'Error: ' + err.message);
108 return;
109 }
110 console.log("here is the JSON data...");
111 console.log(data);
112
113 network.data = data;
114 drawNetwork();
115 });
116
117 $(document).on('click', '.select-object', function() {
118 // when any object of class "select-object" is clicked...
119 // TODO: get a reference to the object via lookup...
120 var obj = network.lookup[$(this).data('id')];
121 if (obj) {
122 selectObject(obj);
123 }
124 // stop propagation of event (I think) ...
125 return false;
126 });
127
128 $(window).on('resize', resize);
129 }
130
131
132 // ========================================================
133
134 function drawNetwork() {
135 $('#view').empty();
136
137 prepareNodesAndLinks();
138 createLayout();
139 console.log("\n\nHere is the augmented network object...");
140 console.warn(network);
141 }
142
143 function prepareNodesAndLinks() {
144 network.lookup = {};
145 network.nodes = [];
146 network.links = [];
147
148 var nw = network.forceWidth,
149 nh = network.forceHeight;
150
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700151 function yPosConstraintForNode(n) {
152 return config.constraints.ypos[n.type || 'host'];
153 }
154
155 // Note that both 'devices' and 'hosts' get mapped into the nodes array
156
157 // first, the devices...
158 network.data.devices.forEach(function(n) {
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700159 var ypc = yPosConstraintForNode(n),
Simon Hunt3ab76a82014-10-22 13:07:32 -0700160 ix = Math.random() * 0.6 * nw + 0.2 * nw,
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700161 iy = ypc * nh,
162 node = {
163 id: n.id,
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700164 labels: n.labels,
165 class: 'device',
166 icon: 'device',
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700167 type: n.type,
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700168 x: ix,
169 y: iy,
170 constraint: {
171 weight: 0.7,
172 y: iy
173 }
174 };
175 network.lookup[n.id] = node;
176 network.nodes.push(node);
177 });
178
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700179 // then, the hosts...
180 network.data.hosts.forEach(function(n) {
181 var ypc = yPosConstraintForNode(n),
182 ix = Math.random() * 0.6 * nw + 0.2 * nw,
183 iy = ypc * nh,
184 node = {
185 id: n.id,
186 labels: n.labels,
187 class: 'host',
188 icon: 'host',
189 type: n.type,
190 x: ix,
191 y: iy,
192 constraint: {
193 weight: 0.7,
194 y: iy
195 }
196 };
197 network.lookup[n.id] = node;
198 network.nodes.push(node);
199 });
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700200
201
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700202 // now, process the explicit links...
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700203 network.data.links.forEach(function(n) {
204 var src = network.lookup[n.src],
205 dst = network.lookup[n.dst],
206 id = src.id + "~" + dst.id;
207
208 var link = {
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700209 class: 'infra',
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700210 id: id,
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700211 type: n.type,
212 width: n.linkWidth,
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700213 source: src,
214 target: dst,
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700215 strength: config.force.linkStrength.infra
216 };
217 network.links.push(link);
218 });
219
220 // finally, infer host links...
221 network.data.hosts.forEach(function(n) {
222 var src = network.lookup[n.id],
223 dst = network.lookup[n.cp.device],
224 id = src.id + "~" + dst.id;
225
226 var link = {
227 class: 'host',
228 id: id,
229 type: 'hostLink',
230 width: config.hostLinkWidth,
231 source: src,
232 target: dst,
233 strength: config.force.linkStrength.host
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700234 };
235 network.links.push(link);
236 });
237 }
238
239 function createLayout() {
240
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700241 var cfg = config.force;
242
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700243 network.force = d3.layout.force()
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700244 .size([network.forceWidth, network.forceHeight])
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700245 .nodes(network.nodes)
246 .links(network.links)
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700247 .linkStrength(function(d) { return cfg.linkStrength[d.class]; })
248 .linkDistance(function(d) { return cfg.linkDistance[d.class]; })
249 .charge(function(d) { return cfg.charge[d.class]; })
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700250 .on('tick', tick);
251
252 network.svg = d3.select('#view').append('svg')
253 .attr('width', view.width)
254 .attr('height', view.height)
255 .append('g')
Simon Huntae968a62014-10-22 14:54:41 -0700256 .attr('transform', config.force.translate());
Simon Hunt3ab76a82014-10-22 13:07:32 -0700257// .attr('id', 'zoomable')
Simon Hunt3ab76a82014-10-22 13:07:32 -0700258// .call(d3.behavior.zoom().on("zoom", zoomRedraw));
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700259
Simon Hunt3ab76a82014-10-22 13:07:32 -0700260// function zoomRedraw() {
261// d3.select("#zoomable").attr("transform",
262// "translate(" + d3.event.translate + ")"
263// + " scale(" + d3.event.scale + ")");
264// }
265
266 // TODO: svg.append('defs') for markers?
267
268 // TODO: move glow/blur stuff to util script
269 var glow = network.svg.append('filter')
270 .attr('x', '-50%')
271 .attr('y', '-50%')
272 .attr('width', '200%')
273 .attr('height', '200%')
274 .attr('id', 'blue-glow');
275
276 glow.append('feColorMatrix')
277 .attr('type', 'matrix')
278 .attr('values', '0 0 0 0 0 ' +
279 '0 0 0 0 0 ' +
280 '0 0 0 0 .7 ' +
281 '0 0 0 1 0 ');
282
283 glow.append('feGaussianBlur')
284 .attr('stdDeviation', 3)
285 .attr('result', 'coloredBlur');
286
287 glow.append('feMerge').selectAll('feMergeNode')
288 .data(['coloredBlur', 'SourceGraphic'])
289 .enter().append('feMergeNode')
290 .attr('in', String);
291
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700292 // TODO: legend (and auto adjust on scroll)
Simon Hunt3ab76a82014-10-22 13:07:32 -0700293// $('#view').on('scroll', function() {
294//
295// });
296
297
298
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700299
300 network.link = network.svg.append('g').selectAll('.link')
301 .data(network.force.links(), function(d) {return d.id})
302 .enter().append('line')
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700303 .attr('class', function(d) {return 'link ' + d.class});
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700304
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700305
306 // == define node drag behavior...
Simon Hunt3ab76a82014-10-22 13:07:32 -0700307 network.draggedThreshold = d3.scale.linear()
308 .domain([0, 0.1])
309 .range([5, 20])
310 .clamp(true);
311
312 function dragged(d) {
313 var threshold = network.draggedThreshold(network.force.alpha()),
314 dx = d.oldX - d.px,
315 dy = d.oldY - d.py;
316 if (Math.abs(dx) >= threshold || Math.abs(dy) >= threshold) {
317 d.dragged = true;
318 }
319 return d.dragged;
320 }
321
322 network.drag = d3.behavior.drag()
323 .origin(function(d) { return d; })
324 .on('dragstart', function(d) {
325 d.oldX = d.x;
326 d.oldY = d.y;
327 d.dragged = false;
328 d.fixed |= 2;
329 })
330 .on('drag', function(d) {
331 d.px = d3.event.x;
332 d.py = d3.event.y;
333 if (dragged(d)) {
334 if (!network.force.alpha()) {
335 network.force.alpha(.025);
336 }
337 }
338 })
339 .on('dragend', function(d) {
340 if (!dragged(d)) {
341 selectObject(d, this);
342 }
343 d.fixed &= ~6;
344 });
345
346 $('#view').on('click', function(e) {
347 if (!$(e.target).closest('.node').length) {
348 deselectObject();
349 }
350 });
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700351
Simon Hunt1c5f8b62014-10-22 14:43:01 -0700352
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700353 network.node = network.svg.selectAll('.node')
354 .data(network.force.nodes(), function(d) {return d.id})
355 .enter().append('g')
Simon Hunt3ab76a82014-10-22 13:07:32 -0700356 .attr('class', function(d) {
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700357 var cls = 'node ' + d.class;
358 if (d.type) {
359 cls += ' ' + d.type;
360 }
361 return cls;
Simon Hunt3ab76a82014-10-22 13:07:32 -0700362 })
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700363 .attr('transform', function(d) {
364 return translate(d.x, d.y);
365 })
Simon Hunt3ab76a82014-10-22 13:07:32 -0700366 .call(network.drag)
367 .on('mouseover', function(d) {
368 if (!selected.obj) {
369 if (network.mouseoutTimeout) {
370 clearTimeout(network.mouseoutTimeout);
371 network.mouseoutTimeout = null;
372 }
373 highlightObject(d);
374 }
375 })
376 .on('mouseout', function(d) {
377 if (!selected.obj) {
378 if (network.mouseoutTimeout) {
379 clearTimeout(network.mouseoutTimeout);
380 network.mouseoutTimeout = null;
381 }
382 network.mouseoutTimeout = setTimeout(function() {
383 highlightObject(null);
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700384 }, config.mouseOutTimerDelayMs);
Simon Hunt3ab76a82014-10-22 13:07:32 -0700385 }
386 });
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700387
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700388 network.nodeRect = network.node.append('rect')
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700389 .attr('rx', 5)
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700390 .attr('ry', 5);
391 // note that width/height are adjusted to fit the label text
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700392
393 network.node.each(function(d) {
394 var node = d3.select(this),
Simon Hunt68ae6652014-10-22 13:58:07 -0700395 rect = node.select('rect'),
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700396 icon = iconUrl(d),
Simon Hunt68ae6652014-10-22 13:58:07 -0700397 text = node.append('text')
Simon Hunt19cb0982014-10-23 16:44:49 -0700398 // TODO: add label cycle behavior
Simon Hunt68ae6652014-10-22 13:58:07 -0700399 .text(d.id)
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700400 .attr('dy', '1.1em');
401
402 if (icon) {
403 var cfg = config.icons;
404 node.append('svg:image')
405 .attr('width', cfg.w)
406 .attr('height', cfg.h)
407 .attr('xlink:href', icon);
408 // note, icon relative positioning (x,y) is done after we have
409 // adjusted the bounds of the rectangle...
410 }
Simon Hunt68ae6652014-10-22 13:58:07 -0700411
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700412 });
413
414 // this function is scheduled to happen soon after the given thread ends
415 setTimeout(function() {
416 network.node.each(function(d) {
417 // for every node, recompute size, padding, etc. so text fits
418 var node = d3.select(this),
419 text = node.selectAll('text'),
420 bounds = {},
421 first = true;
422
423 // NOTE: probably unnecessary code if we only have one line.
Simon Hunt1c5f8b62014-10-22 14:43:01 -0700424 text.each(function() {
425 var box = this.getBBox();
426 if (first || box.x < bounds.x1) {
427 bounds.x1 = box.x;
428 }
429 if (first || box.y < bounds.y1) {
430 bounds.y1 = box.y;
431 }
432 if (first || box.x + box.width < bounds.x2) {
433 bounds.x2 = box.x + box.width;
434 }
435 if (first || box.y + box.height < bounds.y2) {
436 bounds.y2 = box.y + box.height;
437 }
438 first = false;
439 }).attr('text-anchor', 'middle');
440
441 var lab = config.labels,
442 oldWidth = bounds.x2 - bounds.x1;
443
444 bounds.x1 -= oldWidth / 2;
445 bounds.x2 -= oldWidth / 2;
446
447 bounds.x1 -= (lab.padLR + lab.imgPad);
448 bounds.y1 -= lab.padTB;
449 bounds.x2 += lab.padLR;
450 bounds.y2 += lab.padTB;
451
452 node.select('rect')
453 .attr('x', bounds.x1)
454 .attr('y', bounds.y1)
455 .attr('width', bounds.x2 - bounds.x1)
456 .attr('height', bounds.y2 - bounds.y1);
457
458 node.select('image')
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700459 .attr('x', bounds.x1 + config.icons.xoff)
460 .attr('y', bounds.y1 + config.icons.yoff);
Simon Hunt1c219892014-10-22 16:32:39 -0700461
462 d.extent = {
463 left: bounds.x1 - lab.marginLR,
464 right: bounds.x2 + lab.marginLR,
465 top: bounds.y1 - lab.marginTB,
466 bottom: bounds.y2 + lab.marginTB
467 };
468
469 d.edge = {
470 left : new geo.LineSegment(bounds.x1, bounds.y1, bounds.x1, bounds.y2),
471 right : new geo.LineSegment(bounds.x2, bounds.y1, bounds.x2, bounds.y2),
472 top : new geo.LineSegment(bounds.x1, bounds.y1, bounds.x2, bounds.y1),
473 bottom : new geo.LineSegment(bounds.x1, bounds.y2, bounds.x2, bounds.y2)
474 };
475
Simon Hunt1c5f8b62014-10-22 14:43:01 -0700476 // ====
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700477 });
478
479 network.numTicks = 0;
480 network.preventCollisions = false;
481 network.force.start();
Simon Hunt1c219892014-10-22 16:32:39 -0700482 for (var i = 0; i < config.force.ticksWithoutCollisions; i++) {
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700483 network.force.tick();
484 }
485 network.preventCollisions = true;
486 $('#view').css('visibility', 'visible');
487 });
488
489 }
490
Simon Hunt68ae6652014-10-22 13:58:07 -0700491 function iconUrl(d) {
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700492 return config.iconUrl[d.icon];
Simon Hunt68ae6652014-10-22 13:58:07 -0700493 }
494
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700495 function translate(x, y) {
496 return 'translate(' + x + ',' + y + ')';
497 }
498
Simon Hunt1c219892014-10-22 16:32:39 -0700499 function preventCollisions() {
500 var quadtree = d3.geom.quadtree(network.nodes);
501
502 network.nodes.forEach(function(n) {
503 var nx1 = n.x + n.extent.left,
504 nx2 = n.x + n.extent.right,
505 ny1 = n.y + n.extent.top,
506 ny2 = n.y + n.extent.bottom;
507
508 quadtree.visit(function(quad, x1, y1, x2, y2) {
509 if (quad.point && quad.point !== n) {
510 // check if the rectangles intersect
511 var p = quad.point,
512 px1 = p.x + p.extent.left,
513 px2 = p.x + p.extent.right,
514 py1 = p.y + p.extent.top,
515 py2 = p.y + p.extent.bottom,
516 ix = (px1 <= nx2 && nx1 <= px2 && py1 <= ny2 && ny1 <= py2);
517 if (ix) {
518 var xa1 = nx2 - px1, // shift n left , p right
519 xa2 = px2 - nx1, // shift n right, p left
520 ya1 = ny2 - py1, // shift n up , p down
521 ya2 = py2 - ny1, // shift n down , p up
522 adj = Math.min(xa1, xa2, ya1, ya2);
523
524 if (adj == xa1) {
525 n.x -= adj / 2;
526 p.x += adj / 2;
527 } else if (adj == xa2) {
528 n.x += adj / 2;
529 p.x -= adj / 2;
530 } else if (adj == ya1) {
531 n.y -= adj / 2;
532 p.y += adj / 2;
533 } else if (adj == ya2) {
534 n.y += adj / 2;
535 p.y -= adj / 2;
536 }
537 }
538 return ix;
539 }
540 });
541
542 });
543 }
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700544
545 function tick(e) {
546 network.numTicks++;
547
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700548 if (config.options.layering) {
Simon Hunt68ae6652014-10-22 13:58:07 -0700549 // adjust the y-coord of each node, based on y-pos constraints
550 network.nodes.forEach(function (n) {
551 var z = e.alpha * n.constraint.weight;
552 if (!isNaN(n.constraint.y)) {
553 n.y = (n.constraint.y * z + n.y * (1 - z));
554 }
555 });
556 }
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700557
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700558 if (config.options.collisionPrevention && network.preventCollisions) {
Simon Hunt1c219892014-10-22 16:32:39 -0700559 preventCollisions();
560 }
561
562 // TODO: use intersection technique for source end of link also
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700563 network.link
564 .attr('x1', function(d) {
565 return d.source.x;
566 })
567 .attr('y1', function(d) {
568 return d.source.y;
569 })
Simon Hunt1c219892014-10-22 16:32:39 -0700570 .each(function(d) {
571 var x = d.target.x,
572 y = d.target.y,
573 line = new geo.LineSegment(d.source.x, d.source.y, x, y);
574
575 for (var e in d.target.edge) {
576 var ix = line.intersect(d.target.edge[e].offset(x,y));
577 if (ix.in1 && ix.in2) {
578 x = ix.x;
579 y = ix.y;
580 break;
581 }
582 }
583
584 d3.select(this)
585 .attr('x2', x)
586 .attr('y2', y);
587
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700588 });
589
590 network.node
591 .attr('transform', function(d) {
592 return translate(d.x, d.y);
593 });
594
595 }
596
597 // $('#docs-close').on('click', function() {
598 // deselectObject();
599 // return false;
600 // });
601
602 // $(document).on('click', '.select-object', function() {
603 // var obj = graph.data[$(this).data('name')];
604 // if (obj) {
605 // selectObject(obj);
606 // }
607 // return false;
608 // });
609
610 function selectObject(obj, el) {
611 var node;
612 if (el) {
613 node = d3.select(el);
614 } else {
615 network.node.each(function(d) {
616 if (d == obj) {
617 node = d3.select(el = this);
618 }
619 });
620 }
621 if (!node) return;
622
623 if (node.classed('selected')) {
624 deselectObject();
625 return;
626 }
627 deselectObject(false);
628
629 selected = {
630 obj : obj,
631 el : el
632 };
633
634 highlightObject(obj);
635
636 node.classed('selected', true);
637
638 // TODO animate incoming info pane
639 // resize(true);
640 // TODO: check bounds of selected node and scroll into view if needed
641 }
642
643 function deselectObject(doResize) {
644 // Review: logic of 'resize(...)' function.
645 if (doResize || typeof doResize == 'undefined') {
646 resize(false);
647 }
648 // deselect all nodes in the network...
649 network.node.classed('selected', false);
650 selected = {};
651 highlightObject(null);
652 }
653
654 function highlightObject(obj) {
655 if (obj) {
656 if (obj != highlighted) {
657 // TODO set or clear "inactive" class on nodes, based on criteria
658 network.node.classed('inactive', function(d) {
659 // return (obj !== d &&
660 // d.relation(obj.id));
661 return (obj !== d);
662 });
663 // TODO: same with links
664 network.link.classed('inactive', function(d) {
665 return (obj !== d.source && obj !== d.target);
666 });
667 }
668 highlighted = obj;
669 } else {
670 if (highlighted) {
671 // clear the inactive flag (no longer suppressed visually)
672 network.node.classed('inactive', false);
673 network.link.classed('inactive', false);
674 }
675 highlighted = null;
676
677 }
678 }
679
680 function resize(showDetails) {
681 console.log("resize() called...");
682
683 var $details = $('#details');
684
685 if (typeof showDetails == 'boolean') {
686 var showingDetails = showDetails;
687 // TODO: invoke $details.show() or $details.hide()...
688 // $details[showingDetails ? 'show' : 'hide']();
689 }
690
691 view.height = window.innerHeight - config.mastHeight;
692 view.width = window.innerWidth;
693 $('#view')
694 .css('height', view.height + 'px')
695 .css('width', view.width + 'px');
696
697 network.forceWidth = view.width - config.force.marginLR;
698 network.forceHeight = view.height - config.force.marginTB;
699 }
700
701 // ======================================================================
702 // register with the UI framework
703
704 api.addView('network', {
705 load: loadNetworkView
706 });
707
708
709}(ONOS));
710