blob: 7c80616af73062e76c547636f668ae250cfb0cc3 [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 Hunt5cd0e8f2014-10-27 16:18:40 -070034 collisionPrevention: true,
35 showNodeXY: true
Simon Hunt2c9e0c22014-10-23 15:12:58 -070036 },
Thomas Vachuska598924e2014-10-23 22:26:07 -070037 XjsonUrl: 'rs/topology/graph',
Simon Hunt0b05d4a2014-10-21 21:50:15 -070038 jsonUrl: 'network.json',
Simon Hunt68ae6652014-10-22 13:58:07 -070039 iconUrl: {
Simon Hunt2c9e0c22014-10-23 15:12:58 -070040 device: 'img/device.png',
41 host: 'img/host.png',
42 pkt: 'img/pkt.png',
43 opt: 'img/opt.png'
Simon Hunt68ae6652014-10-22 13:58:07 -070044 },
Simon Hunt19cb0982014-10-23 16:44:49 -070045 mastHeight: 36,
Simon Hunt0b05d4a2014-10-21 21:50:15 -070046 force: {
Simon Hunt2c9e0c22014-10-23 15:12:58 -070047 note: 'node.class or link.class is used to differentiate',
48 linkDistance: {
49 infra: 240,
50 host: 100
51 },
52 linkStrength: {
53 infra: 1.0,
54 host: 0.4
55 },
56 charge: {
57 device: -800,
58 host: -400
59 },
Simon Hunt0b05d4a2014-10-21 21:50:15 -070060 ticksWithoutCollisions: 50,
61 marginLR: 20,
62 marginTB: 20,
63 translate: function() {
64 return 'translate(' +
65 config.force.marginLR + ',' +
66 config.force.marginTB + ')';
67 }
68 },
69 labels: {
Simon Hunt19cb0982014-10-23 16:44:49 -070070 imgPad: 16,
Simon Hunt1c5f8b62014-10-22 14:43:01 -070071 padLR: 8,
72 padTB: 6,
Simon Hunt0b05d4a2014-10-21 21:50:15 -070073 marginLR: 3,
74 marginTB: 2
75 },
Simon Hunt2c9e0c22014-10-23 15:12:58 -070076 icons: {
77 w: 32,
78 h: 32,
79 xoff: -12,
Simon Hunt19cb0982014-10-23 16:44:49 -070080 yoff: -8
Simon Hunt2c9e0c22014-10-23 15:12:58 -070081 },
Simon Hunt0b05d4a2014-10-21 21:50:15 -070082 constraints: {
83 ypos: {
Simon Hunt2c9e0c22014-10-23 15:12:58 -070084 host: 0.15,
85 switch: 0.3,
86 roadm: 0.7
Simon Hunt0b05d4a2014-10-21 21:50:15 -070087 }
Simon Hunt2c9e0c22014-10-23 15:12:58 -070088 },
89 hostLinkWidth: 1.0,
90 mouseOutTimerDelayMs: 120
Simon Hunt0b05d4a2014-10-21 21:50:15 -070091 },
92 view = {},
93 network = {},
94 selected = {},
Simon Hunt5cd0e8f2014-10-27 16:18:40 -070095 highlighted = null,
96 viewMode = 'showAll';
Simon Hunt0b05d4a2014-10-21 21:50:15 -070097
98
99 function loadNetworkView() {
100 // Hey, here I am, calling something on the ONOS api:
101 api.printTime();
102
103 resize();
104
105 d3.json(config.jsonUrl, function (err, data) {
106 if (err) {
107 alert('Oops! Error reading JSON...\n\n' +
Simon Huntae968a62014-10-22 14:54:41 -0700108 'URL: ' + config.jsonUrl + '\n\n' +
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700109 'Error: ' + err.message);
110 return;
111 }
112 console.log("here is the JSON data...");
113 console.log(data);
114
115 network.data = data;
116 drawNetwork();
117 });
118
119 $(document).on('click', '.select-object', function() {
120 // when any object of class "select-object" is clicked...
121 // TODO: get a reference to the object via lookup...
122 var obj = network.lookup[$(this).data('id')];
123 if (obj) {
124 selectObject(obj);
125 }
126 // stop propagation of event (I think) ...
127 return false;
128 });
129
130 $(window).on('resize', resize);
Simon Hunt5cd0e8f2014-10-27 16:18:40 -0700131
132 // set up radio button behavior
133 d3.selectAll("#displayModes .radio").on('click', function() {
134 var id = d3.select(this).attr("id");
135 if (id !== viewMode) {
136 radioButton('displayModes', id);
137 viewMode = id;
138 alert("action: " + id);
139 }
140 });
141 }
142
143 function radioButton(group, id) {
144 d3.selectAll("#" + group + " .radio").classed("active", false);
145 d3.select("#" + group + " #" + id).classed("active", true);
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700146 }
147
148
149 // ========================================================
150
151 function drawNetwork() {
152 $('#view').empty();
153
154 prepareNodesAndLinks();
155 createLayout();
156 console.log("\n\nHere is the augmented network object...");
157 console.warn(network);
158 }
159
160 function prepareNodesAndLinks() {
161 network.lookup = {};
162 network.nodes = [];
163 network.links = [];
164
165 var nw = network.forceWidth,
166 nh = network.forceHeight;
167
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700168 function yPosConstraintForNode(n) {
169 return config.constraints.ypos[n.type || 'host'];
170 }
171
172 // Note that both 'devices' and 'hosts' get mapped into the nodes array
173
174 // first, the devices...
175 network.data.devices.forEach(function(n) {
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700176 var ypc = yPosConstraintForNode(n),
Simon Hunt3ab76a82014-10-22 13:07:32 -0700177 ix = Math.random() * 0.6 * nw + 0.2 * nw,
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700178 iy = ypc * nh,
179 node = {
180 id: n.id,
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700181 labels: n.labels,
182 class: 'device',
183 icon: 'device',
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700184 type: n.type,
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700185 x: ix,
186 y: iy,
187 constraint: {
188 weight: 0.7,
189 y: iy
190 }
191 };
192 network.lookup[n.id] = node;
193 network.nodes.push(node);
194 });
195
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700196 // then, the hosts...
197 network.data.hosts.forEach(function(n) {
198 var ypc = yPosConstraintForNode(n),
199 ix = Math.random() * 0.6 * nw + 0.2 * nw,
200 iy = ypc * nh,
201 node = {
202 id: n.id,
203 labels: n.labels,
204 class: 'host',
205 icon: 'host',
206 type: n.type,
207 x: ix,
208 y: iy,
209 constraint: {
210 weight: 0.7,
211 y: iy
212 }
213 };
214 network.lookup[n.id] = node;
215 network.nodes.push(node);
216 });
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700217
218
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700219 // now, process the explicit links...
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700220 network.data.links.forEach(function(n) {
221 var src = network.lookup[n.src],
222 dst = network.lookup[n.dst],
223 id = src.id + "~" + dst.id;
224
225 var link = {
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700226 class: 'infra',
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700227 id: id,
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700228 type: n.type,
229 width: n.linkWidth,
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700230 source: src,
231 target: dst,
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700232 strength: config.force.linkStrength.infra
233 };
234 network.links.push(link);
235 });
236
237 // finally, infer host links...
238 network.data.hosts.forEach(function(n) {
239 var src = network.lookup[n.id],
240 dst = network.lookup[n.cp.device],
241 id = src.id + "~" + dst.id;
242
243 var link = {
244 class: 'host',
245 id: id,
246 type: 'hostLink',
247 width: config.hostLinkWidth,
248 source: src,
249 target: dst,
250 strength: config.force.linkStrength.host
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700251 };
252 network.links.push(link);
253 });
254 }
255
256 function createLayout() {
257
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700258 var cfg = config.force;
259
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700260 network.force = d3.layout.force()
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700261 .size([network.forceWidth, network.forceHeight])
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700262 .nodes(network.nodes)
263 .links(network.links)
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700264 .linkStrength(function(d) { return cfg.linkStrength[d.class]; })
265 .linkDistance(function(d) { return cfg.linkDistance[d.class]; })
266 .charge(function(d) { return cfg.charge[d.class]; })
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700267 .on('tick', tick);
268
269 network.svg = d3.select('#view').append('svg')
270 .attr('width', view.width)
271 .attr('height', view.height)
272 .append('g')
Simon Huntae968a62014-10-22 14:54:41 -0700273 .attr('transform', config.force.translate());
Simon Hunt3ab76a82014-10-22 13:07:32 -0700274// .attr('id', 'zoomable')
Simon Hunt3ab76a82014-10-22 13:07:32 -0700275// .call(d3.behavior.zoom().on("zoom", zoomRedraw));
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700276
Simon Hunt3ab76a82014-10-22 13:07:32 -0700277// function zoomRedraw() {
278// d3.select("#zoomable").attr("transform",
279// "translate(" + d3.event.translate + ")"
280// + " scale(" + d3.event.scale + ")");
281// }
282
Simon Hunt3ab76a82014-10-22 13:07:32 -0700283 // TODO: move glow/blur stuff to util script
284 var glow = network.svg.append('filter')
285 .attr('x', '-50%')
286 .attr('y', '-50%')
287 .attr('width', '200%')
288 .attr('height', '200%')
289 .attr('id', 'blue-glow');
290
291 glow.append('feColorMatrix')
292 .attr('type', 'matrix')
293 .attr('values', '0 0 0 0 0 ' +
294 '0 0 0 0 0 ' +
295 '0 0 0 0 .7 ' +
296 '0 0 0 1 0 ');
297
298 glow.append('feGaussianBlur')
299 .attr('stdDeviation', 3)
300 .attr('result', 'coloredBlur');
301
302 glow.append('feMerge').selectAll('feMergeNode')
303 .data(['coloredBlur', 'SourceGraphic'])
304 .enter().append('feMergeNode')
305 .attr('in', String);
306
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700307 // TODO: legend (and auto adjust on scroll)
Simon Hunt3ab76a82014-10-22 13:07:32 -0700308// $('#view').on('scroll', function() {
309//
310// });
311
312
Simon Hunt5cd0e8f2014-10-27 16:18:40 -0700313 // add links to the display
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700314 network.link = network.svg.append('g').selectAll('.link')
315 .data(network.force.links(), function(d) {return d.id})
316 .enter().append('line')
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700317 .attr('class', function(d) {return 'link ' + d.class});
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700318
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700319
320 // == define node drag behavior...
Simon Hunt3ab76a82014-10-22 13:07:32 -0700321 network.draggedThreshold = d3.scale.linear()
322 .domain([0, 0.1])
323 .range([5, 20])
324 .clamp(true);
325
326 function dragged(d) {
327 var threshold = network.draggedThreshold(network.force.alpha()),
328 dx = d.oldX - d.px,
329 dy = d.oldY - d.py;
330 if (Math.abs(dx) >= threshold || Math.abs(dy) >= threshold) {
331 d.dragged = true;
332 }
333 return d.dragged;
334 }
335
336 network.drag = d3.behavior.drag()
337 .origin(function(d) { return d; })
338 .on('dragstart', function(d) {
339 d.oldX = d.x;
340 d.oldY = d.y;
341 d.dragged = false;
342 d.fixed |= 2;
343 })
344 .on('drag', function(d) {
345 d.px = d3.event.x;
346 d.py = d3.event.y;
347 if (dragged(d)) {
348 if (!network.force.alpha()) {
349 network.force.alpha(.025);
350 }
351 }
352 })
353 .on('dragend', function(d) {
354 if (!dragged(d)) {
355 selectObject(d, this);
356 }
357 d.fixed &= ~6;
358 });
359
360 $('#view').on('click', function(e) {
361 if (!$(e.target).closest('.node').length) {
362 deselectObject();
363 }
364 });
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700365
Simon Hunt1c5f8b62014-10-22 14:43:01 -0700366
Simon Hunt5cd0e8f2014-10-27 16:18:40 -0700367 // add nodes to the display
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700368 network.node = network.svg.selectAll('.node')
369 .data(network.force.nodes(), function(d) {return d.id})
370 .enter().append('g')
Simon Hunt3ab76a82014-10-22 13:07:32 -0700371 .attr('class', function(d) {
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700372 var cls = 'node ' + d.class;
373 if (d.type) {
374 cls += ' ' + d.type;
375 }
376 return cls;
Simon Hunt3ab76a82014-10-22 13:07:32 -0700377 })
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700378 .attr('transform', function(d) {
379 return translate(d.x, d.y);
380 })
Simon Hunt3ab76a82014-10-22 13:07:32 -0700381 .call(network.drag)
382 .on('mouseover', function(d) {
383 if (!selected.obj) {
384 if (network.mouseoutTimeout) {
385 clearTimeout(network.mouseoutTimeout);
386 network.mouseoutTimeout = null;
387 }
388 highlightObject(d);
389 }
390 })
391 .on('mouseout', function(d) {
392 if (!selected.obj) {
393 if (network.mouseoutTimeout) {
394 clearTimeout(network.mouseoutTimeout);
395 network.mouseoutTimeout = null;
396 }
397 network.mouseoutTimeout = setTimeout(function() {
398 highlightObject(null);
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700399 }, config.mouseOutTimerDelayMs);
Simon Hunt3ab76a82014-10-22 13:07:32 -0700400 }
401 });
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700402
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700403 network.nodeRect = network.node.append('rect')
Simon Hunt5cd0e8f2014-10-27 16:18:40 -0700404 .attr({
405 rx: 5,
406 ry: 5,
407 width: 100,
408 height: 12
409 });
410 // note that width/height are adjusted to fit the label text
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700411
412 network.node.each(function(d) {
413 var node = d3.select(this),
Simon Hunt5cd0e8f2014-10-27 16:18:40 -0700414 icon = iconUrl(d);
415
416 node.append('text')
417 // TODO: add label cycle behavior
418 .text(d.id)
419 .attr('dy', '1.1em');
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700420
421 if (icon) {
422 var cfg = config.icons;
423 node.append('svg:image')
Simon Hunt5cd0e8f2014-10-27 16:18:40 -0700424 .attr({
425 width: cfg.w,
426 height: cfg.h,
427 'xlink:href': icon
428 });
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700429 // note, icon relative positioning (x,y) is done after we have
430 // adjusted the bounds of the rectangle...
431 }
Simon Hunt68ae6652014-10-22 13:58:07 -0700432
Simon Hunt5cd0e8f2014-10-27 16:18:40 -0700433 // for debugging...
434 if (config.options.showNodeXY) {
435 node.append('circle')
436 .attr({
437 class: 'debug',
438 cx: 0,
439 cy: 0,
440 r: '3px'
441 });
442 }
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700443 });
444
Simon Hunt5cd0e8f2014-10-27 16:18:40 -0700445
446 // returns the newly computed bounding box
447 function adjustRectToFitText(n) {
448 var text = n.select('text'),
449 box = text.node().getBBox(),
450 lab = config.labels;
451
452 text.attr('text-anchor', 'middle')
453 .attr('y', '-0.8em')
454 .attr('x', lab.imgPad/2)
455 ;
456
457 // TODO: figure out how to access the data on selection
458 console.log("\nadjust rect for " + n.data().id);
459 console.log(box);
460
461 // translate the bbox so that it is centered on [x,y]
462 box.x = -box.width / 2;
463 box.y = -box.height / 2;
464
465 // add padding
466 box.x -= (lab.padLR + lab.imgPad/2);
467 box.width += lab.padLR * 2 + lab.imgPad;
468 box.y -= lab.padTB;
469 box.height += lab.padTB * 2;
470
471 return box;
472 }
473
474 function boundsFromBox(box) {
475 return {
476 x1: box.x,
477 y1: box.y,
478 x2: box.x + box.width,
479 y2: box.y + box.height
480 };
481 }
482
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700483 // this function is scheduled to happen soon after the given thread ends
484 setTimeout(function() {
485 network.node.each(function(d) {
486 // for every node, recompute size, padding, etc. so text fits
487 var node = d3.select(this),
Simon Hunt5cd0e8f2014-10-27 16:18:40 -0700488 text = node.select('text'),
489 box = adjustRectToFitText(node),
490 lab = config.labels;
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700491
Simon Hunt5cd0e8f2014-10-27 16:18:40 -0700492 // now make the computed adjustment
Simon Hunt1c5f8b62014-10-22 14:43:01 -0700493 node.select('rect')
Simon Hunt5cd0e8f2014-10-27 16:18:40 -0700494 .attr(box);
Simon Hunt1c5f8b62014-10-22 14:43:01 -0700495
496 node.select('image')
Simon Hunt5cd0e8f2014-10-27 16:18:40 -0700497 .attr('x', box.x + config.icons.xoff)
498 .attr('y', box.y + config.icons.yoff);
Simon Hunt1c219892014-10-22 16:32:39 -0700499
Simon Hunt5cd0e8f2014-10-27 16:18:40 -0700500 var bounds = boundsFromBox(box);
501
502 // todo: clean up extent and edge work..
Simon Hunt1c219892014-10-22 16:32:39 -0700503 d.extent = {
504 left: bounds.x1 - lab.marginLR,
505 right: bounds.x2 + lab.marginLR,
506 top: bounds.y1 - lab.marginTB,
507 bottom: bounds.y2 + lab.marginTB
508 };
509
510 d.edge = {
511 left : new geo.LineSegment(bounds.x1, bounds.y1, bounds.x1, bounds.y2),
512 right : new geo.LineSegment(bounds.x2, bounds.y1, bounds.x2, bounds.y2),
513 top : new geo.LineSegment(bounds.x1, bounds.y1, bounds.x2, bounds.y1),
514 bottom : new geo.LineSegment(bounds.x1, bounds.y2, bounds.x2, bounds.y2)
515 };
516
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700517 });
518
519 network.numTicks = 0;
520 network.preventCollisions = false;
521 network.force.start();
Simon Hunt1c219892014-10-22 16:32:39 -0700522 for (var i = 0; i < config.force.ticksWithoutCollisions; i++) {
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700523 network.force.tick();
524 }
525 network.preventCollisions = true;
526 $('#view').css('visibility', 'visible');
527 });
528
529 }
530
Simon Hunt68ae6652014-10-22 13:58:07 -0700531 function iconUrl(d) {
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700532 return config.iconUrl[d.icon];
Simon Hunt68ae6652014-10-22 13:58:07 -0700533 }
534
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700535 function translate(x, y) {
536 return 'translate(' + x + ',' + y + ')';
537 }
538
Simon Hunt1c219892014-10-22 16:32:39 -0700539 function preventCollisions() {
540 var quadtree = d3.geom.quadtree(network.nodes);
541
542 network.nodes.forEach(function(n) {
543 var nx1 = n.x + n.extent.left,
544 nx2 = n.x + n.extent.right,
545 ny1 = n.y + n.extent.top,
546 ny2 = n.y + n.extent.bottom;
547
548 quadtree.visit(function(quad, x1, y1, x2, y2) {
549 if (quad.point && quad.point !== n) {
550 // check if the rectangles intersect
551 var p = quad.point,
552 px1 = p.x + p.extent.left,
553 px2 = p.x + p.extent.right,
554 py1 = p.y + p.extent.top,
555 py2 = p.y + p.extent.bottom,
556 ix = (px1 <= nx2 && nx1 <= px2 && py1 <= ny2 && ny1 <= py2);
557 if (ix) {
558 var xa1 = nx2 - px1, // shift n left , p right
559 xa2 = px2 - nx1, // shift n right, p left
560 ya1 = ny2 - py1, // shift n up , p down
561 ya2 = py2 - ny1, // shift n down , p up
562 adj = Math.min(xa1, xa2, ya1, ya2);
563
564 if (adj == xa1) {
565 n.x -= adj / 2;
566 p.x += adj / 2;
567 } else if (adj == xa2) {
568 n.x += adj / 2;
569 p.x -= adj / 2;
570 } else if (adj == ya1) {
571 n.y -= adj / 2;
572 p.y += adj / 2;
573 } else if (adj == ya2) {
574 n.y += adj / 2;
575 p.y -= adj / 2;
576 }
577 }
578 return ix;
579 }
580 });
581
582 });
583 }
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700584
585 function tick(e) {
586 network.numTicks++;
587
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700588 if (config.options.layering) {
Simon Hunt68ae6652014-10-22 13:58:07 -0700589 // adjust the y-coord of each node, based on y-pos constraints
590 network.nodes.forEach(function (n) {
591 var z = e.alpha * n.constraint.weight;
592 if (!isNaN(n.constraint.y)) {
593 n.y = (n.constraint.y * z + n.y * (1 - z));
594 }
595 });
596 }
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700597
Simon Hunt2c9e0c22014-10-23 15:12:58 -0700598 if (config.options.collisionPrevention && network.preventCollisions) {
Simon Hunt1c219892014-10-22 16:32:39 -0700599 preventCollisions();
600 }
601
602 // TODO: use intersection technique for source end of link also
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700603 network.link
604 .attr('x1', function(d) {
605 return d.source.x;
606 })
607 .attr('y1', function(d) {
608 return d.source.y;
609 })
Simon Hunt1c219892014-10-22 16:32:39 -0700610 .each(function(d) {
611 var x = d.target.x,
612 y = d.target.y,
613 line = new geo.LineSegment(d.source.x, d.source.y, x, y);
614
615 for (var e in d.target.edge) {
616 var ix = line.intersect(d.target.edge[e].offset(x,y));
617 if (ix.in1 && ix.in2) {
618 x = ix.x;
619 y = ix.y;
620 break;
621 }
622 }
623
624 d3.select(this)
625 .attr('x2', x)
626 .attr('y2', y);
627
Simon Hunt0b05d4a2014-10-21 21:50:15 -0700628 });
629
630 network.node
631 .attr('transform', function(d) {
632 return translate(d.x, d.y);
633 });
634
635 }
636
637 // $('#docs-close').on('click', function() {
638 // deselectObject();
639 // return false;
640 // });
641
642 // $(document).on('click', '.select-object', function() {
643 // var obj = graph.data[$(this).data('name')];
644 // if (obj) {
645 // selectObject(obj);
646 // }
647 // return false;
648 // });
649
650 function selectObject(obj, el) {
651 var node;
652 if (el) {
653 node = d3.select(el);
654 } else {
655 network.node.each(function(d) {
656 if (d == obj) {
657 node = d3.select(el = this);
658 }
659 });
660 }
661 if (!node) return;
662
663 if (node.classed('selected')) {
664 deselectObject();
665 return;
666 }
667 deselectObject(false);
668
669 selected = {
670 obj : obj,
671 el : el
672 };
673
674 highlightObject(obj);
675
676 node.classed('selected', true);
677
678 // TODO animate incoming info pane
679 // resize(true);
680 // TODO: check bounds of selected node and scroll into view if needed
681 }
682
683 function deselectObject(doResize) {
684 // Review: logic of 'resize(...)' function.
685 if (doResize || typeof doResize == 'undefined') {
686 resize(false);
687 }
688 // deselect all nodes in the network...
689 network.node.classed('selected', false);
690 selected = {};
691 highlightObject(null);
692 }
693
694 function highlightObject(obj) {
695 if (obj) {
696 if (obj != highlighted) {
697 // TODO set or clear "inactive" class on nodes, based on criteria
698 network.node.classed('inactive', function(d) {
699 // return (obj !== d &&
700 // d.relation(obj.id));
701 return (obj !== d);
702 });
703 // TODO: same with links
704 network.link.classed('inactive', function(d) {
705 return (obj !== d.source && obj !== d.target);
706 });
707 }
708 highlighted = obj;
709 } else {
710 if (highlighted) {
711 // clear the inactive flag (no longer suppressed visually)
712 network.node.classed('inactive', false);
713 network.link.classed('inactive', false);
714 }
715 highlighted = null;
716
717 }
718 }
719
720 function resize(showDetails) {
721 console.log("resize() called...");
722
723 var $details = $('#details');
724
725 if (typeof showDetails == 'boolean') {
726 var showingDetails = showDetails;
727 // TODO: invoke $details.show() or $details.hide()...
728 // $details[showingDetails ? 'show' : 'hide']();
729 }
730
731 view.height = window.innerHeight - config.mastHeight;
732 view.width = window.innerWidth;
733 $('#view')
734 .css('height', view.height + 'px')
735 .css('width', view.width + 'px');
736
737 network.forceWidth = view.width - config.force.marginLR;
738 network.forceHeight = view.height - config.force.marginTB;
739 }
740
741 // ======================================================================
742 // register with the UI framework
743
744 api.addView('network', {
745 load: loadNetworkView
746 });
747
748
749}(ONOS));
750